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}