297 lines
8.5 KiB
Rust
297 lines
8.5 KiB
Rust
|
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");
|
|||
|
}
|
|||
|
}
|