From 0d4e2bad8738927d861484eedb3bded42f7f0ec0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 24 Jan 2024 20:06:44 +0000 Subject: [PATCH] Use Mastodon's JSON error-documents. When a Mastodon API request returns an HTTP error status, it generally also delivers a JSON error-document containing a string. This is a much better error message than the plain HTTP code, because often it goes into more useful detail. For example, if you have an auth failure because you haven't confirmed the email address on your new account yet, the HTTP error code can't tell you anything about email address confirmation, but the error-document will. So we'll use those messages in preference, if they're available. --- TODO.md | 9 --------- src/client.rs | 31 ++++++++++++++++++++++++------- src/types.rs | 5 +++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/TODO.md b/TODO.md index eb1393f..eac263d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,3 @@ -# Bugs - -Server error reporting: HTTP error codes from Mastodon API requests -are generally accompanied by a JSON body containing a more useful -message. For example, if you prematurely try to start the full client -after an unconfirmed account registration, the error code just says -"403 Forbidden", but the message body explains that the email address -isn't confirmed yet, so that a user might actually know what to do. - # Missing features ## Verbose help diff --git a/src/client.rs b/src/client.rs index 8a9c9de..270add3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -141,6 +141,23 @@ impl From for ClientError { } } +impl ClientError { + fn from_response(url: &str, rsp: reqwest::blocking::Response) -> Self { + let rspstatus = rsp.status(); + let message = if let Ok(text) = rsp.text() { + if let Ok(err) = serde_json::from_str::(&text) { + err.error + } else { + rspstatus.to_string() + } + } else { + rspstatus.to_string() + }; + + ClientError::UrlError(url.to_owned(), message) + } +} + impl std::fmt::Display for ClientError { fn fmt( &self, @@ -571,7 +588,7 @@ impl Client { let (url, rsp) = self.api_request(Req::get("api/v2/instance"))?; let rspstatus = rsp.status(); let inst: Instance = if !rspstatus.is_success() { - Err(ClientError::UrlError(url, rspstatus.to_string())) + Err(ClientError::from_response(&url, rsp)) } else { match serde_json::from_str(&rsp.text()?) { Ok(ac) => Ok(ac), @@ -647,7 +664,7 @@ impl Client { let (url, rsp) = self.api_request(req)?; let rspstatus = rsp.status(); let ac: Account = if !rspstatus.is_success() { - Err(ClientError::UrlError(url.clone(), rspstatus.to_string())) + Err(ClientError::from_response(&url, rsp)) } else { match serde_json::from_str(&rsp.text()?) { Ok(ac) => Ok(ac), @@ -685,7 +702,7 @@ impl Client { self.api_request(Req::get(&("api/v1/polls/".to_owned() + id)))?; let rspstatus = rsp.status(); let poll: Poll = if !rspstatus.is_success() { - Err(ClientError::UrlError(url.clone(), rspstatus.to_string())) + Err(ClientError::from_response(&url, rsp)) } else { match serde_json::from_str(&rsp.text()?) { Ok(poll) => Ok(poll), @@ -713,7 +730,7 @@ impl Client { )?; let rspstatus = rsp.status(); let rels: Vec = if !rspstatus.is_success() { - Err(ClientError::UrlError(url.clone(), rspstatus.to_string())) + Err(ClientError::from_response(&url, rsp)) } else { match serde_json::from_str(&rsp.text()?) { Ok(ac) => Ok(ac), @@ -760,7 +777,7 @@ impl Client { self.api_request(Req::get(&("api/v1/statuses/".to_owned() + id)))?; let rspstatus = rsp.status(); let st: Status = if !rspstatus.is_success() { - Err(ClientError::UrlError(url.clone(), rspstatus.to_string())) + Err(ClientError::from_response(&url, rsp)) } else { match serde_json::from_str(&rsp.text()?) { Ok(st) => Ok(st), @@ -804,7 +821,7 @@ impl Client { ))?; let rspstatus = rsp.status(); let not: Notification = if !rspstatus.is_success() { - Err(ClientError::UrlError(url.clone(), rspstatus.to_string())) + Err(ClientError::from_response(&url, rsp)) } else { match serde_json::from_str(&rsp.text()?) { Ok(st) => Ok(st), @@ -1493,7 +1510,7 @@ impl Client { let (url, rsp) = self.api_request(req)?; let rspstatus = rsp.status(); let ac: Account = if !rspstatus.is_success() { - Err(ClientError::UrlError(url.clone(), rspstatus.to_string())) + Err(ClientError::from_response(&url, rsp)) } else { match serde_json::from_str(&rsp.text()?) { Ok(ac) => Ok(ac), diff --git a/src/types.rs b/src/types.rs index 3a4f009..ccc8c03 100644 --- a/src/types.rs +++ b/src/types.rs @@ -393,3 +393,8 @@ pub struct Context { pub ancestors: Vec, pub descendants: Vec, } + +#[derive(Deserialize, Debug, Clone)] +pub struct ServerError { + pub error: String, +} -- 2.30.2