Move Error, helpers and macros into a util module.
This commit is contained in:
parent
2a7cf487a4
commit
409a1dafa9
46 changed files with 98 additions and 107 deletions
125
src/util/compare_elements.rs
Normal file
125
src/util/compare_elements.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright (c) 2017 Astro <astro@spaceboyz.net>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use minidom::{Element, Node};
|
||||
|
||||
pub trait NamespaceAwareCompare {
|
||||
/// Namespace-aware comparison for tests
|
||||
fn compare_to(&self, other: &Self) -> bool;
|
||||
}
|
||||
|
||||
impl NamespaceAwareCompare for Node {
|
||||
fn compare_to(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(&Node::Element(ref elem1), &Node::Element(ref elem2)) => {
|
||||
Element::compare_to(elem1, elem2)
|
||||
}
|
||||
(&Node::Text(ref text1), &Node::Text(ref text2)) => text1 == text2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NamespaceAwareCompare for Element {
|
||||
fn compare_to(&self, other: &Self) -> bool {
|
||||
if self.name() == other.name() && self.ns() == other.ns() && self.attrs().eq(other.attrs())
|
||||
{
|
||||
let child_elems = self.children().count();
|
||||
let text_is_whitespace = self
|
||||
.texts()
|
||||
.all(|text| text.chars().all(char::is_whitespace));
|
||||
if child_elems > 0 && text_is_whitespace {
|
||||
// Ignore all the whitespace text nodes
|
||||
self.children()
|
||||
.zip(other.children())
|
||||
.all(|(node1, node2)| node1.compare_to(node2))
|
||||
} else {
|
||||
// Compare with text nodes
|
||||
self.nodes()
|
||||
.zip(other.nodes())
|
||||
.all(|(node1, node2)| node1.compare_to(node2))
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use minidom::Element;
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let elem1: Element = "<a a='b'>x <l/> 3</a>".parse().unwrap();
|
||||
let elem2: Element = "<a a='b'>x <l/> 3</a>".parse().unwrap();
|
||||
assert!(elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_attr_name() {
|
||||
let elem1: Element = "<a a='b'>x 3</a>".parse().unwrap();
|
||||
let elem2: Element = "<a c='b'>x 3</a>".parse().unwrap();
|
||||
assert!(!elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_attr_value() {
|
||||
let elem1: Element = "<a a='b'>x 3</a>".parse().unwrap();
|
||||
let elem2: Element = "<a a='c'>x 3</a>".parse().unwrap();
|
||||
assert!(!elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_order() {
|
||||
let elem1: Element = "<e1 a='b' c='d'/>".parse().unwrap();
|
||||
let elem2: Element = "<e1 c='d' a='b'/>".parse().unwrap();
|
||||
assert!(elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_texts() {
|
||||
let elem1: Element = "<e1>foo</e1>".parse().unwrap();
|
||||
let elem2: Element = "<e1>bar</e1>".parse().unwrap();
|
||||
assert!(!elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn children() {
|
||||
let elem1: Element = "<e1><foo/><bar/></e1>".parse().unwrap();
|
||||
let elem2: Element = "<e1><foo/><bar/></e1>".parse().unwrap();
|
||||
assert!(elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_children() {
|
||||
let elem1: Element = "<e1><foo/></e1>".parse().unwrap();
|
||||
let elem2: Element = "<e1><bar/></e1>".parse().unwrap();
|
||||
assert!(!elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xmlns_wrong() {
|
||||
let elem1: Element = "<e1 xmlns='ns1'><foo/></e1>".parse().unwrap();
|
||||
let elem2: Element = "<e1 xmlns='ns2'><foo/></e1>".parse().unwrap();
|
||||
assert!(!elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xmlns_other_prefix() {
|
||||
let elem1: Element = "<e1 xmlns='ns1'><foo/></e1>".parse().unwrap();
|
||||
let elem2: Element = "<x:e1 xmlns:x='ns1'><x:foo/></x:e1>".parse().unwrap();
|
||||
assert!(elem1.compare_to(&elem2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xmlns_dup() {
|
||||
let elem1: Element = "<e1 xmlns='ns1'><foo/></e1>".parse().unwrap();
|
||||
let elem2: Element = "<e1 xmlns='ns1'><foo xmlns='ns1'/></e1>".parse().unwrap();
|
||||
assert!(elem1.compare_to(&elem2));
|
||||
}
|
||||
}
|
||||
97
src/util/error.rs
Normal file
97
src/util/error.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright (c) 2017-2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use base64;
|
||||
use chrono;
|
||||
use jid;
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
use std::net;
|
||||
use std::num;
|
||||
use std::string;
|
||||
|
||||
/// Contains one of the potential errors triggered while parsing an
|
||||
/// [Element](../struct.Element.html) into a specialised struct.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// The usual error when parsing something.
|
||||
///
|
||||
/// TODO: use a structured error so the user can report it better, instead
|
||||
/// of a freeform string.
|
||||
ParseError(&'static str),
|
||||
|
||||
/// Generated when some base64 content fails to decode, usually due to
|
||||
/// extra characters.
|
||||
Base64Error(base64::DecodeError),
|
||||
|
||||
/// Generated when text which should be an integer fails to parse.
|
||||
ParseIntError(num::ParseIntError),
|
||||
|
||||
/// Generated when text which should be a string fails to parse.
|
||||
ParseStringError(string::ParseError),
|
||||
|
||||
/// Generated when text which should be an IP address (IPv4 or IPv6) fails
|
||||
/// to parse.
|
||||
ParseAddrError(net::AddrParseError),
|
||||
|
||||
/// Generated when text which should be a [JID](../../jid/struct.Jid.html)
|
||||
/// fails to parse.
|
||||
JidParseError(jid::JidParseError),
|
||||
|
||||
/// Generated when text which should be a
|
||||
/// [DateTime](../date/struct.DateTime.html) fails to parse.
|
||||
ChronoParseError(chrono::ParseError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::ParseError(s) => write!(fmt, "{}", s),
|
||||
Error::Base64Error(ref e) => write!(fmt, "{}", e),
|
||||
Error::ParseIntError(ref e) => write!(fmt, "{}", e),
|
||||
Error::ParseStringError(ref e) => write!(fmt, "{}", e),
|
||||
Error::ParseAddrError(ref e) => write!(fmt, "{}", e),
|
||||
Error::JidParseError(_) => write!(fmt, "JID parse error"),
|
||||
Error::ChronoParseError(ref e) => write!(fmt, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for Error {
|
||||
fn from(err: base64::DecodeError) -> Error {
|
||||
Error::Base64Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<num::ParseIntError> for Error {
|
||||
fn from(err: num::ParseIntError) -> Error {
|
||||
Error::ParseIntError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<string::ParseError> for Error {
|
||||
fn from(err: string::ParseError) -> Error {
|
||||
Error::ParseStringError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<net::AddrParseError> for Error {
|
||||
fn from(err: net::AddrParseError) -> Error {
|
||||
Error::ParseAddrError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jid::JidParseError> for Error {
|
||||
fn from(err: jid::JidParseError) -> Error {
|
||||
Error::JidParseError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<chrono::ParseError> for Error {
|
||||
fn from(err: chrono::ParseError) -> Error {
|
||||
Error::ChronoParseError(err)
|
||||
}
|
||||
}
|
||||
53
src/util/helpers.rs
Normal file
53
src/util/helpers.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use crate::util::error::Error;
|
||||
use base64;
|
||||
|
||||
/// Codec for plain text content.
|
||||
pub struct PlainText;
|
||||
|
||||
impl PlainText {
|
||||
pub fn decode(s: &str) -> Result<Option<String>, Error> {
|
||||
Ok(match s {
|
||||
"" => None,
|
||||
text => Some(text.to_owned()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode(string: &Option<String>) -> Option<String> {
|
||||
string.as_ref().map(|text| text.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Codec for trimmed plain text content.
|
||||
pub struct TrimmedPlainText;
|
||||
|
||||
impl TrimmedPlainText {
|
||||
pub fn decode(s: &str) -> Result<String, Error> {
|
||||
Ok(match s.trim() {
|
||||
"" => return Err(Error::ParseError("URI missing in uri.")),
|
||||
text => text.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode(string: &String) -> String {
|
||||
string.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Codec wrapping base64 encode/decode
|
||||
pub struct Base64;
|
||||
|
||||
impl Base64 {
|
||||
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
|
||||
Ok(base64::decode(s)?)
|
||||
}
|
||||
|
||||
pub fn encode(b: &Vec<u8>) -> Option<String> {
|
||||
Some(base64::encode(b))
|
||||
}
|
||||
}
|
||||
598
src/util/macros.rs
Normal file
598
src/util/macros.rs
Normal file
|
|
@ -0,0 +1,598 @@
|
|||
// Copyright (c) 2017-2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
macro_rules! get_attr {
|
||||
($elem:ident, $attr:tt, $type:tt) => {
|
||||
get_attr!($elem, $attr, $type, value, value.parse()?)
|
||||
};
|
||||
($elem:ident, $attr:tt, optional_empty, $value:ident, $func:expr) => {
|
||||
match $elem.attr($attr) {
|
||||
Some("") => None,
|
||||
Some($value) => Some($func),
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
($elem:ident, $attr:tt, optional, $value:ident, $func:expr) => {
|
||||
match $elem.attr($attr) {
|
||||
Some($value) => Some($func),
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
($elem:ident, $attr:tt, required, $value:ident, $func:expr) => {
|
||||
match $elem.attr($attr) {
|
||||
Some($value) => $func,
|
||||
None => {
|
||||
return Err(crate::util::error::Error::ParseError(concat!(
|
||||
"Required attribute '",
|
||||
$attr,
|
||||
"' missing."
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
($elem:ident, $attr:tt, default, $value:ident, $func:expr) => {
|
||||
match $elem.attr($attr) {
|
||||
Some($value) => $func,
|
||||
None => ::std::default::Default::default(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! generate_attribute {
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}) => (
|
||||
generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+});
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}, Default = $default:ident) => (
|
||||
generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+}, Default = $default);
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum $elem {
|
||||
$(
|
||||
$(#[$a_meta])*
|
||||
$a
|
||||
),+
|
||||
}
|
||||
impl ::std::str::FromStr for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
|
||||
Ok(match s {
|
||||
$($b => $elem::$a),+,
|
||||
_ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ::minidom::IntoAttributeValue for $elem {
|
||||
fn into_attribute_value(self) -> Option<String> {
|
||||
Some(String::from(match self {
|
||||
$($elem::$a => $b),+
|
||||
}))
|
||||
}
|
||||
}
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}, Default = $default:ident) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum $elem {
|
||||
$(
|
||||
$(#[$a_meta])*
|
||||
$a
|
||||
),+
|
||||
}
|
||||
impl ::std::str::FromStr for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
|
||||
Ok(match s {
|
||||
$($b => $elem::$a),+,
|
||||
_ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ::minidom::IntoAttributeValue for $elem {
|
||||
#[allow(unreachable_patterns)]
|
||||
fn into_attribute_value(self) -> Option<String> {
|
||||
Some(String::from(match self {
|
||||
$elem::$default => return None,
|
||||
$($elem::$a => $b),+
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl ::std::default::Default for $elem {
|
||||
fn default() -> $elem {
|
||||
$elem::$default
|
||||
}
|
||||
}
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, ($(#[$meta_symbol:meta])* $symbol:ident => $value:tt)) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum $elem {
|
||||
$(#[$meta_symbol])*
|
||||
$symbol,
|
||||
/// Value when absent.
|
||||
None,
|
||||
}
|
||||
impl ::std::str::FromStr for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn from_str(s: &str) -> Result<Self, crate::util::error::Error> {
|
||||
Ok(match s {
|
||||
$value => $elem::$symbol,
|
||||
_ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ::minidom::IntoAttributeValue for $elem {
|
||||
fn into_attribute_value(self) -> Option<String> {
|
||||
match self {
|
||||
$elem::$symbol => Some(String::from($value)),
|
||||
$elem::None => None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ::std::default::Default for $elem {
|
||||
fn default() -> $elem {
|
||||
$elem::None
|
||||
}
|
||||
}
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, bool) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum $elem {
|
||||
/// True value, represented by either 'true' or '1'.
|
||||
True,
|
||||
/// False value, represented by either 'false' or '0'.
|
||||
False,
|
||||
}
|
||||
impl ::std::str::FromStr for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn from_str(s: &str) -> Result<Self, crate::util::error::Error> {
|
||||
Ok(match s {
|
||||
"true" | "1" => $elem::True,
|
||||
"false" | "0" => $elem::False,
|
||||
_ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ::minidom::IntoAttributeValue for $elem {
|
||||
fn into_attribute_value(self) -> Option<String> {
|
||||
match self {
|
||||
$elem::True => Some(String::from("true")),
|
||||
$elem::False => None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ::std::default::Default for $elem {
|
||||
fn default() -> $elem {
|
||||
$elem::False
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! generate_element_enum {
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
|
||||
generate_element_enum!($(#[$meta])* $elem, $name, $ns, {$($(#[$enum_meta])* $enum => $enum_name),+});
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum $elem {
|
||||
$(
|
||||
$(#[$enum_meta])*
|
||||
$enum
|
||||
),+
|
||||
}
|
||||
impl ::try_from::TryFrom<::minidom::Element> for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
|
||||
check_ns_only!(elem, $name, $ns);
|
||||
check_no_children!(elem, $name);
|
||||
check_no_attributes!(elem, $name);
|
||||
Ok(match elem.name() {
|
||||
$($enum_name => $elem::$enum,)+
|
||||
_ => return Err(crate::util::error::Error::ParseError(concat!("This is not a ", $name, " element."))),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<$elem> for ::minidom::Element {
|
||||
fn from(elem: $elem) -> ::minidom::Element {
|
||||
::minidom::Element::builder(
|
||||
match elem {
|
||||
$($elem::$enum => $enum_name,)+
|
||||
}
|
||||
)
|
||||
.ns(crate::ns::$ns)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! generate_attribute_enum {
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
|
||||
generate_attribute_enum!($(#[$meta])* $elem, $name, $ns, $attr, {$($(#[$enum_meta])* $enum => $enum_name),+});
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum $elem {
|
||||
$(
|
||||
$(#[$enum_meta])*
|
||||
$enum
|
||||
),+
|
||||
}
|
||||
impl ::try_from::TryFrom<::minidom::Element> for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
|
||||
check_ns_only!(elem, $name, $ns);
|
||||
check_no_children!(elem, $name);
|
||||
check_no_unknown_attributes!(elem, $name, [$attr]);
|
||||
Ok(match get_attr!(elem, $attr, required) {
|
||||
$($enum_name => $elem::$enum,)+
|
||||
_ => return Err(crate::util::error::Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<$elem> for ::minidom::Element {
|
||||
fn from(elem: $elem) -> ::minidom::Element {
|
||||
::minidom::Element::builder($name)
|
||||
.ns(crate::ns::$ns)
|
||||
.attr($attr, match elem {
|
||||
$($elem::$enum => $enum_name,)+
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! check_self {
|
||||
($elem:ident, $name:tt, $ns:ident) => {
|
||||
check_self!($elem, $name, $ns, $name);
|
||||
};
|
||||
($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
|
||||
if !$elem.is($name, crate::ns::$ns) {
|
||||
return Err(crate::util::error::Error::ParseError(concat!(
|
||||
"This is not a ",
|
||||
$pretty_name,
|
||||
" element."
|
||||
)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_ns_only {
|
||||
($elem:ident, $name:tt, $ns:ident) => {
|
||||
if !$elem.has_ns(crate::ns::$ns) {
|
||||
return Err(crate::util::error::Error::ParseError(concat!(
|
||||
"This is not a ",
|
||||
$name,
|
||||
" element."
|
||||
)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_no_children {
|
||||
($elem:ident, $name:tt) => {
|
||||
#[cfg(not(feature = "disable-validation"))]
|
||||
for _ in $elem.children() {
|
||||
return Err(crate::util::error::Error::ParseError(concat!(
|
||||
"Unknown child in ",
|
||||
$name,
|
||||
" element."
|
||||
)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_no_attributes {
|
||||
($elem:ident, $name:tt) => {
|
||||
#[cfg(not(feature = "disable-validation"))]
|
||||
for _ in $elem.attrs() {
|
||||
return Err(crate::util::error::Error::ParseError(concat!(
|
||||
"Unknown attribute in ",
|
||||
$name,
|
||||
" element."
|
||||
)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_no_unknown_attributes {
|
||||
($elem:ident, $name:tt, [$($attr:tt),*]) => (
|
||||
#[cfg(not(feature = "disable-validation"))]
|
||||
for (_attr, _) in $elem.attrs() {
|
||||
$(
|
||||
if _attr == $attr {
|
||||
continue;
|
||||
}
|
||||
)*
|
||||
return Err(crate::util::error::Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! generate_empty_element {
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct $elem;
|
||||
|
||||
impl ::try_from::TryFrom<::minidom::Element> for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
|
||||
fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
|
||||
check_self!(elem, $name, $ns);
|
||||
check_no_children!(elem, $name);
|
||||
check_no_attributes!(elem, $name);
|
||||
Ok($elem)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$elem> for ::minidom::Element {
|
||||
fn from(_: $elem) -> ::minidom::Element {
|
||||
::minidom::Element::builder($name)
|
||||
.ns(crate::ns::$ns)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! generate_id {
|
||||
($(#[$meta:meta])* $elem:ident) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct $elem(pub String);
|
||||
impl ::std::str::FromStr for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
|
||||
// TODO: add a way to parse that differently when needed.
|
||||
Ok($elem(String::from(s)))
|
||||
}
|
||||
}
|
||||
impl ::minidom::IntoAttributeValue for $elem {
|
||||
fn into_attribute_value(self) -> Option<String> {
|
||||
Some(self.0)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! generate_elem_id {
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct $elem(pub String);
|
||||
impl ::std::str::FromStr for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
|
||||
// TODO: add a way to parse that differently when needed.
|
||||
Ok($elem(String::from(s)))
|
||||
}
|
||||
}
|
||||
impl ::try_from::TryFrom<::minidom::Element> for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
|
||||
check_self!(elem, $name, $ns);
|
||||
check_no_children!(elem, $name);
|
||||
check_no_attributes!(elem, $name);
|
||||
// TODO: add a way to parse that differently when needed.
|
||||
Ok($elem(elem.text()))
|
||||
}
|
||||
}
|
||||
impl From<$elem> for ::minidom::Element {
|
||||
fn from(elem: $elem) -> ::minidom::Element {
|
||||
::minidom::Element::builder($name)
|
||||
.ns(crate::ns::$ns)
|
||||
.append(elem.0)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! start_decl {
|
||||
(Vec, $type:ty) => (
|
||||
Vec<$type>
|
||||
);
|
||||
(Option, $type:ty) => (
|
||||
Option<$type>
|
||||
);
|
||||
(Required, $type:ty) => (
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! start_parse_elem {
|
||||
($temp:ident: Vec) => {
|
||||
let mut $temp = Vec::new();
|
||||
};
|
||||
($temp:ident: Option) => {
|
||||
let mut $temp = None;
|
||||
};
|
||||
($temp:ident: Required) => {
|
||||
let mut $temp = None;
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! do_parse {
|
||||
($elem:ident, Element) => {
|
||||
$elem.clone()
|
||||
};
|
||||
($elem:ident, String) => {
|
||||
$elem.text()
|
||||
};
|
||||
($elem:ident, $constructor:ident) => {
|
||||
$constructor::try_from($elem.clone())?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! do_parse_elem {
|
||||
($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
|
||||
$temp.push(do_parse!($elem, $constructor));
|
||||
};
|
||||
($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
|
||||
if $temp.is_some() {
|
||||
return Err(crate::util::error::Error::ParseError(concat!(
|
||||
"Element ",
|
||||
$parent_name,
|
||||
" must not have more than one ",
|
||||
$name,
|
||||
" child."
|
||||
)));
|
||||
}
|
||||
$temp = Some(do_parse!($elem, $constructor));
|
||||
};
|
||||
($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
|
||||
if $temp.is_some() {
|
||||
return Err(crate::util::error::Error::ParseError(concat!(
|
||||
"Element ",
|
||||
$parent_name,
|
||||
" must not have more than one ",
|
||||
$name,
|
||||
" child."
|
||||
)));
|
||||
}
|
||||
$temp = Some(do_parse!($elem, $constructor));
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! finish_parse_elem {
|
||||
($temp:ident: Vec = $name:tt, $parent_name:tt) => {
|
||||
$temp
|
||||
};
|
||||
($temp:ident: Option = $name:tt, $parent_name:tt) => {
|
||||
$temp
|
||||
};
|
||||
($temp:ident: Required = $name:tt, $parent_name:tt) => {
|
||||
$temp.ok_or(crate::util::error::Error::ParseError(concat!(
|
||||
"Missing child ",
|
||||
$name,
|
||||
" in ",
|
||||
$parent_name,
|
||||
" element."
|
||||
)))?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! generate_serialiser {
|
||||
($parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => {
|
||||
::minidom::Element::builder($name)
|
||||
.ns(crate::ns::$ns)
|
||||
.append($parent.$elem)
|
||||
.build()
|
||||
};
|
||||
($parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
|
||||
$parent.$elem.map(|elem| {
|
||||
::minidom::Element::builder($name)
|
||||
.ns(crate::ns::$ns)
|
||||
.append(elem)
|
||||
.build()
|
||||
})
|
||||
};
|
||||
($parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
|
||||
$parent.$elem
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! generate_element {
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+,]) => (
|
||||
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
|
||||
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
|
||||
);
|
||||
($(#[$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),*]) => (
|
||||
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
|
||||
);
|
||||
($(#[$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),*]) => (
|
||||
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),*]);
|
||||
);
|
||||
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
|
||||
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
|
||||
);
|
||||
($(#[$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 >)) => (
|
||||
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>));
|
||||
);
|
||||
($(#[$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 >))*) => (
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct $elem {
|
||||
$(
|
||||
$(#[$attr_meta])*
|
||||
pub $attr: $attr_type,
|
||||
)*
|
||||
$(
|
||||
$(#[$child_meta])*
|
||||
pub $child_ident: start_decl!($coucou, $child_type),
|
||||
)*
|
||||
$(
|
||||
$(#[$text_meta])*
|
||||
pub $text_ident: $text_type,
|
||||
)*
|
||||
}
|
||||
|
||||
impl ::try_from::TryFrom<::minidom::Element> for $elem {
|
||||
type Err = crate::util::error::Error;
|
||||
|
||||
fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::util::error::Error> {
|
||||
check_self!(elem, $name, $ns);
|
||||
check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
|
||||
$(
|
||||
start_parse_elem!($child_ident: $coucou);
|
||||
)*
|
||||
for _child in elem.children() {
|
||||
$(
|
||||
if _child.is($child_name, crate::ns::$child_ns) {
|
||||
do_parse_elem!($child_ident: $coucou = $child_constructor => _child, $child_name, $name);
|
||||
continue;
|
||||
}
|
||||
)*
|
||||
return Err(crate::util::error::Error::ParseError(concat!("Unknown child in ", $name, " element.")));
|
||||
}
|
||||
Ok($elem {
|
||||
$(
|
||||
$attr: get_attr!(elem, $attr_name, $attr_action),
|
||||
)*
|
||||
$(
|
||||
$child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name),
|
||||
)*
|
||||
$(
|
||||
$text_ident: $codec::decode(&elem.text())?,
|
||||
)*
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$elem> for ::minidom::Element {
|
||||
fn from(elem: $elem) -> ::minidom::Element {
|
||||
::minidom::Element::builder($name)
|
||||
.ns(crate::ns::$ns)
|
||||
$(
|
||||
.attr($attr_name, elem.$attr)
|
||||
)*
|
||||
$(
|
||||
.append(generate_serialiser!(elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns)))
|
||||
)*
|
||||
$(
|
||||
.append($codec::encode(&elem.$text_ident))
|
||||
)*
|
||||
.build()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! assert_size (
|
||||
($t:ty, $sz:expr) => (
|
||||
assert_eq!(::std::mem::size_of::<$t>(), $sz);
|
||||
);
|
||||
);
|
||||
Loading…
Reference in a new issue