@@ -17,8 +17,7 @@ use jid::Jid;
use minidom::Element;
use std::collections::BTreeMap;
use std::fmt;
-use std::str::FromStr;
-use xso::error::{Error, FromElementError};
+use xso::error::Error;
generate_attribute!(
/// The action attribute.
@@ -376,134 +375,106 @@ impl Content {
}
/// Lists the possible reasons to be included in a Jingle iq.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::JINGLE)]
pub enum Reason {
/// The party prefers to use an existing session with the peer rather than
/// initiate a new session; the Jingle session ID of the alternative
/// session SHOULD be provided as the XML character data of the \<sid/\>
/// child.
- AlternativeSession, //(String),
+ #[xml(name = "alternative-session")]
+ AlternativeSession {
+ /// Session ID of the alternative session.
+ #[xml(extract(namespace = ns::JINGLE, name = "sid", default, fields(text(type_ = String))))]
+ sid: Option<String>,
+ },
/// The party is busy and cannot accept a session.
+ #[xml(name = "busy")]
Busy,
/// The initiator wishes to formally cancel the session initiation request.
+ #[xml(name = "cancel")]
Cancel,
/// The action is related to connectivity problems.
+ #[xml(name = "connectivity-error")]
ConnectivityError,
/// The party wishes to formally decline the session.
+ #[xml(name = "decline")]
Decline,
/// The session length has exceeded a pre-defined time limit (e.g., a
/// meeting hosted at a conference service).
+ #[xml(name = "expired")]
Expired,
/// The party has been unable to initialize processing related to the
/// application type.
+ #[xml(name = "failed-application")]
FailedApplication,
/// The party has been unable to establish connectivity for the transport
/// method.
+ #[xml(name = "failed-transport")]
FailedTransport,
/// The action is related to a non-specific application error.
+ #[xml(name = "general-error")]
GeneralError,
/// The entity is going offline or is no longer available.
+ #[xml(name = "gone")]
Gone,
/// The party supports the offered application type but does not support
/// the offered or negotiated parameters.
+ #[xml(name = "incompatible-parameters")]
IncompatibleParameters,
/// The action is related to media processing problems.
+ #[xml(name = "media-error")]
MediaError,
/// The action is related to a violation of local security policies.
+ #[xml(name = "security-error")]
SecurityError,
/// The action is generated during the normal course of state management
/// and does not reflect any error.
+ #[xml(name = "success")]
Success,
/// A request has not been answered so the sender is timing out the
/// request.
+ #[xml(name = "timeout")]
Timeout,
/// The party supports none of the offered application types.
+ #[xml(name = "unsupported-applications")]
UnsupportedApplications,
/// The party supports none of the offered transport methods.
+ #[xml(name = "unsupported-transports")]
UnsupportedTransports,
}
-impl FromStr for Reason {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Reason, Error> {
- Ok(match s {
- "alternative-session" => Reason::AlternativeSession,
- "busy" => Reason::Busy,
- "cancel" => Reason::Cancel,
- "connectivity-error" => Reason::ConnectivityError,
- "decline" => Reason::Decline,
- "expired" => Reason::Expired,
- "failed-application" => Reason::FailedApplication,
- "failed-transport" => Reason::FailedTransport,
- "general-error" => Reason::GeneralError,
- "gone" => Reason::Gone,
- "incompatible-parameters" => Reason::IncompatibleParameters,
- "media-error" => Reason::MediaError,
- "security-error" => Reason::SecurityError,
- "success" => Reason::Success,
- "timeout" => Reason::Timeout,
- "unsupported-applications" => Reason::UnsupportedApplications,
- "unsupported-transports" => Reason::UnsupportedTransports,
-
- _ => return Err(Error::Other("Unknown reason.")),
- })
- }
-}
-
-impl From<Reason> for Element {
- fn from(reason: Reason) -> Element {
- Element::builder(
- match reason {
- Reason::AlternativeSession => "alternative-session",
- Reason::Busy => "busy",
- Reason::Cancel => "cancel",
- Reason::ConnectivityError => "connectivity-error",
- Reason::Decline => "decline",
- Reason::Expired => "expired",
- Reason::FailedApplication => "failed-application",
- Reason::FailedTransport => "failed-transport",
- Reason::GeneralError => "general-error",
- Reason::Gone => "gone",
- Reason::IncompatibleParameters => "incompatible-parameters",
- Reason::MediaError => "media-error",
- Reason::SecurityError => "security-error",
- Reason::Success => "success",
- Reason::Timeout => "timeout",
- Reason::UnsupportedApplications => "unsupported-applications",
- Reason::UnsupportedTransports => "unsupported-transports",
- },
- ns::JINGLE,
- )
- .build()
- }
-}
-
type Lang = String;
/// Informs the recipient of something.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::JINGLE, name = "reason")]
pub struct ReasonElement {
/// The list of possible reasons to be included in a Jingle iq.
+ #[xml(child)]
pub reason: Reason,
/// A human-readable description of this reason.
+ #[xml(extract(n = .., namespace = ns::JINGLE, name = "text", fields(
+ attribute(type_ = String, name = "xml:lang", default),
+ text(type_ = String),
+ )))]
pub texts: BTreeMap<Lang, String>,
}
@@ -519,75 +490,6 @@ impl fmt::Display for ReasonElement {
}
}
-impl TryFrom<Element> for ReasonElement {
- type Error = FromElementError;
-
- fn try_from(elem: Element) -> Result<ReasonElement, FromElementError> {
- check_self!(elem, "reason", JINGLE);
- check_no_attributes!(elem, "reason");
- let mut reason = None;
- let mut texts = BTreeMap::new();
- for child in elem.children() {
- if child.is("text", ns::JINGLE) {
- check_no_children!(child, "text");
- check_no_unknown_attributes!(child, "text", ["xml:lang"]);
- let lang = get_attr!(elem, "xml:lang", Default);
- if texts.insert(lang, child.text()).is_some() {
- return Err(
- Error::Other("Text element present twice for the same xml:lang.").into(),
- );
- }
- } else if child.has_ns(ns::JINGLE) {
- if reason.is_some() {
- return Err(Error::Other("Reason must not have more than one reason.").into());
- }
- check_no_children!(child, "reason");
- check_no_attributes!(child, "reason");
- reason = Some(child.name().parse()?);
- } else {
- return Err(Error::Other("Reason contains a foreign element.").into());
- }
- }
- let reason = reason.ok_or(Error::Other("Reason doesn’t contain a valid reason."))?;
- Ok(ReasonElement { reason, texts })
- }
-}
-
-impl From<ReasonElement> for Element {
- fn from(reason: ReasonElement) -> Element {
- Element::builder("reason", ns::JINGLE)
- .append(Element::from(reason.reason))
- .append_all(reason.texts.into_iter().map(|(lang, text)| {
- Element::builder("text", ns::JINGLE)
- .attr("xml:lang", lang)
- .append(text)
- }))
- .build()
- }
-}
-
-impl ::xso::FromXml for ReasonElement {
- type Builder = ::xso::minidom_compat::FromEventsViaElement<ReasonElement>;
-
- fn from_events(
- qname: ::xso::exports::rxml::QName,
- attrs: ::xso::exports::rxml::AttrMap,
- ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
- if qname.0 != crate::ns::JINGLE || qname.1 != "reason" {
- return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
- }
- Self::Builder::new(qname, attrs)
- }
-}
-
-impl ::xso::AsXml for ReasonElement {
- type ItemIter<'x> = ::xso::minidom_compat::AsItemsViaElement<'x>;
-
- fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, ::xso::error::Error> {
- ::xso::minidom_compat::AsItemsViaElement::new(self.clone())
- }
-}
-
generate_id!(
/// Unique identifier for a session between two JIDs.
SessionId
@@ -681,6 +583,7 @@ impl Jingle {
#[cfg(test)]
mod tests {
use super::*;
+ use xso::error::FromElementError;
#[cfg(target_pointer_width = "32")]
#[test]
@@ -691,10 +594,10 @@ mod tests {
assert_size!(Disposition, 1);
assert_size!(ContentId, 12);
assert_size!(Content, 156);
- assert_size!(Reason, 1);
- assert_size!(ReasonElement, 16);
+ assert_size!(Reason, 12);
+ assert_size!(ReasonElement, 24);
assert_size!(SessionId, 12);
- assert_size!(Jingle, 104);
+ assert_size!(Jingle, 112);
}
#[cfg(target_pointer_width = "64")]
@@ -706,10 +609,10 @@ mod tests {
assert_size!(Disposition, 1);
assert_size!(ContentId, 24);
assert_size!(Content, 312);
- assert_size!(Reason, 1);
- assert_size!(ReasonElement, 32);
+ assert_size!(Reason, 24);
+ assert_size!(ReasonElement, 48);
assert_size!(SessionId, 24);
- assert_size!(Jingle, 208);
+ assert_size!(Jingle, 224);
}
#[test]
@@ -859,7 +762,10 @@ mod tests {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
- assert_eq!(message, "Reason doesn’t contain a valid reason.");
+ assert_eq!(
+ message,
+ "Missing child field 'reason' in ReasonElement element."
+ );
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
@@ -867,7 +773,7 @@ mod tests {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
- assert_eq!(message, "Unknown reason.");
+ assert_eq!(message, "Unknown child in ReasonElement element.");
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a xmlns='http://www.w3.org/1999/xhtml'/></reason></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
@@ -875,7 +781,7 @@ mod tests {
FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(),
};
- assert_eq!(message, "Reason contains a foreign element.");
+ assert_eq!(message, "Unknown child in ReasonElement element.");
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
@@ -888,6 +794,9 @@ mod tests {
"Jingle element must not have more than one child in field 'reason'."
);
+ // TODO: Reenable this test once xso is able to validate that no more than one text is
+ // there for every xml:lang.
+ /*
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><text/><text/></reason></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
@@ -895,6 +804,7 @@ mod tests {
_ => panic!(),
};
assert_eq!(message, "Text element present twice for the same xml:lang.");
+ */
}
#[test]