#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ClientError {
- InternalError(String), // message
- UrlParseError(String, String), // url, message
- UrlError(String, String), // url, message
- NoAccountSource,
+ Internal(String), // message
+ UrlParse(String, String), // url, parsing error message
+ UrlFetchNet(String, String), // url, error message
+ UrlFetchHTTP(String, reqwest::StatusCode), // url, status
+
+ // url, status, error text from the JSON error document
+ UrlFetchHTTPRich(String, reqwest::StatusCode, String),
+
+ LinkParse(String, String), // url, parsing error message
+ JSONParse(String, String), // url, parsing error message
+ UrlConsistency(String, String), // url, error message
+ Consistency(String), // just error message
}
impl super::TopLevelErrorCandidate for ClientError {}
fn from(err: reqwest::Error) -> Self {
match err.url() {
Some(url) => {
- ClientError::UrlError(url.to_string(), err.to_string())
+ ClientError::UrlFetchNet(url.to_string(), err.to_string())
}
- None => ClientError::InternalError(err.to_string()),
+ None => ClientError::Internal(err.to_string()),
}
}
}
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(text) = rsp.text() {
if let Ok(err) = serde_json::from_str::<ServerError>(&text) {
- err.error
+ ClientError::UrlFetchHTTPRich(
+ url.to_owned(),
+ rspstatus,
+ err.error,
+ )
} else {
- rspstatus.to_string()
+ ClientError::UrlFetchHTTP(url.to_owned(), rspstatus)
}
} else {
- rspstatus.to_string()
- };
-
- ClientError::UrlError(url.to_owned(), message)
+ ClientError::UrlFetchHTTP(url.to_owned(), rspstatus)
+ }
}
}
f: &mut std::fmt::Formatter<'_>,
) -> Result<(), std::fmt::Error> {
match self {
- ClientError::InternalError(ref msg) => {
+ ClientError::Internal(ref msg) => {
write!(f, "internal failure: {}", msg)
}
- ClientError::UrlParseError(ref url, ref msg) => {
+ ClientError::UrlParse(ref url, ref msg) => {
write!(f, "Parse failure {} (retrieving URL: {})", msg, url)
}
- ClientError::UrlError(ref url, ref msg) => {
+ ClientError::UrlFetchNet(ref url, ref msg) => {
write!(f, "{} (retrieving URL: {})", msg, url)
}
- ClientError::NoAccountSource => write!(
- f,
- "server did not send 'source' details for our account"
- ),
+ ClientError::UrlFetchHTTP(ref url, status) => {
+ write!(f, "{} (retrieving URL: {})", status.to_string(), url)
+ }
+ ClientError::UrlFetchHTTPRich(ref url, status, ref msg) => {
+ write!(
+ f,
+ "{} (HTTP status {}; retrieving URL: {})",
+ msg,
+ status.to_string(),
+ url
+ )
+ }
+ ClientError::JSONParse(ref url, ref msg) => {
+ write!(f, "{} (parsing JSON returned from URL: {})", msg, url)
+ }
+ ClientError::LinkParse(ref url, ref msg) => {
+ write!(
+ f,
+ "{} (parsing Link header returned from URL: {})",
+ msg, url
+ )
+ }
+ ClientError::UrlConsistency(ref url, ref msg) => {
+ write!(f, "{} (after fetching URL: {})", msg, url)
+ }
+ ClientError::Consistency(ref msg) => {
+ write!(f, "{}", msg)
+ }
}
}
}
let url = match parsed {
Ok(url) => Ok(url),
Err(e) => {
- Err(ClientError::UrlParseError(urlstr.clone(), e.to_string()))
+ Err(ClientError::UrlParse(urlstr.clone(), e.to_string()))
}
}?;
Ok((urlstr, url))
req: Req,
) -> Result<(String, reqwest::blocking::RequestBuilder), ClientError> {
if req.method != reqwest::Method::GET && !self.permit_write {
- return Err(ClientError::InternalError(
+ return Err(ClientError::Internal(
"Non-GET request attempted in readonly mode".to_string(),
));
}
} else {
match serde_json::from_str(&rsp.text()?) {
Ok(ac) => Ok(ac),
- Err(e) => Err(ClientError::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}
}?;
self.instance = Some(inst.clone());
match serde_json::from_str(&rsp.text()?) {
Ok(ac) => Ok(ac),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::JSONParse(url.clone(), e.to_string()))
}
}
}?;
if id.is_some_and(|id| ac.id != id) {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
format!("request returned wrong account id {}", &ac.id),
));
match serde_json::from_str(&rsp.text()?) {
Ok(poll) => Ok(poll),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::JSONParse(url.clone(), e.to_string()))
}
}
}?;
if poll.id != id {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
format!("request returned wrong poll id {}", &poll.id),
));
match serde_json::from_str(&rsp.text()?) {
Ok(ac) => Ok(ac),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::JSONParse(url.clone(), e.to_string()))
}
}
}?;
return Ok(rel);
}
}
- Err(ClientError::UrlError(
+ Err(ClientError::UrlConsistency(
url,
format!("request did not return expected account id {}", id),
))
match serde_json::from_str(&rsp.text()?) {
Ok(st) => Ok(st),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::JSONParse(url.clone(), e.to_string()))
}
}
}?;
if st.id != id {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
format!("request returned wrong status id {}", &st.id),
));
match serde_json::from_str(&rsp.text()?) {
Ok(st) => Ok(st),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::JSONParse(url.clone(), e.to_string()))
}
}
}?;
if not.id != id {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
format!("request returned wrong notification id {}", ¬.id),
));
let (url, rsp) = self.api_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- return Err(ClientError::UrlError(url, rspstatus.to_string()));
+ return Err(ClientError::from_response(&url, rsp));
}
// Keep the Link: headers after we consume the response, for
let sts: Vec<Status> = match serde_json::from_str(&body) {
Ok(sts) => Ok(sts),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::JSONParse(url.clone(), e.to_string()))
}
}?;
for st in &sts {
let mut nots: Vec<Notification> =
match serde_json::from_str(&body) {
Ok(nots) => Ok(nots),
- Err(e) => Err(ClientError::UrlError(
+ Err(e) => Err(ClientError::JSONParse(
url.clone(),
e.to_string(),
)),
let acs: Vec<Account> = match serde_json::from_str(&body) {
Ok(acs) => Ok(acs),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::JSONParse(url.clone(), e.to_string()))
}
}?;
for ac in &acs {
let linkhdr_str = match linkhdr.to_str() {
Ok(s) => Ok(s),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::LinkParse(url.clone(), e.to_string()))
}
}?;
let links = match parse_link_header::parse(linkhdr_str) {
Ok(links) => Ok(links),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::LinkParse(url.clone(), e.to_string()))
}
}?;
for (rel, link) in links {
match rsp.headers().get(reqwest::header::LOCATION) {
None => {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
"received redirection without a Location header"
.to_owned(),
let sval = match std::str::from_utf8(bval) {
Ok(s) => s,
Err(_) => {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
"HTTP redirect URL was invalid UTF-8"
.to_owned(),
let newurl = match rsp.url().join(sval) {
Ok(u) => u,
Err(e) => {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
format!("processing redirection: {}", e),
))
}
};
if !ok {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
format!("redirection to suspicious URL {}", sval),
));
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- return Err(ClientError::UrlError(url, rspstatus.to_string()));
+ return Err(ClientError::from_response(&url, rsp));
}
let id = id.clone();
)?;
let rspstatus = rsp.status();
let ac: Account = 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),
- Err(e) => Err(ClientError::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}
}?;
self.cache_account(&ac);
let (url, rsp) = self.api_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- Err(ClientError::UrlError(url, rspstatus.to_string()))
+ Err(ClientError::from_response(&url, rsp))
} else {
Ok(())
}
let rspstatus = rsp.status();
// Cache the returned status so as to update its faved/boosted flags
let st: Status = 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(st) => Ok(st),
- Err(e) => Err(ClientError::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}
}?;
self.cache_status(&st);
.api_request(Req::get(&format!("api/v1/statuses/{id}/context")))?;
let rspstatus = rsp.status();
let ctx: Context = 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(st) => Ok(st),
- Err(e) => Err(ClientError::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}
}?;
for st in &ctx.ancestors {
let rspstatus = rsp.status();
// Cache the returned poll so as to update its faved/boosted flags
let poll: Poll = 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(poll) => Ok(poll),
- Err(e) => Err(ClientError::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}
}?;
self.cache_poll(&poll);
let (url, rsp) = self.api_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- Err(ClientError::UrlError(url, rspstatus.to_string()))
+ Err(ClientError::from_response(&url, rsp))
} else {
Ok(())
}
let (url, rsp) = self.api_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- Err(ClientError::UrlError(url, rspstatus.to_string()))
+ Err(ClientError::from_response(&url, rsp))
} else {
Ok(())
}
match serde_json::from_str(&rsp.text()?) {
Ok(ac) => Ok(ac),
Err(e) => {
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+ Err(ClientError::JSONParse(url.clone(), e.to_string()))
}
}
}?;
if ac.id != id {
- return Err(ClientError::UrlError(
+ return Err(ClientError::UrlConsistency(
url,
format!("request returned wrong account id {}", &ac.id),
));
let (url, rsp) = self.api_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- Err(ClientError::UrlError(url, rspstatus.to_string()))
+ 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::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}?;
Ok(app)
}
) -> Result<Token, ClientError> {
let client_id = match &app.client_id {
Some(id) => Ok(id),
- None => Err(ClientError::InternalError(
+ None => Err(ClientError::Internal(
"registering application did not return a client id"
.to_owned(),
)),
}?;
let client_secret = match &app.client_secret {
Some(id) => Ok(id),
- None => Err(ClientError::InternalError(
+ None => Err(ClientError::Internal(
"registering application did not return a client secret"
.to_owned(),
)),
let (url, rsp) = self.api_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- Err(ClientError::UrlError(url, rspstatus.to_string()))
+ 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::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}?;
Ok(tok)
}
let (url, rsp) = self.api_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- Err(ClientError::UrlError(url, rspstatus.to_string()))
+ 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::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}?;
Ok(app)
}
) -> Result<String, ClientError> {
let client_id = match &app.client_id {
Some(id) => Ok(id),
- None => Err(ClientError::InternalError(
+ None => Err(ClientError::Internal(
"registering application did not return a client id"
.to_owned(),
)),
let (url, rsp) = self.api_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
- Err(ClientError::UrlError(url, rspstatus.to_string()))
+ 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::UrlError(url, e.to_string())),
+ Err(e) => Err(ClientError::JSONParse(url, e.to_string())),
}?;
Ok(tok)
}
) -> Result<(), ClientError> {
let url = match Url::parse(urlstr) {
Ok(url) => Ok(url),
- Err(e) => Err(ClientError::UrlParseError(
- urlstr.to_owned(),
- e.to_string(),
- )),
+ Err(e) => {
+ Err(ClientError::UrlParse(urlstr.to_owned(), e.to_string()))
+ }
}?;
let req = self.client.request(reqwest::Method::GET, url).build()?;
let (rsp, log) = execute_and_log_request(&self.client, req)?;
if rspstatus.is_redirection() || rspstatus.is_success() {
Ok(())
} else {
- Err(ClientError::UrlError(
- urlstr.to_owned(),
- rspstatus.to_string(),
- ))
+ Err(ClientError::from_response(urlstr, rsp))
}
}
}