// Copyright (c) 2018 Emmanuel Gil Peyrot // // 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::error::Error; use crate::iq::{IqResultPayload, IqSetPayload}; use crate::ns; use jid::Jid; use minidom::Element; use std::str::FromStr; use try_from::TryFrom; /// The request for resource binding, which is the process by which a client /// can obtain a full JID and start exchanging on the XMPP network. /// /// See https://xmpp.org/rfcs/rfc6120.html#bind #[derive(Debug, Clone, PartialEq)] pub enum Bind { /// Requests no particular resource, a random one will be affected by the /// server. None, /// Requests this resource, the server may associate another one though. Resource(String), /// The full JID returned by the server for this client. Jid(Jid), } impl Bind { /// Creates a resource binding request. pub fn new(resource: Option) -> Bind { match resource { None => Bind::None, Some(resource) => Bind::Resource(resource), } } } impl IqSetPayload for Bind {} impl IqResultPayload for Bind {} impl TryFrom for Bind { type Err = Error; fn try_from(elem: Element) -> Result { check_self!(elem, "bind", BIND); check_no_attributes!(elem, "bind"); let mut bind = Bind::None; for child in elem.children() { if bind != Bind::None { return Err(Error::ParseError("Bind can only have one child.")); } if child.is("resource", ns::BIND) { check_no_attributes!(child, "resource"); check_no_children!(child, "resource"); bind = Bind::Resource(child.text()); } else if child.is("jid", ns::BIND) { check_no_attributes!(child, "jid"); check_no_children!(child, "jid"); bind = Bind::Jid(Jid::from_str(&child.text())?); } else { return Err(Error::ParseError("Unknown element in bind.")); } } Ok(bind) } } impl From for Element { fn from(bind: Bind) -> Element { Element::builder("bind") .ns(ns::BIND) .append(match bind { Bind::None => vec![], Bind::Resource(resource) => vec![Element::builder("resource") .ns(ns::BIND) .append(resource) .build()], Bind::Jid(jid) => vec![Element::builder("jid").ns(ns::BIND).append(jid).build()], }) .build() } } #[cfg(test)] mod tests { use super::*; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { assert_size!(Bind, 40); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { assert_size!(Bind, 80); } #[test] fn test_simple() { let elem: Element = "" .parse() .unwrap(); let bind = Bind::try_from(elem).unwrap(); assert_eq!(bind, Bind::None); } #[cfg(not(feature = "disable-validation"))] #[test] fn test_invalid_resource() { let elem: Element = "resource" .parse() .unwrap(); let error = Bind::try_from(elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown attribute in resource element."); let elem: Element = "resource" .parse() .unwrap(); let error = Bind::try_from(elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown child in resource element."); } }