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
pub use soft_ascii_string::{ SoftAsciiStr as _SoftAsciiStr };


/// Defines a new header types with given type name, filed name and component
///
/// Note that the name is not checked/validated, it has to be ascii, a valid
/// header field name AND has to comply with the naming schema (each word
/// separated by `'-'` starts with a capital letter and no capital letter
/// follow, e.g. "Message-Id" is ok **but "Message-ID" isn't**).
///
/// This macro will create a test which will check if the used field names
/// are actually valid and appears only once (_per def_header macro call_)
/// so as long as test's are run any invalid name will be found.
///
/// Note that even if a invalid name was used and test where ignored/not run
/// this will _not_ cause an rust safety issue, but can still cause bugs under
/// some circumstances (e.g. if you have multiple differing definitions of the
/// same header with different spelling (at last one failed the test) like e.g.
/// when you override default implementations of fields).
///
/// The macros expects following items:
///
/// 1. `test_name`, which is the name the auto-generated test will have
/// 2. `scope`, the scope all components are used with, this helps with some
///    name collisions. Use `self` to use the current scope.
/// 3. a list of header definitions consisting of:
///
///    1. `<typename>` the name the type of the header will have, i.e. the name of a zero-sized
///       struct which will be generated
///    3. `unchecked` a hint to make people read the documentation and not forget the the
///       folowing data is `unchecked` / only vaidated in the auto-generated test
///    4. `"<header_name>"` the header name in a syntax using `'-'` to serperate words,
///       also each word has to start with a capital letter and be followed by lowercase
///       letters additionaly to being a valid header field name. E.g. "Message-Id" is
///       ok, but "Message-ID" is not. (Note that header field name are on itself ignore
///       case, but by enforcing a specific case in the encoder equality checks can be
///       done on byte level, which is especially usefull for e.g. placing them as keys
///       into a HashMap or for performance reasons.
///    5. `<component>` the name of the type to use ing `scope` a the component type of
///       the header. E.g. `Unstructured` for an unstructured header field (which still
///       support Utf8 through encoded words)
///    6. `None`/`maxOne`/`<name>`, None, maxOne or the name of a validator function.
///       The validator function is used to validate some contextual limitations a header field
///       might have, like that it can appear at most one time, or that if a `From` with multiple
///       mailboxes is given a `Sender` field needs to be given too.
///       If `maxOne` is used it will automatically generate a function which makes sure that
///       the header appears at most one time. This validator functions are used _after_ creating
///       a header map but before using it to encode a mail (or anywhere in between if you want to).
///       Note that validators are kept separate from the headers and might be run even if the header
///       does not appear in the header map passed to the validator, as such if a validator can not find
///       the header it should validate or if it finds it but it has an unexpected type it _must not_
///       create an error.
///
/// # Example
///
/// ```norun
/// def_headers! {
///     // the name of the auto-generated test
///     test_name: validate_header_names,
///     // the scope from which all components should be imported
///     // E.g. `DateTime` refers to `components::DateTime`.
///     scope: components,
///     // definitions of the headers or the form
///     // <type_name>, unchecked { <struct_name> }, <component>, <validator>
///     Date,     unchecked { "Date"          },  DateTime,       maxOne,
///     From,     unchecked { "From"          },  MailboxList,    validator_from,
///     Subject,  unchecked { "Subject"       },  Unstructured,   maxOne,
///     Comments, unchecked { "Comments"      },  Unstructured,   None,
/// }
/// ```
#[macro_export]
macro_rules! def_headers {
    (
        test_name: $tn:ident,
        scope: $scope:ident,
        $(
            $(#[$attr:meta])*
            $name:ident, unchecked { $hname:tt }, $component:ident,
              $maxOne:ident, $validator:ident
        ),+
    ) => (
        $(
            $(#[$attr])*
            #[derive(Default, Copy, Clone)]
            pub struct $name;

            impl $crate::HeaderKind for $name {

                type Component = $scope::$component;

                fn name() -> $crate::HeaderName {
                    let as_str: &'static str = $hname;
                    $crate::HeaderName::from_ascii_unchecked( as_str )
                }

                const MAX_ONE: bool = def_headers!{ _PRIV_mk_max_one $maxOne };
                const VALIDATOR: ::std::option::Option<$crate::map::HeaderMapValidator> =
                        def_headers!{ _PRIV_mk_validator $validator };
            }

            def_headers!{ _PRIV_mk_marker_impl $name, $maxOne }
        )+

        //TODO warn if header type name and header name diverges
        // (by stringifying the type name and then ziping the
        //  array of type names with header names removing
        //  "-" from the header names and comparing them to
        //  type names)


        #[cfg(test)]
        const HEADER_NAMES: &[ &str ] = &[ $(
            $hname
        ),+ ];

        #[test]
        fn $tn() {
            use std::collections::HashSet;
            use $crate::__internals::encoder::EncodableInHeader;

            let mut name_set = HashSet::new();
            for name in HEADER_NAMES {
                if !name_set.insert(name) {
                    panic!("name appears more than one time in same def_headers macro: {:?}", name);
                }
            }
            fn can_be_trait_object<EN: EncodableInHeader>( v: Option<&EN> ) {
                let _ = v.map( |en| en as &EncodableInHeader );
            }
            $(
                can_be_trait_object::<$scope::$component>( None );
            )+
            for name in HEADER_NAMES {
                let res = $crate::HeaderName::new(
                    $crate::soft_ascii_string::SoftAsciiStr::from_str(name).unwrap()
                );
                if res.is_err() {
                    panic!( "invalid header name: {:?} ({:?})", name, res.unwrap_err() );
                }
            }
        }
    );
    (_PRIV_mk_marker_impl $name:ident, multi) => ();
    (_PRIV_mk_marker_impl $name:ident, maxOne) => (
        impl $crate::MaxOneMarker for $name {}
    );
    (_PRIV_mk_marker_impl $name:ident, $other:ident) => (def_headers!{ _PRIV_max_one_err $other });
    (_PRIV_mk_validator None) => ({ None });
    (_PRIV_mk_validator $validator:ident) => ({ Some($validator) });
    (_PRIV_mk_max_one multi) => ({ false });
    (_PRIV_mk_max_one maxOne) => ({ true });
    (_PRIV_mk_max_one $other:ident) => (def_headers!{ _PRIV_max_one_err $other });
    (_PRIV_max_one_err $other:ident) => (
        compile_error!(concat!(
            "maxOne column can only contain `maxOne` or `multi`, got: ",
            stringify!($other)
        ));
    );
}