use crate::{Error, ErrorKind, Result};
use serde::{de::Error as SerdeError, Deserialize};
use serde_json::Value;
use ureq::{http::Response, Body};
#[derive(Deserialize, Debug)]
pub struct Info {
pub sitename: String,
pub views: u64,
pub hits: u64,
pub created_at: String,
pub last_updated: Option<String>,
pub domain: Option<String>,
pub tags: Vec<String>,
pub latest_ipfs_hash: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct ListEntry {
pub path: String,
pub is_directory: bool,
pub updated_at: String,
pub size: Option<u64>,
pub sha1_hash: Option<String>,
}
#[allow(clippy::result_large_err)]
pub(crate) fn parse_response<T>(field: &'static str, mut res: Response<Body>) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
#[derive(Deserialize)]
#[serde(tag = "result")]
enum OuterResponse {
#[serde(rename = "success")]
Success,
#[serde(rename = "error")]
Error {
error_type: Option<String>,
message: Option<String>,
},
}
let status = res.status().as_u16();
let status_text = res
.status()
.canonical_reason()
.unwrap_or("Unknown")
.to_owned();
serde_json::from_reader::<_, Value>(res.body_mut().as_reader()) .map_err(Error::from)
.and_then(|json| {
let outer = serde_json::from_value::<OuterResponse>(json.clone())?;
match outer {
OuterResponse::Success => Ok(json), OuterResponse::Error {
error_type,
message,
} => Err(Error::Api {
kind: error_type
.unwrap_or_default()
.parse()
.unwrap_or(ErrorKind::Unknown),
message: message.unwrap_or("No error message provided".to_owned()),
}),
}
})
.and_then(|json| {
json.get(field)
.ok_or_else(|| serde_json::Error::missing_field(field))
.and_then(|v| serde_json::from_value::<T>(v.clone()))
.map_err(Error::from)
})
.map_err(|err| {
if matches!(err, Error::Json { .. }) && (400..=599).contains(&status) {
Error::Api {
kind: ErrorKind::Status,
message: format!("{} {}", status, status_text),
}
} else {
err
}
})
}
#[cfg(test)]
mod tests {
use super::*;
fn make_response(status: u16, body: &'static str) -> Response<Body> {
let body = Body::builder().mime_type("application/json").data(body);
ureq::http::Response::builder()
.status(status)
.body(body)
.unwrap()
}
#[test]
fn parse_success() {
#[derive(Deserialize)]
struct Foobar {
foo: String,
bar: String,
}
let res = make_response(
200,
r#"
{
"result": "success",
"foobar": {
"foo": "qux",
"bar": "baz"
},
"we": ["don't", "care", "about", "other", "fields"]
}
"#,
);
let foo = parse_response::<Foobar>("foobar", res).unwrap();
assert_eq!(foo.foo, "qux");
assert_eq!(foo.bar, "baz");
}
#[test]
fn parse_error() {
let res = make_response(
401,
r#"
{
"result": "error",
"error_type": "invalid_auth",
"message": "Invalid API key"
}
"#,
);
let err = parse_response::<String>("foobar", res).unwrap_err();
assert!(matches!(
err,
Error::Api {
kind: ErrorKind::InvalidAuth,
..
}
));
}
#[test]
fn parse_invalid_json() {
let res = make_response(200, "not json");
let err = parse_response::<String>("foobar", res).unwrap_err();
assert!(matches!(err, Error::Json { .. }));
}
#[test]
fn parse_invalid_json_error() {
let res = make_response(401, "not json");
let err = parse_response::<String>("foobar", res).unwrap_err();
let Error::Api { message, kind } = err else {
panic!("Expected an Error::Api {{ .. }}, got {:?}", err);
};
assert_eq!(kind, ErrorKind::Status);
assert_eq!(message, "401 Unauthorized");
}
}