corobel/src/cohost_posts.rs

122 lines
3.7 KiB
Rust
Raw Normal View History

2022-11-01 01:57:20 +00:00
use chrono::{DateTime, Utc};
2022-11-01 19:50:04 +00:00
use serde::{Deserialize, Deserializer};
2022-11-01 01:57:20 +00:00
pub fn cohost_posts_api_url(project: impl AsRef<str>, 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
2022-11-02 19:11:40 +00:00
#[derive(Debug, Clone, Deserialize)]
2022-11-01 01:57:20 +00:00
pub struct CohostPostsPage {
#[serde(rename = "nItems")]
pub number_items: usize,
#[serde(rename = "nPages")]
pub number_pages: u64,
pub items: Vec<CohostPost>,
#[serde(rename = "_links")]
pub links: Vec<CohostPostLink>,
}
2022-11-02 19:11:40 +00:00
#[derive(Debug, Clone, Deserialize)]
2022-11-01 01:57:20 +00:00
pub struct CohostPost {
#[serde(rename = "postId")]
pub id: u64,
2022-11-01 19:50:04 +00:00
#[serde(deserialize_with = "deserialize_null_default", default)]
2022-11-01 01:57:20 +00:00
pub headline: String,
#[serde(rename = "publishedAt")]
pub published_at: DateTime<Utc>,
pub cws: Vec<String>,
pub tags: Vec<String>,
2022-11-01 19:50:04 +00:00
#[serde(
rename = "plainTextBody",
deserialize_with = "deserialize_null_default",
default
)]
2022-11-01 01:57:20 +00:00
pub plain_body: String,
2022-11-01 19:50:04 +00:00
#[serde(
rename = "singlePostPageUrl",
deserialize_with = "deserialize_null_default",
default
)]
2022-11-01 01:57:20 +00:00
pub url: String,
#[serde(rename = "postingProject")]
pub poster: CohostPostingProject,
#[serde(rename = "shareTree")]
pub share_tree: Vec<CohostPost>,
}
2022-11-02 19:11:40 +00:00
#[derive(Debug, Clone, Deserialize)]
2022-11-01 01:57:20 +00:00
pub struct CohostPostingProject {
#[serde(rename = "projectId")]
pub id: u64,
2022-11-01 19:50:04 +00:00
#[serde(deserialize_with = "deserialize_null_default", default)]
2022-11-01 01:57:20 +00:00
pub handle: String,
2022-11-01 19:50:04 +00:00
#[serde(
rename = "displayName",
deserialize_with = "deserialize_null_default",
default
)]
2022-11-01 01:57:20 +00:00
pub display_name: String,
2022-11-01 19:50:04 +00:00
#[serde(deserialize_with = "deserialize_null_default", default)]
2022-11-01 01:57:20 +00:00
pub dek: String,
2022-11-01 19:50:04 +00:00
#[serde(deserialize_with = "deserialize_null_default", default)]
2022-11-01 01:57:20 +00:00
pub description: String,
2022-11-01 19:50:04 +00:00
#[serde(deserialize_with = "deserialize_null_default", default)]
2022-11-01 01:57:20 +00:00
pub pronouns: String,
}
2022-11-02 19:11:40 +00:00
#[derive(Debug, Clone, Deserialize)]
2022-11-01 01:57:20 +00:00
pub struct CohostPostLink {
2022-11-01 19:50:04 +00:00
#[serde(deserialize_with = "deserialize_null_default", default)]
2022-11-01 01:57:20 +00:00
pub href: String,
2022-11-01 19:50:04 +00:00
#[serde(deserialize_with = "deserialize_null_default", default)]
2022-11-01 01:57:20 +00:00
pub rel: String,
2022-11-01 19:50:04 +00:00
#[serde(
rename = "type",
deserialize_with = "deserialize_null_default",
default
)]
2022-11-01 01:57:20 +00:00
pub t_type: String,
}
2022-11-01 19:50:04 +00:00
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())
}
2022-11-01 01:57:20 +00:00
#[test]
fn test_deserialize() -> Result<(), Box<dyn std::error::Error>> {
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(())
}
2022-11-01 19:50:04 +00:00
#[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(())
}