use std::time::Duration;
use chrono::{DateTime, Utc};
use mediatype::{names, MediaTypeBuf};
use serde::Serialize;
use url::Url;
use crate::parser::util;
#[cfg(test)]
use crate::parser::util::parse_timestamp_lenient;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Feed {
pub feed_type: FeedType,
pub id: String,
pub title: Option<Text>,
pub updated: Option<DateTime<Utc>>,
pub authors: Vec<Person>,
pub description: Option<Text>,
pub links: Vec<Link>,
pub categories: Vec<Category>,
pub contributors: Vec<Person>,
pub generator: Option<Generator>,
pub icon: Option<Image>,
pub language: Option<String>,
pub logo: Option<Image>,
pub published: Option<DateTime<Utc>>,
pub rating: Option<MediaRating>,
pub rights: Option<Text>,
pub ttl: Option<u32>,
pub entries: Vec<Entry>,
}
impl Feed {
pub(crate) fn new(feed_type: FeedType) -> Self {
Feed {
feed_type,
id: "".into(),
title: None,
updated: None,
authors: Vec::new(),
description: None,
links: Vec::new(),
categories: Vec::new(),
contributors: Vec::new(),
generator: None,
icon: None,
language: None,
logo: None,
published: None,
rating: None,
rights: None,
ttl: None,
entries: Vec::new(),
}
}
}
#[cfg(test)]
impl Feed {
pub fn author(mut self, person: Person) -> Self {
self.authors.push(person);
self
}
pub fn category(mut self, category: Category) -> Self {
self.categories.push(category);
self
}
pub fn contributor(mut self, person: Person) -> Self {
self.contributors.push(person);
self
}
pub fn description(mut self, description: Text) -> Self {
self.description = Some(description);
self
}
pub fn entry(mut self, entry: Entry) -> Self {
self.entries.push(entry);
self
}
pub fn generator(mut self, generator: Generator) -> Self {
self.generator = Some(generator);
self
}
pub fn icon(mut self, image: Image) -> Self {
self.icon = Some(image);
self
}
pub fn id(mut self, id: &str) -> Self {
self.id = id.to_string();
self
}
pub fn language(mut self, language: &str) -> Self {
self.language = Some(language.to_owned());
self
}
pub fn link(mut self, link: Link) -> Self {
self.links.push(link);
self
}
pub fn logo(mut self, image: Image) -> Self {
self.logo = Some(image);
self
}
pub fn published(mut self, pub_date: &str) -> Self {
self.published = parse_timestamp_lenient(pub_date);
self
}
pub fn rights(mut self, rights: Text) -> Self {
self.rights = Some(rights);
self
}
pub fn title(mut self, title: Text) -> Self {
self.title = Some(title);
self
}
pub fn ttl(mut self, ttl: u32) -> Self {
self.ttl = Some(ttl);
self
}
pub fn updated(mut self, updated: Option<DateTime<Utc>>) -> Self {
self.updated = updated;
self
}
pub fn updated_parsed(mut self, updated: &str) -> Self {
self.updated = parse_timestamp_lenient(updated);
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum FeedType {
Atom,
JSON,
RSS0,
RSS1,
RSS2,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Entry {
pub id: String,
pub title: Option<Text>,
pub updated: Option<DateTime<Utc>>,
pub authors: Vec<Person>,
pub content: Option<Content>,
pub links: Vec<Link>,
pub summary: Option<Text>,
pub categories: Vec<Category>,
pub contributors: Vec<Person>,
pub published: Option<DateTime<Utc>>,
pub source: Option<String>,
pub rights: Option<Text>,
pub media: Vec<MediaObject>,
pub language: Option<String>,
pub base: Option<String>,
}
impl Default for Entry {
fn default() -> Self {
Entry {
id: "".into(),
title: None,
updated: None,
authors: Vec::new(),
content: None,
links: Vec::new(),
summary: None,
categories: Vec::new(),
contributors: Vec::new(),
published: None,
source: None,
rights: None,
media: Vec::new(),
language: None,
base: None,
}
}
}
#[cfg(test)]
impl Entry {
pub fn author(mut self, person: Person) -> Self {
self.authors.push(person);
self
}
pub fn category(mut self, category: Category) -> Self {
self.categories.push(category);
self
}
pub fn content(mut self, content: Content) -> Self {
self.content = Some(content);
self
}
pub fn contributor(mut self, person: Person) -> Self {
self.contributors.push(person);
self
}
pub fn id(mut self, id: &str) -> Self {
self.id = id.trim().to_string();
self
}
pub fn link(mut self, link: Link) -> Self {
self.links.push(link);
self
}
pub fn published(mut self, published: &str) -> Self {
self.published = parse_timestamp_lenient(published);
self
}
pub fn rights(mut self, rights: Text) -> Self {
self.rights = Some(rights);
self
}
pub fn summary(mut self, summary: Text) -> Self {
self.summary = Some(summary);
self
}
pub fn title(mut self, title: Text) -> Self {
self.title = Some(title);
self
}
pub fn updated(mut self, updated: Option<DateTime<Utc>>) -> Self {
self.updated = updated;
self
}
pub fn updated_parsed(mut self, updated: &str) -> Self {
self.updated = parse_timestamp_lenient(updated);
self
}
pub fn media(mut self, media: MediaObject) -> Self {
self.media.push(media);
self
}
pub fn language(mut self, language: &str) -> Self {
self.language = Some(language.to_owned());
self
}
pub fn base(mut self, url: &str) -> Self {
self.base = Some(url.to_owned());
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Category {
pub term: String,
pub scheme: Option<String>,
pub label: Option<String>,
pub subcategories: Vec<Category>,
}
impl Category {
pub fn new(term: &str) -> Category {
Category {
term: term.trim().into(),
scheme: None,
label: None,
subcategories: Vec::new(),
}
}
}
#[cfg(test)]
impl Category {
pub fn label(mut self, label: &str) -> Self {
self.label = Some(label.to_owned());
self
}
pub fn scheme(mut self, scheme: &str) -> Self {
self.scheme = Some(scheme.to_owned());
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Content {
pub body: Option<String>,
pub content_type: MediaTypeBuf,
pub length: Option<u64>,
pub src: Option<Link>,
}
impl Default for Content {
fn default() -> Content {
Content {
body: None,
content_type: MediaTypeBuf::new(names::TEXT, names::PLAIN),
length: None,
src: None,
}
}
}
impl Content {
pub fn sanitize(&mut self) {
#[cfg(feature = "sanitize")]
{
let content_type = self.content_type.as_str();
if content_type == "text/html" || content_type == "application/xhtml+xml" {
if let Some(body) = &self.body {
self.body = Some(ammonia::clean(body.as_str()));
}
}
}
}
}
#[cfg(test)]
impl Content {
pub fn body(mut self, body: &str) -> Self {
self.body = Some(body.to_owned());
self
}
pub fn content_type(mut self, content_type: &str) -> Self {
self.content_type = content_type.parse::<MediaTypeBuf>().unwrap();
self
}
pub fn length(mut self, length: u64) -> Self {
self.length = Some(length);
self
}
pub fn src(mut self, url: &str) -> Self {
self.src = Some(Link::new(url, None));
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Generator {
pub content: String,
pub uri: Option<String>,
pub version: Option<String>,
}
impl Generator {
pub(crate) fn new(content: &str) -> Generator {
Generator {
uri: None,
version: None,
content: content.trim().into(),
}
}
}
#[cfg(test)]
impl Generator {
pub fn uri(mut self, uri: &str) -> Self {
self.uri = Some(uri.to_owned());
self
}
pub fn version(mut self, version: &str) -> Self {
self.version = Some(version.to_owned());
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Image {
pub uri: String,
pub title: Option<String>,
pub link: Option<Link>,
pub width: Option<u32>,
pub height: Option<u32>,
pub description: Option<String>,
}
impl Image {
pub(crate) fn new(uri: String) -> Image {
Image {
uri,
title: None,
link: None,
width: None,
height: None,
description: None,
}
}
}
#[cfg(test)]
impl Image {
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
pub fn height(mut self, height: u32) -> Self {
self.height = Some(height);
self
}
pub fn link(mut self, link: &str) -> Self {
self.link = Some(Link::new(link, None));
self
}
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.to_owned());
self
}
pub fn width(mut self, width: u32) -> Self {
self.width = Some(width);
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Link {
pub href: String,
pub rel: Option<String>,
pub media_type: Option<String>,
pub href_lang: Option<String>,
pub title: Option<String>,
pub length: Option<u64>,
}
impl Link {
pub(crate) fn new<S: AsRef<str>>(href: S, base: Option<&Url>) -> Link {
let href = match util::parse_uri(href.as_ref(), base) {
Some(uri) => uri.to_string(),
None => href.as_ref().to_string(),
}
.trim()
.to_string();
Link {
href,
rel: None,
media_type: None,
href_lang: None,
title: None,
length: None,
}
}
}
#[cfg(test)]
impl Link {
pub fn href_lang(mut self, lang: &str) -> Self {
self.href_lang = Some(lang.to_owned());
self
}
pub fn length(mut self, length: u64) -> Self {
self.length = Some(length);
self
}
pub fn media_type(mut self, media_type: &str) -> Self {
self.media_type = Some(media_type.to_owned());
self
}
pub fn rel(mut self, rel: &str) -> Self {
self.rel = Some(rel.to_owned());
self
}
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.to_owned());
self
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct MediaObject {
pub title: Option<Text>,
pub content: Vec<MediaContent>,
pub duration: Option<Duration>,
pub thumbnails: Vec<MediaThumbnail>,
pub texts: Vec<MediaText>,
pub description: Option<Text>,
pub community: Option<MediaCommunity>,
pub credits: Vec<MediaCredit>,
}
impl MediaObject {
pub(crate) fn has_content(&self) -> bool {
self.title.is_some() || self.description.is_some() || !self.content.is_empty() || !self.thumbnails.is_empty() || !self.texts.is_empty()
}
}
#[cfg(test)]
impl MediaObject {
pub fn community(mut self, community: MediaCommunity) -> Self {
self.community = Some(community);
self
}
pub fn content(mut self, content: MediaContent) -> Self {
self.content.push(content);
self
}
pub fn credit(mut self, entity: &str) -> Self {
self.credits.push(MediaCredit::new(entity.to_string()));
self
}
pub fn description(mut self, description: &str) -> Self {
self.description = Some(Text::new(description.to_string()));
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
pub fn text(mut self, text: MediaText) -> Self {
self.texts.push(text);
self
}
pub fn thumbnail(mut self, thumbnail: MediaThumbnail) -> Self {
self.thumbnails.push(thumbnail);
self
}
pub fn title(mut self, title: &str) -> Self {
self.title = Some(Text::new(title.to_string()));
self
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct MediaCommunity {
pub stars_avg: Option<f64>,
pub stars_count: Option<u64>,
pub stars_min: Option<u64>,
pub stars_max: Option<u64>,
pub stats_views: Option<u64>,
pub stats_favorites: Option<u64>,
}
impl MediaCommunity {
pub(crate) fn new() -> MediaCommunity {
MediaCommunity {
stars_avg: None,
stars_count: None,
stars_min: None,
stars_max: None,
stats_views: None,
stats_favorites: None,
}
}
}
#[cfg(test)]
impl MediaCommunity {
pub fn star_rating(mut self, count: u64, average: f64, min: u64, max: u64) -> Self {
self.stars_count = Some(count);
self.stars_avg = Some(average);
self.stars_min = Some(min);
self.stars_max = Some(max);
self
}
pub fn statistics(mut self, views: u64, favorites: u64) -> Self {
self.stats_views = Some(views);
self.stats_favorites = Some(favorites);
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MediaContent {
pub url: Option<Url>,
pub content_type: Option<MediaTypeBuf>,
pub height: Option<u32>,
pub width: Option<u32>,
pub duration: Option<Duration>,
pub size: Option<u64>,
pub rating: Option<MediaRating>,
}
#[cfg(test)]
impl MediaContent {
pub fn content_type(mut self, content_type: &str) -> Self {
self.content_type = Some(content_type.parse::<MediaTypeBuf>().unwrap());
self
}
pub fn height(mut self, height: u32) -> Self {
self.height = Some(height);
self
}
pub fn url(mut self, url: &str) -> Self {
self.url = Some(Url::parse(url).unwrap());
self
}
pub fn width(mut self, width: u32) -> Self {
self.width = Some(width);
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
pub fn size(mut self, size: u64) -> Self {
self.size = Some(size);
self
}
}
impl MediaContent {
pub(crate) fn new() -> MediaContent {
MediaContent {
url: None,
content_type: None,
height: None,
width: None,
duration: None,
size: None,
rating: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MediaCredit {
pub entity: String,
}
impl MediaCredit {
pub(crate) fn new(entity: String) -> MediaCredit {
MediaCredit { entity }
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MediaRating {
pub urn: String,
pub value: String,
}
impl MediaRating {
pub(crate) fn new(value: String) -> MediaRating {
MediaRating { urn: "simple".into(), value }
}
pub fn urn(mut self, urn: &str) -> Self {
self.urn = urn.to_string();
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MediaText {
pub text: Text,
pub start_time: Option<Duration>,
pub end_time: Option<Duration>,
}
impl MediaText {
pub(crate) fn new(text: Text) -> MediaText {
MediaText {
text,
start_time: None,
end_time: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MediaThumbnail {
pub image: Image,
pub time: Option<Duration>,
}
impl MediaThumbnail {
pub(crate) fn new(image: Image) -> MediaThumbnail {
MediaThumbnail { image, time: None }
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Person {
pub name: String,
pub uri: Option<String>,
pub email: Option<String>,
}
impl Person {
pub(crate) fn new(name: &str) -> Person {
Person {
name: name.trim().into(),
uri: None,
email: None,
}
}
pub fn email(mut self, email: &str) -> Self {
self.email = Some(email.to_owned());
self
}
}
#[cfg(test)]
impl Person {
pub fn uri(mut self, uri: &str) -> Self {
self.uri = Some(uri.to_owned());
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Text {
pub content_type: MediaTypeBuf,
pub src: Option<String>,
pub content: String,
}
impl Text {
pub(crate) fn new(content: String) -> Text {
Text {
content_type: MediaTypeBuf::new(names::TEXT, names::PLAIN),
src: None,
content: content.trim().to_string(),
}
}
pub(crate) fn html(content: String) -> Text {
Text {
content_type: MediaTypeBuf::new(names::TEXT, names::HTML),
src: None,
content: content.trim().to_string(),
}
}
pub fn sanitize(&mut self) {
#[cfg(feature = "sanitize")]
{
if self.content_type.as_str() != "text/plain" {
self.content = ammonia::clean(&self.content);
}
}
}
}
#[cfg(test)]
impl Text {
pub fn content_type(mut self, content_type: &str) -> Self {
self.content_type = content_type.parse::<MediaTypeBuf>().unwrap();
self
}
}