1use crate::{BootError, BootRequest, Result};
2use serde::de::DeserializeOwned;
3use serde::Serialize;
4use serde_json::Value;
5use std::collections::{BTreeMap, BTreeSet};
6use std::sync::Arc;
7
8pub trait Validate {
10 fn validate(&self) -> Result<()> {
11 Ok(())
12 }
13}
14
15pub trait ValidationSchema {
17 fn allowed_fields() -> &'static [&'static str];
18}
19
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
22pub struct ValidationOptions {
23 pub transform: bool,
24 pub whitelist: bool,
25 pub forbid_non_whitelisted: bool,
26}
27
28impl ValidationOptions {
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn whitelist(mut self, enabled: bool) -> Self {
34 self.whitelist = enabled;
35 self
36 }
37
38 pub fn transform(mut self, enabled: bool) -> Self {
39 self.transform = enabled;
40 self
41 }
42
43 pub fn forbid_non_whitelisted(mut self, enabled: bool) -> Self {
44 self.forbid_non_whitelisted = enabled;
45 self
46 }
47
48 pub fn merge(self, other: Self) -> Self {
49 Self {
50 transform: self.transform || other.transform,
51 whitelist: self.whitelist || other.whitelist,
52 forbid_non_whitelisted: self.forbid_non_whitelisted || other.forbid_non_whitelisted,
53 }
54 }
55
56 pub(crate) fn checks_unknown_fields(self) -> bool {
57 self.whitelist || self.forbid_non_whitelisted
58 }
59}
60
61pub(crate) type RequestValidator =
62 Arc<dyn Fn(BootRequest, ValidationOptions) -> Result<BootRequest> + Send + Sync>;
63
64pub(crate) fn body_validator<T>() -> RequestValidator
65where
66 T: DeserializeOwned + Validate + 'static,
67{
68 Arc::new(|request, _| {
69 request.require_json_content_type()?;
70 validate_value(request.json::<T>()?).map(|_| request)
71 })
72}
73
74pub(crate) fn body_validator_with_options<T>(options: ValidationOptions) -> RequestValidator
75where
76 T: DeserializeOwned + Serialize + Validate + ValidationSchema + 'static,
77{
78 Arc::new(move |mut request, inherited_options| {
79 let options = inherited_options.merge(options);
80 request.require_json_content_type()?;
81 let value = request.json::<Value>()?;
82 let value = validate_json_value_with_options::<T>(value, options, "body property")?;
83 if options.transform || options.whitelist {
84 let body =
85 serde_json::to_vec(&value).map_err(|err| BootError::Internal(err.to_string()))?;
86 rewrite_request_body(&mut request, body);
87 }
88 Ok(request)
89 })
90}
91
92pub(crate) fn params_validator<T>() -> RequestValidator
93where
94 T: DeserializeOwned + Validate + 'static,
95{
96 Arc::new(|request, _| validate_value(request.params::<T>()?).map(|_| request))
97}
98
99pub(crate) fn params_validator_with_options<T>(options: ValidationOptions) -> RequestValidator
100where
101 T: DeserializeOwned + Serialize + Validate + ValidationSchema + 'static,
102{
103 Arc::new(move |mut request, inherited_options| {
104 let options = inherited_options.merge(options);
105 apply_map_field_options::<T>(&mut request.params, options, "path parameter")?;
106 let value = validate_value(request.params::<T>()?)?;
107 if options.transform {
108 request.params = serialize_urlencoded_map(&value)?;
109 }
110 Ok(request)
111 })
112}
113
114pub(crate) fn query_validator<T>() -> RequestValidator
115where
116 T: DeserializeOwned + Validate + 'static,
117{
118 Arc::new(|request, _| validate_value(request.query::<T>()?).map(|_| request))
119}
120
121pub(crate) fn query_validator_with_options<T>(options: ValidationOptions) -> RequestValidator
122where
123 T: DeserializeOwned + Serialize + Validate + ValidationSchema + 'static,
124{
125 Arc::new(move |mut request, inherited_options| {
126 let options = inherited_options.merge(options);
127 let pairs = request.query_pairs()?;
128 apply_pair_field_options::<T>(&pairs, &mut request.query, options, "query parameter")?;
129 if options.whitelist {
130 request.query_string = None;
131 }
132 let value = validate_value(request.query::<T>()?)?;
133 if options.transform {
134 request.query = serialize_urlencoded_map(&value)?;
135 request.query_string = None;
136 }
137 Ok(request)
138 })
139}
140
141pub(crate) fn validate_value<T>(value: T) -> Result<T>
142where
143 T: Validate,
144{
145 value
146 .validate()
147 .map_err(|error| validation_bad_request(error, std::any::type_name::<T>()))?;
148 Ok(value)
149}
150
151fn validation_bad_request(error: BootError, type_name: &'static str) -> BootError {
152 match error {
153 BootError::BadRequest(message) => {
154 BootError::BadRequest(format!("validation failed for {type_name}: {message}"))
155 }
156 error => error,
157 }
158}
159
160pub(crate) fn validate_json_value_with_options<T>(
161 value: Value,
162 options: ValidationOptions,
163 label: &'static str,
164) -> Result<Value>
165where
166 T: DeserializeOwned + Serialize + Validate + ValidationSchema,
167{
168 let value = apply_json_field_options::<T>(value, options, label)?;
169 let typed = validate_value(
170 serde_json::from_value::<T>(value.clone())
171 .map_err(|err| BootError::BadRequest(err.to_string()))?,
172 )?;
173 if options.transform {
174 serde_json::to_value(&typed).map_err(|err| BootError::Internal(err.to_string()))
175 } else {
176 Ok(value)
177 }
178}
179
180fn apply_json_field_options<T>(
181 mut value: Value,
182 options: ValidationOptions,
183 label: &'static str,
184) -> Result<Value>
185where
186 T: ValidationSchema,
187{
188 if !options.checks_unknown_fields() {
189 return Ok(value);
190 }
191
192 let Some(object) = value.as_object_mut() else {
193 return Ok(value);
194 };
195 let allowed = allowed_fields::<T>();
196 let unknown = object
197 .keys()
198 .filter(|field| !allowed.contains(field.as_str()))
199 .cloned()
200 .collect::<Vec<_>>();
201 handle_unknown_fields(label, &unknown, options)?;
202 if options.whitelist {
203 object.retain(|field, _| allowed.contains(field.as_str()));
204 }
205 Ok(value)
206}
207
208fn apply_map_field_options<T>(
209 values: &mut BTreeMap<String, String>,
210 options: ValidationOptions,
211 label: &'static str,
212) -> Result<()>
213where
214 T: ValidationSchema,
215{
216 if !options.checks_unknown_fields() {
217 return Ok(());
218 }
219
220 let allowed = allowed_fields::<T>();
221 let unknown = values
222 .keys()
223 .filter(|field| !allowed.contains(field.as_str()))
224 .cloned()
225 .collect::<Vec<_>>();
226 handle_unknown_fields(label, &unknown, options)?;
227 if options.whitelist {
228 values.retain(|field, _| allowed.contains(field.as_str()));
229 }
230 Ok(())
231}
232
233fn apply_pair_field_options<T>(
234 pairs: &[(String, String)],
235 values: &mut BTreeMap<String, String>,
236 options: ValidationOptions,
237 label: &'static str,
238) -> Result<()>
239where
240 T: ValidationSchema,
241{
242 if !options.checks_unknown_fields() {
243 return Ok(());
244 }
245
246 let allowed = allowed_fields::<T>();
247 let unknown = pairs
248 .iter()
249 .map(|(field, _)| field)
250 .filter(|field| !allowed.contains(field.as_str()))
251 .cloned()
252 .collect::<BTreeSet<_>>()
253 .into_iter()
254 .collect::<Vec<_>>();
255 handle_unknown_fields(label, &unknown, options)?;
256 if options.whitelist {
257 values.retain(|field, _| allowed.contains(field.as_str()));
258 }
259 Ok(())
260}
261
262fn allowed_fields<T>() -> BTreeSet<&'static str>
263where
264 T: ValidationSchema,
265{
266 T::allowed_fields().iter().copied().collect()
267}
268
269fn handle_unknown_fields(
270 label: &'static str,
271 unknown: &[String],
272 options: ValidationOptions,
273) -> Result<()> {
274 if unknown.is_empty() || !options.forbid_non_whitelisted {
275 return Ok(());
276 }
277
278 let fields = unknown.join(", ");
279 Err(BootError::BadRequest(format!(
280 "non-whitelisted {}: {fields}",
281 plural_label(label)
282 )))
283}
284
285fn plural_label(label: &'static str) -> &'static str {
286 match label {
287 "body property" => "body properties",
288 "message property" => "message properties",
289 "path parameter" => "path parameters",
290 "query parameter" => "query parameters",
291 value => value,
292 }
293}
294
295fn rewrite_request_body(request: &mut BootRequest, body: Vec<u8>) {
296 let body_len = body.len();
297 request.body = body;
298 if request.header("content-length").is_some() {
299 request
300 .headers
301 .insert("content-length".to_string(), body_len.to_string());
302 request
303 .appended_headers
304 .retain(|(name, _)| !name.eq_ignore_ascii_case("content-length"));
305 }
306}
307
308fn serialize_urlencoded_map<T>(value: &T) -> Result<BTreeMap<String, String>>
309where
310 T: Serialize,
311{
312 let encoded =
313 serde_urlencoded::to_string(value).map_err(|err| BootError::Internal(err.to_string()))?;
314 if encoded.is_empty() {
315 return Ok(BTreeMap::new());
316 }
317
318 serde_urlencoded::from_str(&encoded).map_err(|err| BootError::Internal(err.to_string()))
319}