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::{MpvDataType, MpvError, PlaylistEntry};
8

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

            
14
impl TypeHandler for String {
15
2
    fn get_value(value: Value) -> Result<String, MpvError> {
16
2
        value
17
2
            .as_str()
18
2
            .ok_or(MpvError::ValueContainsUnexpectedType {
19
2
                expected_type: "String".to_string(),
20
2
                received: value.clone(),
21
2
            })
22
2
            .map(|s| s.to_string())
23
2
    }
24

            
25
    fn as_string(&self) -> String {
26
        self.to_string()
27
    }
28
}
29

            
30
impl TypeHandler for bool {
31
202
    fn get_value(value: Value) -> Result<bool, MpvError> {
32
202
        value
33
202
            .as_bool()
34
202
            .ok_or(MpvError::ValueContainsUnexpectedType {
35
202
                expected_type: "bool".to_string(),
36
202
                received: value.clone(),
37
202
            })
38
202
    }
39

            
40
    fn as_string(&self) -> String {
41
        if *self {
42
            "true".to_string()
43
        } else {
44
            "false".to_string()
45
        }
46
    }
47
}
48

            
49
impl TypeHandler for f64 {
50
1586
    fn get_value(value: Value) -> Result<f64, MpvError> {
51
1586
        value.as_f64().ok_or(MpvError::ValueContainsUnexpectedType {
52
1586
            expected_type: "f64".to_string(),
53
1586
            received: value.clone(),
54
1586
        })
55
1586
    }
56

            
57
    fn as_string(&self) -> String {
58
        self.to_string()
59
    }
60
}
61

            
62
impl 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

            
78
impl 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

            
88
impl 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

            
104
impl TypeHandler for Vec<PlaylistEntry> {
105
8
    fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, MpvError> {
106
8
        value
107
8
            .as_array()
108
8
            .ok_or(MpvError::ValueContainsUnexpectedType {
109
8
                expected_type: "Array<Value>".to_string(),
110
8
                received: value.clone(),
111
8
            })
112
8
            .map(|array| json_array_to_playlist(array))
113
8
    }
114

            
115
    fn as_string(&self) -> String {
116
        format!("{:?}", self)
117
    }
118
}
119

            
120
54
pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, MpvError> {
121
54
    match value {
122
3
        Value::Array(array) => Ok(MpvDataType::Array(json_array_to_vec(array)?)),
123
13
        Value::Bool(b) => Ok(MpvDataType::Bool(*b)),
124
26
        Value::Number(n) => {
125
26
            if n.is_i64() && n.as_i64().unwrap() == -1 {
126
3
                Ok(MpvDataType::MinusOne)
127
23
            } else if n.is_u64() {
128
12
                Ok(MpvDataType::Usize(n.as_u64().unwrap() as usize))
129
11
            } else if n.is_f64() {
130
11
                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
3
        Value::Object(map) => Ok(MpvDataType::HashMap(json_map_to_hashmap(map)?)),
139
6
        Value::String(s) => Ok(MpvDataType::String(s.to_string())),
140
3
        Value::Null => Ok(MpvDataType::Null),
141
    }
142
54
}
143

            
144
4
pub(crate) fn json_map_to_hashmap(
145
4
    map: &serde_json::map::Map<String, Value>,
146
4
) -> Result<HashMap<String, MpvDataType>, MpvError> {
147
4
    let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
148
11
    for (ref key, value) in map.iter() {
149
11
        output_map.insert(key.to_string(), json_to_value(value)?);
150
    }
151
4
    Ok(output_map)
152
4
}
153

            
154
5
pub(crate) fn json_array_to_vec(array: &[Value]) -> Result<Vec<MpvDataType>, MpvError> {
155
5
    array.iter().map(json_to_value).collect()
156
5
}
157

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

            
183
#[cfg(test)]
184
mod test {
185
    use super::*;
186
    use crate::MpvDataType;
187
    use serde_json::json;
188
    use std::collections::HashMap;
189

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

            
205
        let mut expected = HashMap::new();
206
        expected.insert(
207
            "array".to_string(),
208
            MpvDataType::Array(vec![
209
                MpvDataType::Usize(1),
210
                MpvDataType::Usize(2),
211
                MpvDataType::Usize(3),
212
            ]),
213
        );
214
        expected.insert("bool".to_string(), MpvDataType::Bool(true));
215
        expected.insert("double".to_string(), MpvDataType::Double(1.0));
216
        expected.insert("usize".to_string(), MpvDataType::Usize(1));
217
        expected.insert("minus_one".to_string(), MpvDataType::MinusOne);
218
        expected.insert("null".to_string(), MpvDataType::Null);
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
        match json_map_to_hashmap(json.as_object().unwrap()) {
232
            Ok(m) => assert_eq!(m, expected),
233
            Err(e) => panic!("{:?}", e),
234
        }
235
    }
236

            
237
    #[test]
238
    fn test_json_array_to_vec() {
239
        let json = json!([
240
            [1, 2, 3],
241
            true,
242
            1.0,
243
            1,
244
            -1,
245
            null,
246
            "string",
247
            {
248
                "key": "value"
249
            }
250
        ]);
251

            
252
        println!("{:?}", json.as_array().unwrap());
253
        println!("{:?}", json_array_to_vec(json.as_array().unwrap()));
254

            
255
        let expected = vec![
256
            MpvDataType::Array(vec![
257
                MpvDataType::Usize(1),
258
                MpvDataType::Usize(2),
259
                MpvDataType::Usize(3),
260
            ]),
261
            MpvDataType::Bool(true),
262
            MpvDataType::Double(1.0),
263
            MpvDataType::Usize(1),
264
            MpvDataType::MinusOne,
265
            MpvDataType::Null,
266
            MpvDataType::String("string".to_string()),
267
            MpvDataType::HashMap(HashMap::from([(
268
                "key".to_string(),
269
                MpvDataType::String("value".to_string()),
270
            )])),
271
        ];
272

            
273
        match json_array_to_vec(json.as_array().unwrap()) {
274
            Ok(v) => assert_eq!(v, expected),
275
            Err(e) => panic!("{:?}", e),
276
        }
277
    }
278

            
279
    #[test]
280
    fn test_json_array_to_playlist() {
281
        let json = json!([
282
            {
283
                "filename": "file1",
284
                "title": "title1",
285
                "current": true
286
            },
287
            {
288
                "filename": "file2",
289
                "title": "title2",
290
                "current": false
291
            }
292
        ]);
293

            
294
        let expected = vec![
295
            PlaylistEntry {
296
                id: 0,
297
                filename: "file1".to_string(),
298
                title: "title1".to_string(),
299
                current: true,
300
            },
301
            PlaylistEntry {
302
                id: 1,
303
                filename: "file2".to_string(),
304
                title: "title2".to_string(),
305
                current: false,
306
            },
307
        ];
308

            
309
        assert_eq!(json_array_to_playlist(json.as_array().unwrap()), expected);
310
    }
311
}