1
//! JSON parsing logic for command responses from [`MpvIpc`](crate::ipc::MpvIpc).
2

            
3
use std::collections::HashMap;
4

            
5
use serde_json::Value;
6

            
7
use crate::{Error, ErrorCode, MpvDataType, PlaylistEntry};
8

            
9
pub trait TypeHandler: Sized {
10
    fn get_value(value: Value) -> Result<Self, Error>;
11
    fn as_string(&self) -> String;
12
}
13

            
14
impl TypeHandler for String {
15
4
    fn get_value(value: Value) -> Result<String, Error> {
16
4
        value
17
4
            .as_str()
18
4
            .ok_or(Error(ErrorCode::ValueDoesNotContainString))
19
4
            .map(|s| s.to_string())
20
4
    }
21

            
22
    fn as_string(&self) -> String {
23
        self.to_string()
24
    }
25
}
26

            
27
impl TypeHandler for bool {
28
404
    fn get_value(value: Value) -> Result<bool, Error> {
29
404
        value
30
404
            .as_bool()
31
404
            .ok_or(Error(ErrorCode::ValueDoesNotContainBool))
32
404
    }
33

            
34
    fn as_string(&self) -> String {
35
        if *self {
36
            "true".to_string()
37
        } else {
38
            "false".to_string()
39
        }
40
    }
41
}
42

            
43
impl TypeHandler for f64 {
44
13708
    fn get_value(value: Value) -> Result<f64, Error> {
45
13708
        value
46
13708
            .as_f64()
47
13708
            .ok_or(Error(ErrorCode::ValueDoesNotContainF64))
48
13708
    }
49

            
50
    fn as_string(&self) -> String {
51
        self.to_string()
52
    }
53
}
54

            
55
impl TypeHandler for usize {
56
    fn get_value(value: Value) -> Result<usize, Error> {
57
        value
58
            .as_u64()
59
            .map(|u| u as usize)
60
            .ok_or(Error(ErrorCode::ValueDoesNotContainUsize))
61
    }
62

            
63
    fn as_string(&self) -> String {
64
        self.to_string()
65
    }
66
}
67

            
68
impl TypeHandler for HashMap<String, MpvDataType> {
69
    fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, Error> {
70
        value
71
            .as_object()
72
            .ok_or(Error(ErrorCode::ValueDoesNotContainHashMap))
73
            .map(json_map_to_hashmap)
74
    }
75

            
76
    fn as_string(&self) -> String {
77
        format!("{:?}", self)
78
    }
79
}
80

            
81
impl TypeHandler for Vec<PlaylistEntry> {
82
16
    fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, Error> {
83
16
        value
84
16
            .as_array()
85
16
            .ok_or(Error(ErrorCode::ValueDoesNotContainPlaylist))
86
16
            .map(|array| json_array_to_playlist(array))
87
16
    }
88

            
89
    fn as_string(&self) -> String {
90
        format!("{:?}", self)
91
    }
92
}
93

            
94
5
pub(crate) fn json_map_to_hashmap(
95
5
    map: &serde_json::map::Map<String, Value>,
96
5
) -> HashMap<String, MpvDataType> {
97
5
    let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
98
10
    for (ref key, value) in map.iter() {
99
10
        match *value {
100
1
            Value::Array(ref array) => {
101
1
                output_map.insert(
102
1
                    key.to_string(),
103
1
                    MpvDataType::Array(json_array_to_vec(array)),
104
1
                );
105
1
            }
106
1
            Value::Bool(ref b) => {
107
1
                output_map.insert(key.to_string(), MpvDataType::Bool(*b));
108
1
            }
109
2
            Value::Number(ref n) => {
110
2
                if n.is_u64() {
111
1
                    output_map.insert(
112
1
                        key.to_string(),
113
1
                        MpvDataType::Usize(n.as_u64().unwrap() as usize),
114
1
                    );
115
1
                } else if n.is_f64() {
116
1
                    output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap()));
117
1
                } else {
118
                    panic!("unimplemented number");
119
                }
120
            }
121
4
            Value::String(ref s) => {
122
4
                output_map.insert(key.to_string(), MpvDataType::String(s.to_string()));
123
4
            }
124
1
            Value::Object(ref m) => {
125
1
                output_map.insert(
126
1
                    key.to_string(),
127
1
                    MpvDataType::HashMap(json_map_to_hashmap(m)),
128
1
                );
129
1
            }
130
            Value::Null => {
131
1
                unimplemented!();
132
            }
133
        }
134
    }
135
4
    output_map
136
4
}
137

            
138
6
pub(crate) fn json_array_to_vec(array: &[Value]) -> Vec<MpvDataType> {
139
6
    array
140
6
        .iter()
141
22
        .map(|entry| match entry {
142
2
            Value::Array(a) => MpvDataType::Array(json_array_to_vec(a)),
143
2
            Value::Bool(b) => MpvDataType::Bool(*b),
144
13
            Value::Number(n) => {
145
13
                if n.is_u64() {
146
11
                    MpvDataType::Usize(n.as_u64().unwrap() as usize)
147
2
                } else if n.is_f64() {
148
2
                    MpvDataType::Double(n.as_f64().unwrap())
149
                } else {
150
                    panic!("unimplemented number");
151
                }
152
            }
153
2
            Value::Object(ref o) => MpvDataType::HashMap(json_map_to_hashmap(o)),
154
2
            Value::String(s) => MpvDataType::String(s.to_owned()),
155
            Value::Null => {
156
1
                unimplemented!();
157
            }
158
21
        })
159
6
        .collect()
160
6
}
161

            
162
17
pub(crate) fn json_array_to_playlist(array: &[Value]) -> Vec<PlaylistEntry> {
163
17
    let mut output: Vec<PlaylistEntry> = Vec::new();
164
26
    for (id, entry) in array.iter().enumerate() {
165
26
        let mut filename: String = String::new();
166
26
        let mut title: String = String::new();
167
26
        let mut current: bool = false;
168
26
        if let Value::String(ref f) = entry["filename"] {
169
26
            filename = f.to_string();
170
26
        }
171
26
        if let Value::String(ref t) = entry["title"] {
172
26
            title = t.to_string();
173
26
        }
174
26
        if let Value::Bool(ref b) = entry["current"] {
175
26
            current = *b;
176
26
        }
177
26
        output.push(PlaylistEntry {
178
26
            id,
179
26
            filename,
180
26
            title,
181
26
            current,
182
26
        });
183
    }
184
17
    output
185
17
}
186

            
187
#[cfg(test)]
188
mod test {
189
    use super::*;
190
    use crate::MpvDataType;
191
    use serde_json::json;
192
    use std::collections::HashMap;
193

            
194
    #[test]
195
    fn test_json_map_to_hashmap() {
196
        let json = json!({
197
            "array": [1, 2, 3],
198
            "bool": true,
199
            "double": 1.0,
200
            "usize": 1,
201
            "string": "string",
202
            "object": {
203
                "key": "value"
204
            }
205
        });
206

            
207
        let mut expected = HashMap::new();
208
        expected.insert(
209
            "array".to_string(),
210
            MpvDataType::Array(vec![
211
                MpvDataType::Usize(1),
212
                MpvDataType::Usize(2),
213
                MpvDataType::Usize(3),
214
            ]),
215
        );
216
        expected.insert("bool".to_string(), MpvDataType::Bool(true));
217
        expected.insert("double".to_string(), MpvDataType::Double(1.0));
218
        expected.insert("usize".to_string(), MpvDataType::Usize(1));
219
        expected.insert(
220
            "string".to_string(),
221
            MpvDataType::String("string".to_string()),
222
        );
223
        expected.insert(
224
            "object".to_string(),
225
            MpvDataType::HashMap(HashMap::from([(
226
                "key".to_string(),
227
                MpvDataType::String("value".to_string()),
228
            )])),
229
        );
230

            
231
        assert_eq!(json_map_to_hashmap(json.as_object().unwrap()), expected);
232
    }
233

            
234
    #[test]
235
    #[should_panic]
236
    fn test_json_map_to_hashmap_fail_on_null() {
237
        json_map_to_hashmap(
238
            json!({
239
                "null": null
240
            })
241
            .as_object()
242
            .unwrap(),
243
        );
244
    }
245

            
246
    #[test]
247
    fn test_json_array_to_vec() {
248
        let json = json!([
249
            [1, 2, 3],
250
            true,
251
            1.0,
252
            1,
253
            "string",
254
            {
255
                "key": "value"
256
            }
257
        ]);
258

            
259
        println!("{:?}", json.as_array().unwrap());
260
        println!("{:?}", json_array_to_vec(json.as_array().unwrap()));
261

            
262
        let expected = vec![
263
            MpvDataType::Array(vec![
264
                MpvDataType::Usize(1),
265
                MpvDataType::Usize(2),
266
                MpvDataType::Usize(3),
267
            ]),
268
            MpvDataType::Bool(true),
269
            MpvDataType::Double(1.0),
270
            MpvDataType::Usize(1),
271
            MpvDataType::String("string".to_string()),
272
            MpvDataType::HashMap(HashMap::from([(
273
                "key".to_string(),
274
                MpvDataType::String("value".to_string()),
275
            )])),
276
        ];
277

            
278
        assert_eq!(json_array_to_vec(json.as_array().unwrap()), expected);
279
    }
280

            
281
    #[test]
282
    #[should_panic]
283
    fn test_json_array_to_vec_fail_on_null() {
284
        json_array_to_vec(json!([null]).as_array().unwrap().as_slice());
285
    }
286

            
287
    #[test]
288
    fn test_json_array_to_playlist() {
289
        let json = json!([
290
            {
291
                "filename": "file1",
292
                "title": "title1",
293
                "current": true
294
            },
295
            {
296
                "filename": "file2",
297
                "title": "title2",
298
                "current": false
299
            }
300
        ]);
301

            
302
        let expected = vec![
303
            PlaylistEntry {
304
                id: 0,
305
                filename: "file1".to_string(),
306
                title: "title1".to_string(),
307
                current: true,
308
            },
309
            PlaylistEntry {
310
                id: 1,
311
                filename: "file2".to_string(),
312
                title: "title2".to_string(),
313
                current: false,
314
            },
315
        ];
316

            
317
        assert_eq!(json_array_to_playlist(json.as_array().unwrap()), expected);
318
    }
319
}