Prepare for merge: Move all xmpp-parsers files into xmpp-parsers/

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2019-10-18 13:45:51 +02:00
commit dc76f296de
No known key found for this signature in database
GPG key ID: DEDA74AEECA9D0F2
79 changed files with 0 additions and 0 deletions

View 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 crate::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));
}
}

View file

@ -0,0 +1,105 @@
// 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 std::error::Error as StdError;
use std::fmt;
/// 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(std::num::ParseIntError),
/// Generated when text which should be a string fails to parse.
ParseStringError(std::string::ParseError),
/// Generated when text which should be an IP address (IPv4 or IPv6) fails
/// to parse.
ParseAddrError(std::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 StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match self {
Error::ParseError(_) => None,
Error::Base64Error(e) => Some(e),
Error::ParseIntError(e) => Some(e),
Error::ParseStringError(e) => Some(e),
Error::ParseAddrError(e) => Some(e),
Error::JidParseError(e) => Some(e),
Error::ChronoParseError(e) => Some(e),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::ParseError(s) => write!(fmt, "parse error: {}", s),
Error::Base64Error(e) => write!(fmt, "base64 error: {}", e),
Error::ParseIntError(e) => write!(fmt, "integer parsing error: {}", e),
Error::ParseStringError(e) => write!(fmt, "string parsing error: {}", e),
Error::ParseAddrError(e) => write!(fmt, "IP address parsing error: {}", e),
Error::JidParseError(e) => write!(fmt, "JID parsing error: {}", e),
Error::ChronoParseError(e) => write!(fmt, "time parsing error: {}", e),
}
}
}
impl From<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Error {
Error::Base64Error(err)
}
}
impl From<std::num::ParseIntError> for Error {
fn from(err: std::num::ParseIntError) -> Error {
Error::ParseIntError(err)
}
}
impl From<std::string::ParseError> for Error {
fn from(err: std::string::ParseError) -> Error {
Error::ParseStringError(err)
}
}
impl From<std::net::AddrParseError> for Error {
fn from(err: std::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)
}
}

View file

@ -0,0 +1,119 @@
// 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 jid::Jid;
use std::str::FromStr;
/// Codec for text content.
pub struct Text;
impl Text {
pub fn decode(s: &str) -> Result<String, Error> {
Ok(s.to_owned())
}
pub fn encode(string: &str) -> Option<String> {
Some(string.to_owned())
}
}
/// 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(ToOwned::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: &str) -> Option<String> {
Some(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: &[u8]) -> Option<String> {
Some(base64::encode(b))
}
}
/// Codec wrapping base64 encode/decode, while ignoring whitespace characters.
pub struct WhitespaceAwareBase64;
impl WhitespaceAwareBase64 {
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
let s: String = s.chars().filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t').collect();
Ok(base64::decode(&s)?)
}
pub fn encode(b: &[u8]) -> Option<String> {
Some(base64::encode(b))
}
}
/// Codec for colon-separated bytes of uppercase hexadecimal.
pub struct ColonSeparatedHex;
impl ColonSeparatedHex {
pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
let mut bytes = vec![];
for i in 0..(1 + s.len()) / 3 {
let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?;
if 3 * i + 2 < s.len() {
assert_eq!(&s[3 * i + 2..3 * i + 3], ":");
}
bytes.push(byte);
}
Ok(bytes)
}
pub fn encode(b: &[u8]) -> Option<String> {
let mut bytes = vec![];
for byte in b {
bytes.push(format!("{:02X}", byte));
}
Some(bytes.join(":"))
}
}
/// Codec for a JID.
pub struct JidCodec;
impl JidCodec {
pub fn decode(s: &str) -> Result<Jid, Error> {
Ok(Jid::from_str(s)?)
}
pub fn encode(jid: &Jid) -> Option<String> {
Some(jid.to_string())
}
}

View file

@ -0,0 +1,808 @@
// 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, OptionEmpty, $value:ident, $func:expr) => {
match $elem.attr($attr) {
Some("") => None,
Some($value) => Some($func),
None => None,
}
};
($elem:ident, $attr:tt, Option, $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, RequiredNonEmpty, $value:ident, $func:expr) => {
match $elem.attr($attr) {
Some("") => {
return Err(crate::util::error::Error::ParseError(concat!(
"Required attribute '",
$attr,
"' must not be empty."
)));
},
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 std::fmt::Display for $elem {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(fmt, "{}", match self {
$($elem::$a => $b),+
})
}
}
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
}
}
);
($(#[$meta:meta])* $elem:ident, $name:tt, $type:tt, Default = $default:expr) => (
$(#[$meta])*
#[derive(Debug, Clone, PartialEq)]
pub struct $elem(pub $type);
impl ::std::str::FromStr for $elem {
type Err = crate::util::error::Error;
fn from_str(s: &str) -> Result<Self, crate::util::error::Error> {
Ok($elem($type::from_str(s)?))
}
}
impl ::minidom::IntoAttributeValue for $elem {
fn into_attribute_value(self) -> Option<String> {
match self {
$elem($default) => None,
$elem(value) => Some(format!("{}", value)),
}
}
}
impl ::std::default::Default for $elem {
fn default() -> $elem {
$elem($default)
}
}
);
}
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 ::std::convert::TryFrom<crate::Element> for $elem {
type Error = crate::util::error::Error;
fn try_from(elem: crate::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 crate::Element {
fn from(elem: $elem) -> crate::Element {
crate::Element::builder(
match elem {
$($elem::$enum => $enum_name,)+
}
)
.ns(crate::ns::$ns)
.build()
}
}
impl From<$elem> for ::minidom::Node {
fn from(elem: $elem) -> ::minidom::Node {
::minidom::Node::Element(elem.into())
}
}
);
}
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 ::std::convert::TryFrom<crate::Element> for $elem {
type Error = crate::util::error::Error;
fn try_from(elem: crate::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 crate::Element {
fn from(elem: $elem) -> crate::Element {
crate::Element::builder($name)
.ns(crate::ns::$ns)
.attr($attr, match elem {
$($elem::$enum => $enum_name,)+
})
.build()
}
}
impl From<$elem> for ::minidom::Node {
fn from(elem: $elem) -> ::minidom::Node {
::minidom::Node::Element(elem.into())
}
}
);
}
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 ::std::convert::TryFrom<crate::Element> for $elem {
type Error = crate::util::error::Error;
fn try_from(elem: crate::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 crate::Element {
fn from(_: $elem) -> crate::Element {
crate::Element::builder($name)
.ns(crate::ns::$ns)
.build()
}
}
impl From<$elem> for ::minidom::Node {
fn from(elem: $elem) -> ::minidom::Node {
::minidom::Node::Element(elem.into())
}
}
);
}
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) => (
generate_elem_id!($(#[$meta])* $elem, $name, $ns, 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)))
}
}
);
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $type:ty) => (
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $elem(pub $type);
impl ::std::convert::TryFrom<crate::Element> for $elem {
type Error = crate::util::error::Error;
fn try_from(elem: crate::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().parse()?))
}
}
impl From<$elem> for crate::Element {
fn from(elem: $elem) -> crate::Element {
crate::Element::builder($name)
.ns(crate::ns::$ns)
.append(elem.0.to_string())
.build()
}
}
impl From<$elem> for ::minidom::Node {
fn from(elem: $elem) -> ::minidom::Node {
::minidom::Node::Element(elem.into())
}
}
);
}
macro_rules! decl_attr {
(OptionEmpty, $type:ty) => (
Option<$type>
);
(Option, $type:ty) => (
Option<$type>
);
(Required, $type:ty) => (
$type
);
(RequiredNonEmpty, $type:ty) => (
$type
);
(Default, $type:ty) => (
$type
);
}
macro_rules! start_decl {
(Vec, $type:ty) => (
Vec<$type>
);
(Option, $type:ty) => (
Option<$type>
);
(Required, $type:ty) => (
$type
);
(Present, $type:ty) => (
bool
);
}
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;
};
($temp:ident: Present) => {
let mut $temp = false;
};
}
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));
};
($temp:ident: Present = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
if $temp {
return Err(crate::util::error::Error::ParseError(concat!(
"Element ",
$parent_name,
" must not have more than one ",
$name,
" child."
)));
}
$temp = true;
};
}
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."
)))?
};
($temp:ident: Present = $name:tt, $parent_name:tt) => {
$temp
};
}
macro_rules! generate_serialiser {
($builder:ident, $parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => {
$builder.append(
crate::Element::builder($name)
.ns(crate::ns::$ns)
.append(::minidom::Node::Text($parent.$elem))
)
};
($builder:ident, $parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
$builder.append_all($parent.$elem.map(|elem| {
crate::Element::builder($name)
.ns(crate::ns::$ns)
.append(::minidom::Node::Text(elem))
})
)
};
($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, *)) => {
$builder.append_all($parent.$elem.map(|elem| {
crate::Element::builder($name)
.ns(elem.get_ns())
.append(::minidom::Node::Element(crate::Element::from(elem)))
})
)
};
($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append_all($parent.$elem.map(|elem| {
crate::Element::builder($name)
.ns(crate::ns::$ns)
.append(::minidom::Node::Element(crate::Element::from(elem)))
})
)
};
($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append_all($parent.$elem.into_iter())
};
($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append(::minidom::Node::Element(crate::Element::builder($name).ns(crate::ns::$ns).build()))
};
($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append(::minidom::Node::Element(crate::Element::from($parent.$elem)))
};
}
macro_rules! generate_child_test {
($child:ident, $name:tt, *) => {
true
};
($child:ident, $name:tt, $ns:tt) => {
$child.is($name, crate::ns::$ns)
};
}
macro_rules! generate_element {
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+,]) => (
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: []);
);
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+]) => (
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], 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:tt) => $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_action:tt<$attr_type:ty> = $attr_name:tt),*,], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*]) => (
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], 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_action:tt<$attr_type:ty> = $attr_name:tt),+], text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
);
($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),*], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $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: decl_attr!($attr_action, $attr_type),
)*
$(
$(#[$child_meta])*
pub $child_ident: start_decl!($coucou, $child_type),
)*
$(
$(#[$text_meta])*
pub $text_ident: $text_type,
)*
}
impl ::std::convert::TryFrom<crate::Element> for $elem {
type Error = crate::util::error::Error;
fn try_from(elem: crate::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 generate_child_test!(_child, $child_name, $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 crate::Element {
fn from(elem: $elem) -> crate::Element {
let mut builder = crate::Element::builder($name)
.ns(crate::ns::$ns);
$(
builder = builder.attr($attr_name, elem.$attr);
)*
$(
builder = generate_serialiser!(builder, elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns));
)*
$(
builder = builder.append_all($codec::encode(&elem.$text_ident).map(::minidom::Node::Text).into_iter());
)*
builder.build()
}
}
impl From<$elem> for ::minidom::Node {
fn from(elem: $elem) -> ::minidom::Node {
::minidom::Node::Element(elem.into())
}
}
);
}
#[cfg(test)]
macro_rules! assert_size (
($t:ty, $sz:expr) => (
assert_eq!(::std::mem::size_of::<$t>(), $sz);
);
);
// TODO: move that to src/pubsub/mod.rs, once we figure out how to use macros from there.
macro_rules! impl_pubsub_item {
($item:ident, $ns:ident) => {
impl ::std::convert::TryFrom<crate::Element> for $item {
type Error = Error;
fn try_from(elem: crate::Element) -> Result<$item, Error> {
check_self!(elem, "item", $ns);
check_no_unknown_attributes!(elem, "item", ["id", "publisher"]);
let mut payloads = elem.children().cloned().collect::<Vec<_>>();
let payload = payloads.pop();
if !payloads.is_empty() {
return Err(Error::ParseError(
"More than a single payload in item element.",
));
}
Ok($item(crate::pubsub::Item {
id: get_attr!(elem, "id", Option),
publisher: get_attr!(elem, "publisher", Option),
payload,
}))
}
}
impl From<$item> for crate::Element {
fn from(item: $item) -> crate::Element {
crate::Element::builder("item")
.ns(ns::$ns)
.attr("id", item.0.id)
.attr("publisher", item.0.publisher)
.append_all(item.0.payload)
.build()
}
}
impl From<$item> for ::minidom::Node {
fn from(item: $item) -> ::minidom::Node {
::minidom::Node::Element(item.into())
}
}
impl ::std::ops::Deref for $item {
type Target = crate::pubsub::Item;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ::std::ops::DerefMut for $item {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
}
}

View file

@ -0,0 +1,19 @@
// Copyright (c) 2019 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/.
/// Error type returned by every parser on failure.
pub mod error;
/// Various helpers.
pub(crate) mod helpers;
/// Helper macros to parse and serialise more easily.
#[macro_use]
mod macros;
#[cfg(test)]
/// Namespace-aware comparison for tests
pub(crate) mod compare_elements;