sip2/
message_json.rs

1//! JSON serializatoin routintes for SIP messages.
2use super::Message;
3use std::collections::HashMap;
4use std::error;
5use std::fmt;
6
7/// Errors related specifically to SIP <=> JSON routines
8#[derive(Debug)]
9pub enum SipJsonError {
10    /// Data does not contain the correct content, e.g. sip message code.
11    MessageFormatError(String),
12
13    /// Data cannot be successfully minipulated as JSON
14    JsonError(json::Error),
15}
16
17impl error::Error for SipJsonError {
18    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
19        match *self {
20            SipJsonError::JsonError(ref err) => Some(err),
21            _ => None,
22        }
23    }
24}
25
26impl fmt::Display for SipJsonError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            SipJsonError::JsonError(ref err) => err.fmt(f),
30            SipJsonError::MessageFormatError(s) => {
31                write!(f, "SIP message could not be translated to/from JSON: {}", s)
32            }
33        }
34    }
35}
36
37impl Message {
38    /// Translate a SIP Message into a JSON object.
39    ///
40    /// ```
41    /// use sip2::{Message, Field, FixedField};
42    /// use sip2::spec;
43    /// use json;
44    ///
45    /// let msg = Message::new(
46    ///     &spec::M_LOGIN,
47    ///     vec![
48    ///         FixedField::new(&spec::FF_UID_ALGO, "0").unwrap(),
49    ///         FixedField::new(&spec::FF_PWD_ALGO, "0").unwrap(),
50    ///     ],
51    ///     vec![
52    ///         Field::new(spec::F_LOGIN_UID.code, "sip_username"),
53    ///         Field::new(spec::F_LOGIN_PWD.code, "sip_password"),
54    ///     ]
55    /// );
56    ///
57    /// let json_val = msg.to_json_value();
58    /// let expected = json::object!{
59    ///   "code":"93",
60    ///   "fixed_fields":["0","0"],
61    ///   "fields":[{"CN":"sip_username"},{"CO":"sip_password"}]};
62    ///
63    /// assert_eq!(expected, json_val);
64    /// ```
65    pub fn to_json_value(&self) -> json::JsonValue {
66        let ff: Vec<String> = self
67            .fixed_fields()
68            .iter()
69            .map(|f| f.value().to_string())
70            .collect();
71
72        let mut fields: Vec<HashMap<String, String>> = Vec::new();
73
74        for f in self.fields().iter() {
75            let mut map = HashMap::new();
76            map.insert(f.code().to_string(), f.value().to_string());
77            fields.push(map);
78        }
79
80        json::object! {
81            "code": self.spec().code,
82            "fixed_fields": ff,
83            "fields": fields
84        }
85    }
86
87    /// Translate a SIP Message into a JSON string.
88    ///
89    /// ```
90    /// use sip2::{Message, Field, FixedField};
91    /// use sip2::spec;
92    ///
93    /// let msg = Message::new(
94    ///     &spec::M_LOGIN,
95    ///     vec![
96    ///         FixedField::new(&spec::FF_UID_ALGO, "0").unwrap(),
97    ///         FixedField::new(&spec::FF_PWD_ALGO, "0").unwrap(),
98    ///     ],
99    ///     vec![
100    ///         Field::new(spec::F_LOGIN_UID.code, "sip_username"),
101    ///         Field::new(spec::F_LOGIN_PWD.code, "sip_password"),
102    ///     ]
103    /// );
104    ///
105    /// let json_str = msg.to_json();
106    ///
107    /// // Comparing JSON strings is nontrivial with hashes.
108    /// // Assume completion means success.  See to_json_value() for
109    /// // more rigorous testing.
110    /// assert_eq!(true, true);
111    /// ```
112    pub fn to_json(&self) -> String {
113        self.to_json_value().dump()
114    }
115
116    /// Translate a JSON object into a SIP Message.
117    ///
118    /// Field and FixedField values must be JSON strings or numbers.
119    ///
120    /// ```
121    /// use sip2::{Message, Field, FixedField};
122    /// use sip2::spec;
123    /// use json;
124    ///
125    /// let expected = Message::new(
126    ///     &spec::M_LOGIN,
127    ///     vec![
128    ///         FixedField::new(&spec::FF_UID_ALGO, "0").unwrap(),
129    ///         FixedField::new(&spec::FF_PWD_ALGO, "0").unwrap(),
130    ///     ],
131    ///     vec![
132    ///         Field::new(spec::F_LOGIN_UID.code, "sip_username"),
133    ///         Field::new(spec::F_LOGIN_PWD.code, "sip_password"),
134    ///     ]
135    /// );
136    ///
137    /// let json_val = json::object!{
138    ///   "code":"93",
139    ///   "fixed_fields":["0",0],
140    ///   "fields":[{"CN":"sip_username"},{"CO":"sip_password"}]};
141    ///
142    /// let msg = Message::from_json_value(json_val).unwrap();
143    ///
144    /// assert_eq!(expected, msg);
145    ///
146    /// let m = Message::from_json_value(json::object! {"code":"93","fixed_fields":[{"bad":"news"}]});
147    /// assert!(m.is_err());
148    /// ```
149    pub fn from_json_value(mut json_value: json::JsonValue) -> Result<Message, SipJsonError> {
150        // Start with a message that's just the code plus fixed fields
151        // as a SIP string.
152        let mut strbuf = json_value["code"].take_string().ok_or_else(|| {
153            SipJsonError::MessageFormatError("Message requires a code".to_string())
154        })?;
155
156        for ff in json_value["fixed_fields"].members() {
157            if let Some(s) = ff.as_str() {
158                strbuf += s;
159            } else if ff.is_number() {
160                strbuf += &format!("{ff}");
161            } else {
162                return Err(SipJsonError::MessageFormatError(format!(
163                    "Fixed field values must be JSON strings or numbers: {}",
164                    ff.dump()
165                )));
166            }
167        }
168
169        // Since we're creating this partial SIP string from raw
170        // JSON values and the buffer this far should not contain
171        // any separater chars, clean it up before parsing as SIP.
172        strbuf = super::util::sip_string(&strbuf);
173
174        let mut msg = Message::from_sip(&strbuf).map_err(|e| {
175            SipJsonError::MessageFormatError(format!(
176                "Message is not correctly formatted: {e} {}",
177                json_value.dump()
178            ))
179        })?;
180
181        for field in json_value["fields"].members() {
182            for (code, value) in field.entries() {
183                if let Some(s) = value.as_str() {
184                    msg.add_field(code, s);
185                } else if value.is_number() {
186                    msg.add_field(code, &format!("{value}"));
187                } else {
188                    return Err(SipJsonError::MessageFormatError(format!(
189                        "Message is not correctly formatted: {}",
190                        json_value.dump()
191                    )));
192                }
193            }
194        }
195
196        Ok(msg)
197    }
198
199    /// Translate a JSON string into a SIP Message.
200    ///
201    /// ```
202    /// use sip2::{Message, Field, FixedField};
203    /// use sip2::spec;
204    /// use json;
205    ///
206    /// let expected = Message::new(
207    ///     &spec::M_LOGIN,
208    ///     vec![
209    ///         FixedField::new(&spec::FF_UID_ALGO, "0").unwrap(),
210    ///         FixedField::new(&spec::FF_PWD_ALGO, "0").unwrap(),
211    ///     ],
212    ///     vec![
213    ///         Field::new(spec::F_LOGIN_UID.code, "sip_username"),
214    ///         Field::new(spec::F_LOGIN_PWD.code, "sip_password"),
215    ///     ]
216    /// );
217    ///
218    /// let json_str = r#"
219    ///   {
220    ///     "code":"93",
221    ///     "fixed_fields":["0","0"],
222    ///     "fields":[{"CN":"sip_username"},{"CO":"sip_password"}]
223    ///   }
224    /// "#;
225    ///
226    /// let msg = Message::from_json(&json_str).unwrap();
227    ///
228    /// assert_eq!(expected, msg);
229    /// ```
230    pub fn from_json(msg_json: &str) -> Result<Message, SipJsonError> {
231        let json_value: json::JsonValue = match json::parse(msg_json) {
232            Ok(v) => v,
233            Err(e) => {
234                return Err(SipJsonError::JsonError(e));
235            }
236        };
237
238        Message::from_json_value(json_value)
239    }
240}