jingle_message.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use try_from::TryFrom;
  8
  9use minidom::Element;
 10
 11use error::Error;
 12
 13use jingle::SessionId;
 14
 15use ns;
 16
 17#[derive(Debug, Clone)]
 18pub enum JingleMI {
 19    Propose {
 20        sid: SessionId,
 21        // TODO: Use a more specialised type here.
 22        description: Element,
 23    },
 24    Retract(SessionId),
 25    Accept(SessionId),
 26    Proceed(SessionId),
 27    Reject(SessionId),
 28}
 29
 30fn get_sid(elem: Element) -> Result<SessionId, Error> {
 31    for (attr, _) in elem.attrs() {
 32        if attr != "id" {
 33            return Err(Error::ParseError("Unknown attribute in Jingle message element."));
 34        }
 35    }
 36    Ok(SessionId(get_attr!(elem, "id", required)))
 37}
 38
 39fn check_empty_and_get_sid(elem: Element) -> Result<SessionId, Error> {
 40    for _ in elem.children() {
 41        return Err(Error::ParseError("Unknown child in Jingle message element."));
 42    }
 43    get_sid(elem)
 44}
 45
 46impl TryFrom<Element> for JingleMI {
 47    type Err = Error;
 48
 49    fn try_from(elem: Element) -> Result<JingleMI, Error> {
 50        if !elem.has_ns(ns::JINGLE_MESSAGE) {
 51            return Err(Error::ParseError("This is not a Jingle message element."));
 52        }
 53        Ok(match elem.name() {
 54            "propose" => {
 55                let mut description = None;
 56                for child in elem.children() {
 57                    if child.name() != "description" {
 58                        return Err(Error::ParseError("Unknown child in propose element."));
 59                    }
 60                    if description.is_some() {
 61                        return Err(Error::ParseError("Too many children in propose element."));
 62                    }
 63                    description = Some(child.clone());
 64                }
 65                JingleMI::Propose {
 66                    sid: get_sid(elem)?,
 67                    description: description.ok_or(Error::ParseError("Propose element doesn’t contain a description."))?,
 68                }
 69            },
 70            "retract" => JingleMI::Retract(check_empty_and_get_sid(elem)?),
 71            "accept" => JingleMI::Accept(check_empty_and_get_sid(elem)?),
 72            "proceed" => JingleMI::Proceed(check_empty_and_get_sid(elem)?),
 73            "reject" => JingleMI::Reject(check_empty_and_get_sid(elem)?),
 74            _ => return Err(Error::ParseError("This is not a Jingle message element.")),
 75        })
 76    }
 77}
 78
 79impl From<JingleMI> for Element {
 80    fn from(jingle_mi: JingleMI) -> Element {
 81        match jingle_mi {
 82            JingleMI::Propose { sid, description } => {
 83                Element::builder("propose")
 84                        .ns(ns::JINGLE_MESSAGE)
 85                        .attr("id", sid)
 86                        .append(description)
 87            },
 88            JingleMI::Retract(sid) => {
 89                Element::builder("retract")
 90                        .ns(ns::JINGLE_MESSAGE)
 91                        .attr("id", sid)
 92            }
 93            JingleMI::Accept(sid) => {
 94                Element::builder("accept")
 95                        .ns(ns::JINGLE_MESSAGE)
 96                        .attr("id", sid)
 97            }
 98            JingleMI::Proceed(sid) => {
 99                Element::builder("proceed")
100                        .ns(ns::JINGLE_MESSAGE)
101                        .attr("id", sid)
102            }
103            JingleMI::Reject(sid) => {
104                Element::builder("reject")
105                        .ns(ns::JINGLE_MESSAGE)
106                        .attr("id", sid)
107            }
108        }.build()
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_simple() {
118        let elem: Element = "<accept xmlns='urn:xmpp:jingle-message:0' id='coucou'/>".parse().unwrap();
119        JingleMI::try_from(elem).unwrap();
120    }
121
122    #[test]
123    fn test_invalid_child() {
124        let elem: Element = "<propose xmlns='urn:xmpp:jingle-message:0' id='coucou'><coucou/></propose>".parse().unwrap();
125        let error = JingleMI::try_from(elem).unwrap_err();
126        let message = match error {
127            Error::ParseError(string) => string,
128            _ => panic!(),
129        };
130        assert_eq!(message, "Unknown child in propose element.");
131    }
132}