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::util::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::util::error::Error;
 62            fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
 63                Ok(match s {
 64                    $($b => $elem::$a),+,
 65                    _ => return Err(crate::util::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::util::error::Error;
 88            fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
 89                Ok(match s {
 90                    $($b => $elem::$a),+,
 91                    _ => return Err(crate::util::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, ($(#[$meta_symbol:meta])* $symbol:ident => $value:tt)) => (
111        $(#[$meta])*
112        #[derive(Debug, Clone, PartialEq)]
113        pub enum $elem {
114            $(#[$meta_symbol])*
115            $symbol,
116            /// Value when absent.
117            None,
118        }
119        impl ::std::str::FromStr for $elem {
120            type Err = crate::util::error::Error;
121            fn from_str(s: &str) -> Result<Self, crate::util::error::Error> {
122                Ok(match s {
123                    $value => $elem::$symbol,
124                    _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
125                })
126            }
127        }
128        impl ::minidom::IntoAttributeValue for $elem {
129            fn into_attribute_value(self) -> Option<String> {
130                match self {
131                    $elem::$symbol => Some(String::from($value)),
132                    $elem::None => None
133                }
134            }
135        }
136        impl ::std::default::Default for $elem {
137            fn default() -> $elem {
138                $elem::None
139            }
140        }
141    );
142    ($(#[$meta:meta])* $elem:ident, $name:tt, bool) => (
143        $(#[$meta])*
144        #[derive(Debug, Clone, PartialEq)]
145        pub enum $elem {
146            /// True value, represented by either 'true' or '1'.
147            True,
148            /// False value, represented by either 'false' or '0'.
149            False,
150        }
151        impl ::std::str::FromStr for $elem {
152            type Err = crate::util::error::Error;
153            fn from_str(s: &str) -> Result<Self, crate::util::error::Error> {
154                Ok(match s {
155                    "true" | "1" => $elem::True,
156                    "false" | "0" => $elem::False,
157                    _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
158                })
159            }
160        }
161        impl ::minidom::IntoAttributeValue for $elem {
162            fn into_attribute_value(self) -> Option<String> {
163                match self {
164                    $elem::True => Some(String::from("true")),
165                    $elem::False => None
166                }
167            }
168        }
169        impl ::std::default::Default for $elem {
170            fn default() -> $elem {
171                $elem::False
172            }
173        }
174    );
175}
176
177macro_rules! generate_element_enum {
178    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
179        generate_element_enum!($(#[$meta])* $elem, $name, $ns, {$($(#[$enum_meta])* $enum => $enum_name),+});
180    );
181    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
182        $(#[$meta])*
183        #[derive(Debug, Clone, PartialEq)]
184        pub enum $elem {
185            $(
186                $(#[$enum_meta])*
187                $enum
188            ),+
189        }
190        impl ::try_from::TryFrom<::minidom::Element> for $elem {
191            type Err = crate::util::error::Error;
192            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
193                check_ns_only!(elem, $name, $ns);
194                check_no_children!(elem, $name);
195                check_no_attributes!(elem, $name);
196                Ok(match elem.name() {
197                    $($enum_name => $elem::$enum,)+
198                    _ => return Err(crate::util::error::Error::ParseError(concat!("This is not a ", $name, " element."))),
199                })
200            }
201        }
202        impl From<$elem> for ::minidom::Element {
203            fn from(elem: $elem) -> ::minidom::Element {
204                ::minidom::Element::builder(
205                    match elem {
206                        $($elem::$enum => $enum_name,)+
207                    }
208                )
209                    .ns(crate::ns::$ns)
210                    .build()
211            }
212        }
213    );
214}
215
216macro_rules! generate_attribute_enum {
217    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
218        generate_attribute_enum!($(#[$meta])* $elem, $name, $ns, $attr, {$($(#[$enum_meta])* $enum => $enum_name),+});
219    );
220    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
221        $(#[$meta])*
222        #[derive(Debug, Clone, PartialEq)]
223        pub enum $elem {
224            $(
225                $(#[$enum_meta])*
226                $enum
227            ),+
228        }
229        impl ::try_from::TryFrom<::minidom::Element> for $elem {
230            type Err = crate::util::error::Error;
231            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
232                check_ns_only!(elem, $name, $ns);
233                check_no_children!(elem, $name);
234                check_no_unknown_attributes!(elem, $name, [$attr]);
235                Ok(match get_attr!(elem, $attr, required) {
236                    $($enum_name => $elem::$enum,)+
237                    _ => return Err(crate::util::error::Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))),
238                })
239            }
240        }
241        impl From<$elem> for ::minidom::Element {
242            fn from(elem: $elem) -> ::minidom::Element {
243                ::minidom::Element::builder($name)
244                    .ns(crate::ns::$ns)
245                    .attr($attr, match elem {
246                         $($elem::$enum => $enum_name,)+
247                     })
248                     .build()
249            }
250        }
251    );
252}
253
254macro_rules! check_self {
255    ($elem:ident, $name:tt, $ns:ident) => {
256        check_self!($elem, $name, $ns, $name);
257    };
258    ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
259        if !$elem.is($name, crate::ns::$ns) {
260            return Err(crate::util::error::Error::ParseError(concat!(
261                "This is not a ",
262                $pretty_name,
263                " element."
264            )));
265        }
266    };
267}
268
269macro_rules! check_ns_only {
270    ($elem:ident, $name:tt, $ns:ident) => {
271        if !$elem.has_ns(crate::ns::$ns) {
272            return Err(crate::util::error::Error::ParseError(concat!(
273                "This is not a ",
274                $name,
275                " element."
276            )));
277        }
278    };
279}
280
281macro_rules! check_no_children {
282    ($elem:ident, $name:tt) => {
283        #[cfg(not(feature = "disable-validation"))]
284        for _ in $elem.children() {
285            return Err(crate::util::error::Error::ParseError(concat!(
286                "Unknown child in ",
287                $name,
288                " element."
289            )));
290        }
291    };
292}
293
294macro_rules! check_no_attributes {
295    ($elem:ident, $name:tt) => {
296        #[cfg(not(feature = "disable-validation"))]
297        for _ in $elem.attrs() {
298            return Err(crate::util::error::Error::ParseError(concat!(
299                "Unknown attribute in ",
300                $name,
301                " element."
302            )));
303        }
304    };
305}
306
307macro_rules! check_no_unknown_attributes {
308    ($elem:ident, $name:tt, [$($attr:tt),*]) => (
309        #[cfg(not(feature = "disable-validation"))]
310        for (_attr, _) in $elem.attrs() {
311            $(
312                if _attr == $attr {
313                    continue;
314                }
315            )*
316            return Err(crate::util::error::Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
317        }
318    );
319}
320
321macro_rules! generate_empty_element {
322    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
323        $(#[$meta])*
324        #[derive(Debug, Clone)]
325        pub struct $elem;
326
327        impl ::try_from::TryFrom<::minidom::Element> for $elem {
328            type Err = crate::util::error::Error;
329
330            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
331                check_self!(elem, $name, $ns);
332                check_no_children!(elem, $name);
333                check_no_attributes!(elem, $name);
334                Ok($elem)
335            }
336        }
337
338        impl From<$elem> for ::minidom::Element {
339            fn from(_: $elem) -> ::minidom::Element {
340                ::minidom::Element::builder($name)
341                    .ns(crate::ns::$ns)
342                    .build()
343            }
344        }
345    );
346}
347
348macro_rules! generate_id {
349    ($(#[$meta:meta])* $elem:ident) => (
350        $(#[$meta])*
351        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
352        pub struct $elem(pub String);
353        impl ::std::str::FromStr for $elem {
354            type Err = crate::util::error::Error;
355            fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
356                // TODO: add a way to parse that differently when needed.
357                Ok($elem(String::from(s)))
358            }
359        }
360        impl ::minidom::IntoAttributeValue for $elem {
361            fn into_attribute_value(self) -> Option<String> {
362                Some(self.0)
363            }
364        }
365    );
366}
367
368macro_rules! generate_elem_id {
369    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
370        $(#[$meta])*
371        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
372        pub struct $elem(pub String);
373        impl ::std::str::FromStr for $elem {
374            type Err = crate::util::error::Error;
375            fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
376                // TODO: add a way to parse that differently when needed.
377                Ok($elem(String::from(s)))
378            }
379        }
380        impl ::try_from::TryFrom<::minidom::Element> for $elem {
381            type Err = crate::util::error::Error;
382            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
383                check_self!(elem, $name, $ns);
384                check_no_children!(elem, $name);
385                check_no_attributes!(elem, $name);
386                // TODO: add a way to parse that differently when needed.
387                Ok($elem(elem.text()))
388            }
389        }
390        impl From<$elem> for ::minidom::Element {
391            fn from(elem: $elem) -> ::minidom::Element {
392                ::minidom::Element::builder($name)
393                    .ns(crate::ns::$ns)
394                    .append(elem.0)
395                    .build()
396            }
397        }
398    );
399}
400
401macro_rules! start_decl {
402    (Vec, $type:ty) => (
403        Vec<$type>
404    );
405    (Option, $type:ty) => (
406        Option<$type>
407    );
408    (Required, $type:ty) => (
409        $type
410    );
411}
412
413macro_rules! start_parse_elem {
414    ($temp:ident: Vec) => {
415        let mut $temp = Vec::new();
416    };
417    ($temp:ident: Option) => {
418        let mut $temp = None;
419    };
420    ($temp:ident: Required) => {
421        let mut $temp = None;
422    };
423}
424
425macro_rules! do_parse {
426    ($elem:ident, Element) => {
427        $elem.clone()
428    };
429    ($elem:ident, String) => {
430        $elem.text()
431    };
432    ($elem:ident, $constructor:ident) => {
433        $constructor::try_from($elem.clone())?
434    };
435}
436
437macro_rules! do_parse_elem {
438    ($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
439        $temp.push(do_parse!($elem, $constructor));
440    };
441    ($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
442        if $temp.is_some() {
443            return Err(crate::util::error::Error::ParseError(concat!(
444                "Element ",
445                $parent_name,
446                " must not have more than one ",
447                $name,
448                " child."
449            )));
450        }
451        $temp = Some(do_parse!($elem, $constructor));
452    };
453    ($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
454        if $temp.is_some() {
455            return Err(crate::util::error::Error::ParseError(concat!(
456                "Element ",
457                $parent_name,
458                " must not have more than one ",
459                $name,
460                " child."
461            )));
462        }
463        $temp = Some(do_parse!($elem, $constructor));
464    };
465}
466
467macro_rules! finish_parse_elem {
468    ($temp:ident: Vec = $name:tt, $parent_name:tt) => {
469        $temp
470    };
471    ($temp:ident: Option = $name:tt, $parent_name:tt) => {
472        $temp
473    };
474    ($temp:ident: Required = $name:tt, $parent_name:tt) => {
475        $temp.ok_or(crate::util::error::Error::ParseError(concat!(
476            "Missing child ",
477            $name,
478            " in ",
479            $parent_name,
480            " element."
481        )))?
482    };
483}
484
485macro_rules! generate_serialiser {
486    ($parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => {
487        ::minidom::Element::builder($name)
488            .ns(crate::ns::$ns)
489            .append($parent.$elem)
490            .build()
491    };
492    ($parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
493        $parent.$elem.map(|elem| {
494            ::minidom::Element::builder($name)
495                .ns(crate::ns::$ns)
496                .append(elem)
497                .build()
498        })
499    };
500    ($parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
501        $parent.$elem
502    };
503}
504
505macro_rules! generate_element {
506    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+,]) => (
507        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
508    );
509    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
510        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
511    );
512    ($(#[$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),*]) => (
513        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
514    );
515    ($(#[$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),*]) => (
516        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),*]);
517    );
518    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
519        generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
520    );
521    ($(#[$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 >)) => (
522        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>));
523    );
524    ($(#[$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 >))*) => (
525        $(#[$meta])*
526        #[derive(Debug, Clone)]
527        pub struct $elem {
528            $(
529                $(#[$attr_meta])*
530                pub $attr: $attr_type,
531            )*
532            $(
533                $(#[$child_meta])*
534                pub $child_ident: start_decl!($coucou, $child_type),
535            )*
536            $(
537                $(#[$text_meta])*
538                pub $text_ident: $text_type,
539            )*
540        }
541
542        impl ::try_from::TryFrom<::minidom::Element> for $elem {
543            type Err = crate::util::error::Error;
544
545            fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
546                check_self!(elem, $name, $ns);
547                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
548                $(
549                    start_parse_elem!($child_ident: $coucou);
550                )*
551                for _child in elem.children() {
552                    $(
553                    if _child.is($child_name, crate::ns::$child_ns) {
554                        do_parse_elem!($child_ident: $coucou = $child_constructor => _child, $child_name, $name);
555                        continue;
556                    }
557                    )*
558                    return Err(crate::util::error::Error::ParseError(concat!("Unknown child in ", $name, " element.")));
559                }
560                Ok($elem {
561                    $(
562                        $attr: get_attr!(elem, $attr_name, $attr_action),
563                    )*
564                    $(
565                        $child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name),
566                    )*
567                    $(
568                        $text_ident: $codec::decode(&elem.text())?,
569                    )*
570                })
571            }
572        }
573
574        impl From<$elem> for ::minidom::Element {
575            fn from(elem: $elem) -> ::minidom::Element {
576                ::minidom::Element::builder($name)
577                    .ns(crate::ns::$ns)
578                    $(
579                        .attr($attr_name, elem.$attr)
580                    )*
581                    $(
582                        .append(generate_serialiser!(elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns)))
583                    )*
584                    $(
585                        .append($codec::encode(&elem.$text_ident))
586                    )*
587                    .build()
588            }
589        }
590    );
591}
592
593#[cfg(test)]
594macro_rules! assert_size (
595    ($t:ty, $sz:expr) => (
596        assert_eq!(::std::mem::size_of::<$t>(), $sz);
597    );
598);