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