From: Ian Jackson Date: Fri, 2 Feb 2024 14:45:33 +0000 (+0000) Subject: Tolerate hidden number of followers in Account X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=90233cde591a539d95f5bddc53eb2b92d5282622;p=mastodonochrome.git Tolerate hidden number of followers in Account This can be -1. Display `(hidden)` when it's not available. --- diff --git a/src/types.rs b/src/types.rs index ccc8c03..e3b653a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,7 @@ use chrono::{DateTime, NaiveDate, Utc}; use serde::{Deserialize, Serialize}; use std::boxed::Box; +use std::fmt::{self, Display}; use std::option::Option; use super::coloured_string::ColouredString; @@ -12,6 +13,71 @@ pub struct AccountField { pub verified_at: Option>, } +/// Value that came from server (as JSON) that was supposedly a u64 +/// +/// Apparently some servers sometimes send negative numbers for these, +/// meaning unavailable: +/// +/// +// +// If it turns out that servers send other values eg "null" +// or absent fields, or that such toleration is needed for other +// informational fields from servers, we should make this type +// more relaxed and/or rename it +// (and we may need to apply #[serde(default)] to fields of this type). +#[derive(Deserialize, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +#[serde(try_from = "i64")] +// Not Option since that's two words and this is one +pub struct FollowersCount(i64); + +impl TryFrom for FollowersCount { + type Error = String; + fn try_from(i: i64) -> Result { + if i >= -1 { + Ok(FollowersCount(i)) + } else { + Err("unexpected nonnegative value, would tolerate -1".into()) + } + } +} + +impl Display for FollowersCount { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.0 >= 0 { + Display::fmt(&self.0, f) + } else if self.0 == -1 { + write!(f, "unavailable") + } else { + // shouldn't happen but let's not panic + write!(f, "unavailable ({})", self.0) + } + } +} + +#[test] +fn test_supposedly_non_negative() { + #[derive(Deserialize, Debug)] + struct S { + a: FollowersCount, + } + + let chk = |j, exp_0, exp_display| { + let s: S = serde_json::from_str(j).unwrap(); + assert_eq!(s.a.0, exp_0); + assert_eq!(s.a.to_string(), exp_display); + }; + let chk_err = |j| { + serde_json::from_str::(j).unwrap_err(); + }; + + chk(r#"{ "a": 0 }"#, 0, "0"); + chk(r#"{ "a": 1 }"#, 1, "1"); + chk(r#"{ "a": -1 }"#, -1, "unavailable"); + chk_err(r#"{}"#); + chk_err(r#"{ "a": -42 }"#); + chk_err(r#"{ "a": null }"#); +} + // Special type wrapping chrono::NaiveDate. This is for use in the // Account fields 'created_at' and 'last_status_at', which I've // observed do not in fact contain full sensible ISO 8601 timestamps: @@ -123,7 +189,7 @@ pub struct Account { pub created_at: ApproxDate, pub last_status_at: Option, pub statuses_count: u64, - pub followers_count: u64, + pub followers_count: FollowersCount, pub following_count: u64, // In the wire protocol, 'CredentialAccount' is a subclass of