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