chiark / gitweb /
Tolerate hidden number of followers in Account (redux) wip.followers
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 2 Feb 2024 23:40:26 +0000 (23:40 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 2 Feb 2024 23:49:41 +0000 (23:49 +0000)
Introduce a bespoke type which allows (just) -1.

src/text.rs
src/types.rs

index 25b8b8705ec607b73c506e4760723431fb9ceca7..be1ab9ec66a3d92b41b7e84c266d53343470f5e2 100644 (file)
@@ -3,7 +3,6 @@ use chrono::NaiveDateTime;
 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};
@@ -3028,13 +3027,9 @@ impl ExamineUserDisplay {
         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),
         ));
index 56f5532584b4ff3ce410707307e89b1bdeb5b0a9..e3b653a1138a1b77ecd987dba2e735c34ac4f9c8 100644 (file)
@@ -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<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:
@@ -123,7 +189,7 @@ pub struct Account {
     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