use serde::de::DeserializeOwned;
use serde_json::Value;
use snafu::ResultExt;
use crate::error::{Error, Result, error};
use crate::transport::WireResponse;
pub fn json(response: &WireResponse) -> Result<Value> {
if !response.is_success() {
return Err(Error::ApiStatus {
status: response.status,
endpoint: response.endpoint.clone(),
body: String::from_utf8_lossy(&response.body).into_owned(),
});
}
if response.body.iter().all(u8::is_ascii_whitespace) {
return Ok(Value::Null);
}
serde_json::from_slice(&response.body).context(error::DecodeSnafu)
}
pub fn typed<T: DeserializeOwned>(value: Value) -> Result<T> {
serde_json::from_value(value).context(error::DecodeSnafu)
}
pub fn json_typed<T: DeserializeOwned>(response: &WireResponse) -> Result<T> {
typed(json(response)?)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use serde::Deserialize;
fn response(status: u16, body: &str) -> WireResponse {
WireResponse::new(
status,
"http://127.0.0.1:8081/v1/sessions".to_owned(),
Vec::new(),
body.as_bytes().to_vec(),
)
}
#[test]
fn an_error_body_is_surfaced_with_the_status() {
let err = json(&response(400, r#"{"error":"invalid cursor"}"#)).unwrap_err();
let rendered = err.to_string();
assert!(rendered.contains("400"), "got: {rendered}");
assert!(rendered.contains("invalid cursor"), "got: {rendered}");
assert!(rendered.contains("/v1/sessions"), "got: {rendered}");
}
#[test]
fn a_successful_empty_body_is_null_not_a_decode_failure() {
assert_eq!(json(&response(204, "")).unwrap(), Value::Null);
assert_eq!(json(&response(200, " \n ")).unwrap(), Value::Null);
}
#[test]
fn the_untyped_decode_passes_unknown_fields_through() {
let got: Value = json_typed(&response(
200,
r#"{"items":[{"a_field_from_the_future":7}]}"#,
))
.unwrap();
assert_eq!(got["items"][0]["a_field_from_the_future"], 7);
}
#[test]
fn a_typed_decode_reads_the_consumers_own_model() {
#[derive(Debug, Deserialize)]
struct Listing {
next_cursor: String,
}
let got: Listing =
json_typed(&response(200, r#"{"items":[],"next_cursor":"abc"}"#)).unwrap();
assert_eq!(got.next_cursor, "abc");
}
#[test]
fn a_body_that_is_not_json_is_a_decode_failure_and_not_a_status_one() {
let err = json(&response(200, "<html>nope</html>")).unwrap_err();
assert!(err.to_string().contains("could not decode"), "got: {err}",);
}
}