use super::*;
use log::{debug, warn};
use serde_json::json;
use std::collections::HashMap;
use std::io::prelude::*;
use std::io::BufReader;
use std::iter::Iterator;
#[derive(Debug)]
pub struct PlaylistEntry {
pub id: usize,
pub filename: String,
pub title: String,
pub current: bool,
}
pub trait TypeHandler: Sized {
fn get_value(value: Value) -> Result<Self, Error>;
fn as_string(&self) -> String;
}
impl TypeHandler for String {
fn get_value(value: Value) -> Result<String, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::String(ref s) = map["data"] {
Ok(s.to_string())
} else {
Err(Error(ErrorCode::ValueDoesNotContainString))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for bool {
fn get_value(value: Value) -> Result<bool, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Bool(ref b) = map["data"] {
Ok(*b)
} else {
Err(Error(ErrorCode::ValueDoesNotContainBool))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
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, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Number(ref num) = map["data"] {
Ok(num.as_f64().unwrap())
} else {
Err(Error(ErrorCode::ValueDoesNotContainF64))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for usize {
fn get_value(value: Value) -> Result<usize, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Number(ref num) = map["data"] {
num.as_u64()
.map(|n| n as usize)
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))
} else {
Err(Error(ErrorCode::ValueDoesNotContainUsize))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for i64 {
fn get_value(value: Value) -> Result<i64, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Number(ref num) = map["data"] {
num.as_i64()
.ok_or(Error(ErrorCode::ValueDoesNotContainI64))
} else {
Err(Error(ErrorCode::ValueDoesNotContainI64))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for HashMap<String, MpvDataType> {
fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Object(ref inner_map) = map["data"] {
Ok(json_map_to_hashmap(inner_map))
} else {
Err(Error(ErrorCode::ValueDoesNotContainHashMap))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
impl TypeHandler for Vec<PlaylistEntry> {
fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Array(ref playlist_vec) = map["data"] {
Ok(json_array_to_playlist(playlist_vec))
} else {
Err(Error(ErrorCode::ValueDoesNotContainPlaylist))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
pub fn get_mpv_property<T: TypeHandler>(instance: &Mpv, property: &str) -> Result<T, Error> {
let ipc_string = json!({"command": ["get_property", property]});
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) {
Ok(val) => T::get_value(val),
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
}
pub fn get_mpv_property_string(instance: &Mpv, property: &str) -> Result<String, Error> {
let ipc_string = json!({"command": ["get_property", property]});
let val = serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string))
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?;
let map = if let Value::Object(map) = val {
Ok(map)
} else {
Err(Error(ErrorCode::UnexpectedValue))
}?;
let error = if let Value::String(ref error) = map["error"] {
Ok(error)
} else {
Err(Error(ErrorCode::UnexpectedValue))
}?;
let data = if error == "success" {
Ok(&map["data"])
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}?;
match data {
Value::Bool(b) => Ok(b.to_string()),
Value::Number(ref n) => Ok(n.to_string()),
Value::String(ref s) => Ok(s.to_string()),
Value::Array(ref array) => Ok(format!("{:?}", array)),
Value::Object(ref map) => Ok(format!("{:?}", map)),
Value::Null => Err(Error(ErrorCode::MissingValue)),
}
}
pub fn set_mpv_property(instance: &Mpv, property: &str, value: Value) -> Result<(), Error> {
let ipc_string = json!({
"command": ["set_property", property, value]
});
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) {
Ok(_) => Ok(()),
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
}
pub fn run_mpv_command(instance: &Mpv, command: &str, args: &[&str]) -> Result<(), Error> {
let mut ipc_string = json!({
"command": [command]
});
if let Value::Array(args_array) = &mut ipc_string["command"] {
for arg in args {
args_array.push(json!(arg));
}
}
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) {
Ok(feedback) => {
if let Value::String(ref error) = feedback["error"] {
if error == "success" {
Ok(())
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedResult))
}
}
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
}
pub fn observe_mpv_property(instance: &Mpv, id: &isize, property: &str) -> Result<(), Error> {
let ipc_string = json!({
"command": ["observe_property", id, property]
});
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) {
Ok(feedback) => {
if let Value::String(ref error) = feedback["error"] {
if error == "success" {
Ok(())
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedResult))
}
}
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
}
pub fn unobserve_mpv_property(instance: &Mpv, id: &isize) -> Result<(), Error> {
let ipc_string = json!({
"command": ["unobserve_property", id]
});
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) {
Ok(feedback) => {
if let Value::String(ref error) = feedback["error"] {
if error == "success" {
Ok(())
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedResult))
}
}
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
}
fn try_convert_property(name: &str, id: usize, data: MpvDataType) -> Event {
let property = match name {
"path" => match data {
MpvDataType::String(value) => Property::Path(Some(value)),
MpvDataType::Null => Property::Path(None),
_ => unimplemented!(),
},
"pause" => match data {
MpvDataType::Bool(value) => Property::Pause(value),
_ => unimplemented!(),
},
"playback-time" => match data {
MpvDataType::Double(value) => Property::PlaybackTime(Some(value)),
MpvDataType::Null => Property::PlaybackTime(None),
_ => unimplemented!(),
},
"audio-pts" => match data {
MpvDataType::Double(value) => Property::AudioPts(Some(value)),
MpvDataType::Null => Property::AudioPts(None),
_ => unimplemented!(),
},
"duration" => match data {
MpvDataType::Double(value) => Property::Duration(Some(value)),
MpvDataType::Null => Property::Duration(None),
_ => unimplemented!(),
},
"metadata" => match data {
MpvDataType::HashMap(value) => Property::Metadata(Some(value)),
MpvDataType::Null => Property::Metadata(None),
_ => unimplemented!(),
},
_ => {
warn!("Property {} not implemented", name);
Property::Unknown {
name: name.to_string(),
data,
}
}
};
Event::PropertyChange { id, property }
}
pub fn listen(instance: &mut Mpv) -> Result<Event, Error> {
let mut e;
let name = loop {
let mut response = String::new();
instance.reader.read_line(&mut response).unwrap();
response = response.trim_end().to_string();
debug!("Event: {}", response);
e = serde_json::from_str::<Value>(&response)
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?;
match e["event"] {
Value::String(ref name) => break name,
_ => {
debug!("Bad response: {:?}", response)
}
}
};
let event = match name.as_str() {
"shutdown" => Event::Shutdown,
"start-file" => Event::StartFile,
"file-loaded" => Event::FileLoaded,
"seek" => Event::Seek,
"playback-restart" => Event::PlaybackRestart,
"idle" => Event::Idle,
"tick" => Event::Tick,
"video-reconfig" => Event::VideoReconfig,
"audio-reconfig" => Event::AudioReconfig,
"tracks-changed" => Event::TracksChanged,
"track-switched" => Event::TrackSwitched,
"pause" => Event::Pause,
"unpause" => Event::Unpause,
"metadata-update" => Event::MetadataUpdate,
"chapter-change" => Event::ChapterChange,
"end-file" => Event::EndFile,
"property-change" => {
let name = match e["name"] {
Value::String(ref n) => Ok(n.to_string()),
_ => Err(Error(ErrorCode::JsonContainsUnexptectedType)),
}?;
let id: usize = match e["id"] {
Value::Number(ref n) => n.as_u64().unwrap() as usize,
_ => 0,
};
let data: MpvDataType = match e["data"] {
Value::String(ref n) => MpvDataType::String(n.to_string()),
Value::Array(ref a) => {
if name == "playlist".to_string() {
MpvDataType::Playlist(Playlist(json_array_to_playlist(a)))
} else {
MpvDataType::Array(json_array_to_vec(a))
}
}
Value::Bool(b) => MpvDataType::Bool(b),
Value::Number(ref n) => {
if n.is_u64() {
MpvDataType::Usize(n.as_u64().unwrap() as usize)
} else if let Some(f) = n.as_f64() {
MpvDataType::Double(f)
} else {
return Err(Error(ErrorCode::JsonContainsUnexptectedType));
}
}
Value::Object(ref m) => MpvDataType::HashMap(json_map_to_hashmap(m)),
Value::Null => MpvDataType::Null,
};
try_convert_property(name.as_ref(), id, data)
}
"client-message" => {
let args = match e["args"] {
Value::Array(ref a) => json_array_to_vec(a)
.iter()
.map(|arg| match arg {
MpvDataType::String(s) => Ok(s.to_owned()),
_ => Err(Error(ErrorCode::JsonContainsUnexptectedType)),
})
.collect::<Result<Vec<_>, _>>(),
_ => return Err(Error(ErrorCode::JsonContainsUnexptectedType)),
}?;
Event::ClientMessage { args }
}
_ => Event::Unimplemented,
};
Ok(event)
}
pub fn listen_raw(instance: &mut Mpv) -> String {
let mut response = String::new();
instance.reader.read_line(&mut response).unwrap();
response.trim_end().to_string()
}
fn send_command_sync(instance: &Mpv, command: Value) -> String {
let source = &instance.source;
match serde_json::to_writer(source, &command) {
Err(why) => panic!("Error: Could not write to socket: {}", why),
Ok(_) => {
let mut source = source;
source.write_all(b"\n").unwrap();
let mut response = String::new();
{
let mut reader = BufReader::new(source);
while !response.contains("\"error\":") {
response.clear();
reader.read_line(&mut response).unwrap();
}
}
debug!("Response: {}", response.trim_end());
response
}
}
}
fn json_map_to_hashmap(map: &serde_json::map::Map<String, Value>) -> HashMap<String, MpvDataType> {
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
for (ref key, ref value) in map.iter() {
match **value {
Value::Array(ref array) => {
output_map.insert(
key.to_string(),
MpvDataType::Array(json_array_to_vec(array)),
);
}
Value::Bool(ref b) => {
output_map.insert(key.to_string(), MpvDataType::Bool(*b));
}
Value::Number(ref n) => {
if n.is_u64() {
output_map.insert(
key.to_string(),
MpvDataType::Usize(n.as_u64().unwrap() as usize),
);
} else if let Some(f) = n.as_f64() {
output_map.insert(key.to_string(), MpvDataType::Double(f));
} else {
warn!("Discarding unrepresentable number for key '{}'", key);
}
}
Value::String(ref s) => {
output_map.insert(key.to_string(), MpvDataType::String(s.to_string()));
}
Value::Object(ref m) => {
output_map.insert(
key.to_string(),
MpvDataType::HashMap(json_map_to_hashmap(m)),
);
}
Value::Null => {
output_map.insert(key.to_string(), MpvDataType::Null);
}
}
}
output_map
}
fn json_array_to_vec(array: &Vec<Value>) -> Vec<MpvDataType> {
let mut output: Vec<MpvDataType> = Vec::new();
if array.len() > 0 {
match array[0] {
Value::Array(_) => {
for entry in array {
if let Value::Array(ref a) = *entry {
output.push(MpvDataType::Array(json_array_to_vec(a)));
}
}
}
Value::Bool(_) => {
for entry in array {
if let Value::Bool(ref b) = *entry {
output.push(MpvDataType::Bool(*b));
}
}
}
Value::Number(_) => {
for entry in array {
if let Value::Number(ref n) = *entry {
if n.is_u64() {
output.push(MpvDataType::Usize(n.as_u64().unwrap() as usize));
} else if let Some(f) = n.as_f64() {
output.push(MpvDataType::Double(f));
} else {
warn!("Discarding unrepresentable number in array");
}
}
}
}
Value::Object(_) => {
for entry in array {
if let Value::Object(ref map) = *entry {
output.push(MpvDataType::HashMap(json_map_to_hashmap(map)));
}
}
}
Value::String(_) => {
for entry in array {
if let Value::String(ref s) = *entry {
output.push(MpvDataType::String(s.to_string()));
}
}
}
Value::Null => {
for entry in array {
if let Value::Null = *entry {
output.push(MpvDataType::Null);
}
}
}
}
}
output
}
fn json_array_to_playlist(array: &Vec<Value>) -> Vec<PlaylistEntry> {
let mut output: Vec<PlaylistEntry> = Vec::new();
for (id, entry) in array.iter().enumerate() {
let mut filename: String = String::new();
let mut title: String = String::new();
let mut current: bool = false;
if let Value::String(ref f) = entry["filename"] {
filename = f.to_string();
}
if let Value::String(ref t) = entry["title"] {
title = t.to_string();
}
if let Value::Bool(ref b) = entry["current"] {
current = *b;
}
output.push(PlaylistEntry {
id,
filename,
title,
current,
});
}
output
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn success_response(data: Value) -> Value {
json!({"error": "success", "data": data})
}
fn error_response(msg: &str) -> Value {
json!({"error": msg})
}
#[test]
fn usize_get_value_positive_integer() {
let val = success_response(json!(42));
assert_eq!(usize::get_value(val).unwrap(), 42);
}
#[test]
fn usize_get_value_zero() {
let val = success_response(json!(0));
assert_eq!(usize::get_value(val).unwrap(), 0);
}
#[test]
fn usize_get_value_negative_returns_error() {
let val = success_response(json!(-1));
let err = usize::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainUsize));
}
#[test]
fn usize_get_value_negative_large_returns_error() {
let val = success_response(json!(-100));
let err = usize::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainUsize));
}
#[test]
fn usize_get_value_float_returns_error() {
let val = success_response(json!(3.14));
let err = usize::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainUsize));
}
#[test]
fn usize_get_value_string_returns_error() {
let val = success_response(json!("not a number"));
let err = usize::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainUsize));
}
#[test]
fn usize_get_value_mpv_error() {
let val = error_response("property not found");
let err = usize::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::MpvError(_)));
}
#[test]
fn usize_get_value_unexpected_value() {
let val = json!("not an object");
let err = usize::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::UnexpectedValue));
}
#[test]
fn i64_get_value_positive() {
let val = success_response(json!(42));
assert_eq!(i64::get_value(val).unwrap(), 42);
}
#[test]
fn i64_get_value_zero() {
let val = success_response(json!(0));
assert_eq!(i64::get_value(val).unwrap(), 0);
}
#[test]
fn i64_get_value_negative() {
let val = success_response(json!(-1));
assert_eq!(i64::get_value(val).unwrap(), -1);
}
#[test]
fn i64_get_value_negative_large() {
let val = success_response(json!(-100));
assert_eq!(i64::get_value(val).unwrap(), -100);
}
#[test]
fn i64_get_value_float_returns_error() {
let val = success_response(json!(3.14));
let err = i64::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainI64));
}
#[test]
fn i64_get_value_string_returns_error() {
let val = success_response(json!("not a number"));
let err = i64::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainI64));
}
#[test]
fn i64_get_value_mpv_error() {
let val = error_response("property not found");
let err = i64::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::MpvError(_)));
}
#[test]
fn f64_get_value_positive() {
let val = success_response(json!(3.14));
let result = f64::get_value(val).unwrap();
assert!((result - 3.14).abs() < f64::EPSILON);
}
#[test]
fn f64_get_value_negative() {
let val = success_response(json!(-1.5));
let result = f64::get_value(val).unwrap();
assert!((result - (-1.5)).abs() < f64::EPSILON);
}
#[test]
fn f64_get_value_integer_as_f64() {
let val = success_response(json!(42));
let result = f64::get_value(val).unwrap();
assert!((result - 42.0).abs() < f64::EPSILON);
}
#[test]
fn f64_get_value_string_returns_error() {
let val = success_response(json!("not a number"));
let err = f64::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainF64));
}
#[test]
fn bool_get_value_true() {
let val = success_response(json!(true));
assert_eq!(bool::get_value(val).unwrap(), true);
}
#[test]
fn bool_get_value_false() {
let val = success_response(json!(false));
assert_eq!(bool::get_value(val).unwrap(), false);
}
#[test]
fn bool_get_value_string_returns_error() {
let val = success_response(json!("true"));
let err = bool::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainBool));
}
#[test]
fn string_get_value_success() {
let val = success_response(json!("hello"));
assert_eq!(String::get_value(val).unwrap(), "hello");
}
#[test]
fn string_get_value_number_returns_error() {
let val = success_response(json!(42));
let err = String::get_value(val).unwrap_err();
assert!(matches!(err.0, ErrorCode::ValueDoesNotContainString));
}
#[test]
fn json_map_handles_null_values() {
let map: serde_json::Map<String, Value> =
serde_json::from_value(json!({"key": null})).unwrap();
let result = json_map_to_hashmap(&map);
assert!(matches!(
result.get("key").unwrap(),
MpvDataType::Null
));
}
#[test]
fn json_map_handles_negative_numbers() {
let map: serde_json::Map<String, Value> =
serde_json::from_value(json!({"pos": -1})).unwrap();
let result = json_map_to_hashmap(&map);
match result.get("pos").unwrap() {
MpvDataType::Double(v) => assert!((*v - (-1.0)).abs() < f64::EPSILON),
other => panic!("Expected Double, got {:?}", other),
}
}
#[test]
fn json_map_handles_positive_integers() {
let map: serde_json::Map<String, Value> =
serde_json::from_value(json!({"count": 5})).unwrap();
let result = json_map_to_hashmap(&map);
match result.get("count").unwrap() {
MpvDataType::Usize(v) => assert_eq!(*v, 5),
other => panic!("Expected Usize, got {:?}", other),
}
}
#[test]
fn json_map_handles_mixed_types() {
let map: serde_json::Map<String, Value> =
serde_json::from_value(json!({
"name": "test",
"count": 5,
"enabled": true,
"rate": 1.5,
"empty": null
}))
.unwrap();
let result = json_map_to_hashmap(&map);
assert!(matches!(result.get("name").unwrap(), MpvDataType::String(_)));
assert!(matches!(result.get("count").unwrap(), MpvDataType::Usize(5)));
assert!(matches!(result.get("enabled").unwrap(), MpvDataType::Bool(true)));
assert!(matches!(result.get("rate").unwrap(), MpvDataType::Double(_)));
assert!(matches!(result.get("empty").unwrap(), MpvDataType::Null));
}
#[test]
fn json_array_handles_empty_array() {
let array: Vec<Value> = vec![];
let result = json_array_to_vec(&array);
assert!(result.is_empty());
}
#[test]
fn json_array_handles_positive_integers() {
let array = vec![json!(1), json!(2), json!(3)];
let result = json_array_to_vec(&array);
assert_eq!(result.len(), 3);
assert!(matches!(result[0], MpvDataType::Usize(1)));
assert!(matches!(result[1], MpvDataType::Usize(2)));
assert!(matches!(result[2], MpvDataType::Usize(3)));
}
#[test]
fn json_array_handles_floats() {
let array = vec![json!(1.5), json!(2.5)];
let result = json_array_to_vec(&array);
assert_eq!(result.len(), 2);
match &result[0] {
MpvDataType::Double(v) => assert!((*v - 1.5).abs() < f64::EPSILON),
other => panic!("Expected Double, got {:?}", other),
}
}
#[test]
fn json_array_handles_strings() {
let array = vec![json!("a"), json!("b")];
let result = json_array_to_vec(&array);
assert_eq!(result.len(), 2);
assert!(matches!(&result[0], MpvDataType::String(s) if s == "a"));
}
#[test]
fn json_array_handles_bools() {
let array = vec![json!(true), json!(false)];
let result = json_array_to_vec(&array);
assert_eq!(result.len(), 2);
assert!(matches!(result[0], MpvDataType::Bool(true)));
assert!(matches!(result[1], MpvDataType::Bool(false)));
}
#[test]
fn json_array_handles_null_values() {
let array = vec![Value::Null, Value::Null];
let result = json_array_to_vec(&array);
assert_eq!(result.len(), 2);
assert!(matches!(result[0], MpvDataType::Null));
assert!(matches!(result[1], MpvDataType::Null));
}
#[test]
fn playlist_parses_entries() {
let array = vec![
json!({"filename": "file1.mp4", "title": "Title 1", "current": true}),
json!({"filename": "file2.mp4", "title": "Title 2", "current": false}),
];
let result = json_array_to_playlist(&array);
assert_eq!(result.len(), 2);
assert_eq!(result[0].id, 0);
assert_eq!(result[0].filename, "file1.mp4");
assert_eq!(result[0].title, "Title 1");
assert!(result[0].current);
assert_eq!(result[1].id, 1);
assert!(!result[1].current);
}
#[test]
fn playlist_handles_missing_fields() {
let array = vec![json!({"filename": "file1.mp4"})];
let result = json_array_to_playlist(&array);
assert_eq!(result.len(), 1);
assert_eq!(result[0].filename, "file1.mp4");
assert_eq!(result[0].title, "");
assert!(!result[0].current);
}
}