|
|
@ -1,5 +1,5 @@ |
|
|
|
use chrono::{DateTime, Utc}; |
|
|
|
use serde::Deserialize; |
|
|
|
use serde::{Deserialize, Deserializer}; |
|
|
|
|
|
|
|
pub fn cohost_posts_api_url(project: impl AsRef<str>, page: u64) -> String { |
|
|
|
format!( |
|
|
@ -11,7 +11,7 @@ pub fn cohost_posts_api_url(project: impl AsRef<str>, page: u64) -> String { |
|
|
|
|
|
|
|
// Cohost doesn't give us Next links ("rel: next") for further pages, so we'll have to ALWAYS populate the rel=next field
|
|
|
|
|
|
|
|
#[derive(Deserialize)] |
|
|
|
#[derive(Debug, Deserialize)] |
|
|
|
pub struct CohostPostsPage { |
|
|
|
#[serde(rename = "nItems")] |
|
|
|
pub number_items: usize, |
|
|
@ -22,19 +22,27 @@ pub struct CohostPostsPage { |
|
|
|
pub links: Vec<CohostPostLink>, |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Deserialize)] |
|
|
|
#[derive(Debug, Deserialize)] |
|
|
|
pub struct CohostPost { |
|
|
|
#[serde(rename = "postId")] |
|
|
|
pub id: u64, |
|
|
|
#[serde(default)] |
|
|
|
#[serde(deserialize_with = "deserialize_null_default", default)] |
|
|
|
pub headline: String, |
|
|
|
#[serde(rename = "publishedAt")] |
|
|
|
pub published_at: DateTime<Utc>, |
|
|
|
pub cws: Vec<String>, |
|
|
|
pub tags: Vec<String>, |
|
|
|
#[serde(rename = "plainTextBody", default)] |
|
|
|
#[serde(
|
|
|
|
rename = "plainTextBody", |
|
|
|
deserialize_with = "deserialize_null_default", |
|
|
|
default |
|
|
|
)] |
|
|
|
pub plain_body: String, |
|
|
|
#[serde(rename = "singlePostPageUrl")] |
|
|
|
#[serde(
|
|
|
|
rename = "singlePostPageUrl", |
|
|
|
deserialize_with = "deserialize_null_default", |
|
|
|
default |
|
|
|
)] |
|
|
|
pub url: String, |
|
|
|
#[serde(rename = "postingProject")] |
|
|
|
pub poster: CohostPostingProject, |
|
|
@ -42,31 +50,49 @@ pub struct CohostPost { |
|
|
|
pub share_tree: Vec<CohostPost>, |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Deserialize)] |
|
|
|
#[derive(Debug, Deserialize)] |
|
|
|
pub struct CohostPostingProject { |
|
|
|
#[serde(rename = "projectId")] |
|
|
|
pub id: u64, |
|
|
|
#[serde(deserialize_with = "deserialize_null_default", default)] |
|
|
|
pub handle: String, |
|
|
|
#[serde(rename = "displayName", default)] |
|
|
|
#[serde(
|
|
|
|
rename = "displayName", |
|
|
|
deserialize_with = "deserialize_null_default", |
|
|
|
default |
|
|
|
)] |
|
|
|
pub display_name: String, |
|
|
|
#[serde(default)] |
|
|
|
#[serde(deserialize_with = "deserialize_null_default", default)] |
|
|
|
pub dek: String, |
|
|
|
#[serde(default)] |
|
|
|
#[serde(deserialize_with = "deserialize_null_default", default)] |
|
|
|
pub description: String, |
|
|
|
#[serde(default)] |
|
|
|
#[serde(deserialize_with = "deserialize_null_default", default)] |
|
|
|
pub pronouns: String, |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Deserialize)] |
|
|
|
#[derive(Debug, Deserialize)] |
|
|
|
pub struct CohostPostLink { |
|
|
|
#[serde(default)] |
|
|
|
#[serde(deserialize_with = "deserialize_null_default", default)] |
|
|
|
pub href: String, |
|
|
|
#[serde(default)] |
|
|
|
#[serde(deserialize_with = "deserialize_null_default", default)] |
|
|
|
pub rel: String, |
|
|
|
#[serde(rename = "type", default)] |
|
|
|
#[serde(
|
|
|
|
rename = "type", |
|
|
|
deserialize_with = "deserialize_null_default", |
|
|
|
default |
|
|
|
)] |
|
|
|
pub t_type: String, |
|
|
|
} |
|
|
|
|
|
|
|
fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error> |
|
|
|
where |
|
|
|
T: Default + Deserialize<'de>, |
|
|
|
D: Deserializer<'de>, |
|
|
|
{ |
|
|
|
let opt = Option::deserialize(deserializer)?; |
|
|
|
Ok(opt.unwrap_or_default()) |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn test_deserialize() -> Result<(), Box<dyn std::error::Error>> { |
|
|
|
let post_page_json = include_str!("../samples/cohost/api/v1/project_posts.json"); |
|
|
@ -77,3 +103,19 @@ fn test_deserialize() -> Result<(), Box<dyn std::error::Error>> { |
|
|
|
assert_eq!(post.poster.id, 32693); |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn test_deserialize_weird() -> Result<(), Box<dyn std::error::Error>> { |
|
|
|
let post_page_json = include_str!("../samples/cohost/api/v1/vogon_pathological.json"); |
|
|
|
let _post_page_actual: CohostPostsPage = serde_json::from_str(post_page_json)?; |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn test_deserialize_empty() -> Result<(), Box<dyn std::error::Error>> { |
|
|
|
let post_page_json = include_str!("../samples/cohost/api/v1/empty_posts_age.json"); |
|
|
|
let post_page_actual: CohostPostsPage = serde_json::from_str(post_page_json)?; |
|
|
|
println!("{:?}", post_page_actual); |
|
|
|
assert!(post_page_actual.items.is_empty()); |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|