Compare commits

...

3 Commits

10 changed files with 371 additions and 256 deletions

View File

@ -12,8 +12,8 @@ async fn main() -> Result<(), MpvError> {
let playlist = mpv.get_playlist().await?;
println!("playlist: {:?}", playlist);
let playback_time: f64 = mpv.get_property("playback-time").await?;
println!("playback-time: {}", playback_time);
let playback_time: Option<f64> = mpv.get_property("playback-time").await?;
println!("playback-time: {:?}", playback_time);
Ok(())
}

View File

@ -30,6 +30,7 @@
])
pkgs.mpv
pkgs.grcov
pkgs.cargo-nextest
];
RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/";
});

View File

@ -134,18 +134,22 @@ impl IntoRawCommandPart for SeekOptions {
pub trait GetPropertyTypeHandler: Sized {
// TODO: fix this
#[allow(async_fn_in_trait)]
async fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, MpvError>;
async fn get_property_generic(instance: &Mpv, property: &str)
-> Result<Option<Self>, MpvError>;
}
impl<T> GetPropertyTypeHandler for T
where
T: TypeHandler,
{
async fn get_property_generic(instance: &Mpv, property: &str) -> Result<T, MpvError> {
async fn get_property_generic(instance: &Mpv, property: &str) -> Result<Option<T>, MpvError> {
instance
.get_property_value(property)
.await
.and_then(T::get_value)
.and_then(|value| match value {
Some(v) => T::get_value(v).map(|v| Some(v)),
None => Ok(None),
})
}
}
@ -172,7 +176,7 @@ where
instance
.command_sender
.send((
MpvIpcCommand::SetProperty(property.to_owned(), value),
MpvIpcCommand::SetProperty(property.to_owned(), value.to_owned()),
res_tx,
))
.await
@ -277,7 +281,7 @@ impl Mpv {
command: &str,
args: &[&str],
) -> Result<Option<Value>, MpvError> {
let command = Vec::from(
let command_vec = Vec::from(
[command]
.iter()
.chain(args.iter())
@ -287,7 +291,7 @@ impl Mpv {
);
let (res_tx, res_rx) = oneshot::channel();
self.command_sender
.send((MpvIpcCommand::Command(command), res_tx))
.send((MpvIpcCommand::Command(command_vec.clone()), res_tx))
.await
.map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
@ -462,7 +466,10 @@ impl Mpv {
pub async fn get_property<T: GetPropertyTypeHandler>(
&self,
property: &str,
) -> Result<T, MpvError> {
) -> Result<Option<T>, MpvError> {
// NOTE: `get_property_generic` is implemented in terms of `get_property_value`,
// which wraps errors in `MpvError::GetPropertyFailure`. That is why it is
// not needed here.
T::get_property_generic(self, property).await
}
@ -487,7 +494,7 @@ impl Mpv {
/// Ok(())
/// }
/// ```
pub async fn get_property_value(&self, property: &str) -> Result<Value, MpvError> {
pub async fn get_property_value(&self, property: &str) -> Result<Option<Value>, MpvError> {
let (res_tx, res_rx) = oneshot::channel();
self.command_sender
.send((MpvIpcCommand::GetProperty(property.to_owned()), res_tx))
@ -495,9 +502,7 @@ impl Mpv {
.map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
match res_rx.await {
Ok(MpvIpcResponse(response)) => {
response.and_then(|value| value.ok_or(MpvError::MissingMpvData))
}
Ok(MpvIpcResponse(response)) => response,
Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
}
}
@ -526,11 +531,10 @@ impl Mpv {
/// Ok(())
/// }
/// ```
pub async fn set_property<T: SetPropertyTypeHandler<T>>(
&self,
property: &str,
value: T,
) -> Result<(), MpvError> {
T::set_property_generic(self, property, value).await
pub async fn set_property<T>(&self, property: &str, value: T) -> Result<(), MpvError>
where
T: SetPropertyTypeHandler<T> + Clone + fmt::Debug,
{
T::set_property_generic(self, property, value.clone()).await
}
}

View File

@ -46,3 +46,48 @@ pub enum MpvError {
#[error("Unknown error: {0}")]
Other(String),
}
impl PartialEq for MpvError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::MpvError(l0), Self::MpvError(r0)) => l0 == r0,
(Self::MpvSocketConnectionError(l0), Self::MpvSocketConnectionError(r0)) => l0 == r0,
(Self::InternalConnectionError(l0), Self::InternalConnectionError(r0)) => l0 == r0,
(Self::JsonParseError(l0), Self::JsonParseError(r0)) => {
l0.to_string() == r0.to_string()
}
(
Self::ValueContainsUnexpectedType {
expected_type: l_expected_type,
received: l_received,
},
Self::ValueContainsUnexpectedType {
expected_type: r_expected_type,
received: r_received,
},
) => l_expected_type == r_expected_type && l_received == r_received,
(
Self::DataContainsUnexpectedType {
expected_type: l_expected_type,
received: l_received,
},
Self::DataContainsUnexpectedType {
expected_type: r_expected_type,
received: r_received,
},
) => l_expected_type == r_expected_type && l_received == r_received,
(
Self::MissingKeyInObject {
key: l_key,
map: l_map,
},
Self::MissingKeyInObject {
key: r_key,
map: r_map,
},
) => l_key == r_key && l_map == r_map,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}

View File

@ -1,9 +1,13 @@
//! JSON parsing logic for properties returned in [`Event::PropertyChange`]
//! JSON parsing logic for properties returned either by
//! [`Mpv::get_property`] or [`Event::PropertyChange`]
//!
//! This module is used to parse the json data from the `data` field of the
//! [`Event::PropertyChange`] variant. Mpv has about 1000 different properties
//! This module is used to parse the json data from the `data` field of
//! known properties. Mpv has about 1000 different properties
//! as of `v0.38.0`, so this module will only implement the most common ones.
// TODO: reuse this logic for providing a more typesafe response API to `Mpv::get_property()`
// Although this data is currently of type `Option<`
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
@ -129,14 +133,19 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
};
Ok((id, Property::Metadata(metadata)))
}
// "playlist" => {
// let playlist = match data {
// MpvDataType::Array(a) => json_array_to_playlist(&a),
// MpvDataType::Null => Vec::new(),
// _ => return Err(Error(ErrorCode::ValueDoesNotContainPlaylist)),
// };
// Ok((id, Property::Playlist(playlist)))
// }
"playlist" => {
let playlist = match data {
Some(MpvDataType::Array(a)) => mpv_array_to_playlist(&a)?,
None => Vec::new(),
Some(data) => {
return Err(MpvError::DataContainsUnexpectedType {
expected_type: "Array".to_owned(),
received: data,
})
}
};
Ok((id, Property::Playlist(playlist)))
}
"playlist-pos" => {
let playlist_pos = match data {
Some(MpvDataType::Usize(u)) => Some(u),
@ -246,3 +255,59 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
_ => Ok((id, Property::Unknown { name, data })),
}
}
fn mpv_data_to_playlist_entry(
map: &HashMap<String, MpvDataType>,
) -> Result<PlaylistEntry, MpvError> {
let filename = match map.get("filename") {
Some(MpvDataType::String(s)) => s.to_string(),
Some(data) => {
return Err(MpvError::DataContainsUnexpectedType {
expected_type: "String".to_owned(),
received: data.clone(),
})
}
None => return Err(MpvError::MissingMpvData),
};
let title = match map.get("title") {
Some(MpvDataType::String(s)) => s.to_string(),
Some(data) => {
return Err(MpvError::DataContainsUnexpectedType {
expected_type: "String".to_owned(),
received: data.clone(),
})
}
None => return Err(MpvError::MissingMpvData),
};
let current = match map.get("current") {
Some(MpvDataType::Bool(b)) => *b,
Some(data) => {
return Err(MpvError::DataContainsUnexpectedType {
expected_type: "bool".to_owned(),
received: data.clone(),
})
}
None => return Err(MpvError::MissingMpvData),
};
Ok(PlaylistEntry {
id: 0,
filename,
title,
current,
})
}
fn mpv_array_to_playlist(array: &[MpvDataType]) -> Result<Vec<PlaylistEntry>, MpvError> {
array
.iter()
.map(|value| match value {
MpvDataType::HashMap(map) => mpv_data_to_playlist_entry(map),
_ => Err(MpvError::DataContainsUnexpectedType {
expected_type: "HashMap".to_owned(),
received: value.clone(),
}),
})
.enumerate()
.map(|(id, entry)| entry.map(|entry| PlaylistEntry { id, ..entry }))
.collect()
}

View File

@ -141,12 +141,15 @@ pub trait MpvExt {
impl MpvExt for Mpv {
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError> {
self.get_property("metadata").await
self.get_property("metadata")
.await?
.ok_or(MpvError::MissingMpvData)
}
async fn get_playlist(&self) -> Result<Playlist, MpvError> {
self.get_property::<Vec<PlaylistEntry>>("playlist")
.await
.await?
.ok_or(MpvError::MissingMpvData)
.map(Playlist)
}
@ -174,15 +177,15 @@ impl MpvExt for Mpv {
let enabled = match option {
Switch::On => "yes",
Switch::Off => "no",
Switch::Toggle => {
self.get_property::<String>("pause")
.await
.map(|s| match s.as_str() {
"yes" => "no",
"no" => "yes",
_ => "no",
})?
}
Switch::Toggle => self
.get_property::<String>("pause")
.await?
.ok_or(MpvError::MissingMpvData)
.map(|s| match s.as_str() {
"yes" => "no",
"no" => "yes",
_ => "no",
})?,
};
self.set_property("pause", enabled).await
}
@ -238,16 +241,16 @@ impl MpvExt for Mpv {
}
async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError> {
match self.get_property::<usize>("playlist-pos").await {
Ok(current_id) => {
self.run_command(MpvCommand::PlaylistMove {
from: id,
to: current_id + 1,
})
.await
}
Err(msg) => Err(msg),
}
let current_id = self
.get_property::<usize>("playlist-pos")
.await?
.ok_or(MpvError::MissingMpvData)?;
self.run_command(MpvCommand::PlaylistMove {
from: id,
to: current_id + 1,
})
.await
}
async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError> {
@ -266,15 +269,15 @@ impl MpvExt for Mpv {
let enabled = match option {
Switch::On => "inf",
Switch::Off => "no",
Switch::Toggle => {
self.get_property::<String>("loop-file")
.await
.map(|s| match s.as_str() {
"inf" => "no",
"no" => "inf",
_ => "no",
})?
}
Switch::Toggle => self
.get_property::<String>("loop-file")
.await?
.ok_or(MpvError::MissingMpvData)
.map(|s| match s.as_str() {
"inf" => "no",
"no" => "inf",
_ => "no",
})?,
};
self.set_property("loop-file", enabled).await
}
@ -283,32 +286,32 @@ impl MpvExt for Mpv {
let enabled = match option {
Switch::On => "inf",
Switch::Off => "no",
Switch::Toggle => {
self.get_property::<String>("loop-playlist")
.await
.map(|s| match s.as_str() {
"inf" => "no",
"no" => "inf",
_ => "no",
})?
}
Switch::Toggle => self
.get_property::<String>("loop-playlist")
.await?
.ok_or(MpvError::MissingMpvData)
.map(|s| match s.as_str() {
"inf" => "no",
"no" => "inf",
_ => "no",
})?,
};
self.set_property("loo-playlist", enabled).await
self.set_property("loop-playlist", enabled).await
}
async fn set_mute(&self, option: Switch) -> Result<(), MpvError> {
let enabled = match option {
Switch::On => "yes",
Switch::Off => "no",
Switch::Toggle => {
self.get_property::<String>("mute")
.await
.map(|s| match s.as_str() {
"yes" => "no",
"no" => "yes",
_ => "no",
})?
}
Switch::Toggle => self
.get_property::<String>("mute")
.await?
.ok_or(MpvError::MissingMpvData)
.map(|s| match s.as_str() {
"yes" => "no",
"no" => "yes",
_ => "no",
})?,
};
self.set_property("mute", enabled).await
}
@ -318,19 +321,15 @@ impl MpvExt for Mpv {
input_speed: f64,
option: NumberChangeOptions,
) -> Result<(), MpvError> {
match self.get_property::<f64>("speed").await {
Ok(speed) => match option {
NumberChangeOptions::Increase => {
self.set_property("speed", speed + input_speed).await
}
let speed = self
.get_property::<f64>("speed")
.await?
.ok_or(MpvError::MissingMpvData)?;
NumberChangeOptions::Decrease => {
self.set_property("speed", speed - input_speed).await
}
NumberChangeOptions::Absolute => self.set_property("speed", input_speed).await,
},
Err(msg) => Err(msg),
match option {
NumberChangeOptions::Increase => self.set_property("speed", speed + input_speed).await,
NumberChangeOptions::Decrease => self.set_property("speed", speed - input_speed).await,
NumberChangeOptions::Absolute => self.set_property("speed", input_speed).await,
}
}
@ -339,19 +338,19 @@ impl MpvExt for Mpv {
input_volume: f64,
option: NumberChangeOptions,
) -> Result<(), MpvError> {
match self.get_property::<f64>("volume").await {
Ok(volume) => match option {
NumberChangeOptions::Increase => {
self.set_property("volume", volume + input_volume).await
}
let volume = self
.get_property::<f64>("volume")
.await?
.ok_or(MpvError::MissingMpvData)?;
NumberChangeOptions::Decrease => {
self.set_property("volume", volume - input_volume).await
}
NumberChangeOptions::Absolute => self.set_property("volume", input_volume).await,
},
Err(msg) => Err(msg),
match option {
NumberChangeOptions::Increase => {
self.set_property("volume", volume + input_volume).await
}
NumberChangeOptions::Decrease => {
self.set_property("volume", volume - input_volume).await
}
NumberChangeOptions::Absolute => self.set_property("volume", input_volume).await,
}
}

View File

@ -109,7 +109,7 @@ impl TypeHandler for Vec<PlaylistEntry> {
expected_type: "Array<Value>".to_string(),
received: value.clone(),
})
.map(|array| json_array_to_playlist(array))
.and_then(|array| json_array_to_playlist(array))
}
fn as_string(&self) -> String {
@ -155,29 +155,65 @@ pub(crate) fn json_array_to_vec(array: &[Value]) -> Result<Vec<MpvDataType>, Mpv
array.iter().map(json_to_value).collect()
}
pub(crate) fn json_array_to_playlist(array: &[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();
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(),
})
}
if let Value::String(ref t) = entry["title"] {
title = t.to_string();
None => return Err(MpvError::MissingMpvData),
};
let title = match map.get("title") {
Some(Value::String(s)) => s.to_string(),
Some(data) => {
return Err(MpvError::ValueContainsUnexpectedType {
expected_type: "String".to_owned(),
received: data.clone(),
})
}
if let Value::Bool(ref b) = entry["current"] {
current = *b;
None => return Err(MpvError::MissingMpvData),
};
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(),
})
}
output.push(PlaylistEntry {
id,
filename,
title,
current,
});
}
output
None => return Err(MpvError::MissingMpvData),
};
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)]
@ -277,7 +313,7 @@ mod test {
}
#[test]
fn test_json_array_to_playlist() {
fn test_json_array_to_playlist() -> Result<(), MpvError> {
let json = json!([
{
"filename": "file1",
@ -306,6 +342,8 @@ mod test {
},
];
assert_eq!(json_array_to_playlist(json.as_array().unwrap()), expected);
assert_eq!(json_array_to_playlist(json.as_array().unwrap())?, expected);
Ok(())
}
}

View File

@ -1,26 +1,30 @@
use mpvipc::MpvExt;
use mpvipc::{MpvError, MpvExt};
use super::*;
#[tokio::test]
#[cfg(target_family = "unix")]
async fn test_get_mpv_version() {
async fn test_get_mpv_version() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
let version: String = mpv.get_property("mpv-version").await.unwrap();
let version: String = mpv.get_property("mpv-version").await?.unwrap();
assert!(version.starts_with("mpv"));
mpv.kill().await.unwrap();
proc.kill().await.unwrap();
Ok(())
}
#[tokio::test]
#[cfg(target_family = "unix")]
async fn test_set_property() {
async fn test_set_property() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
mpv.set_property("pause", true).await.unwrap();
let paused: bool = mpv.get_property("pause").await.unwrap();
let paused: bool = mpv.get_property("pause").await?.unwrap();
assert!(paused);
mpv.kill().await.unwrap();
proc.kill().await.unwrap();
Ok(())
}

View File

@ -22,73 +22,77 @@ fn test_socket(answers: Vec<String>) -> (UnixStream, JoinHandle<Result<(), Lines
}
#[test(tokio::test)]
async fn test_get_property_successful() {
async fn test_get_property_successful() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![
json!({ "data": 100.0, "request_id": 0, "error": "success" }).to_string(),
]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let volume: f64 = mpv.get_property("volume").await.unwrap();
let mpv = Mpv::connect_socket(server).await?;
let volume: Option<f64> = mpv.get_property("volume").await?;
assert_eq!(volume, 100.0);
assert_eq!(volume, Some(100.0));
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
async fn test_get_property_broken_pipe() {
async fn test_get_property_broken_pipe() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let maybe_volume = mpv.get_property::<f64>("volume").await;
match maybe_volume {
Err(MpvError::MpvSocketConnectionError(err)) => {
assert_eq!(err.to_string(), "Broken pipe (os error 32)");
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}
assert_eq!(
maybe_volume,
Err(MpvError::MpvSocketConnectionError(
"Broken pipe (os error 32)".to_string()
))
);
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
async fn test_get_property_wrong_type() {
async fn test_get_property_wrong_type() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![
json!({ "data": 100.0, "request_id": 0, "error": "success" }).to_string(),
]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let mpv = Mpv::connect_socket(server).await?;
let maybe_volume = mpv.get_property::<bool>("volume").await;
match maybe_volume {
assert_eq!(
maybe_volume,
Err(MpvError::ValueContainsUnexpectedType {
expected_type,
received,
}) => {
assert_eq!(expected_type, "bool");
assert_eq!(received, json!(100.0));
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}
expected_type: "bool".to_string(),
received: json!(100.0)
})
);
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
async fn test_get_property_error() {
async fn test_get_property_error() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![
json!({ "error": "property unavailable", "request_id": 0 }).to_string(),
]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let mpv = Mpv::connect_socket(server).await?;
let maybe_volume = mpv.get_property::<f64>("volume").await;
match maybe_volume {
Err(MpvError::MpvError(err)) => {
assert_eq!(err, "property unavailable");
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}
assert_eq!(
maybe_volume,
Err(MpvError::MpvError("property unavailable".to_string()))
);
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
@ -130,8 +134,8 @@ async fn test_get_property_simultaneous_requests() {
let mpv_clone_1 = mpv.clone();
let mpv_poller_1 = tokio::spawn(async move {
loop {
let volume: f64 = mpv_clone_1.get_property("volume").await.unwrap();
assert_eq!(volume, 100.0);
let volume: Option<f64> = mpv_clone_1.get_property("volume").await.unwrap();
assert_eq!(volume, Some(100.0));
}
});
@ -139,8 +143,8 @@ async fn test_get_property_simultaneous_requests() {
let mpv_poller_2 = tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_millis(1)).await;
let paused: bool = mpv_clone_2.get_property("pause").await.unwrap();
assert!(paused);
let paused: Option<bool> = mpv_clone_2.get_property("pause").await.unwrap();
assert_eq!(paused, Some(true));
}
});
@ -173,7 +177,7 @@ async fn test_get_property_simultaneous_requests() {
}
#[test(tokio::test)]
async fn test_get_playlist() {
async fn test_get_playlist() -> Result<(), MpvError> {
let expected = Playlist(vec![
PlaylistEntry {
id: 0,
@ -208,22 +212,26 @@ async fn test_get_playlist() {
})
.to_string()]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let playlist = mpv.get_playlist().await.unwrap();
let mpv = Mpv::connect_socket(server).await?;
let playlist = mpv.get_playlist().await?;
assert_eq!(playlist, expected);
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
async fn test_get_playlist_empty() {
async fn test_get_playlist_empty() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![
json!({ "data": [], "request_id": 0, "error": "success" }).to_string(),
]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let playlist = mpv.get_playlist().await.unwrap();
let mpv = Mpv::connect_socket(server).await?;
let playlist = mpv.get_playlist().await?;
assert_eq!(playlist, Playlist(vec![]));
join_handle.await.unwrap().unwrap();
Ok(())
}

View File

@ -1,7 +1,7 @@
use std::{panic, time::Duration};
use futures::{stream::FuturesUnordered, SinkExt, StreamExt};
use mpvipc::{Mpv, MpvError, MpvExt, Playlist, PlaylistEntry};
use mpvipc::{Mpv, MpvError};
use serde_json::{json, Value};
use test_log::test;
use tokio::{net::UnixStream, task::JoinHandle};
@ -22,69 +22,76 @@ fn test_socket(answers: Vec<String>) -> (UnixStream, JoinHandle<Result<(), Lines
}
#[test(tokio::test)]
async fn test_set_property_successful() {
async fn test_set_property_successful() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![
json!({ "data": null, "request_id": 0, "error": "success" }).to_string(),
]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let volume = mpv.set_property("volume", 64.0).await;
let mpv = Mpv::connect_socket(server).await?;
mpv.set_property("volume", 64.0).await?;
assert!(volume.is_ok());
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
async fn test_set_property_broken_pipe() {
async fn test_set_property_broken_pipe() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let mpv = Mpv::connect_socket(server).await?;
let maybe_set_volume = mpv.set_property("volume", 64.0).await;
match maybe_set_volume {
Err(MpvError::MpvSocketConnectionError(err)) => {
assert_eq!(err.to_string(), "Broken pipe (os error 32)");
}
_ => panic!("Unexpected result: {:?}", maybe_set_volume),
}
assert_eq!(
maybe_set_volume,
Err(MpvError::MpvSocketConnectionError(
"Broken pipe (os error 32)".to_string()
))
);
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
async fn test_set_property_wrong_type() {
async fn test_set_property_wrong_type() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![
json!({"request_id":0,"error":"unsupported format for accessing property"}).to_string(),
]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let mpv = Mpv::connect_socket(server).await?;
let maybe_volume = mpv.set_property::<bool>("volume", true).await;
match maybe_volume {
Err(MpvError::MpvError(err)) => {
assert_eq!(err, "unsupported format for accessing property");
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}
assert_eq!(
maybe_volume,
Err(MpvError::MpvError(
"unsupported format for accessing property".to_string()
))
);
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
async fn test_get_property_error() {
async fn test_get_property_error() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![
json!({"request_id":0,"error":"property not found"}).to_string(),
]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let mpv = Mpv::connect_socket(server).await?;
let maybe_volume = mpv.set_property("nonexistent", true).await;
match maybe_volume {
Err(MpvError::MpvError(err)) => {
assert_eq!(err, "property not found");
}
_ => panic!("Unexpected result: {:?}", maybe_volume),
}
assert_eq!(
maybe_volume,
Err(MpvError::MpvError("property not found".to_string()))
);
join_handle.await.unwrap().unwrap();
Ok(())
}
#[test(tokio::test)]
@ -175,59 +182,3 @@ async fn test_set_property_simultaneous_requests() {
panic!("One of the pollers quit unexpectedly");
};
}
#[test(tokio::test)]
async fn test_get_playlist() {
let expected = Playlist(vec![
PlaylistEntry {
id: 0,
filename: "file1".to_string(),
title: "title1".to_string(),
current: false,
},
PlaylistEntry {
id: 1,
filename: "file2".to_string(),
title: "title2".to_string(),
current: true,
},
PlaylistEntry {
id: 2,
filename: "file3".to_string(),
title: "title3".to_string(),
current: false,
},
]);
let (server, join_handle) = test_socket(vec![json!({
"data": expected.0.iter().map(|entry| {
json!({
"filename": entry.filename,
"title": entry.title,
"current": entry.current
})
}).collect::<Vec<Value>>(),
"request_id": 0,
"error": "success"
})
.to_string()]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let playlist = mpv.get_playlist().await.unwrap();
assert_eq!(playlist, expected);
join_handle.await.unwrap().unwrap();
}
#[test(tokio::test)]
async fn test_get_playlist_empty() {
let (server, join_handle) = test_socket(vec![
json!({ "data": [], "request_id": 0, "error": "success" }).to_string(),
]);
let mpv = Mpv::connect_socket(server).await.unwrap();
let playlist = mpv.get_playlist().await.unwrap();
assert_eq!(playlist, Playlist(vec![]));
join_handle.await.unwrap().unwrap();
}