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(
54 status: StatusCode,
55 message: String,
56 body: &Bytes,
57) -> Error {
58 Error::Deserialize {
59 status,
60 message,
61 body: Some(body.clone()),
62 }
63}
64
65pub(crate) fn deserialize<T: DeserializeOwned>(
66 body: &Bytes,
67 status: StatusCode,
68 parser: Option<&JsonParserFn>,
69) -> Result<T> {
70 match parser {
71 None => serde_json::from_slice(body).map_err(|source| {
72 deserialize_error(status, source.to_string(), body)
73 }),
74 Some(parse) => {
75 let value = parse(body)
76 .map_err(|message| deserialize_error(status, message, body))?;
77 serde_json::from_value(value).map_err(|source| {
78 deserialize_error(status, source.to_string(), body)
79 })
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use serde::Deserialize;
88
89 #[derive(Debug, Deserialize, PartialEq)]
90 struct IdOnly {
91 id: u64,
92 }
93
94 fn strip_bom(body: &Bytes) -> std::result::Result<serde_json::Value, String> {
95 let slice = body.strip_prefix(b"\xef\xbb\xbf").unwrap_or(body);
96 serde_json::from_slice(slice).map_err(|e| e.to_string())
97 }
98
99 #[test]
100 fn fast_path_without_parser() {
101 let body = Bytes::from_static(br#"{"id":1}"#);
102 let parsed: IdOnly =
103 deserialize(&body, StatusCode::OK, None).expect("serde_json fast path");
104 assert_eq!(parsed, IdOnly { id: 1 });
105 }
106
107 #[test]
108 fn custom_parser_strips_bom() {
109 let body = Bytes::from_static(b"\xef\xbb\xbf{\"id\":2}");
110 let parser = json_parser(strip_bom);
111 let parsed: IdOnly =
112 deserialize(&body, StatusCode::OK, Some(&parser)).expect("custom parser");
113 assert_eq!(parsed, IdOnly { id: 2 });
114 }
115
116 #[test]
117 fn custom_parser_error_maps_to_deserialize() {
118 let body = Bytes::from_static(b"not-json");
119 let parser = json_parser(|_| Err("bad json".into()));
120 let err = deserialize::<IdOnly>(&body, StatusCode::OK, Some(&parser)).unwrap_err();
121 assert!(matches!(err, Error::Deserialize { message, .. } if message == "bad json"));
122 }
123}