103 lines
2.9 KiB
Rust
103 lines
2.9 KiB
Rust
use serde::{de, de::value::MapDeserializer, forward_to_deserialize_any};
|
|
use std::collections::HashMap;
|
|
mod error;
|
|
use error::{Error, MpdResult};
|
|
mod structs;
|
|
pub use structs::Track;
|
|
|
|
/// some unprintable character to separate repeated keys
|
|
const SEPARATOR: char = '\x02';
|
|
|
|
struct MPDeserializer<'de, Iter: Iterator<Item = (&'de str, &'de str)>> {
|
|
inner: MapDeserializer<'de, Iter, Error>,
|
|
}
|
|
|
|
impl<'de, Iter: Iterator<Item = (&'de str, &'de str)>> de::Deserializer<'de> for MPDeserializer<'de, Iter> {
|
|
type Error = Error;
|
|
|
|
fn deserialize_any<V>(self, visitor: V) -> MpdResult<V::Value>
|
|
where V: de::Visitor<'de> {
|
|
self.deserialize_map(visitor)
|
|
}
|
|
|
|
fn deserialize_map<V>(self, visitor: V) -> MpdResult<V::Value>
|
|
where V: de::Visitor<'de> {
|
|
visitor.visit_map(self.inner)
|
|
}
|
|
|
|
forward_to_deserialize_any! {
|
|
bool char str string bytes byte_buf unit unit_struct seq
|
|
tuple tuple_struct struct identifier ignored_any
|
|
i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 option newtype_struct enum
|
|
}
|
|
}
|
|
|
|
pub fn deserialize_response<'a, I: Iterator<Item = &'a str>, T: de::Deserialize<'a>>(input: I) -> Result<Track, Error> {
|
|
let mut map: HashMap<String, String> = HashMap::new();
|
|
for line in input {
|
|
if line.starts_with("OK") {
|
|
break;
|
|
} else if let Some(message) = line.strip_prefix("ACK") {
|
|
return Err(Error::from_str(message.trim()));
|
|
}
|
|
let mut fields = line.splitn(2, ": ");
|
|
match (fields.next(), fields.next()) {
|
|
(Some(k), Some(v)) => {
|
|
if let Some(existing) = map.get_mut(k) {
|
|
existing.push(SEPARATOR);
|
|
existing.push_str(v);
|
|
} else {
|
|
map.insert(k.to_string(), v.to_string());
|
|
}
|
|
}
|
|
_ => panic!("invalid response line: {:?}", line),
|
|
}
|
|
}
|
|
return Ok(envy::from_iter(map).unwrap());
|
|
// Eventually, I’d like to use my own deserializer instead of envy
|
|
// let deser = MPDeserializer {
|
|
// inner: MapDeserializer::new(map.into_iter()),
|
|
// };
|
|
// Song::deserialize(deser)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn track_de_test() {
|
|
let input_str = "file: American Pie.flac
|
|
Last-Modified: 2019-12-16T21:38:54Z
|
|
Album: American Pie
|
|
Artist: Don McLean
|
|
Date: 1979
|
|
Genre: Rock
|
|
Title: American Pie
|
|
Track: 1
|
|
Time: 512
|
|
duration: 512.380
|
|
Pos: 1367
|
|
Id: 1368
|
|
OK";
|
|
let t = deserialize_response::<'_, _, Track>(input_str.lines()).unwrap();
|
|
assert_eq!(
|
|
t,
|
|
Track {
|
|
file: "American Pie.flac".to_string(),
|
|
last_modified: Some("2019-12-16T21:38:54Z".to_string()),
|
|
album: Some("American Pie".to_string()),
|
|
artist: Some("Don McLean".to_string()),
|
|
date: Some(1979),
|
|
genre: Some("Rock".to_string()),
|
|
title: Some("American Pie".to_string()),
|
|
track: Some(1),
|
|
duration: std::time::Duration::from_secs_f64(512.380),
|
|
pos: 1367,
|
|
id: 1368,
|
|
..Track::default()
|
|
}
|
|
);
|
|
}
|
|
}
|