form_net/extract/mod.rs
1//! Typed extraction: plain Rust structs filled from a form, without serde
2//! and without a procedural macro.
3//!
4//! [`form!`](crate::form) generates a struct and its [`FromForm`]
5//! implementation; each field type implements [`FromField`]. Unknown names
6//! are refused, a scalar given twice is refused, a missing required field is
7//! refused, and every error names the field by its Rust-side name.
8
9mod file;
10mod macros;
11mod secret;
12mod value;
13
14pub use file::FileData;
15pub use secret::Secret;
16
17use crate::error::{ExtractError, FormError};
18use crate::hook::{Inspector, NoInspector};
19use crate::kind::FormKind;
20use crate::limits::{Limits, Policy};
21use crate::multipart::Part;
22
23/// Why one field's value did not convert. [`form!`](crate::form) turns it
24/// into `FormError::Extract` with the field's name.
25///
26/// # Examples
27///
28/// ```
29/// use form_net::{FromField, ValueError};
30///
31/// let mut slot = <u8 as FromField>::Slot::default();
32/// assert_eq!(u8::accept_text(&mut slot, "300"), Err(ValueError::Invalid("out of range")));
33/// ```
34#[non_exhaustive]
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum ValueError {
37 /// A required field is absent.
38 Missing,
39 /// A single-valued field was given twice.
40 Duplicate,
41 /// The value does not convert; the text says why.
42 Invalid(&'static str),
43 /// Text was given to a file field.
44 ExpectedFile,
45 /// A file was given to a text field.
46 ExpectedText,
47}
48
49/// A file field's value.
50#[non_exhaustive]
51pub enum FileValue<'a> {
52 /// A part of a buffered body.
53 Buffered(&'a Part<'a>),
54 /// A stored file from an upload.
55 #[cfg(feature = "fs")]
56 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
57 Stored(crate::store::StoredFile),
58}
59
60impl core::fmt::Debug for FileValue<'_> {
61 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62 match self {
63 FileValue::Buffered(p) => f.debug_tuple("Buffered").field(p).finish(),
64 #[cfg(feature = "fs")]
65 FileValue::Stored(s) => f.debug_tuple("Stored").field(s).finish(),
66 }
67 }
68}
69
70/// How one field type takes its values.
71///
72/// The slot holds what has arrived so far, so every arity rule lives in one
73/// place: a scalar's slot is `Option<T>` (a second value is `Duplicate`,
74/// none is `Missing`), `Vec<T>` pushes, `bool` finishes a missing value as
75/// `false`. Implement it for an application type to validate while
76/// extracting.
77///
78/// # Examples
79///
80/// ```
81/// use form_net::{FromField, ValueError};
82///
83/// pub struct Slug(String);
84///
85/// impl FromField for Slug {
86/// type Slot = Option<Slug>;
87/// fn accept_text(slot: &mut Option<Slug>, value: &str) -> Result<(), ValueError> {
88/// if slot.is_some() { return Err(ValueError::Duplicate); }
89/// if value.is_empty() || value.len() > 64 || !value.bytes().all(|b| b.is_ascii_lowercase() || b == b'-') {
90/// return Err(ValueError::Invalid("lowercase letters and hyphens, 1 to 64"));
91/// }
92/// *slot = Some(Slug(value.to_owned()));
93/// Ok(())
94/// }
95/// fn finish(slot: Option<Slug>) -> Result<Slug, ValueError> {
96/// slot.ok_or(ValueError::Missing)
97/// }
98/// }
99/// ```
100pub trait FromField: Sized {
101 /// What has arrived so far.
102 type Slot: Default;
103
104 /// Takes one text value.
105 ///
106 /// # Errors
107 ///
108 /// `ExpectedFile` by default; the type's own conversion errors.
109 fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
110 let _ = (slot, value);
111 Err(ValueError::ExpectedFile)
112 }
113
114 /// Takes one file.
115 ///
116 /// # Errors
117 ///
118 /// `ExpectedText` by default; the type's own errors.
119 fn accept_file(slot: &mut Self::Slot, file: FileValue<'_>) -> Result<(), ValueError> {
120 let _ = (slot, file);
121 Err(ValueError::ExpectedText)
122 }
123
124 /// The value, once every field has arrived.
125 ///
126 /// # Errors
127 ///
128 /// `Missing`, or the type's own errors.
129 fn finish(slot: Self::Slot) -> Result<Self, ValueError>;
130}
131
132/// What [`form!`](crate::form) generates, and what a hand-written
133/// [`FromForm`] implementation provides. Reports problems as
134/// `FormError::Extract { reason, field }`.
135///
136/// # Examples
137///
138/// ```
139/// use form_net::{ExtractError, FileValue, FormBuilder, FormError, FromForm};
140///
141/// /// Accepts any field names, keeping the last `q`.
142/// #[derive(Default)]
143/// struct LastQ(Option<String>);
144///
145/// impl FormBuilder for LastQ {
146/// type Output = Query;
147/// fn text(&mut self, name: &str, value: &str) -> Result<(), FormError> {
148/// if name == "q" { self.0 = Some(value.to_owned()); }
149/// Ok(())
150/// }
151/// fn file(&mut self, _name: &str, _file: FileValue<'_>) -> Result<(), FormError> {
152/// Err(FormError::Extract { reason: ExtractError::WrongKind, field: Some("q") })
153/// }
154/// fn finish(self) -> Result<Query, FormError> {
155/// Ok(Query { q: self.0.unwrap_or_default() })
156/// }
157/// }
158///
159/// struct Query { q: String }
160/// impl FromForm for Query { type Builder = LastQ; }
161///
162/// assert_eq!(Query::from_query(b"q=a&q=b")?.q, "b");
163/// # Ok::<(), FormError>(())
164/// ```
165pub trait FormBuilder {
166 /// The struct being built.
167 type Output;
168
169 /// One text field.
170 ///
171 /// # Errors
172 ///
173 /// `Extract { .. }`.
174 fn text(&mut self, name: &str, value: &str) -> Result<(), FormError>;
175
176 /// One file.
177 ///
178 /// # Errors
179 ///
180 /// `Extract { .. }`.
181 fn file(&mut self, name: &str, file: FileValue<'_>) -> Result<(), FormError>;
182
183 /// The struct, once every field has arrived.
184 ///
185 /// # Errors
186 ///
187 /// `Extract { Missing | Invalid, .. }`.
188 fn finish(self) -> Result<Self::Output, FormError>;
189
190 /// What a file under `name` is to the struct: `Some(true)` a declared
191 /// field, `Some(false)` an ignored name, `None` an unknown one.
192 /// `FromForm::from_upload_mut` (feature `fs`) moves only declared files out of the
193 /// upload. The default says every name is declared.
194 fn file_field(&self, name: &str) -> Option<bool> {
195 let _ = name;
196 Some(true)
197 }
198
199 /// As [`finish`](Self::finish), but records every field's error in
200 /// `errors` instead of stopping at the first; `None` when any field
201 /// failed. The default records the one error `finish` returns.
202 fn finish_all(self, errors: &mut alloc::vec::Vec<FormError>) -> Option<Self::Output>
203 where
204 Self: Sized,
205 {
206 match self.finish() {
207 Ok(v) => Some(v),
208 Err(e) => {
209 errors.push(e);
210 None
211 }
212 }
213 }
214}
215
216/// Every problem with one submitted form, for showing the form again with
217/// a message beside each field: what
218/// [`FromForm::from_body_all`] returns.
219///
220/// A parse error (a malformed body, a limit) stops the parse, so it is the
221/// only error; extraction errors are collected for every field, at most one
222/// per received value plus one per missing field. No value is ever kept.
223///
224/// # Examples
225///
226/// ```
227/// use form_net::{ExtractError, FromForm};
228///
229/// form_net::form! {
230/// #[derive(Debug)]
231/// pub struct Signup { pub email: String, pub age: u8, pub tos: bool }
232/// }
233///
234/// let errors = Signup::from_body_all(b"application/x-www-form-urlencoded", b"age=300&role=admin").unwrap_err();
235/// assert_eq!(errors.len(), 3);
236/// assert_eq!(errors.field("age"), Some(ExtractError::Invalid("out of range")));
237/// assert_eq!(errors.field("email"), Some(ExtractError::Missing));
238/// assert_eq!(errors.unknown(), 1);
239/// assert_eq!(errors.status_hint(), 422);
240/// ```
241#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct FormErrors(alloc::vec::Vec<FormError>);
243
244impl FormErrors {
245 /// Every error, in the order found.
246 pub fn iter(&self) -> impl Iterator<Item = &FormError> {
247 self.0.iter()
248 }
249
250 /// How many errors.
251 #[must_use]
252 pub fn len(&self) -> usize {
253 self.0.len()
254 }
255
256 /// Whether there are none (never true for a returned value).
257 #[must_use]
258 pub fn is_empty(&self) -> bool {
259 self.0.is_empty()
260 }
261
262 /// The first error.
263 #[must_use]
264 pub fn first(&self) -> Option<&FormError> {
265 self.0.first()
266 }
267
268 /// The first extraction error for the field with this Rust name.
269 #[must_use]
270 pub fn field(&self, name: &str) -> Option<ExtractError> {
271 self.0.iter().find_map(|e| match e {
272 FormError::Extract {
273 reason,
274 field: Some(f),
275 } if *f == name => Some(*reason),
276 _ => None,
277 })
278 }
279
280 /// How many received names the struct neither declares nor ignores.
281 #[must_use]
282 pub fn unknown(&self) -> usize {
283 self.0
284 .iter()
285 .filter(|e| {
286 matches!(
287 e,
288 FormError::Extract {
289 reason: ExtractError::Unknown,
290 ..
291 }
292 )
293 })
294 .count()
295 }
296
297 /// The status hint of the first error (422 for extraction errors).
298 #[must_use]
299 pub fn status_hint(&self) -> u16 {
300 self.first().map_or(422, FormError::status_hint)
301 }
302}
303
304impl From<FormError> for FormErrors {
305 fn from(e: FormError) -> FormErrors {
306 FormErrors(alloc::vec![e])
307 }
308}
309
310impl core::fmt::Display for FormErrors {
311 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
312 match self.first() {
313 Some(e) if self.0.len() > 1 => {
314 write!(f, "{e} (and {} more)", self.0.len().saturating_sub(1))
315 }
316 Some(e) => write!(f, "{e}"),
317 None => f.write_str("no errors"),
318 }
319 }
320}
321
322#[cfg(feature = "std")]
323impl std::error::Error for FormErrors {}
324
325/// Feeds a builder and keeps going after extraction errors, recording them.
326struct Collect<'e, B> {
327 inner: B,
328 errors: &'e mut alloc::vec::Vec<FormError>,
329}
330
331impl<B: FormBuilder> FormBuilder for Collect<'_, B> {
332 type Output = B::Output;
333
334 fn text(&mut self, name: &str, value: &str) -> Result<(), FormError> {
335 match self.inner.text(name, value) {
336 Err(e @ FormError::Extract { .. }) => {
337 self.errors.push(e);
338 Ok(())
339 }
340 other => other,
341 }
342 }
343
344 fn file(&mut self, name: &str, file: FileValue<'_>) -> Result<(), FormError> {
345 match self.inner.file(name, file) {
346 Err(e @ FormError::Extract { .. }) => {
347 self.errors.push(e);
348 Ok(())
349 }
350 other => other,
351 }
352 }
353
354 fn finish(self) -> Result<B::Output, FormError> {
355 self.inner.finish()
356 }
357}
358
359/// The bytes the decoders use without allocating.
360const STACK: usize = 512;
361
362/// Feeds a urlencoded body to `builder`, decoding each name and value into a
363/// stack buffer when it fits and a heap buffer otherwise.
364fn feed_urlencoded<B: FormBuilder, I: Inspector>(
365 builder: &mut B,
366 input: &[u8],
367 limits: &Limits,
368 policy: &Policy,
369 inspector: &mut I,
370) -> Result<(), FormError> {
371 let mut name_stack = [0u8; STACK];
372 let mut value_stack = [0u8; STACK];
373 // The most of each stack buffer any pair wrote: only that is wiped.
374 let (mut name_hi, mut value_hi) = (0usize, 0usize);
375 let mut heap = alloc::vec::Vec::new();
376 // One UTF-8 pass over the body; plain names and values are cut from it.
377 let whole = core::str::from_utf8(input).ok();
378 let fed = (|| {
379 for pair in crate::urlencoded::raw::raw_pairs(input, limits) {
380 let pair = pair?;
381 // Nothing to decode (the usual case): borrow, no copy.
382 if let (Some(name), Some(value)) = (pair.name_text(whole), pair.value_text(whole)) {
383 let at = crate::error::Location::at(pair.offset());
384 match inspector.field(name, value.as_bytes()) {
385 crate::Verdict::Refuse(refusal) => {
386 return Err(FormError::Refused { refusal, at });
387 }
388 crate::Verdict::Skip => {}
389 _ => builder.text(name, value)?,
390 }
391 continue;
392 }
393 let name_len = pair.name_raw().len();
394 let value_len = pair.value_raw().len();
395 let (name_buf, value_buf): (&mut [u8], &mut [u8]) =
396 if name_len <= STACK && value_len <= STACK {
397 name_hi = name_hi.max(name_len);
398 value_hi = value_hi.max(value_len);
399 (&mut name_stack, &mut value_stack)
400 } else {
401 let needed = name_len.saturating_add(value_len);
402 crate::wipe::wipe(&mut heap);
403 heap.clear();
404 if needed > heap.capacity() {
405 // A new allocation; the old one is wiped, not freed
406 // with a decoded value in it.
407 let mut old =
408 core::mem::replace(&mut heap, alloc::vec::Vec::with_capacity(needed));
409 crate::wipe::wipe_vec(&mut old);
410 }
411 heap.resize(needed, 0);
412 heap.split_at_mut(name_len)
413 };
414 let name = pair.decode_name(name_buf, policy.percent)?;
415 let value = pair.decode_value(value_buf, policy.percent)?;
416 let at = crate::error::Location::at(pair.offset());
417 match inspector.field(name, value.as_bytes()) {
418 crate::Verdict::Refuse(refusal) => return Err(FormError::Refused { refusal, at }),
419 crate::Verdict::Skip => {}
420 _ => builder.text(name, value)?,
421 }
422 }
423 Ok(())
424 })();
425 crate::wipe::wipe(name_stack.get_mut(..name_hi).unwrap_or(&mut []));
426 crate::wipe::wipe(value_stack.get_mut(..value_hi).unwrap_or(&mut []));
427 crate::wipe::wipe_vec(&mut heap);
428 fed?;
429 if let crate::Verdict::Refuse(refusal) = inspector.end() {
430 return Err(FormError::Refused {
431 refusal,
432 at: crate::error::Location::at(input.len() as u64),
433 });
434 }
435 Ok(())
436}
437
438fn feed_multipart<B: FormBuilder>(
439 builder: &mut B,
440 mp: &crate::Multipart<'_>,
441) -> Result<(), FormError> {
442 for part in mp.parts() {
443 if part.is_file() {
444 builder.file(part.name(), FileValue::Buffered(part))?;
445 } else {
446 builder.text(part.name(), part.text()?)?;
447 }
448 }
449 Ok(())
450}
451
452/// A struct that can be read from a form. Implemented by
453/// [`form!`](crate::form); every method is provided.
454///
455/// # Examples
456///
457/// ```
458/// use form_net::FromForm;
459///
460/// form_net::form! {
461/// pub struct Login {
462/// pub email: String,
463/// pub password: form_net::Secret,
464/// pub remember: bool,
465/// }
466/// ignore = ["csrf_token"];
467/// }
468///
469/// let login = Login::from_body(b"application/x-www-form-urlencoded", b"email=a%40b.example&password=x&csrf_token=t")?;
470/// assert_eq!((login.email.as_str(), login.remember), ("a@b.example", false));
471/// # Ok::<(), form_net::FormError>(())
472/// ```
473pub trait FromForm: Sized {
474 /// The builder that collects the fields.
475 type Builder: FormBuilder<Output = Self> + Default;
476
477 /// From a urlencoded body, with the default limits and policy.
478 ///
479 /// # Errors
480 ///
481 /// The parser's errors; `Extract { .. }`.
482 fn from_urlencoded(input: &[u8]) -> Result<Self, FormError> {
483 Self::from_urlencoded_with(input, &Limits::DEFAULT, &Policy::DEFAULT)
484 }
485
486 /// From a query string; one leading `?` is stripped.
487 ///
488 /// # Errors
489 ///
490 /// As [`from_urlencoded`](Self::from_urlencoded).
491 fn from_query(query: &[u8]) -> Result<Self, FormError> {
492 Self::from_urlencoded(query.strip_prefix(b"?").unwrap_or(query))
493 }
494
495 /// From a buffered `multipart/form-data` body.
496 ///
497 /// # Errors
498 ///
499 /// The parser's errors; `Extract { .. }`.
500 fn from_multipart(content_type: &[u8], body: &[u8]) -> Result<Self, FormError> {
501 let mp = crate::parse_multipart(content_type, body)?;
502 let mut b = Self::Builder::default();
503 feed_multipart(&mut b, &mp)?;
504 b.finish()
505 }
506
507 /// From either encoding, by the `Content-Type`.
508 ///
509 /// # Errors
510 ///
511 /// `UnsupportedMediaType`; the parsers' errors; `Extract { .. }`.
512 fn from_body(content_type: &[u8], body: &[u8]) -> Result<Self, FormError> {
513 Self::from_body_with(content_type, body, &Limits::DEFAULT, &Policy::DEFAULT)
514 }
515
516 /// From a urlencoded body with explicit limits and policy.
517 ///
518 /// # Errors
519 ///
520 /// As [`from_urlencoded`](Self::from_urlencoded); `InvalidLimits`.
521 fn from_urlencoded_with(
522 input: &[u8],
523 limits: &Limits,
524 policy: &Policy,
525 ) -> Result<Self, FormError> {
526 limits.validate()?;
527 let mut b = Self::Builder::default();
528 feed_urlencoded(&mut b, input, limits, policy, &mut NoInspector)?;
529 b.finish()
530 }
531
532 /// From either encoding with explicit limits and policy.
533 ///
534 /// # Errors
535 ///
536 /// As [`from_body`](Self::from_body); `InvalidLimits`.
537 fn from_body_with(
538 content_type: &[u8],
539 body: &[u8],
540 limits: &Limits,
541 policy: &Policy,
542 ) -> Result<Self, FormError> {
543 Self::from_body_inspected(content_type, body, limits, policy, &mut NoInspector)
544 }
545
546 /// From either encoding, with an inspector in the same pass: the
547 /// inspector and the struct see the same bytes.
548 ///
549 /// # Errors
550 ///
551 /// As [`from_body_with`](Self::from_body_with); `Refused`.
552 fn from_body_inspected<I: Inspector>(
553 content_type: &[u8],
554 body: &[u8],
555 limits: &Limits,
556 policy: &Policy,
557 inspector: &mut I,
558 ) -> Result<Self, FormError> {
559 let result = (|| {
560 let mut b = Self::Builder::default();
561 match FormKind::detect(content_type)? {
562 FormKind::Urlencoded => {
563 limits.validate()?;
564 feed_urlencoded(&mut b, body, limits, policy, inspector)?;
565 }
566 FormKind::Multipart(_) => {
567 let mp = crate::multipart::parse_multipart_inspected_raw(
568 content_type,
569 body,
570 limits,
571 policy,
572 inspector,
573 )?;
574 feed_multipart(&mut b, &mp)?;
575 }
576 }
577 b.finish()
578 })();
579 if let Err(e) = &result {
580 inspector.failed(e);
581 }
582 result
583 }
584
585 /// From either encoding, collecting every field's error instead of
586 /// stopping at the first (D-090), so a form can be shown again with a
587 /// message beside each field.
588 ///
589 /// # Errors
590 ///
591 /// [`FormErrors`]: the parse error alone, or every extraction error.
592 fn from_body_all(content_type: &[u8], body: &[u8]) -> Result<Self, FormErrors> {
593 Self::from_body_all_with(content_type, body, &Limits::DEFAULT, &Policy::DEFAULT)
594 }
595
596 /// As [`from_body_all`](Self::from_body_all), with explicit limits and
597 /// policy.
598 ///
599 /// # Errors
600 ///
601 /// As [`from_body_all`](Self::from_body_all); `InvalidLimits`.
602 fn from_body_all_with(
603 content_type: &[u8],
604 body: &[u8],
605 limits: &Limits,
606 policy: &Policy,
607 ) -> Result<Self, FormErrors> {
608 let mut errors = alloc::vec::Vec::new();
609 let mut b = Collect {
610 inner: Self::Builder::default(),
611 errors: &mut errors,
612 };
613 match FormKind::detect(content_type)? {
614 FormKind::Urlencoded => {
615 limits.validate()?;
616 feed_urlencoded(&mut b, body, limits, policy, &mut NoInspector)?;
617 }
618 FormKind::Multipart(_) => {
619 let mp = crate::parse_multipart_with(content_type, body, limits, policy)?;
620 feed_multipart(&mut b, &mp)?;
621 }
622 }
623 let Collect { inner, errors: _ } = b;
624 let value = inner.finish_all(&mut errors);
625 match value {
626 Some(v) if errors.is_empty() => Ok(v),
627 _ => Err(FormErrors(errors)),
628 }
629 }
630
631 /// From an upload that stays usable (D-090): text fields are read by
632 /// reference, and only the files the struct declares are moved out of
633 /// it; files under `ignore = [..]` names stay in the upload for the
634 /// caller. Unknown names are refused as in
635 /// [`from_upload`](Self::from_upload). On error, files already moved
636 /// into the builder are dropped (deleted).
637 ///
638 /// # Errors
639 ///
640 /// `Extract { .. }`.
641 ///
642 /// # Examples
643 ///
644 /// ```
645 /// use form_net::FromForm;
646 ///
647 /// form_net::form! {
648 /// pub struct Post { pub title: String, pub cover: form_net::StoredFile }
649 /// ignore = ["attachment"];
650 /// }
651 ///
652 /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHi\r\n--X\r\nContent-Disposition: form-data; name=cover; filename=c.png\r\n\r\nC\r\n--X\r\nContent-Disposition: form-data; name=attachment; filename=a.pdf\r\n\r\nA\r\n--X--";
653 /// let mut upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
654 /// let post = Post::from_upload_mut(&mut upload)?;
655 /// assert_eq!((post.title.as_str(), post.cover.len()), ("Hi", 1));
656 /// assert_eq!(upload.field("title"), Some("Hi"));
657 /// assert!(upload.file("cover").is_none() && upload.file("attachment").is_some());
658 /// # Ok::<(), form_net::FormError>(())
659 /// ```
660 #[cfg(feature = "fs")]
661 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
662 fn from_upload_mut(upload: &mut crate::store::Upload) -> Result<Self, FormError> {
663 let mut b = Self::Builder::default();
664 for (name, value) in upload.iter() {
665 b.text(name, value)?;
666 }
667 let files = upload.files_mut();
668 let mut i = 0usize;
669 while let Some(file) = files.get(i) {
670 match b.file_field(file.name()) {
671 Some(true) => {
672 let file = files.remove(i);
673 let name = alloc::string::String::from(file.name());
674 b.file(&name, FileValue::Stored(file))?;
675 }
676 Some(false) => i = i.wrapping_add(1),
677 None => return Err(__unknown()),
678 }
679 }
680 b.finish()
681 }
682
683 /// From a finished upload: text fields as text, files moved into the
684 /// struct as `StoredFile`s.
685 ///
686 /// # Errors
687 ///
688 /// `Extract { .. }`.
689 #[cfg(feature = "fs")]
690 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
691 fn from_upload(upload: crate::store::Upload) -> Result<Self, FormError> {
692 let (fields, files) = upload.into_parts();
693 let mut b = Self::Builder::default();
694 for (name, value) in fields.iter() {
695 b.text(name, value)?;
696 }
697 for file in files {
698 let name = alloc::string::String::from(file.name());
699 b.file(&name, FileValue::Stored(file))?;
700 }
701 b.finish()
702 }
703}
704
705/// Maps a field's [`ValueError`] to the error `form!` returns. Not part of
706/// the API: used by the macro expansion.
707#[doc(hidden)]
708#[must_use]
709pub fn __value_error(e: ValueError, field: &'static str) -> FormError {
710 let reason = match e {
711 ValueError::Missing => ExtractError::Missing,
712 ValueError::Duplicate => ExtractError::Duplicate,
713 ValueError::Invalid(why) => ExtractError::Invalid(why),
714 _ => ExtractError::WrongKind,
715 };
716 FormError::Extract {
717 reason,
718 field: Some(field),
719 }
720}
721
722/// Records a field's error at `finish_all`, leaving out a `Missing` for a
723/// field that already failed when its value arrived. Not part of the API.
724#[doc(hidden)]
725pub fn __push_field_error(
726 errors: &mut alloc::vec::Vec<FormError>,
727 e: ValueError,
728 field: &'static str,
729) {
730 let seen = errors
731 .iter()
732 .any(|x| matches!(x, FormError::Extract { field: Some(f), .. } if *f == field));
733 if !(seen && e == ValueError::Missing) {
734 errors.push(__value_error(e, field));
735 }
736}
737
738/// The error for a name the struct does not declare. Not part of the API.
739#[doc(hidden)]
740#[must_use]
741pub const fn __unknown() -> FormError {
742 FormError::Extract {
743 reason: ExtractError::Unknown,
744 field: None,
745 }
746}