mail_headers_ng/
header_macro.rs

1pub use soft_ascii_string::{ SoftAsciiStr as _SoftAsciiStr };
2
3
4/// Defines a new header types with given type name, filed name and component
5///
6/// Note that the name is not checked/validated, it has to be ascii, a valid
7/// header field name AND has to comply with the naming schema (each word
8/// separated by `'-'` starts with a capital letter and no capital letter
9/// follow, e.g. "Message-Id" is ok **but "Message-ID" isn't**).
10///
11/// This macro will create a test which will check if the used field names
12/// are actually valid and appears only once (_per def_header macro call_)
13/// so as long as test's are run any invalid name will be found.
14///
15/// Note that even if a invalid name was used and test where ignored/not run
16/// this will _not_ cause an rust safety issue, but can still cause bugs under
17/// some circumstances (e.g. if you have multiple differing definitions of the
18/// same header with different spelling (at last one failed the test) like e.g.
19/// when you override default implementations of fields).
20///
21/// The macros expects following items:
22///
23/// 1. `test_name`, which is the name the auto-generated test will have
24/// 2. `scope`, the scope all components are used with, this helps with some
25///    name collisions. Use `self` to use the current scope.
26/// 3. a list of header definitions consisting of:
27///
28///    1. `<typename>` the name the type of the header will have, i.e. the name of a zero-sized
29///       struct which will be generated
30///    3. `unchecked` a hint to make people read the documentation and not forget the the
31///       folowing data is `unchecked` / only vaidated in the auto-generated test
32///    4. `"<header_name>"` the header name in a syntax using `'-'` to serperate words,
33///       also each word has to start with a capital letter and be followed by lowercase
34///       letters additionaly to being a valid header field name. E.g. "Message-Id" is
35///       ok, but "Message-ID" is not. (Note that header field name are on itself ignore
36///       case, but by enforcing a specific case in the encoder equality checks can be
37///       done on byte level, which is especially usefull for e.g. placing them as keys
38///       into a HashMap or for performance reasons.
39///    5. `<component>` the name of the type to use ing `scope` a the component type of
40///       the header. E.g. `Unstructured` for an unstructured header field (which still
41///       support Utf8 through encoded words)
42///    6. `None`/`maxOne`/`<name>`, None, maxOne or the name of a validator function.
43///       The validator function is used to validate some contextual limitations a header field
44///       might have, like that it can appear at most one time, or that if a `From` with multiple
45///       mailboxes is given a `Sender` field needs to be given too.
46///       If `maxOne` is used it will automatically generate a function which makes sure that
47///       the header appears at most one time. This validator functions are used _after_ creating
48///       a header map but before using it to encode a mail (or anywhere in between if you want to).
49///       Note that validators are kept separate from the headers and might be run even if the header
50///       does not appear in the header map passed to the validator, as such if a validator can not find
51///       the header it should validate or if it finds it but it has an unexpected type it _must not_
52///       create an error.
53///
54/// # Example
55///
56/// ```norun
57/// def_headers! {
58///     // the name of the auto-generated test
59///     test_name: validate_header_names,
60///     // the scope from which all components should be imported
61///     // E.g. `DateTime` refers to `components::DateTime`.
62///     scope: components,
63///     // definitions of the headers or the form
64///     // <type_name>, unchecked { <struct_name> }, <component>, <validator>
65///     Date,     unchecked { "Date"          },  DateTime,       maxOne,
66///     From,     unchecked { "From"          },  MailboxList,    validator_from,
67///     Subject,  unchecked { "Subject"       },  Unstructured,   maxOne,
68///     Comments, unchecked { "Comments"      },  Unstructured,   None,
69/// }
70/// ```
71#[macro_export]
72macro_rules! def_headers {
73    (
74        test_name: $tn:ident,
75        scope: $scope:ident,
76        $(
77            $(#[$attr:meta])*
78            $name:ident, unchecked { $hname:tt }, $component:ident,
79              $maxOne:ident, $validator:ident
80        ),+
81    ) => (
82        $(
83            $(#[$attr])*
84            #[derive(Default, Copy, Clone)]
85            pub struct $name;
86
87            impl $crate::HeaderKind for $name {
88
89                type Component = $scope::$component;
90
91                fn name() -> $crate::HeaderName {
92                    let as_str: &'static str = $hname;
93                    $crate::HeaderName::from_ascii_unchecked( as_str )
94                }
95
96                const MAX_ONE: bool = def_headers!{ _PRIV_mk_max_one $maxOne };
97                const VALIDATOR: ::std::option::Option<$crate::map::HeaderMapValidator> =
98                        def_headers!{ _PRIV_mk_validator $validator };
99            }
100
101            def_headers!{ _PRIV_mk_marker_impl $name, $maxOne }
102        )+
103
104        //TODO warn if header type name and header name diverges
105        // (by stringifying the type name and then ziping the
106        //  array of type names with header names removing
107        //  "-" from the header names and comparing them to
108        //  type names)
109
110
111        #[cfg(test)]
112        const HEADER_NAMES: &[ &str ] = &[ $(
113            $hname
114        ),+ ];
115
116        #[test]
117        fn $tn() {
118            use std::collections::HashSet;
119            use $crate::__internals::encoder::EncodableInHeader;
120
121            let mut name_set = HashSet::new();
122            for name in HEADER_NAMES {
123                if !name_set.insert(name) {
124                    panic!("name appears more than one time in same def_headers macro: {:?}", name);
125                }
126            }
127            fn can_be_trait_object<EN: EncodableInHeader>( v: Option<&EN> ) {
128                let _ = v.map( |en| en as &EncodableInHeader );
129            }
130            $(
131                can_be_trait_object::<$scope::$component>( None );
132            )+
133            for name in HEADER_NAMES {
134                let res = $crate::HeaderName::new(
135                    $crate::soft_ascii_string::SoftAsciiStr::from_str(name).unwrap()
136                );
137                if res.is_err() {
138                    panic!( "invalid header name: {:?} ({:?})", name, res.unwrap_err() );
139                }
140            }
141        }
142    );
143    (_PRIV_mk_marker_impl $name:ident, multi) => ();
144    (_PRIV_mk_marker_impl $name:ident, maxOne) => (
145        impl $crate::MaxOneMarker for $name {}
146    );
147    (_PRIV_mk_marker_impl $name:ident, $other:ident) => (def_headers!{ _PRIV_max_one_err $other });
148    (_PRIV_mk_validator None) => ({ None });
149    (_PRIV_mk_validator $validator:ident) => ({ Some($validator) });
150    (_PRIV_mk_max_one multi) => ({ false });
151    (_PRIV_mk_max_one maxOne) => ({ true });
152    (_PRIV_mk_max_one $other:ident) => (def_headers!{ _PRIV_max_one_err $other });
153    (_PRIV_max_one_err $other:ident) => (
154        compile_error!(concat!(
155            "maxOne column can only contain `maxOne` or `multi`, got: ",
156            stringify!($other)
157        ));
158    );
159}