use super::Category;
use super::Tag;
use crate::ApiError;
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize)]
pub struct Entry {
pub id: String,
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub content: Option<Content>,
#[serde(default)]
pub summary: Option<Content>,
#[serde(default)]
pub author: Option<String>,
pub crawled: i64,
#[serde(default)]
pub recrawled: Option<i64>,
pub published: i64,
#[serde(default)]
pub updated: Option<i64>,
#[serde(default)]
pub alternate: Option<Vec<Link>>,
#[serde(default)]
pub origin: Option<Origin>,
#[serde(default)]
pub keywords: Option<Vec<String>>,
#[serde(default)]
pub visual: Option<Visual>,
pub unread: bool,
#[serde(default)]
pub tags: Option<Vec<Tag>>,
#[serde(default)]
pub categories: Option<Vec<Category>>,
#[serde(default)]
pub engagement: Option<i64>,
#[serde(default)]
#[serde(rename = "actionTimestamp")]
pub action_timestamp: Option<i64>,
#[serde(default)]
pub enclosure: Option<Vec<Link>>,
#[serde(default)]
pub fingerprint: Option<String>,
#[serde(default)]
#[serde(rename = "originId")]
pub origin_id: Option<String>,
#[serde(default)]
pub sid: Option<String>,
}
impl Entry {
#[allow(clippy::type_complexity)]
pub fn decompose(
self,
) -> (
String,
Option<String>,
Option<Content>,
Option<Content>,
Option<String>,
i64,
Option<i64>,
i64,
Option<i64>,
Option<Vec<Link>>,
Option<Origin>,
Option<Vec<String>>,
Option<Visual>,
bool,
Option<Vec<Tag>>,
Option<Vec<Category>>,
Option<i64>,
Option<i64>,
Option<Vec<Link>>,
Option<String>,
Option<String>,
Option<String>,
) {
(
self.id,
self.title,
self.content,
self.summary,
self.author,
self.crawled,
self.recrawled,
self.published,
self.updated,
self.alternate,
self.origin,
self.keywords,
self.visual,
self.unread,
self.tags,
self.categories,
self.engagement,
self.action_timestamp,
self.enclosure,
self.fingerprint,
self.origin_id,
self.sid,
)
}
fn manual_deserialize(data: &Value) -> Result<Entry, ApiError> {
let id = data["id"]
.as_str()
.ok_or(ApiError::ManualJson)
.map_err(|e| {
log::error!("entry doesn't contain an ID: {}", e);
log::error!("entry value: {:?}", data);
e
})?
.into();
let title = data["title"].as_str().map(|t| t.into());
let content = Content::manual_deserialize(&data["content"]).ok();
let summary = Content::manual_deserialize(&data["summary"]).ok();
let author = data["author"].as_str().map(|t| t.into());
let crawled = data["crawled"]
.as_u64()
.ok_or(ApiError::ManualJson)
.map_err(|e| {
log::error!("entry doesn't contain 'crawled'");
log::error!("entry value: {:?}", data);
e
})? as i64;
let recrawled = data["recrawled"].as_u64().map(|r| r as i64);
let published = data["published"]
.as_i64()
.ok_or(ApiError::ManualJson)
.map_err(|e| {
log::error!("entry doesn't contain 'published'");
log::error!("entry value: {:?}", data);
e
})? as i64;
let updated = data["updated"].as_u64().map(|u| u as i64);
let alternate = match Link::manual_deserialize_vec(&data["alternate"]) {
Ok(alternate) => {
if alternate.is_empty() {
None
} else {
Some(alternate)
}
}
Err(_error) => None,
};
let origin = Origin::manual_deserialize(&data["origin"]).ok();
let keywords = if let Some(keywords_values) = data["keywords"].as_array() {
let mut keywords = Vec::new();
for keyword_value in keywords_values {
if let Some(keyword_str) = keyword_value.as_str() {
keywords.push(keyword_str.into());
}
}
if keywords.is_empty() {
None
} else {
Some(keywords)
}
} else {
None
};
let visual = Visual::manual_deserialize(&data["visual"]).ok();
let unread = data["unread"].as_bool().unwrap_or(false);
let tags = match Tag::manual_deserialize_vec(&data["tags"]) {
Ok(tags) => {
if tags.is_empty() {
None
} else {
Some(tags)
}
}
Err(_) => None,
};
let categories = match Category::manual_deserialize_vec(&data["categories"]) {
Ok(categories) => {
if categories.is_empty() {
None
} else {
Some(categories)
}
}
Err(_) => None,
};
let engagement = data["engagement"].as_i64();
let action_timestamp = data["actionTimestamp"].as_i64();
let enclosure = match Link::manual_deserialize_vec(&data["enclosure"]) {
Ok(enclosure) => {
if enclosure.is_empty() {
None
} else {
Some(enclosure)
}
}
Err(_) => None,
};
let fingerprint = data["fingerprint"].as_str().map(|f| f.into());
let origin_id = data["originId"].as_str().map(|f| f.into());
let sid = data["sid"].as_str().map(|f| f.into());
Ok(Entry {
id,
title,
content,
summary,
author,
crawled,
recrawled,
published,
updated,
alternate,
origin,
keywords,
visual,
unread,
tags,
categories,
engagement,
action_timestamp,
enclosure,
fingerprint,
origin_id,
sid,
})
}
pub fn manual_deserialize_vec(data: &Value) -> Result<Vec<Entry>, ApiError> {
let mut entries = Vec::new();
let vector = match data.as_array() {
Some(vector) => vector,
None => {
return Ok(entries);
}
};
for entry_value in vector {
match Self::manual_deserialize(entry_value) {
Ok(entry) => entries.push(entry),
Err(_error) => {
continue;
}
}
}
Ok(entries)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Content {
pub content: String,
#[serde(default)]
pub direction: Option<String>,
}
impl Content {
pub fn decompose(self) -> (String, Option<String>) {
(self.content, self.direction)
}
pub fn manual_deserialize(data: &Value) -> Result<Content, ApiError> {
let content = data["content"].as_str().ok_or(ApiError::ManualJson)?.into();
let direction = data["direction"].as_str().map(|t| t.into());
Ok(Content { content, direction })
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Link {
pub href: String,
#[serde(default)]
#[serde(rename = "type")]
pub _type: Option<String>,
}
impl Link {
pub fn decompose(self) -> (String, Option<String>) {
(self.href, self._type)
}
fn manual_deserialize(data: &Value) -> Result<Link, ApiError> {
let href = data["href"]
.as_str()
.ok_or(ApiError::ManualJson)
.map_err(|e| {
log::error!("Link doesn't contain 'href': {:?}", data);
e
})?
.into();
let _type = data["type"].as_str().map(|t| t.into());
Ok(Link { href, _type })
}
pub fn manual_deserialize_vec(data: &Value) -> Result<Vec<Link>, ApiError> {
let mut links = Vec::new();
let vector = match data.as_array() {
Some(vector) => vector,
None => {
return Ok(links);
}
};
for link_value in vector {
match Self::manual_deserialize(link_value) {
Ok(link) => links.push(link),
Err(_error) => {
continue;
}
}
}
Ok(links)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Origin {
#[serde(default)]
#[serde(rename = "streamId")]
pub stream_id: Option<String>,
pub title: Option<String>,
#[serde(default)]
#[serde(rename = "htmlUrl")]
pub html_url: Option<String>,
}
impl Origin {
pub fn decompose(self) -> (Option<String>, Option<String>, Option<String>) {
(self.stream_id, self.title, self.html_url)
}
pub fn manual_deserialize(data: &Value) -> Result<Origin, ApiError> {
let stream_id = data["streamId"].as_str().map(|t| t.into());
let title = data["title"].as_str().map(|t| t.into());
let html_url = data["htmlUrl"].as_str().map(|t| t.into());
Ok(Origin {
stream_id,
title,
html_url,
})
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Visual {
pub url: String,
#[serde(default)]
pub width: Option<u32>,
#[serde(default)]
pub height: Option<u32>,
#[serde(default)]
#[serde(rename = "contentType")]
pub content_type: Option<String>,
}
impl Visual {
pub fn decompose(self) -> (String, Option<u32>, Option<u32>, Option<String>) {
(self.url, self.width, self.height, self.content_type)
}
pub fn manual_deserialize(data: &Value) -> Result<Visual, ApiError> {
let url = data["url"].as_str().ok_or(ApiError::ManualJson)?.into();
let width = data["width"].as_u64().map(|w| w as u32);
let height = data["height"].as_u64().map(|w| w as u32);
let content_type = data["contentType"].as_str().map(|t| t.into());
Ok(Visual {
url,
width,
height,
content_type,
})
}
}