better_fetch/
json_parser.rs1use std::sync::Arc;
2
3use bytes::Bytes;
4use http::StatusCode;
5use serde::de::DeserializeOwned;
6
7use crate::error::Error;
8use crate::Result;
9
10pub type JsonParserFn =
12 Arc<dyn Fn(&Bytes) -> std::result::Result<serde_json::Value, String> + Send + Sync>;
13
14pub 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
22pub 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}