Skip to main content

better_fetch/
json_parser.rs

1use std::sync::Arc;
2
3use bytes::Bytes;
4use http::StatusCode;
5use serde::de::DeserializeOwned;
6
7use crate::error::Error;
8use crate::Result;
9
10/// Parses response bytes into JSON before deserializing to `T`.
11pub type JsonParserFn =
12    Arc<dyn Fn(&Bytes) -> std::result::Result<serde_json::Value, String> + Send + Sync>;
13
14/// Wraps a custom JSON parse function for use with [`ClientBuilder::json_parser`](crate::client::ClientBuilder::json_parser).
15pub fn json_parser<F>(f: F) -> JsonParserFn
16where
17    F: Fn(&Bytes) -> std::result::Result<serde_json::Value, String> + Send + Sync + 'static,
18{
19    Arc::new(f)
20}
21
22/// Default parser using `serde_json::from_slice`.
23pub fn serde_json_parser() -> JsonParserFn {
24    json_parser(|body| serde_json::from_slice(body).map_err(|e| e.to_string()))
25}
26
27pub(crate) fn deserialize<T: DeserializeOwned>(
28    body: &Bytes,
29    status: StatusCode,
30    parser: Option<&JsonParserFn>,
31) -> Result<T> {
32    match parser {
33        None => serde_json::from_slice(body).map_err(|source| Error::Deserialize {
34            status,
35            message: source.to_string(),
36            body: Some(body.clone()),
37        }),
38        Some(parse) => {
39            let value = parse(body).map_err(|message| Error::Deserialize {
40                status,
41                message,
42                body: Some(body.clone()),
43            })?;
44            serde_json::from_value(value).map_err(|source| Error::Deserialize {
45                status,
46                message: source.to_string(),
47                body: Some(body.clone()),
48            })
49        }
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use serde::Deserialize;
57
58    #[derive(Debug, Deserialize, PartialEq)]
59    struct IdOnly {
60        id: u64,
61    }
62
63    fn strip_bom(body: &Bytes) -> std::result::Result<serde_json::Value, String> {
64        let slice = body.strip_prefix(b"\xef\xbb\xbf").unwrap_or(body);
65        serde_json::from_slice(slice).map_err(|e| e.to_string())
66    }
67
68    #[test]
69    fn fast_path_without_parser() {
70        let body = Bytes::from_static(br#"{"id":1}"#);
71        let parsed: IdOnly =
72            deserialize(&body, StatusCode::OK, None).expect("serde_json fast path");
73        assert_eq!(parsed, IdOnly { id: 1 });
74    }
75
76    #[test]
77    fn custom_parser_strips_bom() {
78        let body = Bytes::from_static(b"\xef\xbb\xbf{\"id\":2}");
79        let parser = json_parser(strip_bom);
80        let parsed: IdOnly =
81            deserialize(&body, StatusCode::OK, Some(&parser)).expect("custom parser");
82        assert_eq!(parsed, IdOnly { id: 2 });
83    }
84
85    #[test]
86    fn custom_parser_error_maps_to_deserialize() {
87        let body = Bytes::from_static(b"not-json");
88        let parser = json_parser(|_| Err("bad json".into()));
89        let err = deserialize::<IdOnly>(&body, StatusCode::OK, Some(&parser)).unwrap_err();
90        assert!(matches!(err, Error::Deserialize { message, .. } if message == "bad json"));
91    }
92}