Skip to main content

security_client_rs/http/
body.rs

1use 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/// A request body. Endpoint builders create these; use them directly with
14/// [`Transport::send`](super::Transport::send) or [`Request::body`](crate::Request::body).
15///
16/// Cloning shares the encoded bytes.
17#[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    /// Fails with [`Error::InvalidRequest`] when the JSON would repeat an object
33    /// key, as a flattened extension map repeating a modeled field does.
34    pub fn json<T: Serialize + ?Sized>(value: &T) -> Result<Self> {
35        Ok(Self(Content::Json(encode_json(value)?.into())))
36    }
37
38    /// A `multipart/form-data` body with a single file part, as used by Kibana import APIs.
39    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
86/// Serializes `value` as JSON, rejecting objects that repeat a key.
87///
88/// A struct whose flattened extension map repeats a modeled field, such as
89/// `id`, serializes that key twice; `serde_json::to_value` would silently keep
90/// the extension's value, so the transmitted identity would differ from the
91/// typed one.
92pub(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
106/// Validates object keys without retaining or converting values.
107struct 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}