chiark / gitweb /
client: Introduce api_request_parse and use it in 14 places
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 2 Feb 2024 14:19:36 +0000 (14:19 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 2 Feb 2024 22:56:16 +0000 (22:56 +0000)
src/client.rs

index e85008ebe145988c9843b4c483e28d1ff848bda5..b8d66cd95fa4255849683265c640339434b0b760 100644 (file)
@@ -1,5 +1,6 @@
 use chrono::{DateTime, Utc};
 use reqwest::Url;
+use serde::de::DeserializeOwned;
 use std::collections::{HashMap, HashSet, VecDeque};
 use std::fs::File;
 use std::io::{IoSlice, Read, Write};
@@ -657,21 +658,32 @@ impl Client {
         Ok((urlstr, rsp))
     }
 
+    /// Makes a request and deserialises the returned JSON
+    ///
+    /// Insists that the response is a success.
+    /// Returns the `T` and the URL.
+    fn api_request_parse<T: DeserializeOwned>(
+        &mut self,
+        req: Req,
+    ) -> Result<(T, String), ClientError> {
+        let (url, rsp) = self.api_request(req)?;
+        let rspstatus = rsp.status();
+        if !rspstatus.is_success() {
+            return Err(ClientError::from_response(&url, rsp));
+        }
+        let t: T = serde_json::from_str(&rsp.text()?)
+            .map_err(|e| ClientError::JSONParse(url.clone(), e.to_string()))?;
+        Ok((t, url))
+    }
+
     pub fn instance(&mut self) -> Result<Instance, ClientError> {
         if let Some(ref inst) = self.instance {
             return Ok(inst.clone());
         }
 
-        let (url, rsp) = self.api_request(Req::get("api/v2/instance"))?;
-        let rspstatus = rsp.status();
-        let inst: Instance = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(ac) => Ok(ac),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }
-        }?;
+        let inst: Instance =
+            self.api_request_parse(Req::get("api/v2/instance"))?.0;
+
         self.instance = Some(inst.clone());
         Ok(inst)
     }
@@ -738,18 +750,7 @@ impl Client {
         } else {
             Req::get(&format!("api/v1/accounts/{}", id.unwrap()))
         };
-        let (url, rsp) = self.api_request(req)?;
-        let rspstatus = rsp.status();
-        let ac: Account = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(ac) => Ok(ac),
-                Err(e) => {
-                    Err(ClientError::JSONParse(url.clone(), e.to_string()))
-                }
-            }
-        }?;
+        let (ac, url) = self.api_request_parse::<Account>(req)?;
         if id.is_some_and(|id| ac.id != id) {
             return Err(ClientError::UrlConsistency(
                 url,
@@ -775,19 +776,8 @@ impl Client {
             return Ok(st.clone());
         }
 
-        let (url, rsp) =
-            self.api_request(Req::get(&("api/v1/polls/".to_owned() + id)))?;
-        let rspstatus = rsp.status();
-        let poll: Poll = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(poll) => Ok(poll),
-                Err(e) => {
-                    Err(ClientError::JSONParse(url.clone(), e.to_string()))
-                }
-            }
-        }?;
+        let req = Req::get(&("api/v1/polls/".to_owned() + id));
+        let (poll, url) = self.api_request_parse::<Poll>(req)?;
         if poll.id != id {
             return Err(ClientError::UrlConsistency(
                 url,
@@ -802,20 +792,8 @@ impl Client {
         &mut self,
         id: &str,
     ) -> Result<Relationship, ClientError> {
-        let (url, rsp) = self.api_request(
-            Req::get("api/v1/accounts/relationships").param("id", id),
-        )?;
-        let rspstatus = rsp.status();
-        let rels: Vec<Relationship> = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(ac) => Ok(ac),
-                Err(e) => {
-                    Err(ClientError::JSONParse(url.clone(), e.to_string()))
-                }
-            }
-        }?;
+       let req = Req::get("api/v1/accounts/relationships").param("id", id);
+        let (rels, url) = self.api_request_parse::<Vec<Relationship>>(req)?;
         for rel in rels {
             if rel.id == id {
                 return Ok(rel);
@@ -850,19 +828,8 @@ impl Client {
             return Ok(st);
         }
 
-        let (url, rsp) =
-            self.api_request(Req::get(&("api/v1/statuses/".to_owned() + id)))?;
-        let rspstatus = rsp.status();
-        let st: Status = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(st) => Ok(st),
-                Err(e) => {
-                    Err(ClientError::JSONParse(url.clone(), e.to_string()))
-                }
-            }
-        }?;
+        let req = Req::get(&("api/v1/statuses/".to_owned() + id));
+        let (st, url) = self.api_request_parse::<Status>(req)?;
         if st.id != id {
             return Err(ClientError::UrlConsistency(
                 url,
@@ -893,20 +860,8 @@ impl Client {
             return Ok(not);
         }
 
-        let (url, rsp) = self.api_request(Req::get(
-            &("api/v1/notifications/".to_owned() + id),
-        ))?;
-        let rspstatus = rsp.status();
-        let not: Notification = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(st) => Ok(st),
-                Err(e) => {
-                    Err(ClientError::JSONParse(url.clone(), e.to_string()))
-                }
-            }
-        }?;
+        let req = Req::get(&("api/v1/notifications/".to_owned() + id));
+        let (not, url) = self.api_request_parse::<Notification>(req)?;
         if not.id != id {
             return Err(ClientError::UrlConsistency(
                 url,
@@ -1379,18 +1334,8 @@ impl Client {
         &mut self,
         name: &str,
     ) -> Result<Account, ClientError> {
-        let (url, rsp) = self.api_request(
-            Req::get("api/v1/accounts/lookup").param("acct", name),
-        )?;
-        let rspstatus = rsp.status();
-        let ac: Account = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(ac) => Ok(ac),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }
-        }?;
+        let req = Req::get("api/v1/accounts/lookup").param("acct", name);
+        let ac = self.api_request_parse::<Account>(req)?.0;
         self.cache_account(&ac);
         Ok(ac)
     }
@@ -1426,18 +1371,9 @@ impl Client {
         id: &str,
         verb: &str,
     ) -> Result<(), ClientError> {
-        let (url, rsp) = self
-            .api_request(Req::post(&format!("api/v1/statuses/{id}/{verb}")))?;
-        let rspstatus = rsp.status();
+        let req = Req::post(&format!("api/v1/statuses/{id}/{verb}"));
+        let st = self.api_request_parse::<Status>(req)?.0;
         // Cache the returned status so as to update its faved/boosted flags
-        let st: Status = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(st) => Ok(st),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }
-        }?;
         self.cache_status(&st);
         Ok(())
     }
@@ -1464,17 +1400,8 @@ impl Client {
         &mut self,
         id: &str,
     ) -> Result<Context, ClientError> {
-        let (url, rsp) = self
-            .api_request(Req::get(&format!("api/v1/statuses/{id}/context")))?;
-        let rspstatus = rsp.status();
-        let ctx: Context = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(st) => Ok(st),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }
-        }?;
+        let req = Req::get(&format!("api/v1/statuses/{id}/context"));
+        let ctx = self.api_request_parse::<Context>(req)?.0;
         for st in &ctx.ancestors {
             self.cache_status(st);
         }
@@ -1494,17 +1421,8 @@ impl Client {
         for choice in choices {
             req = req.param("choices[]", choice);
         }
-        let (url, rsp) = self.api_request(req)?;
-        let rspstatus = rsp.status();
+        let poll = self.api_request_parse::<Poll>(req)?.0;
         // Cache the returned poll so as to update its faved/boosted flags
-        let poll: Poll = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(poll) => Ok(poll),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }
-        }?;
         self.cache_poll(&poll);
         Ok(())
     }
@@ -1588,18 +1506,7 @@ impl Client {
             req
         };
 
-        let (url, rsp) = self.api_request(req)?;
-        let rspstatus = rsp.status();
-        let ac: Account = if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            match serde_json::from_str(&rsp.text()?) {
-                Ok(ac) => Ok(ac),
-                Err(e) => {
-                    Err(ClientError::JSONParse(url.clone(), e.to_string()))
-                }
-            }
-        }?;
+        let (ac, url) = self.api_request_parse::<Account>(req)?;
         if ac.id != id {
             return Err(ClientError::UrlConsistency(
                 url,
@@ -1641,17 +1548,8 @@ impl Client {
             .param("client_name", "Mastodonochrome")
             .param("scopes", "read write push")
             .param("website", WEBSITE);
-        let (url, rsp) = self.api_request(req)?;
-        let rspstatus = rsp.status();
-        if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            let app: Application = match serde_json::from_str(&rsp.text()?) {
-                Ok(app) => Ok(app),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }?;
-            Ok(app)
-        }
+        let app = self.api_request_parse::<Application>(req)?.0;
+        Ok(app)
     }
 
     pub fn get_app_token(
@@ -1691,34 +1589,16 @@ impl Client {
                 .param("code", code)
                 .param("scope", "read write push"),
         };
-        let (url, rsp) = self.api_request(req)?;
-        let rspstatus = rsp.status();
-        if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            let tok: Token = match serde_json::from_str(&rsp.text()?) {
-                Ok(tok) => Ok(tok),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }?;
-            Ok(tok)
-        }
+        let tok = self.api_request_parse::<Token>(req)?.0;
+        Ok(tok)
     }
 
     pub fn verify_app_credentials(
         &mut self,
     ) -> Result<Application, ClientError> {
         let req = Req::get("api/v1/apps/verify_credentials");
-        let (url, rsp) = self.api_request(req)?;
-        let rspstatus = rsp.status();
-        if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            let app: Application = match serde_json::from_str(&rsp.text()?) {
-                Ok(app) => Ok(app),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }?;
-            Ok(app)
-        }
+        let app = self.api_request_parse::<Application>(req)?.0;
+        Ok(app)
     }
 
     pub fn get_auth_url(
@@ -1755,17 +1635,8 @@ impl Client {
             .param("password", password)
             .param("agreement", true) // the calling UI should have confirmed
             .param("locale", language);
-        let (url, rsp) = self.api_request(req)?;
-        let rspstatus = rsp.status();
-        if !rspstatus.is_success() {
-            Err(ClientError::from_response(&url, rsp))
-        } else {
-            let tok: Token = match serde_json::from_str(&rsp.text()?) {
-                Ok(tok) => Ok(tok),
-                Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
-            }?;
-            Ok(tok)
-        }
+        let tok = self.api_request_parse::<Token>(req)?.0;
+        Ok(tok)
     }
 
     pub fn register_confirmation(