macros.rs

  1// Copyright (c) 2017-2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7macro_rules! get_attr {
  8    ($elem:ident, $attr:tt, $type:tt) => {
  9        get_attr!($elem, $attr, $type, value, value.parse()?)
 10    };
 11    ($elem:ident, $attr:tt, optional_empty, $value:ident, $func:expr) => {
 12        match $elem.attr($attr) {
 13            Some("") => None,
 14            Some($value) => Some($func),
 15            None => None,
 16        }
 17    };
 18    ($elem:ident, $attr:tt, optional, $value:ident, $func:expr) => {
 19        match $elem.attr($attr) {
 20            Some($value) => Some($func),
 21            None => None,
 22        }
 23    };
 24    ($elem:ident, $attr:tt, required, $value:ident, $func:expr) => {
 25        match $elem.attr($attr) {
 26            Some($value) => $func,
 27            None => {
 28                return Err(crate::error::Error::ParseError(concat!(
 29                    "Required attribute '",
 30                    $attr,
 31                    "' missing."
 32                )))
 33            }
 34        }
 35    };
 36    ($elem:ident, $attr:tt, default, $value:ident, $func:expr) => {
 37        match $elem.attr($attr) {
 38            Some($value) => $func,
 39            None => ::std::default::Default::default(),
 40        }
 41    };
 42}
 43
 44macro_rules! generate_attribute {
 45    ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}) => (
 46        generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+});
 47    );
 48    ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}, Default = $default:ident) => (
 49        generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+}, Default = $default);
 50    );
 51    ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}) => (
 52        $(#[$meta])*
 53        #[derive(Debug, Clone, PartialEq)]
 54        pub enum $elem {
 55            $(
 56                $(#[$a_meta])*
 57                $a
 58            ),+
 59        }
 60        impl ::std::str::FromStr for $elem {
 61            type Err = crate::error::Error;
 62            fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
 63                Ok(match s {
 64                    $($b => $elem::$a),+,
 65                    _ => return Err(crate::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
 66                })
 67            }
 68        }
 69        impl ::minidom::IntoAttributeValue for $elem {
 70            fn into_attribute_value(self) -> Option<String> {
 71                Some(String::from(match self {
 72                    $($elem::$a => $b),+
 73                }))
 74            }
 75        }
 76    );
 77    ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}, Default = $default:ident) => (
 78        $(#[$meta])*
 79        #[derive(Debug, Clone, PartialEq)]
 80        pub enum $elem {
 81            $(
 82                $(#[$a_meta])*
 83                $a
 84            ),+
 85        }
 86        impl ::std::str::FromStr for $elem {
 87            type Err = crate::error::Error;
 88            fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
 89                Ok(match s {
 90                    $($b => $elem::$a),+,
 91                    _ => return Err(crate::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
 92                })
 93            }
 94        }
 95        impl ::minidom::IntoAttributeValue for $elem {
 96            #[allow(unreachable_patterns)]
 97            fn into_attribute_value(self) -> Option<String> {
 98                Some(String::from(match self {
 99                    $elem::$default => return None,
100                    $($elem::$a => $b),+
101                }))
102            }
103        }
104        impl ::std::default::Default for $elem {
105            fn default() -> $elem {
106                $elem::$default
107            }
108        }
109    );
110    ($(#[$meta:meta])* $elem:ident, $name:tt, bool) => (
111        $(#[$meta])*
112        #[derive(Debug, Clone, PartialEq)]
113        pub enum $elem {
114            /// True value, represented by either 'true' or '1'.
115            True,
116            /// False value, represented by either 'false' or '0'.
117            False,
118        }
119        impl ::std::str::FromStr for $elem {
120            type Err = crate::error::Error;
121            fn from_str(s: &str) -> Result<Self, crate::error::Error> {
122                Ok(match s {
123                    "true" | "1" => $elem::True,
124                    "false" | "0" => $elem::False,
125                    _ => return Err(crate::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
126                })
127            }
128        }
129        impl ::minidom::IntoAttributeValue for $elem {
130            fn into_attribute_value(self) -> Option<String> {
131                match self {
132                    $elem::True => Some(String::from("true")),
133                    $elem::False => None
134                }
135            }
136        }
137        impl ::std::default::Default for $elem {
138            fn default() -> $elem {
139                $elem::False
140            }
141        }
142    );
143}
144
145macro_rules! generate_element_enum {
146    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
147        generate_element_enum!($(#[$meta])* $elem, $name, $ns, {$($(#[$enum_meta])* $enum => $enum_name),+});
148    );
149    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
150        $(#[$meta])*
151        #[derive(Debug, Clone, PartialEq)]
152        pub enum $elem {
153            $(
154            $(#[$enum_meta])*
155            $enum
156            ),+
157        }
158        impl ::try_from::TryFrom<::minidom::Element> for $elem {
159            type Err = crate::error::Error;
160            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
161                check_ns_only!(elem, $name, $ns);
162                check_no_children!(elem, $name);
163                check_no_attributes!(elem, $name);
164                Ok(match elem.name() {
165                    $($enum_name => $elem::$enum,)+
166                    _ => return Err(crate::error::Error::ParseError(concat!("This is not a ", $name, " element."))),
167                })
168            }
169        }
170        impl From<$elem> for ::minidom::Element {
171            fn from(elem: $elem) -> ::minidom::Element {
172                ::minidom::Element::builder(match elem {
173                    $($elem::$enum => $enum_name,)+
174                }).ns(crate::ns::$ns)
175                  .build()
176            }
177        }
178    );
179}
180
181macro_rules! generate_attribute_enum {
182    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
183        generate_attribute_enum!($(#[$meta])* $elem, $name, $ns, $attr, {$($(#[$enum_meta])* $enum => $enum_name),+});
184    );
185    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
186        $(#[$meta])*
187        #[derive(Debug, Clone, PartialEq)]
188        pub enum $elem {
189            $(
190            $(#[$enum_meta])*
191            $enum
192            ),+
193        }
194        impl ::try_from::TryFrom<::minidom::Element> for $elem {
195            type Err = crate::error::Error;
196            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
197                check_ns_only!(elem, $name, $ns);
198                check_no_children!(elem, $name);
199                check_no_unknown_attributes!(elem, $name, [$attr]);
200                Ok(match get_attr!(elem, $attr, required) {
201                    $($enum_name => $elem::$enum,)+
202                    _ => return Err(crate::error::Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))),
203                })
204            }
205        }
206        impl From<$elem> for ::minidom::Element {
207            fn from(elem: $elem) -> ::minidom::Element {
208                ::minidom::Element::builder($name)
209                        .ns(crate::ns::$ns)
210                        .attr($attr, match elem {
211                             $($elem::$enum => $enum_name,)+
212                         })
213                         .build()
214            }
215        }
216    );
217}
218
219macro_rules! check_self {
220    ($elem:ident, $name:tt, $ns:ident) => {
221        check_self!($elem, $name, $ns, $name);
222    };
223    ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
224        if !$elem.is($name, crate::ns::$ns) {
225            return Err(crate::error::Error::ParseError(concat!(
226                "This is not a ",
227                $pretty_name,
228                " element."
229            )));
230        }
231    };
232}
233
234macro_rules! check_ns_only {
235    ($elem:ident, $name:tt, $ns:ident) => {
236        if !$elem.has_ns(crate::ns::$ns) {
237            return Err(crate::error::Error::ParseError(concat!(
238                "This is not a ",
239                $name,
240                " element."
241            )));
242        }
243    };
244}
245
246macro_rules! check_no_children {
247    ($elem:ident, $name:tt) => {
248        for _ in $elem.children() {
249            return Err(crate::error::Error::ParseError(concat!(
250                "Unknown child in ",
251                $name,
252                " element."
253            )));
254        }
255    };
256}
257
258macro_rules! check_no_attributes {
259    ($elem:ident, $name:tt) => {
260        for _ in $elem.attrs() {
261            return Err(crate::error::Error::ParseError(concat!(
262                "Unknown attribute in ",
263                $name,
264                " element."
265            )));
266        }
267    };
268}
269
270macro_rules! check_no_unknown_attributes {
271    ($elem:ident, $name:tt, [$($attr:tt),*]) => (
272        for (_attr, _) in $elem.attrs() {
273            $(
274            if _attr == $attr {
275                continue;
276            }
277            )*
278            return Err(crate::error::Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
279        }
280    );
281}
282
283macro_rules! generate_empty_element {
284    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
285        $(#[$meta])*
286        #[derive(Debug, Clone)]
287        pub struct $elem;
288
289        impl ::try_from::TryFrom<::minidom::Element> for $elem {
290            type Err = crate::error::Error;
291
292            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
293                check_self!(elem, $name, $ns);
294                check_no_children!(elem, $name);
295                check_no_attributes!(elem, $name);
296                Ok($elem)
297            }
298        }
299
300        impl From<$elem> for ::minidom::Element {
301            fn from(_: $elem) -> ::minidom::Element {
302                ::minidom::Element::builder($name)
303                        .ns(crate::ns::$ns)
304                        .build()
305            }
306        }
307    );
308}
309
310macro_rules! generate_id {
311    ($(#[$meta:meta])* $elem:ident) => (
312        $(#[$meta])*
313        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
314        pub struct $elem(pub String);
315        impl ::std::str::FromStr for $elem {
316            type Err = crate::error::Error;
317            fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
318                // TODO: add a way to parse that differently when needed.
319                Ok($elem(String::from(s)))
320            }
321        }
322        impl ::minidom::IntoAttributeValue for $elem {
323            fn into_attribute_value(self) -> Option<String> {
324                Some(self.0)
325            }
326        }
327    );
328}
329
330macro_rules! generate_elem_id {
331    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
332        $(#[$meta])*
333        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
334        pub struct $elem(pub String);
335        impl ::std::str::FromStr for $elem {
336            type Err = crate::error::Error;
337            fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
338                // TODO: add a way to parse that differently when needed.
339                Ok($elem(String::from(s)))
340            }
341        }
342        impl ::try_from::TryFrom<::minidom::Element> for $elem {
343            type Err = crate::error::Error;
344            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
345                check_self!(elem, $name, $ns);
346                check_no_children!(elem, $name);
347                check_no_attributes!(elem, $name);
348                // TODO: add a way to parse that differently when needed.
349                Ok($elem(elem.text()))
350            }
351        }
352        impl From<$elem> for ::minidom::Element {
353            fn from(elem: $elem) -> ::minidom::Element {
354                ::minidom::Element::builder($name)
355                        .ns(crate::ns::$ns)
356                        .append(elem.0)
357                        .build()
358            }
359        }
360    );
361}
362
363macro_rules! start_decl {
364    (Vec, $type:ty) => (
365        Vec<$type>
366    );
367    (Option, $type:ty) => (
368        Option<$type>
369    );
370    (Required, $type:ty) => (
371        $type
372    );
373}
374
375macro_rules! start_parse_elem {
376    ($temp:ident: Vec) => {
377        let mut $temp = Vec::new();
378    };
379    ($temp:ident: Option) => {
380        let mut $temp = None;
381    };
382    ($temp:ident: Required) => {
383        let mut $temp = None;
384    };
385}
386
387macro_rules! do_parse {
388    ($elem:ident, Element) => {
389        $elem.clone()
390    };
391    ($elem:ident, String) => {
392        $elem.text()
393    };
394    ($elem:ident, $constructor:ident) => {
395        $constructor::try_from($elem.clone())?
396    };
397}
398
399macro_rules! do_parse_elem {
400    ($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
401        $temp.push(do_parse!($elem, $constructor));
402    };
403    ($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
404        if $temp.is_some() {
405            return Err(crate::error::Error::ParseError(concat!(
406                "Element ",
407                $parent_name,
408                " must not have more than one ",
409                $name,
410                " child."
411            )));
412        }
413        $temp = Some(do_parse!($elem, $constructor));
414    };
415    ($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
416        if $temp.is_some() {
417            return Err(crate::error::Error::ParseError(concat!(
418                "Element ",
419                $parent_name,
420                " must not have more than one ",
421                $name,
422                " child."
423            )));
424        }
425        $temp = Some(do_parse!($elem, $constructor));
426    };
427}
428
429macro_rules! finish_parse_elem {
430    ($temp:ident: Vec = $name:tt, $parent_name:tt) => {
431        $temp
432    };
433    ($temp:ident: Option = $name:tt, $parent_name:tt) => {
434        $temp
435    };
436    ($temp:ident: Required = $name:tt, $parent_name:tt) => {
437        $temp.ok_or(crate::error::Error::ParseError(concat!(
438            "Missing child ",
439            $name,
440            " in ",
441            $parent_name,
442            " element."
443        )))?
444    };
445}
446
447macro_rules! generate_serialiser {
448    ($parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => {
449        ::minidom::Element::builder($name)
450            .ns(crate::ns::$ns)
451            .append($parent.$elem)
452            .build()
453    };
454    ($parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
455        $parent.$elem.map(|elem| {
456            ::minidom::Element::builder($name)
457                .ns(crate::ns::$ns)
458                .append(elem)
459                .build()
460        })
461    };
462    ($parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
463        $parent.$elem
464    };
465}
466
467macro_rules! generate_element {
468    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+,]) => (
469        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
470    );
471    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
472        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
473    );
474    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:ident) => $child_constructor:ident),*]) => (
475        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
476    );
477    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),*,], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:ident) => $child_constructor:ident),*]) => (
478        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
479    );
480    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
481        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
482    );
483    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+], text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
484        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
485    );
486    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),*], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:ident) => $child_constructor:ident),*] $(, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >))*) => (
487        $(#[$meta])*
488        #[derive(Debug, Clone)]
489        pub struct $elem {
490            $(
491            $(#[$attr_meta])*
492            pub $attr: $attr_type,
493            )*
494            $(
495            $(#[$child_meta])*
496            pub $child_ident: start_decl!($coucou, $child_type),
497            )*
498            $(
499            $(#[$text_meta])*
500            pub $text_ident: $text_type,
501            )*
502        }
503
504        impl ::try_from::TryFrom<::minidom::Element> for $elem {
505            type Err = crate::error::Error;
506
507            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
508                check_self!(elem, $name, $ns);
509                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
510                $(
511                start_parse_elem!($child_ident: $coucou);
512                )*
513                for _child in elem.children() {
514                    $(
515                    if _child.is($child_name, crate::ns::$child_ns) {
516                        do_parse_elem!($child_ident: $coucou = $child_constructor => _child, $child_name, $name);
517                        continue;
518                    }
519                    )*
520                    return Err(crate::error::Error::ParseError(concat!("Unknown child in ", $name, " element.")));
521                }
522                Ok($elem {
523                    $(
524                    $attr: get_attr!(elem, $attr_name, $attr_action),
525                    )*
526                    $(
527                    $child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name),
528                    )*
529                    $(
530                    $text_ident: $codec::decode(&elem.text())?,
531                    )*
532                })
533            }
534        }
535
536        impl From<$elem> for ::minidom::Element {
537            fn from(elem: $elem) -> ::minidom::Element {
538                ::minidom::Element::builder($name)
539                        .ns(crate::ns::$ns)
540                        $(
541                        .attr($attr_name, elem.$attr)
542                        )*
543                        $(
544                        .append(generate_serialiser!(elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns)))
545                        )*
546                        $(
547                        .append($codec::encode(&elem.$text_ident))
548                        )*
549                        .build()
550            }
551        }
552    );
553}
554
555#[cfg(test)]
556macro_rules! assert_size (
557    ($t:ty, $sz:expr) => (
558        assert_eq!(::std::mem::size_of::<$t>(), $sz);
559    );
560);