Skip to main content

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}