use chrono::{DateTime, Local, Utc};
use core::cmp::{max, min};
use std::collections::{BTreeMap, HashSet};
-use std::fmt::Display;
use unicode_width::UnicodeWidthStr;
use super::client::{Client, ClientError};
let post_count = Paragraph::new().add(ColouredString::plain(
&format!("Number of posts: {}", ac.statuses_count),
));
- let followers_count = Paragraph::new().add(ColouredString::plain(&{
- let followers_count = u64::try_from(ac.followers_count);
- format!("Number of followers: {}", match &followers_count {
- Ok(y) => y as &dyn Display,
- Err(_) => &"(hidden)",
- })
- }));
+ let followers_count = Paragraph::new().add(ColouredString::plain(
+ &format!("Number of followers: {}", ac.followers_count),
+ ));
let following_count = Paragraph::new().add(ColouredString::plain(
&format!("Number of users followed: {}", ac.following_count),
));
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;
pub verified_at: Option<DateTime<Utc>>,
}
+/// Value that came from server (as JSON) that was supposedly a u64
+///
+/// Apparently some servers sometimes send negative numbers for these,
+/// meaning unavailable:
+///
+/// <https://glitch-soc.github.io/docs/features/hide-follower-count/>
+//
+// 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<u64> since that's two words and this is one
+pub struct FollowersCount(i64);
+
+impl TryFrom<i64> for FollowersCount {
+ type Error = String;
+ fn try_from(i: i64) -> Result<FollowersCount, Self::Error> {
+ 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::<S>(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:
pub created_at: ApproxDate,
pub last_status_at: Option<ApproxDate>,
pub statuses_count: u64,
- pub followers_count: i64,
+ pub followers_count: FollowersCount,
pub following_count: u64,
// In the wire protocol, 'CredentialAccount' is a subclass of