mpvipc_async/
message_parser.rs

1//! JSON parsing logic for command responses from [`MpvIpc`](crate::ipc::MpvIpc).
2
3use std::collections::HashMap;
4
5use serde_json::Value;
6
7use crate::{MpvDataType, MpvError, PlaylistEntry};
8
9pub trait TypeHandler: Sized {
10    fn get_value(value: Value) -> Result<Self, MpvError>;
11    fn as_string(&self) -> String;
12}
13
14impl TypeHandler for String {
15    fn get_value(value: Value) -> Result<String, MpvError> {
16        value
17            .as_str()
18            .ok_or(MpvError::ValueContainsUnexpectedType {
19                expected_type: "String".to_string(),
20                received: value.clone(),
21            })
22            .map(|s| s.to_string())
23    }
24
25    fn as_string(&self) -> String {
26        self.to_string()
27    }
28}
29
30impl TypeHandler for bool {
31    fn get_value(value: Value) -> Result<bool, MpvError> {
32        value
33            .as_bool()
34            .ok_or(MpvError::ValueContainsUnexpectedType {
35                expected_type: "bool".to_string(),
36                received: value.clone(),
37            })
38    }
39
40    fn as_string(&self) -> String {
41        if *self {
42            "true".to_string()
43        } else {
44            "false".to_string()
45        }
46    }
47}
48
49impl TypeHandler for f64 {
50    fn get_value(value: Value) -> Result<f64, MpvError> {
51        value.as_f64().ok_or(MpvError::ValueContainsUnexpectedType {
52            expected_type: "f64".to_string(),
53            received: value.clone(),
54        })
55    }
56
57    fn as_string(&self) -> String {
58        self.to_string()
59    }
60}
61
62impl TypeHandler for usize {
63    fn get_value(value: Value) -> Result<usize, MpvError> {
64        value
65            .as_u64()
66            .map(|u| u as usize)
67            .ok_or(MpvError::ValueContainsUnexpectedType {
68                expected_type: "usize".to_string(),
69                received: value.clone(),
70            })
71    }
72
73    fn as_string(&self) -> String {
74        self.to_string()
75    }
76}
77
78impl TypeHandler for MpvDataType {
79    fn get_value(value: Value) -> Result<MpvDataType, MpvError> {
80        json_to_value(&value)
81    }
82
83    fn as_string(&self) -> String {
84        format!("{:?}", self)
85    }
86}
87
88impl TypeHandler for HashMap<String, MpvDataType> {
89    fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, MpvError> {
90        value
91            .as_object()
92            .ok_or(MpvError::ValueContainsUnexpectedType {
93                expected_type: "Map<String, Value>".to_string(),
94                received: value.clone(),
95            })
96            .and_then(json_map_to_hashmap)
97    }
98
99    fn as_string(&self) -> String {
100        format!("{:?}", self)
101    }
102}
103
104impl TypeHandler for Vec<PlaylistEntry> {
105    fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, MpvError> {
106        value
107            .as_array()
108            .ok_or(MpvError::ValueContainsUnexpectedType {
109                expected_type: "Array<Value>".to_string(),
110                received: value.clone(),
111            })
112            .and_then(|array| json_array_to_playlist(array))
113    }
114
115    fn as_string(&self) -> String {
116        format!("{:?}", self)
117    }
118}
119
120pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, MpvError> {
121    match value {
122        Value::Array(array) => Ok(MpvDataType::Array(json_array_to_vec(array)?)),
123        Value::Bool(b) => Ok(MpvDataType::Bool(*b)),
124        Value::Number(n) => {
125            if n.is_i64() && n.as_i64().unwrap() == -1 {
126                Ok(MpvDataType::MinusOne)
127            } else if n.is_u64() {
128                Ok(MpvDataType::Usize(n.as_u64().unwrap() as usize))
129            } else if n.is_f64() {
130                Ok(MpvDataType::Double(n.as_f64().unwrap()))
131            } else {
132                Err(MpvError::ValueContainsUnexpectedType {
133                    expected_type: "i64, u64, or f64".to_string(),
134                    received: value.clone(),
135                })
136            }
137        }
138        Value::Object(map) => Ok(MpvDataType::HashMap(json_map_to_hashmap(map)?)),
139        Value::String(s) => Ok(MpvDataType::String(s.to_string())),
140        Value::Null => Ok(MpvDataType::Null),
141    }
142}
143
144pub(crate) fn json_map_to_hashmap(
145    map: &serde_json::map::Map<String, Value>,
146) -> Result<HashMap<String, MpvDataType>, MpvError> {
147    let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
148    for (ref key, value) in map.iter() {
149        output_map.insert(key.to_string(), json_to_value(value)?);
150    }
151    Ok(output_map)
152}
153
154pub(crate) fn json_array_to_vec(array: &[Value]) -> Result<Vec<MpvDataType>, MpvError> {
155    array.iter().map(json_to_value).collect()
156}
157
158fn json_map_to_playlist_entry(
159    map: &serde_json::map::Map<String, Value>,
160) -> Result<PlaylistEntry, MpvError> {
161    let filename = match map.get("filename") {
162        Some(Value::String(s)) => s.to_string(),
163        Some(data) => {
164            return Err(MpvError::ValueContainsUnexpectedType {
165                expected_type: "String".to_owned(),
166                received: data.clone(),
167            })
168        }
169        None => return Err(MpvError::MissingMpvData),
170    };
171    let title = match map.get("title") {
172        Some(Value::String(s)) => Some(s.to_string()),
173        Some(data) => {
174            return Err(MpvError::ValueContainsUnexpectedType {
175                expected_type: "String".to_owned(),
176                received: data.clone(),
177            })
178        }
179        None => None,
180    };
181    let current = match map.get("current") {
182        Some(Value::Bool(b)) => *b,
183        Some(data) => {
184            return Err(MpvError::ValueContainsUnexpectedType {
185                expected_type: "bool".to_owned(),
186                received: data.clone(),
187            })
188        }
189        None => false,
190    };
191    Ok(PlaylistEntry {
192        id: 0,
193        filename,
194        title,
195        current,
196    })
197}
198
199pub(crate) fn json_array_to_playlist(array: &[Value]) -> Result<Vec<PlaylistEntry>, MpvError> {
200    array
201        .iter()
202        .map(|entry| match entry {
203            Value::Object(map) => json_map_to_playlist_entry(map),
204            data => Err(MpvError::ValueContainsUnexpectedType {
205                expected_type: "Map<String, Value>".to_owned(),
206                received: data.clone(),
207            }),
208        })
209        .enumerate()
210        .map(|(id, entry)| {
211            entry.map(|mut entry| {
212                entry.id = id;
213                entry
214            })
215        })
216        .collect()
217}
218
219#[cfg(test)]
220mod test {
221    use super::*;
222    use crate::MpvDataType;
223    use serde_json::json;
224    use std::collections::HashMap;
225
226    #[test]
227    fn test_json_map_to_hashmap() {
228        let json = json!({
229            "array": [1, 2, 3],
230            "bool": true,
231            "double": 1.0,
232            "usize": 1,
233            "minus_one": -1,
234            "null": null,
235            "string": "string",
236            "object": {
237                "key": "value"
238            }
239        });
240
241        let mut expected = HashMap::new();
242        expected.insert(
243            "array".to_string(),
244            MpvDataType::Array(vec![
245                MpvDataType::Usize(1),
246                MpvDataType::Usize(2),
247                MpvDataType::Usize(3),
248            ]),
249        );
250        expected.insert("bool".to_string(), MpvDataType::Bool(true));
251        expected.insert("double".to_string(), MpvDataType::Double(1.0));
252        expected.insert("usize".to_string(), MpvDataType::Usize(1));
253        expected.insert("minus_one".to_string(), MpvDataType::MinusOne);
254        expected.insert("null".to_string(), MpvDataType::Null);
255        expected.insert(
256            "string".to_string(),
257            MpvDataType::String("string".to_string()),
258        );
259        expected.insert(
260            "object".to_string(),
261            MpvDataType::HashMap(HashMap::from([(
262                "key".to_string(),
263                MpvDataType::String("value".to_string()),
264            )])),
265        );
266
267        match json_map_to_hashmap(json.as_object().unwrap()) {
268            Ok(m) => assert_eq!(m, expected),
269            Err(e) => panic!("{:?}", e),
270        }
271    }
272
273    #[test]
274    fn test_json_array_to_vec() {
275        let json = json!([
276            [1, 2, 3],
277            true,
278            1.0,
279            1,
280            -1,
281            null,
282            "string",
283            {
284                "key": "value"
285            }
286        ]);
287
288        println!("{:?}", json.as_array().unwrap());
289        println!("{:?}", json_array_to_vec(json.as_array().unwrap()));
290
291        let expected = vec![
292            MpvDataType::Array(vec![
293                MpvDataType::Usize(1),
294                MpvDataType::Usize(2),
295                MpvDataType::Usize(3),
296            ]),
297            MpvDataType::Bool(true),
298            MpvDataType::Double(1.0),
299            MpvDataType::Usize(1),
300            MpvDataType::MinusOne,
301            MpvDataType::Null,
302            MpvDataType::String("string".to_string()),
303            MpvDataType::HashMap(HashMap::from([(
304                "key".to_string(),
305                MpvDataType::String("value".to_string()),
306            )])),
307        ];
308
309        match json_array_to_vec(json.as_array().unwrap()) {
310            Ok(v) => assert_eq!(v, expected),
311            Err(e) => panic!("{:?}", e),
312        }
313    }
314
315    #[test]
316    fn test_json_array_to_playlist() -> Result<(), MpvError> {
317        let json = json!([
318            {
319                "filename": "file1",
320                "title": "title1",
321                "current": true
322            },
323            {
324                "filename": "file2",
325                "title": "title2",
326                "current": false
327            },
328            {
329                "filename": "file3",
330                "current": false
331            }
332        ]);
333
334        let expected = vec![
335            PlaylistEntry {
336                id: 0,
337                filename: "file1".to_string(),
338                title: Some("title1".to_string()),
339                current: true,
340            },
341            PlaylistEntry {
342                id: 1,
343                filename: "file2".to_string(),
344                title: Some("title2".to_string()),
345                current: false,
346            },
347            PlaylistEntry {
348                id: 2,
349                filename: "file3".to_string(),
350                title: None,
351                current: false,
352            },
353        ];
354
355        assert_eq!(json_array_to_playlist(json.as_array().unwrap())?, expected);
356
357        Ok(())
358    }
359}