Allow a few more highlevel types to be nullable

This commit is contained in:
Oystein Kristoffer Tveit 2024-05-05 13:20:07 +02:00
parent 4cc824d164
commit 93366593c7
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
7 changed files with 79 additions and 30 deletions

View File

@ -90,7 +90,7 @@ pub struct Playlist(pub Vec<PlaylistEntry>);
pub struct PlaylistEntry { pub struct PlaylistEntry {
pub id: usize, pub id: usize,
pub filename: String, pub filename: String,
pub title: String, pub title: Option<String>,
pub current: bool, pub current: bool,
} }

View File

@ -154,10 +154,10 @@ pub trait MpvExt {
async fn get_speed(&self) -> Result<f64, MpvError>; async fn get_speed(&self) -> Result<f64, MpvError>;
/// Get the current position in the current video. /// Get the current position in the current video.
async fn get_time_pos(&self) -> Result<f64, MpvError>; async fn get_time_pos(&self) -> Result<Option<f64>, MpvError>;
/// Get the amount of time remaining in the current video. /// Get the amount of time remaining in the current video.
async fn get_time_remaining(&self) -> Result<f64, MpvError>; async fn get_time_remaining(&self) -> Result<Option<f64>, MpvError>;
/// Get the total duration of the current video. /// Get the total duration of the current video.
async fn get_duration(&self) -> Result<f64, MpvError>; async fn get_duration(&self) -> Result<f64, MpvError>;
@ -415,7 +415,7 @@ impl MpvExt for Mpv {
} }
} }
async fn get_time_pos(&self) -> Result<f64, MpvError> { async fn get_time_pos(&self) -> Result<Option<f64>, MpvError> {
let data = self.get_property("time-pos").await?; let data = self.get_property("time-pos").await?;
match parse_property("time-pos", data)? { match parse_property("time-pos", data)? {
Property::TimePos(value) => Ok(value), Property::TimePos(value) => Ok(value),
@ -423,7 +423,7 @@ impl MpvExt for Mpv {
} }
} }
async fn get_time_remaining(&self) -> Result<f64, MpvError> { async fn get_time_remaining(&self) -> Result<Option<f64>, MpvError> {
let data = self.get_property("time-remaining").await?; let data = self.get_property("time-remaining").await?;
match parse_property("time-remaining", data)? { match parse_property("time-remaining", data)? {
Property::TimeRemaining(value) => Ok(value), Property::TimeRemaining(value) => Ok(value),

View File

@ -224,6 +224,7 @@ fn parse_mpv_response_data(value: Value) -> Result<Option<Value>, MpvError> {
}) })
.and_then(|(error, data)| match error { .and_then(|(error, data)| match error {
"success" => Ok(data), "success" => Ok(data),
"property unavailable" => Ok(None),
err => Err(MpvError::MpvError(err.to_string())), err => Err(MpvError::MpvError(err.to_string())),
}); });

View File

@ -169,14 +169,14 @@ fn json_map_to_playlist_entry(
None => return Err(MpvError::MissingMpvData), None => return Err(MpvError::MissingMpvData),
}; };
let title = match map.get("title") { let title = match map.get("title") {
Some(Value::String(s)) => s.to_string(), Some(Value::String(s)) => Some(s.to_string()),
Some(data) => { Some(data) => {
return Err(MpvError::ValueContainsUnexpectedType { return Err(MpvError::ValueContainsUnexpectedType {
expected_type: "String".to_owned(), expected_type: "String".to_owned(),
received: data.clone(), received: data.clone(),
}) })
} }
None => return Err(MpvError::MissingMpvData), None => None,
}; };
let current = match map.get("current") { let current = match map.get("current") {
Some(Value::Bool(b)) => *b, Some(Value::Bool(b)) => *b,
@ -186,7 +186,7 @@ fn json_map_to_playlist_entry(
received: data.clone(), received: data.clone(),
}) })
} }
None => return Err(MpvError::MissingMpvData), None => false,
}; };
Ok(PlaylistEntry { Ok(PlaylistEntry {
id: 0, id: 0,
@ -324,6 +324,10 @@ mod test {
"filename": "file2", "filename": "file2",
"title": "title2", "title": "title2",
"current": false "current": false
},
{
"filename": "file3",
"current": false
} }
]); ]);
@ -331,13 +335,19 @@ mod test {
PlaylistEntry { PlaylistEntry {
id: 0, id: 0,
filename: "file1".to_string(), filename: "file1".to_string(),
title: "title1".to_string(), title: Some("title1".to_string()),
current: true, current: true,
}, },
PlaylistEntry { PlaylistEntry {
id: 1, id: 1,
filename: "file2".to_string(), filename: "file2".to_string(),
title: "title2".to_string(), title: Some("title2".to_string()),
current: false,
},
PlaylistEntry {
id: 2,
filename: "file3".to_string(),
title: None,
current: false, current: false,
}, },
]; ];

View File

@ -34,11 +34,12 @@ pub enum Property {
PlaylistPos(Option<usize>), PlaylistPos(Option<usize>),
LoopFile(LoopProperty), LoopFile(LoopProperty),
LoopPlaylist(LoopProperty), LoopPlaylist(LoopProperty),
TimePos(f64), TimePos(Option<f64>),
TimeRemaining(f64), TimeRemaining(Option<f64>),
Speed(f64), Speed(f64),
Volume(f64), Volume(f64),
Mute(bool), Mute(bool),
EofReached(bool),
Unknown { Unknown {
name: String, name: String,
data: Option<MpvDataType>, data: Option<MpvDataType>,
@ -204,31 +205,28 @@ pub fn parse_property(name: &str, data: Option<MpvDataType>) -> Result<Property,
} }
"time-pos" => { "time-pos" => {
let time_pos = match data { let time_pos = match data {
Some(MpvDataType::Double(d)) => d, Some(MpvDataType::Double(d)) => Some(d),
Some(data) => { Some(data) => {
return Err(MpvError::DataContainsUnexpectedType { return Err(MpvError::DataContainsUnexpectedType {
expected_type: "f64".to_owned(), expected_type: "f64".to_owned(),
received: data, received: data,
}) })
} }
None => { None => None,
return Err(MpvError::MissingMpvData);
}
}; };
Ok(Property::TimePos(time_pos)) Ok(Property::TimePos(time_pos))
} }
"time-remaining" => { "time-remaining" => {
let time_remaining = match data { let time_remaining = match data {
Some(MpvDataType::Double(d)) => d, Some(MpvDataType::Double(d)) => Some(d),
Some(data) => { Some(data) => {
return Err(MpvError::DataContainsUnexpectedType { return Err(MpvError::DataContainsUnexpectedType {
expected_type: "f64".to_owned(), expected_type: "f64".to_owned(),
received: data, received: data,
}) })
} }
None => { None => None,
return Err(MpvError::MissingMpvData);
}
}; };
Ok(Property::TimeRemaining(time_remaining)) Ok(Property::TimeRemaining(time_remaining))
} }
@ -277,6 +275,19 @@ pub fn parse_property(name: &str, data: Option<MpvDataType>) -> Result<Property,
}; };
Ok(Property::Mute(mute)) Ok(Property::Mute(mute))
} }
"eof-reached" => {
let eof_reached = match data {
Some(MpvDataType::Bool(b)) => b,
Some(data) => {
return Err(MpvError::DataContainsUnexpectedType {
expected_type: "bool".to_owned(),
received: data,
})
}
None => true,
};
Ok(Property::EofReached(eof_reached))
}
// TODO: add missing cases // TODO: add missing cases
_ => Ok(Property::Unknown { _ => Ok(Property::Unknown {
name: name.to_owned(), name: name.to_owned(),
@ -299,14 +310,14 @@ fn mpv_data_to_playlist_entry(
None => return Err(MpvError::MissingMpvData), None => return Err(MpvError::MissingMpvData),
}; };
let title = match map.get("title") { let title = match map.get("title") {
Some(MpvDataType::String(s)) => s.to_string(), Some(MpvDataType::String(s)) => Some(s.to_string()),
Some(data) => { Some(data) => {
return Err(MpvError::DataContainsUnexpectedType { return Err(MpvError::DataContainsUnexpectedType {
expected_type: "String".to_owned(), expected_type: "String".to_owned(),
received: data.clone(), received: data.clone(),
}) })
} }
None => return Err(MpvError::MissingMpvData), None => None,
}; };
let current = match map.get("current") { let current = match map.get("current") {
Some(MpvDataType::Bool(b)) => *b, Some(MpvDataType::Bool(b)) => *b,
@ -316,7 +327,7 @@ fn mpv_data_to_playlist_entry(
received: data.clone(), received: data.clone(),
}) })
} }
None => return Err(MpvError::MissingMpvData), None => false
}; };
Ok(PlaylistEntry { Ok(PlaylistEntry {
id: 0, id: 0,

View File

@ -28,3 +28,30 @@ async fn test_set_property() -> Result<(), MpvError> {
Ok(()) Ok(())
} }
#[tokio::test]
#[cfg(target_family = "unix")]
async fn test_get_unavailable_property() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
let time_pos = mpv.get_property::<f64>("time-pos").await;
assert_eq!(time_pos, Ok(None));
mpv.kill().await.unwrap();
proc.kill().await.unwrap();
Ok(())
}
#[tokio::test]
#[cfg(target_family = "unix")]
async fn test_get_nonexistent_property() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap();
let nonexistent = mpv.get_property::<f64>("nonexistent").await;
assert_eq!(nonexistent, Err(MpvError::MpvError("property not found".to_string())));
mpv.kill().await.unwrap();
proc.kill().await.unwrap();
Ok(())
}

View File

@ -77,7 +77,7 @@ async fn test_get_property_wrong_type() -> Result<(), MpvError> {
} }
#[test(tokio::test)] #[test(tokio::test)]
async fn test_get_property_error() -> Result<(), MpvError> { async fn test_get_unavailable_property() -> Result<(), MpvError> {
let (server, join_handle) = test_socket(vec![ let (server, join_handle) = test_socket(vec![
json!({ "error": "property unavailable", "request_id": 0 }).to_string(), json!({ "error": "property unavailable", "request_id": 0 }).to_string(),
]); ]);
@ -87,7 +87,7 @@ async fn test_get_property_error() -> Result<(), MpvError> {
assert_eq!( assert_eq!(
maybe_volume, maybe_volume,
Err(MpvError::MpvError("property unavailable".to_string())) Ok(None),
); );
join_handle.await.unwrap().unwrap(); join_handle.await.unwrap().unwrap();
@ -119,7 +119,7 @@ async fn test_get_property_simultaneous_requests() {
} }
_ => { _ => {
let response = let response =
json!({ "error": "property unavailable", "request_id": 0 }).to_string(); json!({ "error": "property not found", "request_id": 0 }).to_string();
framed.send(response).await.unwrap(); framed.send(response).await.unwrap();
} }
} }
@ -155,7 +155,7 @@ async fn test_get_property_simultaneous_requests() {
let maybe_volume = mpv_clone_3.get_property::<f64>("nonexistent").await; let maybe_volume = mpv_clone_3.get_property::<f64>("nonexistent").await;
match maybe_volume { match maybe_volume {
Err(MpvError::MpvError(err)) => { Err(MpvError::MpvError(err)) => {
assert_eq!(err, "property unavailable"); assert_eq!(err, "property not found");
} }
_ => panic!("Unexpected result: {:?}", maybe_volume), _ => panic!("Unexpected result: {:?}", maybe_volume),
} }
@ -182,19 +182,19 @@ async fn test_get_playlist() -> Result<(), MpvError> {
PlaylistEntry { PlaylistEntry {
id: 0, id: 0,
filename: "file1".to_string(), filename: "file1".to_string(),
title: "title1".to_string(), title: Some("title1".to_string()),
current: false, current: false,
}, },
PlaylistEntry { PlaylistEntry {
id: 1, id: 1,
filename: "file2".to_string(), filename: "file2".to_string(),
title: "title2".to_string(), title: Some("title2".to_string()),
current: true, current: true,
}, },
PlaylistEntry { PlaylistEntry {
id: 2, id: 2,
filename: "file3".to_string(), filename: "file3".to_string(),
title: "title3".to_string(), title: Some("title3".to_string()),
current: false, current: false,
}, },
]); ]);