jsonfeed 0.2.0

Parser for the JSONFeed (http://jsonfeed.org) specification
Documentation
use std::default::Default;

use item::Item;
use builder::Builder;

const VERSION_1: &'static str = "https://jsonfeed.org/version/1";

/// Represents a single feed
///
/// # Examples
///
/// ```rust
/// // Serialize a feed object to a JSON string
///
/// # extern crate jsonfeed;
/// # use std::default::Default;
/// # use jsonfeed::Feed;
/// # fn main() {
/// let feed: Feed = Feed::default();
/// assert_eq!(
///     jsonfeed::to_string(&feed).unwrap(),
///     "{\"version\":\"https://jsonfeed.org/version/1\",\"title\":\"\",\"items\":[]}"
/// );
/// # }
/// ```
///
/// ```rust
/// // Deserialize a feed objects from a JSON String
///
/// # extern crate jsonfeed;
/// # use jsonfeed::Feed;
/// # fn main() {
/// let json = "{\"version\":\"https://jsonfeed.org/version/1\",\"title\":\"\",\"items\":[]}";
/// let feed: Feed = jsonfeed::from_str(&json).unwrap();
/// assert_eq!(
///     feed,
///     Feed::default()
/// );
/// # }
/// ```
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Feed {
    pub version: String,
    pub title: String,
    pub items: Vec<Item>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub home_page_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub feed_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_comment: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub icon: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub favicon: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub author: Option<Author>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expired: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hubs: Option<Vec<Hub>>,
}

impl Feed {
    /// Used to construct a Feed object
    pub fn builder() -> Builder {
        Builder::new()
    }
}

impl Default for Feed {
    fn default() -> Feed {
        Feed {
            version: VERSION_1.to_string(),
            title: "".to_string(),
            items: vec![],
            home_page_url: None,
            feed_url: None,
            description: None,
            user_comment: None,
            next_url: None,
            icon: None,
            favicon: None,
            author: None,
            expired: None,
            hubs: None,
        }
    }
}

/// Represents an `attachment` for an item
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Attachment {
    url: String,
    mime_type: String,
    title: Option<String>,
    size_in_bytes: Option<u64>,
    duration_in_seconds: Option<u64>,
}

/// Represents an `author` in both a feed and a feed item
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Author {
    name: Option<String>,
    url: Option<String>,
    avatar: Option<String>,
}

impl Author {
    pub fn new() -> Author {
        Author {
            name: None,
            url: None,
            avatar: None,
        }
    }

    pub fn name<I: Into<String>>(mut self, name: I) -> Self {
        self.name = Some(name.into());
        self
    }

    pub fn url<I: Into<String>>(mut self, url: I) -> Self {
        self.url = Some(url.into());
        self
    }

    pub fn avatar<I: Into<String>>(mut self, avatar: I) -> Self {
        self.avatar = Some(avatar.into());
        self
    }
}

/// Represents a `hub` for a feed
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Hub {
    #[serde(rename = "type")]
    type_: String,
    url: String,
}

#[cfg(test)]
mod tests {
    use serde_json;
    use std::default::Default;
    use super::*;

    #[test]
    fn serialize_feed() {
        let feed = Feed {
            version: "https://jsonfeed.org/version/1".to_string(),
            title: "some title".to_string(),
            items: vec![],
            home_page_url: None,
            description: None,
            expired: Some(true),
            ..Default::default()
        };
        assert_eq!(
            serde_json::to_string(&feed).unwrap(),
            r#"{"version":"https://jsonfeed.org/version/1","title":"some title","items":[],"expired":true}"#
        );
    }

    #[test]
    fn deserialize_feed() {
        let json = r#"{"version":"https://jsonfeed.org/version/1","title":"some title","items":[]}"#;
        let feed: Feed = serde_json::from_str(&json).unwrap();
        let expected = Feed {
            version: "https://jsonfeed.org/version/1".to_string(),
             title: "some title".to_string(),
             items: vec![],
             ..Default::default()
        };
        assert_eq!(
            feed,
            expected
        );
    }

    #[test]
    fn serialize_attachment() {
        let attachment = Attachment {
            url: "http://example.com".to_string(),
            mime_type: "application/json".to_string(),
            title: Some("some title".to_string()),
            size_in_bytes: Some(1),
            duration_in_seconds: Some(1),
        };
        assert_eq!(
            serde_json::to_string(&attachment).unwrap(),
            r#"{"url":"http://example.com","mime_type":"application/json","title":"some title","size_in_bytes":1,"duration_in_seconds":1}"#
        );
    }

    #[test]
    fn deserialize_attachment() {
        let json = r#"{"url":"http://example.com","mime_type":"application/json","title":"some title","size_in_bytes":1,"duration_in_seconds":1}"#;
        let attachment: Attachment = serde_json::from_str(&json).unwrap();
        let expected = Attachment {
            url: "http://example.com".to_string(),
            mime_type: "application/json".to_string(),
            title: Some("some title".to_string()),
            size_in_bytes: Some(1),
            duration_in_seconds: Some(1),
        };
        assert_eq!(
            attachment,
            expected
        );
    }

    #[test]
    fn serialize_author() {
        let author = Author {
            name: Some("bob jones".to_string()),
            url: Some("http://example.com".to_string()),
            avatar: Some("http://img.com/blah".to_string()),
        };
        assert_eq!(
            serde_json::to_string(&author).unwrap(),
            r#"{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"}"#
        );
    }

    #[test]
    fn deserialize_author() {
        let json = r#"{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"}"#;
        let author: Author = serde_json::from_str(&json).unwrap();
        let expected = Author {
            name: Some("bob jones".to_string()),
            url: Some("http://example.com".to_string()),
            avatar: Some("http://img.com/blah".to_string()),
        };
        assert_eq!(
            author,
            expected
        );
    }

    #[test]
    fn serialize_hub() {
        let hub = Hub {
            type_: "some-type".to_string(),
            url: "http://example.com".to_string(),
        };
        assert_eq!(
            serde_json::to_string(&hub).unwrap(),
            r#"{"type":"some-type","url":"http://example.com"}"#
        )
    }

    #[test]
    fn deserialize_hub() {
        let json = r#"{"type":"some-type","url":"http://example.com"}"#;
        let hub: Hub = serde_json::from_str(&json).unwrap();
        let expected = Hub {
            type_: "some-type".to_string(),
            url: "http://example.com".to_string(),
        };
        assert_eq!(
            hub,
            expected
        );
    }

    #[test]
    fn deser_podcast() {
        let json = r#"{
  "version": "https://jsonfeed.org/version/1",
  "title": "Timetable",
  "home_page_url": "http://timetable.manton.org/",
  "items": [
    {
      "id": "http://timetable.manton.org/2017/04/episode-45-launch-week/",
      "url": "http://timetable.manton.org/2017/04/episode-45-launch-week/",
      "title": "Episode 45: Launch week",
      "content_html": "I’m rolling out early access to Micro.blog this week. I talk about how the first 2 days have gone, mistakes with TestFlight, and what to do next.",
      "date_published": "2017-04-26T01:09:45+00:00",
      "attachments": [
        {
          "url": "http://timetable.manton.org/podcast-download/139/episode-45-launch-week.mp3",
          "mime_type": "audio/mpeg",
          "size_in_bytes": 5236920
        }
      ]
    }
  ]
}"#;
        serde_json::from_str::<Feed>(&json).expect("Failed to deserialize podcast feed");
    }
}