use std::collections::HashMap;
use serde_json::Value;
use crate::{MpvDataType, MpvError, PlaylistEntry};
pub trait TypeHandler: Sized {
fn get_value(value: Value) -> Result<Self, MpvError>;
fn as_string(&self) -> String;
}
impl TypeHandler for String {
fn get_value(value: Value) -> Result<String, MpvError> {
value
.as_str()
.ok_or(MpvError::ValueContainsUnexpectedType {
expected_type: "String".to_string(),
received: value.clone(),
})
.map(|s| s.to_string())
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for bool {
fn get_value(value: Value) -> Result<bool, MpvError> {
value
.as_bool()
.ok_or(MpvError::ValueContainsUnexpectedType {
expected_type: "bool".to_string(),
received: value.clone(),
})
}
fn as_string(&self) -> String {
if *self {
"true".to_string()
} else {
"false".to_string()
}
}
}
impl TypeHandler for f64 {
fn get_value(value: Value) -> Result<f64, MpvError> {
value.as_f64().ok_or(MpvError::ValueContainsUnexpectedType {
expected_type: "f64".to_string(),
received: value.clone(),
})
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for usize {
fn get_value(value: Value) -> Result<usize, MpvError> {
value
.as_u64()
.map(|u| u as usize)
.ok_or(MpvError::ValueContainsUnexpectedType {
expected_type: "usize".to_string(),
received: value.clone(),
})
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for MpvDataType {
fn get_value(value: Value) -> Result<MpvDataType, MpvError> {
json_to_value(&value)
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
impl TypeHandler for HashMap<String, MpvDataType> {
fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, MpvError> {
value
.as_object()
.ok_or(MpvError::ValueContainsUnexpectedType {
expected_type: "Map<String, Value>".to_string(),
received: value.clone(),
})
.and_then(json_map_to_hashmap)
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
impl TypeHandler for Vec<PlaylistEntry> {
fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, MpvError> {
value
.as_array()
.ok_or(MpvError::ValueContainsUnexpectedType {
expected_type: "Array<Value>".to_string(),
received: value.clone(),
})
.and_then(|array| json_array_to_playlist(array))
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, MpvError> {
match value {
Value::Array(array) => Ok(MpvDataType::Array(json_array_to_vec(array)?)),
Value::Bool(b) => Ok(MpvDataType::Bool(*b)),
Value::Number(n) => {
if n.is_i64() && n.as_i64().unwrap() == -1 {
Ok(MpvDataType::MinusOne)
} else if n.is_u64() {
Ok(MpvDataType::Usize(n.as_u64().unwrap() as usize))
} else if n.is_f64() {
Ok(MpvDataType::Double(n.as_f64().unwrap()))
} else {
Err(MpvError::ValueContainsUnexpectedType {
expected_type: "i64, u64, or f64".to_string(),
received: value.clone(),
})
}
}
Value::Object(map) => Ok(MpvDataType::HashMap(json_map_to_hashmap(map)?)),
Value::String(s) => Ok(MpvDataType::String(s.to_string())),
Value::Null => Ok(MpvDataType::Null),
}
}
pub(crate) fn json_map_to_hashmap(
map: &serde_json::map::Map<String, Value>,
) -> Result<HashMap<String, MpvDataType>, MpvError> {
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
for (ref key, value) in map.iter() {
output_map.insert(key.to_string(), json_to_value(value)?);
}
Ok(output_map)
}
pub(crate) fn json_array_to_vec(array: &[Value]) -> Result<Vec<MpvDataType>, MpvError> {
array.iter().map(json_to_value).collect()
}
fn json_map_to_playlist_entry(
map: &serde_json::map::Map<String, Value>,
) -> Result<PlaylistEntry, MpvError> {
let filename = match map.get("filename") {
Some(Value::String(s)) => s.to_string(),
Some(data) => {
return Err(MpvError::ValueContainsUnexpectedType {
expected_type: "String".to_owned(),
received: data.clone(),
})
}
None => return Err(MpvError::MissingMpvData),
};
let title = match map.get("title") {
Some(Value::String(s)) => Some(s.to_string()),
Some(data) => {
return Err(MpvError::ValueContainsUnexpectedType {
expected_type: "String".to_owned(),
received: data.clone(),
})
}
None => None,
};
let current = match map.get("current") {
Some(Value::Bool(b)) => *b,
Some(data) => {
return Err(MpvError::ValueContainsUnexpectedType {
expected_type: "bool".to_owned(),
received: data.clone(),
})
}
None => false,
};
Ok(PlaylistEntry {
id: 0,
filename,
title,
current,
})
}
pub(crate) fn json_array_to_playlist(array: &[Value]) -> Result<Vec<PlaylistEntry>, MpvError> {
array
.iter()
.map(|entry| match entry {
Value::Object(map) => json_map_to_playlist_entry(map),
data => Err(MpvError::ValueContainsUnexpectedType {
expected_type: "Map<String, Value>".to_owned(),
received: data.clone(),
}),
})
.enumerate()
.map(|(id, entry)| {
entry.map(|mut entry| {
entry.id = id;
entry
})
})
.collect()
}
#[cfg(test)]
mod test {
use super::*;
use crate::MpvDataType;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn test_json_map_to_hashmap() {
let json = json!({
"array": [1, 2, 3],
"bool": true,
"double": 1.0,
"usize": 1,
"minus_one": -1,
"null": null,
"string": "string",
"object": {
"key": "value"
}
});
let mut expected = HashMap::new();
expected.insert(
"array".to_string(),
MpvDataType::Array(vec![
MpvDataType::Usize(1),
MpvDataType::Usize(2),
MpvDataType::Usize(3),
]),
);
expected.insert("bool".to_string(), MpvDataType::Bool(true));
expected.insert("double".to_string(), MpvDataType::Double(1.0));
expected.insert("usize".to_string(), MpvDataType::Usize(1));
expected.insert("minus_one".to_string(), MpvDataType::MinusOne);
expected.insert("null".to_string(), MpvDataType::Null);
expected.insert(
"string".to_string(),
MpvDataType::String("string".to_string()),
);
expected.insert(
"object".to_string(),
MpvDataType::HashMap(HashMap::from([(
"key".to_string(),
MpvDataType::String("value".to_string()),
)])),
);
match json_map_to_hashmap(json.as_object().unwrap()) {
Ok(m) => assert_eq!(m, expected),
Err(e) => panic!("{:?}", e),
}
}
#[test]
fn test_json_array_to_vec() {
let json = json!([
[1, 2, 3],
true,
1.0,
1,
-1,
null,
"string",
{
"key": "value"
}
]);
println!("{:?}", json.as_array().unwrap());
println!("{:?}", json_array_to_vec(json.as_array().unwrap()));
let expected = vec![
MpvDataType::Array(vec![
MpvDataType::Usize(1),
MpvDataType::Usize(2),
MpvDataType::Usize(3),
]),
MpvDataType::Bool(true),
MpvDataType::Double(1.0),
MpvDataType::Usize(1),
MpvDataType::MinusOne,
MpvDataType::Null,
MpvDataType::String("string".to_string()),
MpvDataType::HashMap(HashMap::from([(
"key".to_string(),
MpvDataType::String("value".to_string()),
)])),
];
match json_array_to_vec(json.as_array().unwrap()) {
Ok(v) => assert_eq!(v, expected),
Err(e) => panic!("{:?}", e),
}
}
#[test]
fn test_json_array_to_playlist() -> Result<(), MpvError> {
let json = json!([
{
"filename": "file1",
"title": "title1",
"current": true
},
{
"filename": "file2",
"title": "title2",
"current": false
},
{
"filename": "file3",
"current": false
}
]);
let expected = vec![
PlaylistEntry {
id: 0,
filename: "file1".to_string(),
title: Some("title1".to_string()),
current: true,
},
PlaylistEntry {
id: 1,
filename: "file2".to_string(),
title: Some("title2".to_string()),
current: false,
},
PlaylistEntry {
id: 2,
filename: "file3".to_string(),
title: None,
current: false,
},
];
assert_eq!(json_array_to_playlist(json.as_array().unwrap())?, expected);
Ok(())
}
}