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
7#![allow(missing_docs)]
8
9use std::collections::HashMap;
10use try_from::TryFrom;
11
12use minidom::Element;
13
14use error::Error;
15
16use iq::{IqGetPayload, IqSetPayload, IqResultPayload};
17use data_forms::DataForm;
18
19use ns;
20
21#[derive(Debug, Clone)]
22pub struct Query {
23 pub fields: HashMap<String, String>,
24 pub registered: bool,
25 pub remove: bool,
26 pub form: Option<DataForm>,
27 // Not yet implemented.
28 //pub oob: Option<Oob>,
29}
30
31impl IqGetPayload for Query {}
32impl IqSetPayload for Query {}
33impl IqResultPayload for Query {}
34
35impl TryFrom<Element> for Query {
36 type Err = Error;
37
38 fn try_from(elem: Element) -> Result<Query, Error> {
39 check_self!(elem, "query", REGISTER, "IBR query");
40 let mut query = Query {
41 registered: false,
42 fields: HashMap::new(),
43 remove: false,
44 form: None,
45 };
46 for child in elem.children() {
47 let namespace = child.ns().unwrap();
48 if namespace == ns::REGISTER {
49 let name = child.name();
50 let fields = vec!["address", "city", "date", "email", "first", "instructions",
51 "key", "last", "misc", "name", "nick", "password", "phone",
52 "state", "text", "url", "username", "zip"];
53 if fields.binary_search(&name).is_ok() {
54 query.fields.insert(name.to_owned(), child.text());
55 } else if name == "registered" {
56 query.registered = true;
57 } else if name == "remove" {
58 query.remove = true;
59 } else {
60 return Err(Error::ParseError("Wrong field in ibr element."));
61 }
62 } else if child.is("x", ns::DATA_FORMS) {
63 query.form = Some(DataForm::try_from(child.clone())?);
64 } else {
65 return Err(Error::ParseError("Unknown child in ibr element."));
66 }
67 }
68 Ok(query)
69 }
70}
71
72impl From<Query> for Element {
73 fn from(query: Query) -> Element {
74 Element::builder("query")
75 .ns(ns::REGISTER)
76 .append(if query.registered { Some(Element::builder("registered").ns(ns::REGISTER)) } else { None })
77 .append(query.fields.into_iter().map(|(name, value)| {
78 Element::builder(name).ns(ns::REGISTER).append(value)
79 }).collect::<Vec<_>>())
80 .append(if query.remove { Some(Element::builder("remove").ns(ns::REGISTER)) } else { None })
81 .append(query.form)
82 .build()
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use compare_elements::NamespaceAwareCompare;
90
91 #[test]
92 fn test_simple() {
93 let elem: Element = "<query xmlns='jabber:iq:register'/>".parse().unwrap();
94 Query::try_from(elem).unwrap();
95 }
96
97 #[test]
98 fn test_ex2() {
99 let elem: Element = r#"
100<query xmlns='jabber:iq:register'>
101 <instructions>
102 Choose a username and password for use with this service.
103 Please also provide your email address.
104 </instructions>
105 <username/>
106 <password/>
107 <email/>
108</query>
109"#.parse().unwrap();
110 let query = Query::try_from(elem).unwrap();
111 assert_eq!(query.registered, false);
112 assert_eq!(query.fields["instructions"], "\n Choose a username and password for use with this service.\n Please also provide your email address.\n ");
113 assert_eq!(query.fields["username"], "");
114 assert_eq!(query.fields["password"], "");
115 assert_eq!(query.fields["email"], "");
116 assert_eq!(query.fields.contains_key("name"), false);
117
118 // FIXME: HashMap doesn’t keep the order right.
119 //let elem2 = query.into();
120 //assert_eq!(elem, elem2);
121 }
122
123 #[test]
124 fn test_ex9() {
125 let elem: Element = r#"
126<query xmlns='jabber:iq:register'>
127 <instructions>
128 Use the enclosed form to register. If your Jabber client does not
129 support Data Forms, visit http://www.shakespeare.lit/contests.php
130 </instructions>
131 <x xmlns='jabber:x:data' type='form'>
132 <title>Contest Registration</title>
133 <instructions>
134 Please provide the following information
135 to sign up for our special contests!
136 </instructions>
137 <field type='hidden' var='FORM_TYPE'>
138 <value>jabber:iq:register</value>
139 </field>
140 <field label='Given Name' var='first'>
141 <required/>
142 </field>
143 <field label='Family Name' var='last'>
144 <required/>
145 </field>
146 <field label='Email Address' var='email'>
147 <required/>
148 </field>
149 <field type='list-single' label='Gender' var='x-gender'>
150 <option label='Male'><value>M</value></option>
151 <option label='Female'><value>F</value></option>
152 </field>
153 </x>
154</query>
155"#.parse().unwrap();
156 let elem1 = elem.clone();
157 let query = Query::try_from(elem).unwrap();
158 assert_eq!(query.registered, false);
159 assert!(!query.fields["instructions"].is_empty());
160 let form = query.form.clone().unwrap();
161 assert!(!form.instructions.unwrap().is_empty());
162 assert!(form.fields.binary_search_by(|field| field.var.cmp(&String::from("first"))).is_ok());
163 assert!(form.fields.binary_search_by(|field| field.var.cmp(&String::from("x-gender"))).is_ok());
164 assert!(form.fields.binary_search_by(|field| field.var.cmp(&String::from("coucou"))).is_err());
165 let elem2 = query.into();
166 assert!(elem1.compare_to(&elem2));
167 }
168
169 #[test]
170 fn test_ex10() {
171 let elem: Element = r#"
172<query xmlns='jabber:iq:register'>
173 <x xmlns='jabber:x:data' type='submit'>
174 <field type='hidden' var='FORM_TYPE'>
175 <value>jabber:iq:register</value>
176 </field>
177 <field label='Given Name' var='first'>
178 <value>Juliet</value>
179 </field>
180 <field label='Family Name' var='last'>
181 <value>Capulet</value>
182 </field>
183 <field label='Email Address' var='email'>
184 <value>juliet@capulet.com</value>
185 </field>
186 <field type='list-single' label='Gender' var='x-gender'>
187 <value>F</value>
188 </field>
189 </x>
190</query>
191"#.parse().unwrap();
192 let elem1 = elem.clone();
193 let query = Query::try_from(elem).unwrap();
194 assert_eq!(query.registered, false);
195 for _ in &query.fields {
196 panic!();
197 }
198 let elem2 = query.into();
199 assert!(elem1.compare_to(&elem2));
200 }
201}