security_client_rs/http/
body.rs1use std::{collections::HashSet, fmt};
2
3use bytes::Bytes;
4use reqwest::multipart::{Form, Part};
5use serde::{
6 Deserialize, Deserializer, Serialize,
7 de::{self, MapAccess, SeqAccess, Visitor},
8};
9use serde_json::Value;
10
11use crate::{Error, Result};
12
13#[derive(Clone)]
18pub struct Body(pub(crate) Content);
19
20#[derive(Clone)]
21pub(crate) enum Content {
22 Json(Bytes),
23 File {
24 field: String,
25 file_name: String,
26 content_type: String,
27 bytes: Bytes,
28 },
29}
30
31impl Body {
32 pub fn json<T: Serialize + ?Sized>(value: &T) -> Result<Self> {
35 Ok(Self(Content::Json(encode_json(value)?.into())))
36 }
37
38 pub fn file(
40 field: impl Into<String>,
41 file_name: impl Into<String>,
42 content_type: &str,
43 bytes: impl Into<Vec<u8>>,
44 ) -> Result<Self> {
45 Part::bytes(Vec::new())
46 .mime_str(content_type)
47 .map_err(|_| Error::InvalidRequest(format!("invalid content type {content_type:?}")))?;
48 Ok(Self(Content::File {
49 field: field.into(),
50 file_name: file_name.into(),
51 content_type: content_type.to_owned(),
52 bytes: Bytes::from(bytes.into()),
53 }))
54 }
55}
56
57impl Content {
58 pub(crate) fn form(
59 field: String,
60 file_name: String,
61 content_type: &str,
62 bytes: Bytes,
63 ) -> Result<Form> {
64 let length = bytes.len() as u64;
65 let part = Part::stream_with_length(bytes, length)
66 .file_name(file_name)
67 .mime_str(content_type)
68 .map_err(|_| Error::InvalidRequest(format!("invalid content type {content_type:?}")))?;
69 Ok(Form::new().part(field, part))
70 }
71}
72
73impl fmt::Debug for Body {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match &self.0 {
76 Content::Json(bytes) => write!(f, "Body::Json({} bytes)", bytes.len()),
77 Content::File {
78 file_name, bytes, ..
79 } => {
80 write!(f, "Body::File({file_name:?}, {} bytes)", bytes.len())
81 }
82 }
83 }
84}
85
86pub(crate) fn to_json<T: Serialize + ?Sized>(value: &T) -> Result<Value> {
93 encode_json(value)?;
94 serde_json::to_value(value).map_err(Error::serialize)
95}
96
97fn encode_json<T: Serialize + ?Sized>(value: &T) -> Result<Vec<u8>> {
98 let encoded = serde_json::to_vec(value).map_err(Error::serialize)?;
99 let mut decoder = serde_json::Deserializer::from_slice(&encoded);
100 Unique::deserialize(&mut decoder)
101 .and_then(|_| decoder.end())
102 .map_err(|error| Error::InvalidRequest(format!("request body {error}")))?;
103 Ok(encoded)
104}
105
106struct Unique;
108
109impl<'de> Deserialize<'de> for Unique {
110 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
111 deserializer.deserialize_any(UniqueVisitor).map(|()| Unique)
112 }
113}
114
115struct UniqueVisitor;
116
117impl<'de> Visitor<'de> for UniqueVisitor {
118 type Value = ();
119
120 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 f.write_str("a JSON value")
122 }
123
124 fn visit_bool<E>(self, _value: bool) -> std::result::Result<(), E> {
125 Ok(())
126 }
127
128 fn visit_i64<E>(self, _value: i64) -> std::result::Result<(), E> {
129 Ok(())
130 }
131
132 fn visit_u64<E>(self, _value: u64) -> std::result::Result<(), E> {
133 Ok(())
134 }
135
136 fn visit_f64<E>(self, _value: f64) -> std::result::Result<(), E> {
137 Ok(())
138 }
139
140 fn visit_str<E>(self, _value: &str) -> std::result::Result<(), E> {
141 Ok(())
142 }
143
144 fn visit_string<E>(self, _value: String) -> std::result::Result<(), E> {
145 Ok(())
146 }
147
148 fn visit_unit<E>(self) -> std::result::Result<(), E> {
149 Ok(())
150 }
151
152 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> std::result::Result<(), A::Error> {
153 while seq.next_element::<Unique>()?.is_some() {}
154 Ok(())
155 }
156
157 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> std::result::Result<(), A::Error> {
158 let mut keys = HashSet::new();
159 while let Some(key) = map.next_key::<String>()? {
160 if keys.contains(&key) {
161 return Err(de::Error::custom(format_args!(
162 "repeats the field {key:?}; an extension map may not set a modeled field"
163 )));
164 }
165 keys.insert(key);
166 map.next_value::<Unique>()?;
167 }
168 Ok(())
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use serde_json::{Map, json};
175
176 use super::*;
177
178 #[test]
179 fn json_keeps_encoded_numbers_exactly() {
180 #[derive(Serialize)]
181 struct Numbers {
182 positive: u128,
183 negative: i128,
184 fraction: f64,
185 }
186 let numbers = Numbers {
187 positive: u128::from(u64::MAX) + 2,
188 negative: i128::from(i64::MIN) - 2,
189 fraction: 2.291_712_365_432_881e-9,
190 };
191 let Content::Json(bytes) = Body::json(&numbers).unwrap().0 else {
192 panic!("expected JSON");
193 };
194 assert_eq!(
195 std::str::from_utf8(&bytes).unwrap(),
196 serde_json::to_string(&numbers).unwrap()
197 );
198 }
199
200 #[test]
201 fn repeated_keys_are_rejected_at_any_depth() {
202 #[derive(Serialize)]
203 struct Flattened {
204 id: &'static str,
205 #[serde(flatten)]
206 extra: Map<String, Value>,
207 }
208 let clean = Flattened {
209 id: "a",
210 extra: Map::from_iter([("color".into(), json!("red"))]),
211 };
212 assert_eq!(to_json(&clean).unwrap(), json!({"id": "a", "color": "red"}));
213
214 let colliding = Flattened {
215 id: "a",
216 extra: Map::from_iter([("id".into(), json!("b"))]),
217 };
218 let error = to_json(&colliding).unwrap_err();
219 assert!(
220 matches!(&error, Error::InvalidRequest(m) if m.contains("\"id\"")),
221 "{error:?}"
222 );
223 assert!(to_json(&json!({"outer": [colliding.extra.clone()]})).is_ok());
224 assert!(Body::json(&colliding).is_err());
225 assert!(to_json(&vec![colliding]).is_err());
226 assert!(Body::json(&clean).is_ok());
227 }
228}