use chrono::{DateTime, Utc}; use serde::Deserialize; pub fn cohost_posts_api_url(project: impl AsRef, page: u64) -> String { format!( "https://cohost.org/api/v1/project/{}/posts?page={}", project.as_ref(), page ) } // 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)] pub struct CohostPostsPage { #[serde(rename = "nItems")] pub number_items: usize, #[serde(rename = "nPages")] pub number_pages: u64, pub items: Vec, #[serde(rename = "_links")] pub links: Vec, } #[derive(Deserialize)] pub struct CohostPost { #[serde(rename = "postId")] pub id: u64, pub headline: String, #[serde(rename = "publishedAt")] pub published_at: DateTime, pub cws: Vec, pub tags: Vec, #[serde(rename = "plainTextBody")] pub plain_body: String, #[serde(rename = "singlePostPageUrl")] pub url: String, #[serde(rename = "postingProject")] pub poster: CohostPostingProject, #[serde(rename = "shareTree")] pub share_tree: Vec, } #[derive(Deserialize)] pub struct CohostPostingProject { #[serde(rename = "projectId")] pub id: u64, pub handle: String, #[serde(rename = "displayName")] pub display_name: String, pub dek: String, pub description: String, pub pronouns: String, } #[derive(Deserialize)] pub struct CohostPostLink { pub href: String, pub rel: String, #[serde(rename = "type")] pub t_type: String, } #[test] fn test_deserialize() -> Result<(), Box> { let post_page_json = include_str!("../samples/cohost/api/v1/project_posts.json"); let post_page_actual: CohostPostsPage = serde_json::from_str(post_page_json)?; assert_eq!(post_page_actual.number_items, post_page_actual.items.len()); let post = &post_page_actual.items[0]; assert_eq!(post.id, 149268); assert_eq!(post.poster.id, 32693); Ok(()) }