xesite/lib/jsonfeed/src/feed.rs

297 lines
8.5 KiB
Rust
Raw Normal View History

Rewrite site backend in Rust (#178) * add shell.nix changes for Rust #176 * set up base crate layout * add first set of dependencies * start adding basic app modules * start html templates * serve index page * add contact and feeds pages * add resume rendering support * resume cleanups * get signalboost page working * rewrite config to be in dhall * more work * basic generic post loading * more tests * initial blog index support * fix routing? * render blogposts * X-Clacks-Overhead * split blog handlers into blog.rs * gallery index * gallery posts * fix hashtags * remove instantpage (it messes up the metrics) * talk support + prometheus * Create rust.yml * Update rust.yml * Update codeql-analysis.yml * add jsonfeed library * jsonfeed support * rss/atom * go mod tidy * atom: add posted date * rss: add publishing date * nix: build rust program * rip out go code * rip out go templates * prepare for serving in docker * create kubernetes deployment * create automagic deployment * build docker images on non-master * more fixes * fix timestamps * fix RSS/Atom/JSONFeed validation errors * add go vanity import redirecting * templates/header: remove this * atom feed: fixes * fix? * fix?? * fix rust tests * Update rust.yml * automatically show snow during the winter * fix dates * show commit link in footer * sitemap support * fix compiler warning * start basic patreon client * integrate kankyo * fix patreon client * add patrons page * remove this * handle patron errors better * fix build * clean up deploy * sort envvars for deploy * remove deps.nix * shell.nix: remove go * update README * fix envvars for tests * nice * blog: add rewrite in rust post * blog/site-update: more words
2020-07-16 19:32:30 +00:00
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": "Im 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");
}
}