1use crate::auth::IClaims;
23use crate::error::{Error, Result};
24use crate::form::{FormFile, MultipartForm};
25use crate::http::IHttpContext;
26use crate::problem::{FieldError, ProblemDetails};
27use crate::route::scan::ParamMeta;
28use std::collections::HashMap;
29use std::future::Future;
30use std::pin::Pin;
31use std::str::FromStr;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum Source {
46 Path,
48 Query,
50 Form,
52 Header,
54 Claim,
56 Body,
58 Auto,
60}
61
62impl Source {
63 pub const fn as_str(self) -> &'static str {
65 match self {
66 Source::Path => "path",
67 Source::Query => "query",
68 Source::Form => "form",
69 Source::Header => "header",
70 Source::Claim => "claim",
71 Source::Body => "body",
72 Source::Auto => "",
73 }
74 }
75
76 pub const fn is_explicit(self) -> bool {
78 !matches!(self, Source::Auto)
79 }
80}
81
82pub fn is_body_method(method: &str) -> bool {
84 matches!(method, "POST" | "PUT" | "PATCH")
85}
86
87#[diagnostic::on_unimplemented(
103 message = "`{Self}` cannot be bound from an HTTP request",
104 label = "add `#[derive(WebxRequestMeta)]` to this request struct",
105 note = "the endpoint macros call `RequestModel::bind`; the derive is what implements it"
106)]
107pub trait RequestModel: Sized + Send + 'static {
108 fn param_meta() -> &'static [ParamMeta];
113
114 fn bind<'a>(
116 ctx: &'a mut dyn IHttpContext,
117 ) -> Pin<Box<dyn Future<Output = Result<Self>> + Send + 'a>>;
118}
119
120pub struct BindInput<'a> {
125 pub path: &'a HashMap<String, String>,
127 pub query: &'a HashMap<String, String>,
129 pub query_multi: Option<&'a HashMap<String, Vec<String>>>,
132 pub claims: Option<&'a dyn IClaims>,
134 pub form: Option<&'a MultipartForm>,
136 pub is_body: bool,
138 pub is_multipart: bool,
140}
141
142impl<'a> BindInput<'a> {
143 pub fn path_one(&self, name: &str) -> Option<&'a str> {
145 self.path.get(name).map(String::as_str)
146 }
147
148 pub fn path_list(&self, name: &str) -> Vec<&'a str> {
154 match self.path.get(name) {
155 Some(value) => split_one(value),
156 None => Vec::new(),
157 }
158 }
159
160 pub fn query_one(&self, name: &str) -> Option<&'a str> {
162 if let Some(multi) = self.query_multi {
163 if let Some(values) = multi.get(name) {
164 return values.last().map(String::as_str);
165 }
166 }
167 self.query.get(name).map(String::as_str)
168 }
169
170 pub fn query_list(&self, name: &str) -> Vec<&'a str> {
172 if let Some(multi) = self.query_multi {
173 if let Some(values) = multi.get(name) {
174 return split_values(values);
175 }
176 }
177 match self.query.get(name) {
178 Some(value) => split_one(value),
179 None => Vec::new(),
180 }
181 }
182
183 pub fn form_one(&self, name: &str) -> Option<&'a str> {
185 self.form?.text(name)
186 }
187
188 pub fn form_list(&self, name: &str) -> Vec<&'a str> {
190 match self.form {
191 Some(form) => form.texts(name).flat_map(split_one).collect(),
192 None => Vec::new(),
193 }
194 }
195
196 pub fn file(&self, name: &str) -> Option<&'a FormFile> {
198 self.form?.file(name)
199 }
200
201 pub fn files(&self, name: &str) -> Vec<&'a FormFile> {
203 match self.form {
204 Some(form) => form.files_named(name).collect(),
205 None => Vec::new(),
206 }
207 }
208
209 pub fn claim(&self, key: &str) -> Option<&'a str> {
214 let claims = self.claims?;
215 match key {
216 "sub" | "subject" | "user_id" => Some(claims.subject()),
217 "tenant_id" | "tenant" => claims.get_tenantid(),
218 other => claims.claims().get(other).map(String::as_str),
219 }
220 }
221
222 pub fn claim_list(&self, key: &str) -> Vec<&'a str> {
225 let Some(claims) = self.claims else {
226 return Vec::new();
227 };
228 match key {
229 "roles" => claims.roles().iter().map(String::as_str).collect(),
230 "permissions" => claims.permissions().iter().map(String::as_str).collect(),
231 other => match self.claim(other) {
232 Some(value) => split_one(value),
233 None => Vec::new(),
234 },
235 }
236 }
237}
238
239pub fn body_error(error: serde_json::Error) -> Error {
246 let message = error.to_string();
247 let field = extract_error_field(&message)
248 .unwrap_or_default()
249 .to_string();
250 ProblemDetails::validation(vec![FieldError::new(field, message)]).to_error()
251}
252
253fn extract_error_field(message: &str) -> Option<&str> {
254 for marker in ["missing field `", "unknown field `", "duplicate field `"] {
255 if let Some((_, rest)) = message.split_once(marker) {
256 if let Some((name, _)) = rest.split_once('`') {
257 return Some(name);
258 }
259 }
260 }
261 None
262}
263
264pub fn body_field_error(field: &str, error: serde_json::Error) -> Error {
267 ProblemDetails::validation(vec![FieldError::new(
268 field,
269 format!("The `{field}` field is invalid: {error}"),
270 )])
271 .to_error()
272}
273
274pub fn take_body_field(
280 object: &mut serde_json::Map<String, serde_json::Value>,
281 key: &str,
282 aliases: &[&str],
283) -> Option<serde_json::Value> {
284 if let Some(value) = object.remove(key) {
285 return Some(value);
286 }
287 for alias in aliases {
288 if let Some(value) = object.remove(*alias) {
289 return Some(value);
290 }
291 }
292 None
293}
294
295fn split_one(value: &str) -> Vec<&str> {
297 if value.contains(',') {
298 value
299 .split(',')
300 .map(str::trim)
301 .filter(|part| !part.is_empty())
302 .collect()
303 } else {
304 vec![value]
305 }
306}
307
308pub fn split_csv(value: Option<&str>) -> Vec<&str> {
313 match value {
314 Some(value) => split_one(value),
315 None => Vec::new(),
316 }
317}
318
319fn split_values(values: &[String]) -> Vec<&str> {
321 let mut out = Vec::with_capacity(values.len());
322 for value in values {
323 out.extend(split_one(value));
324 }
325 out
326}
327
328#[derive(Default)]
334pub struct Binder {
335 errors: Vec<FieldError>,
336}
337
338impl Binder {
339 pub fn new() -> Self {
341 Self::default()
342 }
343
344 pub fn has_errors(&self) -> bool {
346 !self.errors.is_empty()
347 }
348
349 pub fn errors(&self) -> &[FieldError] {
351 &self.errors
352 }
353
354 pub fn into_error(self) -> Error {
358 ProblemDetails::validation(self.errors).to_error()
359 }
360
361 pub fn missing(&mut self, field: &str) {
363 self.errors.push(FieldError::new(
364 field,
365 format!("The `{field}` field is required."),
366 ));
367 }
368
369 pub fn invalid(&mut self, field: &str, kind: &str, value: &str) {
371 self.errors.push(FieldError::new(
372 field,
373 format!("The `{field}` field must be a valid {kind} (got `{value}`)."),
374 ));
375 }
376
377 pub fn unknown_field(&mut self, name: &str) {
379 self.errors.push(FieldError::new(
380 "",
381 format!("The body contains an unknown field `{name}`."),
382 ));
383 }
384
385 pub fn required<T: FromStr>(
391 &mut self,
392 raw: Option<&str>,
393 field: &str,
394 kind: &str,
395 ) -> Option<T> {
396 let Some(raw) = raw else {
397 self.missing(field);
398 return None;
399 };
400 match raw.parse::<T>() {
401 Ok(value) => Some(value),
402 Err(_) => {
403 self.invalid(field, kind, raw);
404 None
405 }
406 }
407 }
408
409 pub fn optional<T: FromStr>(
412 &mut self,
413 raw: Option<&str>,
414 field: &str,
415 kind: &str,
416 ) -> Option<T> {
417 let raw = raw?;
418 match raw.parse::<T>() {
419 Ok(value) => Some(value),
420 Err(_) => {
421 self.invalid(field, kind, raw);
422 None
423 }
424 }
425 }
426
427 pub fn list<T: FromStr>(&mut self, raw: &[&str], field: &str, kind: &str) -> Vec<T> {
432 let mut out = Vec::with_capacity(raw.len());
433 for value in raw {
434 match value.parse::<T>() {
435 Ok(parsed) => out.push(parsed),
436 Err(_) => self.invalid(field, kind, value),
437 }
438 }
439 out
440 }
441
442 pub fn take<T>(&mut self, value: Option<T>, field: &str) -> Option<T> {
447 if value.is_none() {
448 self.missing(field);
449 }
450 value
451 }
452
453 pub fn required_file(&mut self, file: Option<&FormFile>, field: &str) -> Option<FormFile> {
455 match file {
456 Some(file) => Some(file.clone()),
457 None => {
458 self.missing(field);
459 None
460 }
461 }
462 }
463
464 pub fn optional_file(&mut self, file: Option<&FormFile>) -> Option<FormFile> {
466 file.cloned()
467 }
468
469 pub fn files(&mut self, files: Vec<&FormFile>) -> Vec<FormFile> {
471 files.into_iter().cloned().collect()
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use std::collections::HashMap;
479
480 #[test]
481 fn required_reports_missing_and_invalid() {
482 let mut binder = Binder::new();
483 assert_eq!(binder.required::<u32>(None, "page", "integer"), None);
484 assert_eq!(binder.required::<u32>(Some("abc"), "page", "integer"), None);
485 assert_eq!(
486 binder.required::<u32>(Some("2"), "page", "integer"),
487 Some(2)
488 );
489 assert_eq!(binder.errors().len(), 2);
490 assert!(binder.errors()[0].message.contains("required"));
491 }
492
493 #[test]
494 fn optional_keeps_absent_as_none_and_rejects_invalid() {
495 let mut binder = Binder::new();
496 assert_eq!(binder.optional::<u32>(None, "page", "integer"), None);
497 assert!(!binder.has_errors());
498 assert_eq!(binder.optional::<u32>(Some("x"), "page", "integer"), None);
499 assert!(binder.has_errors());
500 }
501
502 #[test]
503 fn list_binds_every_occurrence() {
504 let mut binder = Binder::new();
505 assert_eq!(
506 binder.list::<u32>(&["1", "2", "3"], "ids", "integer"),
507 vec![1, 2, 3]
508 );
509 assert!(!binder.has_errors());
510 assert_eq!(binder.list::<u32>(&["1", "x"], "ids", "integer"), vec![1]);
511 assert!(binder.has_errors());
512 }
513
514 #[test]
515 fn query_values_keep_every_occurrence_and_expand_comma_lists() {
516 let path = HashMap::new();
517 let query = HashMap::new();
518 let mut multi: HashMap<String, Vec<String>> = HashMap::new();
519 multi.insert("ids".into(), vec!["1".into(), "2,3".into()]);
520
521 let input = BindInput {
522 path: &path,
523 query: &query,
524 query_multi: Some(&multi),
525 claims: None,
526 form: None,
527 is_body: false,
528 is_multipart: false,
529 };
530 assert_eq!(input.query_list("ids"), vec!["1", "2", "3"]);
531 assert_eq!(input.query_one("ids"), Some("2,3"));
532 }
533
534 #[test]
535 fn error_body_groups_fields() {
536 let mut binder = Binder::new();
537 binder.missing("a");
538 binder.missing("a");
539 binder.missing("b");
540 let error = binder.into_error();
541 let Error::Problem(problem) = error else {
542 panic!("expected Problem, got {error:?}");
543 };
544 assert_eq!(problem.status, 400);
545 let errors = problem.extensions.as_ref().unwrap().get("errors").unwrap();
546 assert_eq!(
547 errors["a"],
548 serde_json::json!(["The `a` field is required.", "The `a` field is required."])
549 );
550 assert_eq!(
551 errors["b"],
552 serde_json::json!(["The `b` field is required."])
553 );
554 }
555}