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}