diff --git a/ChangeLog b/ChangeLog index eec3bbb..5ecea28 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ Version NEXT: DATE Emmanuel Gil Peyrot * New parsers/serialisers: - XHTML-IM (XEP-0071) + - User Tune (XEP-0118) - Bits of Binary (XEP-0231) - Message Carbons (XEP-0280) * Breaking changes: diff --git a/doap.xml b/doap.xml index a3165dc..561f49d 100644 --- a/doap.xml +++ b/doap.xml @@ -183,6 +183,14 @@ 0.4.0 + + + + complete + 1.2 + NEXT + + diff --git a/src/lib.rs b/src/lib.rs index af2fc12..d989812 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,9 @@ pub mod component; /// XEP-0115: Entity Capabilities pub mod caps; +/// XEP-0118: User Tune +pub mod tune; + /// XEP-0157: Contact Addresses for XMPP Services pub mod server_info; diff --git a/src/ns.rs b/src/ns.rs index 5c59ec0..2c932c3 100644 --- a/src/ns.rs +++ b/src/ns.rs @@ -84,6 +84,9 @@ pub const COMPONENT: &str = "jabber:component:accept"; /// XEP-0115: Entity Capabilities pub const CAPS: &str = "http://jabber.org/protocol/caps"; +/// XEP-0118: User Tune +pub const TUNE: &str = "http://jabber.org/protocol/tune"; + /// XEP-0157: Contact Addresses for XMPP Services pub const SERVER_INFO: &str = "http://jabber.org/network/serverinfo"; diff --git a/src/tune.rs b/src/tune.rs new file mode 100644 index 0000000..7a0b9f3 --- /dev/null +++ b/src/tune.rs @@ -0,0 +1,214 @@ +// Copyright (c) 2019 Emmanuel Gil Peyrot +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::util::error::Error; +use crate::pubsub::PubSubPayload; +use crate::ns; +use minidom::Element; +use std::convert::TryFrom; + +generate_elem_id!( + /// The artist or performer of the song or piece. + Artist, "artist", TUNE +); + +generate_elem_id!( + /// The duration of the song or piece in seconds. + Length, "length", TUNE, + u16 +); + +generate_elem_id!( + /// The user's rating of the song or piece, from 1 (lowest) to 10 (highest). + Rating, "rating", TUNE, + u8 +); + +generate_elem_id!( + /// The collection (e.g., album) or other source (e.g., a band website that hosts streams or + /// audio files). + Source, "source", TUNE +); + +generate_elem_id!( + /// The title of the song or piece. + Title, "title", TUNE +); + +generate_elem_id!( + /// A unique identifier for the tune; e.g., the track number within a collection or the + /// specific URI for the object (e.g., a stream or audio file). + Track, "track", TUNE +); + +generate_elem_id!( + /// A URI or URL pointing to information about the song, collection, or artist. + Uri, "uri", TUNE +); + +/// Container for formatted text. +#[derive(Debug, Clone)] +pub struct Tune { + /// The artist or performer of the song or piece. + artist: Option, + + /// The duration of the song or piece in seconds. + length: Option, + + /// The user's rating of the song or piece, from 1 (lowest) to 10 (highest). + rating: Option, + + /// The collection (e.g., album) or other source (e.g., a band website that hosts streams or + /// audio files). + source: Option, + + /// The title of the song or piece. + title: Option, + + /// A unique identifier for the tune; e.g., the track number within a collection or the + /// specific URI for the object (e.g., a stream or audio file). + track: Option<Track>, + + /// A URI or URL pointing to information about the song, collection, or artist. + uri: Option<Uri>, +} + +impl PubSubPayload for Tune {} + +impl Tune { + fn new() -> Tune { + Tune { + artist: None, + length: None, + rating: None, + source: None, + title: None, + track: None, + uri: None, + } + } +} + +impl TryFrom<Element> for Tune { + type Error = Error; + + fn try_from(elem: Element) -> Result<Tune, Error> { + check_self!(elem, "tune", TUNE); + check_no_attributes!(elem, "tune"); + + let mut tune = Tune::new(); + for child in elem.children() { + if child.is("artist", ns::TUNE) { + if tune.artist.is_some() { + return Err(Error::ParseError("Tune can’t have more than one artist.")); + } + tune.artist = Some(Artist::try_from(child.clone())?); + } else if child.is("length", ns::TUNE) { + if tune.length.is_some() { + return Err(Error::ParseError("Tune can’t have more than one length.")); + } + tune.length = Some(Length::try_from(child.clone())?); + } else if child.is("rating", ns::TUNE) { + if tune.rating.is_some() { + return Err(Error::ParseError("Tune can’t have more than one rating.")); + } + tune.rating = Some(Rating::try_from(child.clone())?); + } else if child.is("source", ns::TUNE) { + if tune.source.is_some() { + return Err(Error::ParseError("Tune can’t have more than one source.")); + } + tune.source = Some(Source::try_from(child.clone())?); + } else if child.is("title", ns::TUNE) { + if tune.title.is_some() { + return Err(Error::ParseError("Tune can’t have more than one title.")); + } + tune.title = Some(Title::try_from(child.clone())?); + } else if child.is("track", ns::TUNE) { + if tune.track.is_some() { + return Err(Error::ParseError("Tune can’t have more than one track.")); + } + tune.track = Some(Track::try_from(child.clone())?); + } else if child.is("uri", ns::TUNE) { + if tune.uri.is_some() { + return Err(Error::ParseError("Tune can’t have more than one uri.")); + } + tune.uri = Some(Uri::try_from(child.clone())?); + } else { + return Err(Error::ParseError("Unknown element in User Tune.")); + } + } + + Ok(tune) + } +} + +impl From<Tune> for Element { + fn from(tune: Tune) -> Element { + Element::builder("tune") + .ns(ns::TUNE) + .append(tune.artist) + .append(tune.length) + .append(tune.rating) + .append(tune.source) + .append(tune.title) + .append(tune.track) + .append(tune.uri) + .build() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[cfg(target_pointer_width = "32")] + #[test] + #[ignore] + fn test_size() { + assert_size!(Tune, 0); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_size() { + assert_size!(Tune, 128); + } + + #[test] + fn empty() { + let elem: Element = "<tune xmlns='http://jabber.org/protocol/tune'/>" + .parse() + .unwrap(); + let elem2 = elem.clone(); + let tune = Tune::try_from(elem).unwrap(); + assert!(tune.artist.is_none()); + assert!(tune.length.is_none()); + assert!(tune.rating.is_none()); + assert!(tune.source.is_none()); + assert!(tune.title.is_none()); + assert!(tune.track.is_none()); + assert!(tune.uri.is_none()); + + let elem3 = tune.into(); + assert_eq!(elem2, elem3); + } + + #[test] + fn full() { + let elem: Element = "<tune xmlns='http://jabber.org/protocol/tune'><artist>Yes</artist><length>686</length><rating>8</rating><source>Yessongs</source><title>Heart of the Sunrise3http://www.yesworld.com/lyrics/Fragile.html#9" + .parse() + .unwrap(); + let tune = Tune::try_from(elem).unwrap(); + assert_eq!(tune.artist, Some(Artist::from_str("Yes").unwrap())); + assert_eq!(tune.length, Some(Length(686))); + assert_eq!(tune.rating, Some(Rating(8))); + assert_eq!(tune.source, Some(Source::from_str("Yessongs").unwrap())); + assert_eq!(tune.title, Some(Title::from_str("Heart of the Sunrise").unwrap())); + assert_eq!(tune.track, Some(Track::from_str("3").unwrap())); + assert_eq!(tune.uri, Some(Uri::from_str("http://www.yesworld.com/lyrics/Fragile.html#9").unwrap())); + } +}