Properly handle repeated fields
This commit is contained in:
parent
8253e4b5de
commit
e8c8be6725
75
src/lib.rs
75
src/lib.rs
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||||
mod error;
|
mod error;
|
||||||
use error::{Error, MpdResult};
|
use error::{Error, MpdResult};
|
||||||
mod structs;
|
mod structs;
|
||||||
pub use structs::Track;
|
pub use structs::{Position, Track};
|
||||||
|
|
||||||
/// some unprintable character to separate repeated keys
|
/// some unprintable character to separate repeated keys
|
||||||
const SEPARATOR: char = '\x02';
|
const SEPARATOR: char = '\x02';
|
||||||
|
@ -53,7 +53,7 @@ pub fn deserialize_response<'a, I: Iterator<Item = &'a str>, T: de::Deserialize<
|
||||||
_ => panic!("invalid response line: {:?}", line),
|
_ => panic!("invalid response line: {:?}", line),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(envy::from_iter(map).unwrap());
|
Ok(envy::from_iter(map).unwrap())
|
||||||
// Eventually, I’d like to use my own deserializer instead of envy
|
// Eventually, I’d like to use my own deserializer instead of envy
|
||||||
// let deser = MPDeserializer {
|
// let deser = MPDeserializer {
|
||||||
// inner: MapDeserializer::new(map.into_iter()),
|
// inner: MapDeserializer::new(map.into_iter()),
|
||||||
|
@ -91,7 +91,10 @@ OK";
|
||||||
date: Some(1979),
|
date: Some(1979),
|
||||||
genre: Some("Rock".to_string()),
|
genre: Some("Rock".to_string()),
|
||||||
title: Some("American Pie".to_string()),
|
title: Some("American Pie".to_string()),
|
||||||
track: Some(1),
|
track: Some(Position {
|
||||||
|
item_position: 1,
|
||||||
|
total_items: None,
|
||||||
|
}),
|
||||||
duration: std::time::Duration::from_secs_f64(512.380),
|
duration: std::time::Duration::from_secs_f64(512.380),
|
||||||
pos: 1367,
|
pos: 1367,
|
||||||
id: 1368,
|
id: 1368,
|
||||||
|
@ -99,4 +102,70 @@ OK";
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repeated_field_test() {
|
||||||
|
let input_str = r#"file: 06 Symphonie No. 41 en ut majeur, K. 551 _Jupiter_ I. Allegro Vivace.flac
|
||||||
|
Last-Modified: 2018-11-11T09:01:54Z
|
||||||
|
Album: Symphonies n°40 & n°41
|
||||||
|
AlbumArtist: Wolfgang Amadeus Mozart; Royal Philharmonic Orchestra, Jane Glover
|
||||||
|
AlbumArtistSort: Mozart, Wolfgang Amadeus; Royal Philharmonic Orchestra, Glover, Jane
|
||||||
|
Artist: Wolfgang Amadeus Mozart
|
||||||
|
ArtistSort: Mozart, Wolfgang Amadeus
|
||||||
|
Composer: Wolfgang Amadeus Mozart
|
||||||
|
Date: 2005
|
||||||
|
Disc: 1
|
||||||
|
Disc: 1
|
||||||
|
MUSICBRAINZ_ALBUMARTISTID: b972f589-fb0e-474e-b64a-803b0364fa75
|
||||||
|
MUSICBRAINZ_ALBUMID: 688d9252-f897-4ab6-879d-5cb83bb71087
|
||||||
|
MUSICBRAINZ_ARTISTID: b972f589-fb0e-474e-b64a-803b0364fa75
|
||||||
|
MUSICBRAINZ_RELEASETRACKID: 54a2632f-fa98-3713-bd75-d7effc03d0b1
|
||||||
|
MUSICBRAINZ_TRACKID: 2dd10dd8-e8de-4479-a092-9a04c2760fd6
|
||||||
|
OriginalDate: 1993
|
||||||
|
Title: Symphonie No. 41 en ut majeur, K. 551 "Jupiter": I. Allegro Vivace
|
||||||
|
Track: 6
|
||||||
|
Track: 6
|
||||||
|
Time: 683
|
||||||
|
Performer: Royal Philharmonic Orchestra
|
||||||
|
duration: 682.840
|
||||||
|
Performer: Jane Glover
|
||||||
|
Pos: 3439
|
||||||
|
Id: 3440
|
||||||
|
OK"#;
|
||||||
|
let t = deserialize_response::<'_, _, Track>(input_str.lines()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
t,
|
||||||
|
Track {
|
||||||
|
file: "06 Symphonie No. 41 en ut majeur, K. 551 _Jupiter_ I. Allegro Vivace.flac".to_string(),
|
||||||
|
last_modified: Some("2018-11-11T09:01:54Z".to_string()),
|
||||||
|
album: Some("Symphonies n°40 & n°41".to_string()),
|
||||||
|
artist: Some("Wolfgang Amadeus Mozart".to_string()),
|
||||||
|
artist_sort: Some("Mozart, Wolfgang Amadeus".to_string()),
|
||||||
|
album_artist: Some("Wolfgang Amadeus Mozart; Royal Philharmonic Orchestra, Jane Glover".to_string()),
|
||||||
|
album_artist_sort: Some("Mozart, Wolfgang Amadeus; Royal Philharmonic Orchestra, Glover, Jane".to_string()),
|
||||||
|
composer: Some("Wolfgang Amadeus Mozart".to_string()),
|
||||||
|
date: Some(2005),
|
||||||
|
original_date: Some(1993),
|
||||||
|
title: Some("Symphonie No. 41 en ut majeur, K. 551 \"Jupiter\": I. Allegro Vivace".to_string()),
|
||||||
|
disc: Some(Position {
|
||||||
|
item_position: 1,
|
||||||
|
total_items: Some(1),
|
||||||
|
}),
|
||||||
|
track: Some(Position {
|
||||||
|
item_position: 6,
|
||||||
|
total_items: Some(6),
|
||||||
|
}),
|
||||||
|
duration: std::time::Duration::from_secs_f64(682.840),
|
||||||
|
pos: 3439,
|
||||||
|
id: 3440,
|
||||||
|
performers: vec!["Royal Philharmonic Orchestra".to_string(), "Jane Glover".to_string()],
|
||||||
|
musicbrainz_albumartistid: Some("b972f589-fb0e-474e-b64a-803b0364fa75".to_string()),
|
||||||
|
musicbrainz_albumid: Some("688d9252-f897-4ab6-879d-5cb83bb71087".to_string()),
|
||||||
|
musicbrainz_artistid: Some("b972f589-fb0e-474e-b64a-803b0364fa75".to_string()),
|
||||||
|
musicbrainz_releasetrackid: Some("54a2632f-fa98-3713-bd75-d7effc03d0b1".to_string()),
|
||||||
|
musicbrainz_trackid: Some("2dd10dd8-e8de-4479-a092-9a04c2760fd6".to_string()),
|
||||||
|
..Track::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
174
src/structs.rs
174
src/structs.rs
|
@ -4,88 +4,112 @@ use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug, Default, PartialEq)]
|
#[derive(Deserialize, Clone, Debug, Default, PartialEq)]
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
pub file: String,
|
pub file: String,
|
||||||
pub artist_sort: Option<String>,
|
#[serde(rename = "artistsort")]
|
||||||
pub album_artist: Option<String>,
|
pub artist_sort: Option<String>,
|
||||||
pub album_sort: Option<String>,
|
#[serde(rename = "albumartist")]
|
||||||
pub album_artist_sort: Option<String>,
|
pub album_artist: Option<String>,
|
||||||
#[serde(deserialize_with = "string_or_vec")]
|
#[serde(rename = "albumsort")]
|
||||||
#[serde(default)]
|
pub album_sort: Option<String>,
|
||||||
#[serde(rename = "performer")]
|
#[serde(rename = "albumartistsort")]
|
||||||
pub performers: Vec<String>,
|
pub album_artist_sort: Option<String>,
|
||||||
pub genre: Option<String>,
|
#[serde(deserialize_with = "de_string_or_vec")]
|
||||||
pub title: Option<String>,
|
#[serde(default)]
|
||||||
pub track: Option<u32>,
|
#[serde(rename = "performer")]
|
||||||
pub album: Option<String>,
|
pub performers: Vec<String>,
|
||||||
pub artist: Option<String>,
|
pub genre: Option<String>,
|
||||||
pub pos: u32,
|
pub title: Option<String>,
|
||||||
pub id: u32,
|
#[serde(default)]
|
||||||
// TODO: use proper time here
|
#[serde(deserialize_with = "de_position")]
|
||||||
#[serde(rename = "last-modified")]
|
pub track: Option<Position>,
|
||||||
pub last_modified: Option<String>,
|
pub album: Option<String>,
|
||||||
pub original_date: Option<u16>,
|
pub artist: Option<String>,
|
||||||
// probably not needed. it’s just the duration but as an int
|
pub pos: u32,
|
||||||
// pub time: u16,
|
pub id: u32,
|
||||||
pub format: Option<String>,
|
// TODO: use proper time here
|
||||||
#[serde(deserialize_with = "de_time_float")]
|
#[serde(rename = "last-modified")]
|
||||||
pub duration: Duration,
|
pub last_modified: Option<String>,
|
||||||
pub label: Option<String>,
|
#[serde(rename = "originaldate")]
|
||||||
pub date: Option<u16>,
|
pub original_date: Option<u16>,
|
||||||
pub disc: Option<u16>,
|
// probably not needed. it’s just the duration but as an int
|
||||||
pub musicbraiz_trackid: Option<String>,
|
// pub time: u16,
|
||||||
pub musicbrainz_albumid: Option<String>,
|
pub format: Option<String>,
|
||||||
pub musicbrainz_albumartistid: Option<String>,
|
#[serde(deserialize_with = "de_time_float")]
|
||||||
pub musicbrainz_artistid: Option<String>,
|
pub duration: Duration,
|
||||||
pub musicbraiz_releasetrackid: Option<String>,
|
pub label: Option<String>,
|
||||||
pub composer: Option<String>,
|
pub date: Option<u16>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(deserialize_with = "de_position")]
|
||||||
|
pub disc: Option<Position>,
|
||||||
|
pub musicbraiz_trackid: Option<String>,
|
||||||
|
pub musicbrainz_albumid: Option<String>,
|
||||||
|
pub musicbrainz_albumartistid: Option<String>,
|
||||||
|
pub musicbrainz_artistid: Option<String>,
|
||||||
|
pub musicbrainz_releasetrackid: Option<String>,
|
||||||
|
pub musicbrainz_trackid: Option<String>,
|
||||||
|
pub composer: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The position of an item in a list with an optional total length.
|
||||||
|
/// Can be used for e.g. track number,
|
||||||
|
/// where `Position { 3, 12 }` represents track 3 of a CD with 12 tracks,
|
||||||
|
/// or disc numbers in a multi-disc set.
|
||||||
|
#[derive(Deserialize, Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct Position {
|
||||||
|
pub item_position: u16,
|
||||||
|
pub total_items: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialization helpers to handle the quirks of mpd’s output.
|
/// Deserialization helpers to handle the quirks of mpd’s output.
|
||||||
mod helpers {
|
mod helpers {
|
||||||
use crate::SEPARATOR;
|
use super::*;
|
||||||
use serde::de;
|
use crate::SEPARATOR;
|
||||||
use serde::Deserialize;
|
use serde::{de, Deserialize};
|
||||||
use std::time::Duration;
|
use std::{str::FromStr, time::Duration};
|
||||||
|
|
||||||
/// Deserialize time from an integer that represents the seconds.
|
/// Deserialize time from an integer that represents the seconds.
|
||||||
/// mpd uses int for the database stats (e.g. total time played).
|
/// mpd uses int for the database stats (e.g. total time played).
|
||||||
pub fn de_time_int<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
pub fn de_time_int<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||||
where
|
where D: de::Deserializer<'de> {
|
||||||
D: de::Deserializer<'de>,
|
u64::deserialize(deserializer).map(Duration::from_secs)
|
||||||
{
|
}
|
||||||
u64::deserialize(deserializer).map(Duration::from_secs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize time from a float that represents the seconds.
|
/// Deserialize time from a float that represents the seconds.
|
||||||
/// mpd uses floats for the current status (e.g. time elapsed in song).
|
/// mpd uses floats for the current status (e.g. time elapsed in song).
|
||||||
pub fn de_time_float<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
pub fn de_time_float<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||||
where
|
where D: de::Deserializer<'de> {
|
||||||
D: de::Deserializer<'de>,
|
f64::deserialize(deserializer).map(Duration::from_secs_f64)
|
||||||
{
|
}
|
||||||
f64::deserialize(deserializer).map(Duration::from_secs_f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
pub fn de_string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||||
where
|
where D: de::Deserializer<'de> {
|
||||||
D: de::Deserializer<'de>,
|
String::deserialize(deserializer).map(|s| s.split(SEPARATOR).map(std::string::String::from).collect())
|
||||||
{
|
}
|
||||||
String::deserialize(deserializer)
|
|
||||||
.map(|s| s.split(SEPARATOR).map(std::string::String::from).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// mpd uses bints (0 or 1) to represent booleans,
|
/// mpd uses bints (0 or 1) to represent booleans,
|
||||||
/// so we need a special parser for those.
|
/// so we need a special parser for those.
|
||||||
pub fn de_bint<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
pub fn de_bint<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
||||||
where
|
where D: de::Deserializer<'de> {
|
||||||
D: de::Deserializer<'de>,
|
match u8::deserialize(deserializer)? {
|
||||||
{
|
0 => Ok(false),
|
||||||
match u8::deserialize(deserializer)? {
|
1 => Ok(true),
|
||||||
0 => Ok(false),
|
n => Err(de::Error::invalid_value(de::Unexpected::Unsigned(n as u64), &"zero or one")),
|
||||||
1 => Ok(true),
|
|
||||||
n => Err(de::Error::invalid_value(
|
|
||||||
de::Unexpected::Unsigned(n as u64),
|
|
||||||
&"zero or one",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a position with an optional total length.
|
||||||
|
/// The input string here is either a number or two numbers separated by `SEPARATOR`.
|
||||||
|
pub fn de_position<'de, D>(deserializer: D) -> Result<Option<Position>, D::Error>
|
||||||
|
where D: de::Deserializer<'de> {
|
||||||
|
if let Ok(s) = String::deserialize(deserializer) {
|
||||||
|
let mut ints = s.split(SEPARATOR).map(u16::from_str);
|
||||||
|
if let Some(Ok(n)) = ints.next() {
|
||||||
|
return Ok(Some(Position {
|
||||||
|
item_position: n,
|
||||||
|
total_items: ints.next().and_then(Result::ok),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user