conf_derive 0.1.2

Derive macro crate used with conf
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
use super::StructItem;
use crate::util::type_is_bool;

use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{punctuated::Punctuated, Error, Field, Ident, LitStr, Meta, Token, Type};

mod flag_item;
mod flatten_item;
mod parameter_item;
mod repeat_item;
mod subcommands_item;

use flag_item::FlagItem;
use flatten_item::FlattenItem;
use parameter_item::ParameterItem;
use repeat_item::RepeatItem;
use subcommands_item::SubcommandsItem;

/// #[conf(...)] options listed in a field of a struct which has `#[derive(Conf)]`
pub enum FieldItem {
    Flag(FlagItem),
    Parameter(ParameterItem),
    Repeat(RepeatItem),
    Flatten(FlattenItem),
    Subcommands(SubcommandsItem),
}

impl FieldItem {
    pub fn new(field: &Field, struct_item: &StructItem) -> Result<Self, Error> {
        // First, inspect the first field attribute.
        // If the first attribute is 'flag', 'parameter', 'repeat', or 'flatten', then that's how
        // we're going to handle it.
        for attr in &field.attrs {
            if attr.path().is_ident("conf") || attr.path().is_ident("arg") {
                let nested =
                    attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
                if let Some(meta) = nested.first() {
                    let path = meta.path();
                    if path.is_ident("flag") {
                        return Ok(Self::Flag(FlagItem::new(field, struct_item)?));
                    } else if path.is_ident("parameter") {
                        return Ok(Self::Parameter(ParameterItem::new(field, struct_item)?));
                    } else if path.is_ident("repeat") {
                        return Ok(Self::Repeat(RepeatItem::new(field, struct_item)?));
                    } else if path.is_ident("flatten") {
                        return Ok(Self::Flatten(FlattenItem::new(field, struct_item)?));
                    } else if path.is_ident("subcommands") {
                        return Ok(Self::Subcommands(SubcommandsItem::new(field, struct_item)?));
                    }
                }
            }
        }

        // We're still not sure, so inspect the type.
        // If it's bool, it's a flag. Otherwise it's a parameter.
        Ok(if type_is_bool(&field.ty) {
            Self::Flag(FlagItem::new(field, struct_item)?)
        } else {
            Self::Parameter(ParameterItem::new(field, struct_item)?)
        })
    }

    /// True if this field represents a single program option
    pub fn is_single_option(&self) -> bool {
        matches!(
            self,
            Self::Flag(..) | Self::Parameter(..) | Self::Repeat(..)
        )
    }

    /// Get the field name
    pub fn get_field_name(&self) -> &Ident {
        match self {
            Self::Flag(item) => item.get_field_name(),
            Self::Parameter(item) => item.get_field_name(),
            Self::Repeat(item) => item.get_field_name(),
            Self::Flatten(item) => item.get_field_name(),
            Self::Subcommands(item) => item.get_field_name(),
        }
    }

    /// Get the field type
    pub fn get_field_type(&self) -> Type {
        match self {
            Self::Flag(item) => item.get_field_type(),
            Self::Parameter(item) => item.get_field_type(),
            Self::Repeat(item) => item.get_field_type(),
            Self::Flatten(item) => item.get_field_type(),
            Self::Subcommands(item) => item.get_field_type(),
        }
    }

    /// Generate code that constructs (one or more) ProgramOption as needed and pushes them onto
    /// program_options_ident
    pub fn gen_push_program_options(
        &self,
        program_options_ident: &Ident,
    ) -> Result<TokenStream, Error> {
        match self {
            Self::Flag(item) => item.gen_push_program_options(program_options_ident),
            Self::Parameter(item) => item.gen_push_program_options(program_options_ident),
            Self::Repeat(item) => item.gen_push_program_options(program_options_ident),
            Self::Flatten(item) => item.gen_push_program_options(program_options_ident),
            Self::Subcommands(item) => item.gen_push_program_options(program_options_ident),
        }
    }

    /// Generate code that constructs (one or more) subcommands as needed and pushes them onto
    /// subcommands_ident
    pub fn gen_push_subcommands(
        &self,
        subcommands_ident: &Ident,
        parsed_env: &Ident,
    ) -> Result<TokenStream, Error> {
        match self {
            Self::Flag(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
            Self::Parameter(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
            Self::Repeat(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
            Self::Flatten(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
            Self::Subcommands(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
        }
    }

    /// Generate code for a struct initializer for this field, reading from conf_context
    ///
    /// Returns:
    /// * a TokenStream for initializer expression, which can use `?` to return errors,
    /// * a bool which is true if the error type is `Vec<InnerError>` and false if it is
    ///   `InnerError`
    fn gen_initializer(&self, conf_context_ident: &Ident) -> Result<(TokenStream, bool), Error> {
        match self {
            Self::Flag(item) => item.gen_initializer(conf_context_ident),
            Self::Parameter(item) => item.gen_initializer(conf_context_ident),
            Self::Repeat(item) => item.gen_initializer(conf_context_ident),
            Self::Flatten(item) => item.gen_initializer(conf_context_ident),
            Self::Subcommands(item) => item.gen_initializer(conf_context_ident),
        }
    }

    /// Generate code for a struct initializer for this field, reading from conf_context
    ///
    /// Returns:
    /// * a TokenStream for initializer expression, which can use `?` to return errors,
    /// * a bool which is true if the error type is `Vec<InnerError>` and false if it is
    ///   `InnerError`
    fn gen_initializer_with_doc_val(
        &self,
        conf_context_ident: &Ident,
        doc_name_ident: &Ident,
        doc_val_ident: &Ident,
    ) -> Result<(TokenStream, bool), Error> {
        match self {
            Self::Flag(item) => {
                item.gen_initializer_with_doc_val(conf_context_ident, doc_name_ident, doc_val_ident)
            }
            Self::Parameter(item) => {
                item.gen_initializer_with_doc_val(conf_context_ident, doc_name_ident, doc_val_ident)
            }
            Self::Repeat(item) => {
                item.gen_initializer_with_doc_val(conf_context_ident, doc_name_ident, doc_val_ident)
            }
            Self::Flatten(_item) => unimplemented!("uses a custom match arm"),
            Self::Subcommands(_item) => unimplemented!("would have to use a custom match arm"),
        }
    }

    /// Generate code of the form
    ///
    /// {
    ///   fn #field_name(conf_context: &...) -> Result<#field_type, InnerError> { .. }
    ///   match field_name(...) {
    ///     Ok(t) => Some(t),
    ///     Err(err) => { errors.push(err); None },
    ///   }
    /// }
    ///
    /// This value can then be assigned to a variable, e.g.
    ///
    /// let #field_name = #initializer;
    ///
    /// The code block reads from a conf context and pushes any errors to given errors buffer.
    ///
    /// Arguments:
    /// * conf_context_ident is a variable of type ConfContext which is in scope, which we won't
    ///   consume
    /// * errors_ident is a variable of type mut Vec<InnerError> which is in scope, which we can
    ///   push to.
    pub fn gen_initialize_from_conf_context_and_push_errors(
        &self,
        conf_context_ident: &Ident,
        errors_ident: &Ident,
    ) -> Result<TokenStream, Error> {
        let field_name = self.get_field_name();
        let field_type = self.get_field_type();
        let (initializer, returns_multiple_errors) = self.gen_initializer(conf_context_ident)?;
        // The initializer is the portion e.g.
        // fn(conf_context: &...) -> Result<T, conf::InnerError> { ... }
        //
        // It returns `Result<T, Vec<conf::InnerError>>` if returns_multiple_errors is true,
        // otherwise it's Result<T, conf::InnerError> It is allowed to read
        // #conf_context_ident but not modify it We have to put it inside a locally defined
        // fn so that it cannot modify the errors buffer etc.

        let (error_type, extend_fn) = if returns_multiple_errors {
            (
                quote! { ::std::vec::Vec<::conf::InnerError> },
                quote! { extend },
            )
        } else {
            (quote! { ::conf::InnerError }, quote! { push })
        };

        Ok(quote! {
          {
            fn #field_name(
              #conf_context_ident: &::conf::ConfContext<'_>
            ) -> Result<#field_type, #error_type> {
              #initializer
            }
            match #field_name(&#conf_context_ident) {
              Ok(val) => Some(val),
              Err(errs) => {
                #errors_ident.#extend_fn(errs);
                None
              }
            }
          }
        })
    }

    /// Generate code of the form
    ///
    /// {
    ///   fn #field_name(c: &ConfContext, d: ...) -> Result<#field_type, Vec<InnerError> {
    ///      ..
    ///   }
    ///   match #field_name(...) {
    ///     Ok(val) => Some(val),
    ///     Err(err) => #errors_ident.extend(err);
    ///   }
    /// }
    ///
    /// which can then be assigned to a variable, e.g. #field_name
    ///
    /// The code block reads from a conf context and a value from document, resolves it, and pushes
    /// any errors to given errors ident.
    ///
    /// Arguments:
    /// * conf_context_ident is a variable of type ConfContext which is in scope, which we won't
    ///   consume
    /// * doc_name_ident is a variable of type &str, which is the name of the document this value
    ///   came from
    /// * doc_val_ident is a variable of type #serde_type, which was parsed from serde successfully.
    /// * errors_ident is a variable of type mut Vec<InnerError> which is in scope, which we can
    ///   push to.
    pub fn gen_initialize_from_conf_context_and_doc_val_and_push_errors(
        &self,
        conf_context_ident: &Ident,
        doc_name_ident: &Ident,
        doc_val_ident: &Ident,
        errors_ident: &Ident,
    ) -> Result<TokenStream, Error> {
        let field_name = self.get_field_name();
        let field_type = self.get_field_type();
        let serde_type = self.get_serde_type();
        let (initializer, returns_multiple_errors) =
            self.gen_initializer_with_doc_val(conf_context_ident, doc_name_ident, doc_val_ident)?;
        // The initializer is the portion e.g.
        // fn(conf_context: &..., doc_val: T) -> Result<T, conf::InnerError> { ... }
        //
        // It returns `Result<T, Vec<conf::InnerError>>` if returns_multiple_errors is true,
        // otherwise it's Result<T, conf::InnerError>
        // It is allowed to read #conf_context_ident but not modify it
        // We have to put it inside a locally defined
        // fn so that it cannot modify the errors buffer etc.

        let (error_type, extend_fn) = if returns_multiple_errors {
            (
                quote! { ::std::vec::Vec<::conf::InnerError> },
                quote! { extend },
            )
        } else {
            (quote! { ::conf::InnerError }, quote! { push })
        };

        Ok(quote! {
          {
            fn #field_name(
              #conf_context_ident: &::conf::ConfContext<'_>,
              #doc_name_ident: &str,
              #doc_val_ident: #serde_type
            ) -> Result<#field_type, #error_type> {
              #initializer
            }
            match #field_name(&#conf_context_ident, #doc_name_ident, #doc_val_ident) {
              Ok(val) => Some(val),
              Err(errs) => {
                #errors_ident.#extend_fn(errs);
                None
              }
            }
          }
        })
    }

    /// Generate (one or more) match arms for iterating a serde MapAccess object.
    /// The match takes place on a `&str` representing the struct key.
    /// The match arm(s) generated here should identify one or more `&str`
    /// and perform initialization appropriately.
    ///
    /// Returns a TokenStream for the match arm, and a list of `serde_name`'s which
    /// we want to advertise in error messages.
    ///
    /// Arguments:
    /// * Ident for conf_serde_context which is in scope and may be consumed
    /// * Ident for map_access object which is in scope and may be consumed
    /// * Ident for map_access type
    /// * Ident for errors buffer which is in scope, to which we may push.
    pub fn gen_serde_match_arm(
        &self,
        ctxt: &Ident,
        map_access: &Ident,
        map_access_type: &Ident,
        errors_ident: &Ident,
    ) -> Result<(TokenStream, Vec<LitStr>), Error> {
        if self.get_serde_skip() {
            return Ok((quote! {}, vec![]));
        }
        match self {
            Self::Flag(_) | Self::Parameter(_) | Self::Repeat(_) => {
                self.gen_simple_serde_match_arm(ctxt, map_access, map_access_type, errors_ident)
            }
            Self::Flatten(item) => {
                item.gen_serde_match_arm(ctxt, map_access, map_access_type, errors_ident)
            }
            Self::Subcommands(item) => {
                item.gen_serde_match_arm(ctxt, map_access, map_access_type, errors_ident)
            }
        }
    }

    // A simple serde match arm is used at a "terminal", i.e. a flag, parameter, or repeat field.
    // These are fields that represent only a single program option.
    // This is a terminal in the sense that we don't recurse further using `DeserializeSeed` and our
    // own traits.
    //
    // The field has controls on what happens in this match arm via:
    // * get_serde_name()
    // * get_serde_type()
    // * gen_initializer_with_doc_val()
    fn gen_simple_serde_match_arm(
        &self,
        ctxt: &Ident,
        map_access: &Ident,
        map_access_type: &Ident,
        errors_ident: &Ident,
    ) -> Result<(TokenStream, Vec<LitStr>), Error> {
        let field_name = self.get_field_name();
        let field_name_str = field_name.to_string();

        let serde_name_str = self.get_serde_name();
        let serde_type = self.get_serde_type();

        let conf_context_ident = Ident::new("__conf_context__", Span::call_site());
        let doc_name_ident = Ident::new("__doc_name__", Span::call_site());
        let doc_val_ident = Ident::new("__doc_val__", Span::call_site());
        let initializer = self.gen_initialize_from_conf_context_and_doc_val_and_push_errors(
            &conf_context_ident,
            &doc_name_ident,
            &doc_val_ident,
            errors_ident,
        )?;

        let match_arm = quote! {
          #serde_name_str => {
            if #field_name.is_some() {
              #errors_ident.push(
                InnerError::serde(
                  #ctxt.document_name,
                  #field_name_str,
                  #map_access_type::Error::duplicate_field(#serde_name_str)
                )
              );
            } else {
              #field_name = Some(match #map_access.next_value::<#serde_type>() {
                Ok(#doc_val_ident) => {
                  let #conf_context_ident: &::conf::ConfContext = &#ctxt.conf_context;
                  let #doc_name_ident: &str = #ctxt.document_name;
                  #initializer
                }
                Err(__err__) => {
                  #errors_ident.push(
                    InnerError::serde(
                      #ctxt.document_name,
                      #field_name_str,
                      __err__
                    )
                  );
                  None
                }
              });
            }
          },
        };
        Ok((match_arm, vec![serde_name_str]))
    }

    /// Get the serde name (only when "is_single_option" is true)
    fn get_serde_name(&self) -> LitStr {
        match self {
            Self::Flag(item) => item.get_serde_name(),
            Self::Parameter(item) => item.get_serde_name(),
            Self::Repeat(item) => item.get_serde_name(),
            Self::Flatten(_item) => unimplemented!(),
            Self::Subcommands(_item) => unimplemented!(),
        }
    }

    /// Get the serde type (only when "is_single_option" is true)
    fn get_serde_type(&self) -> Type {
        match self {
            Self::Flag(item) => item.get_serde_type(),
            Self::Parameter(item) => item.get_serde_type(),
            Self::Repeat(item) => item.get_serde_type(),
            Self::Flatten(_item) => unimplemented!(),
            Self::Subcommands(_item) => unimplemented!(),
        }
    }

    /// Get the serde(skip) option
    fn get_serde_skip(&self) -> bool {
        match self {
            Self::Flag(item) => item.get_serde_skip(),
            Self::Parameter(item) => item.get_serde_skip(),
            Self::Repeat(item) => item.get_serde_skip(),
            Self::Flatten(item) => item.get_serde_skip(),
            Self::Subcommands(item) => item.get_serde_skip(),
        }
    }
}