better_fetch/
json_parser.rs1use std::sync::Arc;
25
26use bytes::Bytes;
27use http::StatusCode;
28use serde::de::DeserializeOwned;
29
30use crate::error::Error;
31use crate::Result;
32
33pub type JsonParserFn =
38 Arc<dyn Fn(&Bytes) -> std::result::Result<serde_json::Value, String> + Send + Sync>;
39
40pub fn json_parser<F>(f: F) -> JsonParserFn
42where
43 F: Fn(&Bytes) -> std::result::Result<serde_json::Value, String> + Send + Sync + 'static,
44{
45 Arc::new(f)
46}
47
48pub fn serde_json_parser() -> JsonParserFn {
50 json_parser(|body| serde_json::from_slice(body).map_err(|e| e.to_string()))
51}
52
53pub(crate) fn deserialize_error(status: StatusCode, message: String, body: &Bytes) -> Error {
54 Error::Deserialize {
55 status,
56 message,
57 body: Some(body.clone()),
58 }
59}
60
61pub(crate) fn deserialize<T: DeserializeOwned>(
62 body: &Bytes,
63 status: StatusCode,
64 parser: Option<&JsonParserFn>,
65) -> Result<T> {
66 match parser {
67 None => serde_json::from_slice(body)
68 .map_err(|source| deserialize_error(status, source.to_string(), body)),
69 Some(parse) => {
70 let value = parse(body).map_err(|message| deserialize_error(status, message, body))?;
71 serde_json::from_value(value)
72 .map_err(|source| deserialize_error(status, source.to_string(), body))
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use serde::Deserialize;
81
82 #[derive(Debug, Deserialize, PartialEq)]
83 struct IdOnly {
84 id: u64,
85 }
86
87 fn strip_bom(body: &Bytes) -> std::result::Result<serde_json::Value, String> {
88 let slice = body.strip_prefix(b"\xef\xbb\xbf").unwrap_or(body);
89 serde_json::from_slice(slice).map_err(|e| e.to_string())
90 }
91
92 #[test]
93 fn fast_path_without_parser() {
94 let body = Bytes::from_static(br#"{"id":1}"#);
95 let parsed: IdOnly =
96 deserialize(&body, StatusCode::OK, None).expect("serde_json fast path");
97 assert_eq!(parsed, IdOnly { id: 1 });
98 }
99
100 #[test]
101 fn custom_parser_strips_bom() {
102 let body = Bytes::from_static(b"\xef\xbb\xbf{\"id\":2}");
103 let parser = json_parser(strip_bom);
104 let parsed: IdOnly =
105 deserialize(&body, StatusCode::OK, Some(&parser)).expect("custom parser");
106 assert_eq!(parsed, IdOnly { id: 2 });
107 }
108
109 #[test]
110 fn custom_parser_error_maps_to_deserialize() {
111 let body = Bytes::from_static(b"not-json");
112 let parser = json_parser(|_| Err("bad json".into()));
113 let err = deserialize::<IdOnly>(&body, StatusCode::OK, Some(&parser)).unwrap_err();
114 assert!(matches!(err, Error::Deserialize { message, .. } if message == "bad json"));
115 }
116}