parsers: port extracts over to derive macros
This commit is contained in:
parent
2c5f1f096b
commit
1e0bccc504
4 changed files with 240 additions and 65 deletions
|
@ -5,6 +5,10 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
|
||||||
`xmpp_parsers::stanza_error::StanzaError` has been moved into the
|
`xmpp_parsers::stanza_error::StanzaError` has been moved into the
|
||||||
corresponding enum variants of the
|
corresponding enum variants of the
|
||||||
`xmpp_parsers::stanza_error::DefinedCondition` where it may occur.
|
`xmpp_parsers::stanza_error::DefinedCondition` where it may occur.
|
||||||
|
- The `xmpp_parsers::mix::Participant::jid` and `..::Mix::jid` fields
|
||||||
|
are now of type `BareJid` (instead of `String`), as they should always
|
||||||
|
have been. This influences various other places, such as these
|
||||||
|
struct's constructors.
|
||||||
* New parsers/serialisers:
|
* New parsers/serialisers:
|
||||||
- Stream Features (RFC 6120) (!400)
|
- Stream Features (RFC 6120) (!400)
|
||||||
|
|
||||||
|
|
|
@ -15,18 +15,18 @@ use crate::media_element::MediaElement;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use minidom::Element;
|
use minidom::Element;
|
||||||
|
|
||||||
generate_element!(
|
/// Represents one of the possible values for a list- field.
|
||||||
/// Represents one of the possible values for a list- field.
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
Option_, "option", DATA_FORMS,
|
#[xml(namespace = ns::DATA_FORMS, name = "option")]
|
||||||
attributes: [
|
pub struct Option_ {
|
||||||
/// The optional label to be displayed to the user for this option.
|
/// The optional label to be displayed to the user for this option.
|
||||||
label: Option<String> = "label"
|
#[xml(attribute(default))]
|
||||||
],
|
pub label: Option<String>,
|
||||||
children: [
|
|
||||||
/// The value returned to the server when selecting this option.
|
/// The value returned to the server when selecting this option.
|
||||||
value: Required<String> = ("value", DATA_FORMS) => String
|
#[xml(extract(namespace = ns::DATA_FORMS, name = "value", fields(text)))]
|
||||||
]
|
pub value: String,
|
||||||
);
|
}
|
||||||
|
|
||||||
generate_attribute!(
|
generate_attribute!(
|
||||||
/// The type of a [field](struct.Field.html) element.
|
/// The type of a [field](struct.Field.html) element.
|
||||||
|
@ -588,7 +588,7 @@ mod tests {
|
||||||
FromElementError::Invalid(Error::Other(string)) => string,
|
FromElementError::Invalid(Error::Other(string)) => string,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "Missing child value in option element.");
|
assert_eq!(message, "Missing child field 'value' in Option_ element.");
|
||||||
|
|
||||||
let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value><value>error</value></option>".parse().unwrap();
|
let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value><value>error</value></option>".parse().unwrap();
|
||||||
let error = Option_::try_from(elem).unwrap_err();
|
let error = Option_::try_from(elem).unwrap_err();
|
||||||
|
@ -598,7 +598,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
message,
|
message,
|
||||||
"Element option must not have more than one value child."
|
"Option_ element must not have more than one child in field 'value'."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,25 +32,25 @@ generate_id!(
|
||||||
ChannelId
|
ChannelId
|
||||||
);
|
);
|
||||||
|
|
||||||
generate_element!(
|
/// Represents a participant in a MIX channel, usually returned on the
|
||||||
/// Represents a participant in a MIX channel, usually returned on the
|
/// urn:xmpp:mix:nodes:participants PubSub node.
|
||||||
/// urn:xmpp:mix:nodes:participants PubSub node.
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
Participant, "participant", MIX_CORE,
|
#[xml(namespace = ns::MIX_CORE, name = "participant")]
|
||||||
children: [
|
pub struct Participant {
|
||||||
/// The nick of this participant.
|
/// The nick of this participant.
|
||||||
nick: Required<String> = ("nick", MIX_CORE) => String,
|
#[xml(extract(namespace = ns::MIX_CORE, name = "nick", fields(text)))]
|
||||||
|
pub nick: String,
|
||||||
|
|
||||||
/// The bare JID of this participant.
|
/// The bare JID of this participant.
|
||||||
// TODO: should be a BareJid!
|
#[xml(extract(namespace = ns::MIX_CORE, name = "jid", fields(text)))]
|
||||||
jid: Required<String> = ("jid", MIX_CORE) => String
|
pub jid: BareJid,
|
||||||
]
|
}
|
||||||
);
|
|
||||||
|
|
||||||
impl PubSubPayload for Participant {}
|
impl PubSubPayload for Participant {}
|
||||||
|
|
||||||
impl Participant {
|
impl Participant {
|
||||||
/// Create a new MIX participant.
|
/// Create a new MIX participant.
|
||||||
pub fn new<J: Into<String>, N: Into<String>>(jid: J, nick: N) -> Participant {
|
pub fn new<J: Into<BareJid>, N: Into<String>>(jid: J, nick: N) -> Participant {
|
||||||
Participant {
|
Participant {
|
||||||
nick: nick.into(),
|
nick: nick.into(),
|
||||||
jid: jid.into(),
|
jid: jid.into(),
|
||||||
|
@ -76,21 +76,22 @@ impl Subscribe {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_element!(
|
/// A request from a user’s server to join a MIX channel.
|
||||||
/// A request from a user’s server to join a MIX channel.
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
Join, "join", MIX_CORE,
|
#[xml(namespace = ns::MIX_CORE, name = "join")]
|
||||||
attributes: [
|
pub struct Join {
|
||||||
/// The participant identifier returned by the MIX service on successful join.
|
/// The participant identifier returned by the MIX service on successful join.
|
||||||
id: Option<ParticipantId> = "id",
|
#[xml(attribute(default))]
|
||||||
],
|
pub id: Option<ParticipantId>,
|
||||||
children: [
|
|
||||||
/// The nick requested by the user or set by the service.
|
/// The nick requested by the user or set by the service.
|
||||||
nick: Required<String> = ("nick", MIX_CORE) => String,
|
#[xml(extract(namespace = ns::MIX_CORE, name = "nick", fields(text)))]
|
||||||
|
pub nick: String,
|
||||||
|
|
||||||
/// Which MIX nodes to subscribe to.
|
/// Which MIX nodes to subscribe to.
|
||||||
subscribes: Vec<Subscribe> = ("subscribe", MIX_CORE) => Subscribe
|
#[xml(child(n = ..))]
|
||||||
]
|
pub subscribes: Vec<Subscribe>,
|
||||||
);
|
}
|
||||||
|
|
||||||
impl IqSetPayload for Join {}
|
impl IqSetPayload for Join {}
|
||||||
impl IqResultPayload for Join {}
|
impl IqResultPayload for Join {}
|
||||||
|
@ -158,14 +159,14 @@ pub struct Leave;
|
||||||
impl IqSetPayload for Leave {}
|
impl IqSetPayload for Leave {}
|
||||||
impl IqResultPayload for Leave {}
|
impl IqResultPayload for Leave {}
|
||||||
|
|
||||||
generate_element!(
|
/// A request to change the user’s nick.
|
||||||
/// A request to change the user’s nick.
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
SetNick, "setnick", MIX_CORE,
|
#[xml(namespace = ns::MIX_CORE, name = "setnick")]
|
||||||
children: [
|
pub struct SetNick {
|
||||||
/// The new requested nick.
|
/// The new requested nick.
|
||||||
nick: Required<String> = ("nick", MIX_CORE) => String
|
#[xml(extract(namespace = ns::MIX_CORE, name = "nick", fields(text)))]
|
||||||
]
|
pub nick: String,
|
||||||
);
|
}
|
||||||
|
|
||||||
impl IqSetPayload for SetNick {}
|
impl IqSetPayload for SetNick {}
|
||||||
impl IqResultPayload for SetNick {}
|
impl IqResultPayload for SetNick {}
|
||||||
|
@ -177,25 +178,25 @@ impl SetNick {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_element!(
|
/// Message payload describing who actually sent the message, since unlike in MUC, all messages
|
||||||
/// Message payload describing who actually sent the message, since unlike in MUC, all messages
|
/// are sent from the channel’s JID.
|
||||||
/// are sent from the channel’s JID.
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
Mix, "mix", MIX_CORE,
|
#[xml(namespace = ns::MIX_CORE, name = "mix")]
|
||||||
children: [
|
pub struct Mix {
|
||||||
/// The nick of the user who said something.
|
/// The nick of the user who said something.
|
||||||
nick: Required<String> = ("nick", MIX_CORE) => String,
|
#[xml(extract(namespace = ns::MIX_CORE, name = "nick", fields(text)))]
|
||||||
|
pub nick: String,
|
||||||
|
|
||||||
/// The JID of the user who said something.
|
/// The JID of the user who said something.
|
||||||
// TODO: should be a BareJid!
|
#[xml(extract(namespace = ns::MIX_CORE, name = "jid", fields(text)))]
|
||||||
jid: Required<String> = ("jid", MIX_CORE) => String
|
pub jid: BareJid,
|
||||||
]
|
}
|
||||||
);
|
|
||||||
|
|
||||||
impl MessagePayload for Mix {}
|
impl MessagePayload for Mix {}
|
||||||
|
|
||||||
impl Mix {
|
impl Mix {
|
||||||
/// Create a new Mix element.
|
/// Create a new Mix element.
|
||||||
pub fn new<N: Into<String>, J: Into<String>>(nick: N, jid: J) -> Mix {
|
pub fn new<N: Into<String>, J: Into<BareJid>>(nick: N, jid: J) -> Mix {
|
||||||
Mix {
|
Mix {
|
||||||
nick: nick.into(),
|
nick: nick.into(),
|
||||||
jid: jid.into(),
|
jid: jid.into(),
|
||||||
|
@ -263,7 +264,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let participant = Participant::try_from(elem).unwrap();
|
let participant = Participant::try_from(elem).unwrap();
|
||||||
assert_eq!(participant.nick, "coucou");
|
assert_eq!(participant.nick, "coucou");
|
||||||
assert_eq!(participant.jid, "foo@bar");
|
assert_eq!(participant.jid.as_str(), "foo@bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -316,7 +317,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mix = Mix::try_from(elem).unwrap();
|
let mix = Mix::try_from(elem).unwrap();
|
||||||
assert_eq!(mix.nick, "coucou");
|
assert_eq!(mix.nick, "coucou");
|
||||||
assert_eq!(mix.jid, "foo@bar");
|
assert_eq!(mix.jid.as_str(), "foo@bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -362,7 +363,7 @@ mod tests {
|
||||||
"<setnick xmlns='urn:xmpp:mix:core:1'><nick>coucou</nick></setnick>"
|
"<setnick xmlns='urn:xmpp:mix:core:1'><nick>coucou</nick></setnick>"
|
||||||
);
|
);
|
||||||
|
|
||||||
let elem: Element = Mix::new("coucou", "coucou@example").into();
|
let elem: Element = Mix::new("coucou", "coucou@example".parse::<BareJid>().unwrap()).into();
|
||||||
let xml = String::from(&elem);
|
let xml = String::from(&elem);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
xml,
|
xml,
|
||||||
|
|
|
@ -852,6 +852,78 @@ fn text_extract_positive() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_extract_negative_absent_child() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<TextExtract>("<parent xmlns='urn:example:ns1'/>") {
|
||||||
|
Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e)))
|
||||||
|
if e.contains("Missing child field") =>
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_extract_negative_unexpected_attribute_in_child() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<TextExtract>("<parent xmlns='urn:example:ns1'><child foo='bar'/></parent>") {
|
||||||
|
Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e)))
|
||||||
|
if e.contains("Unknown attribute") =>
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_extract_negative_unexpected_child_in_child() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<TextExtract>(
|
||||||
|
"<parent xmlns='urn:example:ns1'><child><quak/></child></parent>",
|
||||||
|
) {
|
||||||
|
Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e)))
|
||||||
|
if e.contains("Unknown child in extraction") =>
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_extract_negative_duplicate_child() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<TextExtract>(
|
||||||
|
"<parent xmlns='urn:example:ns1'><child>hello world</child><child>more</child></parent>",
|
||||||
|
) {
|
||||||
|
Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e)))
|
||||||
|
if e.contains("must not have more than one") =>
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn text_extract_roundtrip() {
|
fn text_extract_roundtrip() {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
@ -888,6 +960,42 @@ fn attribute_extract_positive() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attribute_extract_negative_absent_attribute() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<AttributeExtract>("<parent xmlns='urn:example:ns1'><child/></parent>") {
|
||||||
|
Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e)))
|
||||||
|
if e.contains("Required attribute") =>
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attribute_extract_negative_unexpected_text_in_child() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<AttributeExtract>(
|
||||||
|
"<parent xmlns='urn:example:ns1'><child foo='hello world'>fnord</child></parent>",
|
||||||
|
) {
|
||||||
|
Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e)))
|
||||||
|
if e.contains("Unexpected text") =>
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn attribute_extract_roundtrip() {
|
fn attribute_extract_roundtrip() {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
@ -900,6 +1008,68 @@ fn attribute_extract_roundtrip() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(namespace = NS1, name = "parent")]
|
||||||
|
struct OptionalAttributeExtract {
|
||||||
|
#[xml(extract(namespace = NS1, name = "child", fields(attribute(name = "foo", default))))]
|
||||||
|
contents: ::std::option::Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_attribute_extract_positive_present() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<OptionalAttributeExtract>(
|
||||||
|
"<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
|
||||||
|
) {
|
||||||
|
Ok(OptionalAttributeExtract {
|
||||||
|
contents: Some(contents),
|
||||||
|
}) => {
|
||||||
|
assert_eq!(contents, "hello world");
|
||||||
|
}
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_attribute_extract_positive_absent() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<OptionalAttributeExtract>("<parent xmlns='urn:example:ns1'><child/></parent>")
|
||||||
|
{
|
||||||
|
Ok(OptionalAttributeExtract { contents: None }) => (),
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_attribute_extract_roundtrip_present() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<OptionalAttributeExtract>(
|
||||||
|
"<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_attribute_extract_roundtrip_absent() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<OptionalAttributeExtract>("<parent xmlns='urn:example:ns1'><child/></parent>")
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
#[xml(namespace = NS1, name = "parent")]
|
#[xml(namespace = NS1, name = "parent")]
|
||||||
struct ChildExtract {
|
struct ChildExtract {
|
||||||
|
|
Loading…
Reference in a new issue