1
1
use darling:: { error:: Accumulator , util:: path_to_string, FromMeta } ;
2
+ use quote:: ToTokens ;
2
3
use serde_derive_internals:: { ast as serde_ast, attr as serde_attr} ;
3
4
4
5
mod container;
@@ -8,9 +9,11 @@ mod variant;
8
9
9
10
pub use container:: Container ;
10
11
pub use field:: Field ;
11
- use syn:: NestedMeta ;
12
+ use syn:: { Expr , NestedMeta } ;
12
13
pub use variant:: Variant ;
13
- use vector_config_common:: attributes:: CustomAttribute ;
14
+
15
+ const INVALID_VALUE_EXPR : & str =
16
+ "got function call-style literal value but could not parse as expression" ;
14
17
15
18
/// The style of a data container, applying to both enum variants and structs.
16
19
///
@@ -81,19 +84,19 @@ impl Tagging {
81
84
/// This is typically added to the metadata for an enum's overall schema to better describe how
82
85
/// the various subschemas relate to each other and how they're used on the Rust side, for the
83
86
/// purpose of generating usable documentation from the schema.
84
- pub fn as_enum_metadata ( & self ) -> Vec < CustomAttribute > {
87
+ pub fn as_enum_metadata ( & self ) -> Vec < LazyCustomAttribute > {
85
88
match self {
86
- Self :: External => vec ! [ CustomAttribute :: kv( "docs::enum_tagging" , "external" ) ] ,
89
+ Self :: External => vec ! [ LazyCustomAttribute :: kv( "docs::enum_tagging" , "external" ) ] ,
87
90
Self :: Internal { tag } => vec ! [
88
- CustomAttribute :: kv( "docs::enum_tagging" , "internal" ) ,
89
- CustomAttribute :: kv( "docs::enum_tag_field" , tag) ,
91
+ LazyCustomAttribute :: kv( "docs::enum_tagging" , "internal" ) ,
92
+ LazyCustomAttribute :: kv( "docs::enum_tag_field" , tag) ,
90
93
] ,
91
94
Self :: Adjacent { tag, content } => vec ! [
92
- CustomAttribute :: kv( "docs::enum_tagging" , "adjacent" ) ,
93
- CustomAttribute :: kv( "docs::enum_tag_field" , tag) ,
94
- CustomAttribute :: kv( "docs::enum_content_field" , content) ,
95
+ LazyCustomAttribute :: kv( "docs::enum_tagging" , "adjacent" ) ,
96
+ LazyCustomAttribute :: kv( "docs::enum_tag_field" , tag) ,
97
+ LazyCustomAttribute :: kv( "docs::enum_content_field" , content) ,
95
98
] ,
96
- Self :: None => vec ! [ CustomAttribute :: kv( "docs::enum_tagging" , "untagged" ) ] ,
99
+ Self :: None => vec ! [ LazyCustomAttribute :: kv( "docs::enum_tagging" , "untagged" ) ] ,
97
100
}
98
101
}
99
102
}
@@ -120,14 +123,47 @@ pub enum Data<'a> {
120
123
Struct ( Style , Vec < Field < ' a > > ) ,
121
124
}
122
125
126
+ /// A lazy version of `CustomAttribute`.
127
+ ///
128
+ /// This is used to capture the value at the macro callsite without having to evaluate it, which
129
+ /// lets us generate code where, for example, the value of a metadata key/value pair can be
130
+ /// evaulated by an expression given in the attribute.
131
+ ///
132
+ /// This is similar to how `serde` takes an expression for things like `#[serde(default =
133
+ /// "exprhere")]`, and so on.
134
+ #[ derive( Clone , Debug ) ]
135
+ pub enum LazyCustomAttribute {
136
+ /// A standalone flag.
137
+ Flag ( String ) ,
138
+
139
+ /// A key/value pair.
140
+ KeyValue {
141
+ key : String ,
142
+ value : proc_macro2:: TokenStream ,
143
+ } ,
144
+ }
145
+
146
+ impl LazyCustomAttribute {
147
+ pub fn kv < K , V > ( key : K , value : V ) -> Self
148
+ where
149
+ K : std:: fmt:: Display ,
150
+ V : ToTokens ,
151
+ {
152
+ Self :: KeyValue {
153
+ key : key. to_string ( ) ,
154
+ value : value. to_token_stream ( ) ,
155
+ }
156
+ }
157
+ }
158
+
123
159
/// Metadata items defined on containers, variants, or fields.
124
160
#[ derive( Clone , Debug ) ]
125
161
pub struct Metadata {
126
- items : Vec < CustomAttribute > ,
162
+ items : Vec < LazyCustomAttribute > ,
127
163
}
128
164
129
165
impl Metadata {
130
- pub fn attributes ( & self ) -> impl Iterator < Item = CustomAttribute > {
166
+ pub fn attributes ( & self ) -> impl Iterator < Item = LazyCustomAttribute > {
131
167
self . items . clone ( ) . into_iter ( )
132
168
}
133
169
}
@@ -148,20 +184,41 @@ impl FromMeta for Metadata {
148
184
. iter ( )
149
185
. filter_map ( |nmeta| match nmeta {
150
186
NestedMeta :: Meta ( meta) => match meta {
151
- syn:: Meta :: Path ( path) => Some ( CustomAttribute :: Flag ( path_to_string ( path) ) ) ,
187
+ syn:: Meta :: Path ( path) => Some ( LazyCustomAttribute :: Flag ( path_to_string ( path) ) ) ,
152
188
syn:: Meta :: List ( _) => {
153
189
errors. push ( darling:: Error :: unexpected_type ( "list" ) . with_span ( nmeta) ) ;
154
190
None
155
191
}
156
192
syn:: Meta :: NameValue ( nv) => match & nv. lit {
157
- syn:: Lit :: Str ( s) => Some ( CustomAttribute :: KeyValue {
193
+ // When dealing with a string literal, we check if it ends in `()`. If so,
194
+ // we emit that as-is, leading to doing a function call and using the return
195
+ // value of that function as the value for this key/value pair.
196
+ //
197
+ // Otherwise, we just treat the string literal normally.
198
+ syn:: Lit :: Str ( s) => {
199
+ if s. value ( ) . ends_with ( "()" ) {
200
+ if let Ok ( expr) = s. parse :: < Expr > ( ) {
201
+ Some ( LazyCustomAttribute :: KeyValue {
202
+ key : path_to_string ( & nv. path ) ,
203
+ value : expr. to_token_stream ( ) ,
204
+ } )
205
+ } else {
206
+ errors. push (
207
+ darling:: Error :: custom ( INVALID_VALUE_EXPR ) . with_span ( nmeta) ,
208
+ ) ;
209
+ None
210
+ }
211
+ } else {
212
+ Some ( LazyCustomAttribute :: KeyValue {
213
+ key : path_to_string ( & nv. path ) ,
214
+ value : s. value ( ) . to_token_stream ( ) ,
215
+ } )
216
+ }
217
+ }
218
+ lit => Some ( LazyCustomAttribute :: KeyValue {
158
219
key : path_to_string ( & nv. path ) ,
159
- value : s . value ( ) ,
220
+ value : lit . to_token_stream ( ) ,
160
221
} ) ,
161
- lit => {
162
- errors. push ( darling:: Error :: unexpected_lit_type ( lit) ) ;
163
- None
164
- }
165
222
} ,
166
223
} ,
167
224
NestedMeta :: Lit ( _) => {
0 commit comments