rocket/form/
context.rs

1use serde::Serialize;
2use indexmap::{IndexMap, IndexSet};
3
4use crate::form::prelude::*;
5use crate::http::Status;
6
7/// An infallible form guard that records form fields and errors during parsing.
8///
9/// This form guard _never fails_. It should be use _only_ when the form
10/// [`Context`] is required. In all other cases, prefer to use `T` directly.
11///
12/// # Usage
13///
14/// `Contextual` acts as a proxy for any form type, recording all submitted form
15/// values and produced errors and associating them with their corresponding
16/// field name. `Contextual` is particularly useful for rendering forms with
17/// previously submitted values and errors associated with form input.
18///
19/// To retrieve the context for a form, use `Form<Contextual<'_, T>>` as a data
20/// guard, where `T` implements `FromForm`. The `context` field contains the
21/// form's [`Context`]:
22///
23/// ```rust
24/// # use rocket::post;
25/// # type T = String;
26/// use rocket::form::{Form, Contextual};
27///
28/// #[post("/submit", data = "<form>")]
29/// fn submit(form: Form<Contextual<'_, T>>) {
30///     if let Some(ref value) = form.value {
31///         // The form parsed successfully. `value` is the `T`.
32///     }
33///
34///     // We can retrieve raw field values and errors.
35///     let raw_id_value = form.context.field_value("id");
36///     let id_errors = form.context.field_errors("id");
37/// }
38/// ```
39///
40/// `Context` serializes as a map, so it can be rendered in templates that
41/// require `Serialize` types. See the [forms guide] for further usage details.
42///
43/// [forms guide]: https://rocket.rs/master/guide/requests/#context
44#[derive(Debug)]
45pub struct Contextual<'v, T> {
46    /// The value, if it was successfully parsed, or `None` otherwise.
47    pub value: Option<T>,
48    /// The context with all submitted fields and associated values and errors.
49    pub context: Context<'v>,
50}
51
52/// A form context containing received fields, values, and encountered errors.
53///
54/// A value of this type is produced by the [`Contextual`] form guard in its
55/// [`context`](Contextual::context) field. `Context` contains an entry for
56/// every form field submitted by the client regardless of whether the field
57/// parsed or validated successfully.
58///
59/// # Field Values
60///
61/// The original, submitted field value(s) for a _value_ field can be retrieved
62/// via [`Context::field_value()`] or [`Context::field_values()`]. Data fields do not have
63/// their values recorded. All submitted field names, including data field
64/// names, can be retrieved via [`Context::fields()`].
65///
66/// # Field Errors
67///
68/// # Serialization
69///
70/// When a value of this type is serialized, a `struct` or map with the
71/// following fields is emitted:
72///
73/// | field         | type                               | description                          |
74/// |---------------|------------------------------------|--------------------------------------|
75/// | `errors`      | map: string to array of [`Error`]s | maps a field name to its errors      |
76/// | `values`      | map: string to array of strings    | maps a field name to its form values |
77/// | `data_fields` | array of strings                   | field names of all form data fields  |
78/// | `form_errors` | array of [`Error`]s                | errors not associated with a field   |
79///
80/// See [`Error`](Error#serialization) for `Error` serialization details.
81#[derive(Debug, Default, Serialize)]
82pub struct Context<'v> {
83    errors: IndexMap<NameBuf<'v>, Errors<'v>>,
84    values: IndexMap<&'v Name, Vec<&'v str>>,
85    data_fields: IndexSet<&'v Name>,
86    form_errors: Errors<'v>,
87    #[serde(skip)]
88    status: Status,
89}
90
91impl<'v> Context<'v> {
92    /// Returns the names of all submitted form fields, both _value_ and _data_
93    /// fields.
94    ///
95    /// # Example
96    ///
97    /// ```rust
98    /// # use rocket::post;
99    /// # type T = String;
100    /// use rocket::form::{Form, Contextual};
101    ///
102    /// #[post("/submit", data = "<form>")]
103    /// fn submit(form: Form<Contextual<'_, T>>) {
104    ///     let field_names = form.context.fields();
105    /// }
106    /// ```
107    pub fn fields(&self) -> impl Iterator<Item = &'v Name> + '_ {
108        self.values.iter()
109            .map(|(name, _)| *name)
110            .chain(self.data_fields.iter().copied())
111    }
112
113    /// Returns the _first_ value, if any, submitted for the _value_ field named
114    /// `name`.
115    ///
116    /// The type of `name` may be `&Name`, `&str`, or `&RawStr`. Lookup is
117    /// case-sensitive but key-separator (`.` or `[]`) insensitive.
118    ///
119    /// # Example
120    ///
121    /// ```rust
122    /// # use rocket::post;
123    /// # type T = String;
124    /// use rocket::form::{Form, Contextual};
125    ///
126    /// #[post("/submit", data = "<form>")]
127    /// fn submit(form: Form<Contextual<'_, T>>) {
128    ///     let first_value_for_id = form.context.field_value("id");
129    ///     let first_value_for_foo_bar = form.context.field_value("foo.bar");
130    /// }
131    /// ```
132    pub fn field_value<N: AsRef<Name>>(&self, name: N) -> Option<&'v str> {
133        self.values.get(name.as_ref())?.first().cloned()
134    }
135
136    /// Returns the values, if any, submitted for the _value_ field named
137    /// `name`.
138    ///
139    /// The type of `name` may be `&Name`, `&str`, or `&RawStr`. Lookup is
140    /// case-sensitive but key-separator (`.` or `[]`) insensitive.
141    ///
142    /// # Example
143    ///
144    /// ```rust
145    /// # use rocket::post;
146    /// # type T = String;
147    /// use rocket::form::{Form, Contextual};
148    ///
149    /// #[post("/submit", data = "<form>")]
150    /// fn submit(form: Form<Contextual<'_, T>>) {
151    ///     let values_for_id = form.context.field_values("id");
152    ///     let values_for_foo_bar = form.context.field_values("foo.bar");
153    /// }
154    /// ```
155    pub fn field_values<N>(&self, name: N) -> impl Iterator<Item = &'v str> + '_
156        where N: AsRef<Name>
157    {
158        self.values
159            .get(name.as_ref())
160            .map(|e| e.iter().cloned())
161            .into_iter()
162            .flatten()
163    }
164
165    /// Returns an iterator over all of the errors in the context, including
166    /// those not associated with any field.
167    ///
168    /// # Example
169    ///
170    /// ```rust
171    /// # use rocket::post;
172    /// # type T = String;
173    /// use rocket::form::{Form, Contextual};
174    ///
175    /// #[post("/submit", data = "<form>")]
176    /// fn submit(form: Form<Contextual<'_, T>>) {
177    ///     let errors = form.context.errors();
178    /// }
179    /// ```
180    pub fn errors(&self) -> impl Iterator<Item = &Error<'v>> {
181        self.errors.values()
182            .flat_map(|e| e.iter())
183            .chain(self.form_errors.iter())
184    }
185
186    /// Returns the errors associated with the field `name`. This method is
187    /// roughly equivalent to:
188    ///
189    /// ```rust
190    /// # use rocket::form::{Context, name::Name};
191    /// # let context = Context::default();
192    /// # let name = Name::new("foo");
193    /// context.errors().filter(|e| e.is_for(name))
194    /// # ;
195    /// ```
196    ///
197    /// That is, it uses [`Error::is_for()`] to determine which errors are
198    /// associated with the field named `name`. This considers all errors whose
199    /// associated field name is a prefix of `name` to be an error for the field
200    /// named `name`. In other words, it associates parent field errors with
201    /// their children: `a.b`'s errors apply to `a.b.c`, `a.b.d` and so on but
202    /// not `a.c`.
203    ///
204    /// Lookup is case-sensitive but key-separator (`.` or `[]`) insensitive.
205    ///
206    /// # Example
207    ///
208    /// ```rust
209    /// # use rocket::post;
210    /// # type T = String;
211    /// use rocket::form::{Form, Contextual};
212    ///
213    /// #[post("/submit", data = "<form>")]
214    /// fn submit(form: Form<Contextual<'_, T>>) {
215    ///     // Get all errors for field `id`.
216    ///     let id = form.context.field_errors("id");
217    ///
218    ///     // Get all errors for `foo.bar` or `foo` if `foo` failed first.
219    ///     let foo_bar = form.context.field_errors("foo.bar");
220    /// }
221    /// ```
222    pub fn field_errors<'a, N>(&'a self, name: N) -> impl Iterator<Item = &'a Error<'v>> + 'a
223        where N: AsRef<Name> + 'a
224    {
225        self.errors.values()
226            .flat_map(|e| e.iter())
227            .filter(move |e| e.is_for(&name))
228    }
229
230    /// Returns the errors associated _exactly_ with the field `name`. Prefer
231    /// [`Context::field_errors()`] instead.
232    ///
233    /// This method is roughly equivalent to:
234    ///
235    /// ```rust
236    /// # use rocket::form::{Context, name::Name};
237    /// # let context = Context::default();
238    /// # let name = Name::new("foo");
239    /// context.errors().filter(|e| e.is_for_exactly(name))
240    /// # ;
241    /// ```
242    ///
243    /// That is, it uses [`Error::is_for_exactly()`] to determine which errors
244    /// are associated with the field named `name`. This considers _only_ errors
245    /// whose associated field name is _exactly_ `name` to be an error for the
246    /// field named `name`. This is _not_ what is typically desired as it
247    /// ignores errors that occur in the parent which will result in missing
248    /// errors associated with its children. Use [`Context::field_errors()`] in
249    /// almost all cases.
250    ///
251    /// Lookup is case-sensitive but key-separator (`.` or `[]`) insensitive.
252    ///
253    /// # Example
254    ///
255    /// ```rust
256    /// # use rocket::post;
257    /// # type T = String;
258    /// use rocket::form::{Form, Contextual};
259    ///
260    /// #[post("/submit", data = "<form>")]
261    /// fn submit(form: Form<Contextual<'_, T>>) {
262    ///     // Get all errors for field `id`.
263    ///     let id = form.context.exact_field_errors("id");
264    ///
265    ///     // Get all errors exactly for `foo.bar`. If `foo` failed, we will
266    ///     // this will return no errors. Use `Context::field_errors()`.
267    ///     let foo_bar = form.context.exact_field_errors("foo.bar");
268    /// }
269    /// ```
270    pub fn exact_field_errors<'a, N>(&'a self, name: N) -> impl Iterator<Item = &'a Error<'v>> + 'a
271        where N: AsRef<Name> + 'a
272    {
273        self.errors.values()
274            .flat_map(|e| e.iter())
275            .filter(move |e| e.is_for_exactly(&name))
276    }
277
278    /// Returns the `max` of the statuses associated with all field errors.
279    ///
280    /// See [`Error::status()`] for details on how an error status is computed.
281    ///
282    /// # Example
283    ///
284    /// ```rust
285    /// # use rocket::post;
286    /// # type T = String;
287    /// use rocket::http::Status;
288    /// use rocket::form::{Form, Contextual};
289    ///
290    /// #[post("/submit", data = "<form>")]
291    /// fn submit(form: Form<Contextual<'_, T>>) -> (Status, &'static str) {
292    ///     (form.context.status(), "Thanks!")
293    /// }
294    /// ```
295    pub fn status(&self) -> Status {
296        self.status
297    }
298
299    /// Inject a single error `error` into the context.
300    ///
301    /// # Example
302    ///
303    /// ```rust
304    /// # use rocket::post;
305    /// # type T = String;
306    /// use rocket::http::Status;
307    /// use rocket::form::{Form, Contextual, Error};
308    ///
309    /// #[post("/submit", data = "<form>")]
310    /// fn submit(mut form: Form<Contextual<'_, T>>) {
311    ///     let error = Error::validation("a good error message")
312    ///         .with_name("field_name")
313    ///         .with_value("some field value");
314    ///
315    ///     form.context.push_error(error);
316    /// }
317    /// ```
318    pub fn push_error(&mut self, error: Error<'v>) {
319        self.status = std::cmp::max(self.status, error.status());
320        match error.name {
321            Some(ref name) => match self.errors.get_mut(name) {
322                Some(errors) => errors.push(error),
323                None => { self.errors.insert(name.clone(), error.into()); },
324            }
325            None => self.form_errors.push(error)
326        }
327    }
328
329    /// Inject all of the errors in `errors` into the context.
330    ///
331    /// # Example
332    ///
333    /// ```rust
334    /// # use rocket::post;
335    /// # type T = String;
336    /// use rocket::http::Status;
337    /// use rocket::form::{Form, Contextual, Error};
338    ///
339    /// #[post("/submit", data = "<form>")]
340    /// fn submit(mut form: Form<Contextual<'_, T>>) {
341    ///     let error = Error::validation("a good error message")
342    ///         .with_name("field_name")
343    ///         .with_value("some field value");
344    ///
345    ///     form.context.push_errors(vec![error]);
346    /// }
347    /// ```
348    pub fn push_errors<E: Into<Errors<'v>>>(&mut self, errors: E) {
349        errors.into().into_iter().for_each(|e| self.push_error(e))
350    }
351}
352
353impl<'f> From<Errors<'f>> for Context<'f> {
354    fn from(errors: Errors<'f>) -> Self {
355        let mut context = Context::default();
356        context.push_errors(errors);
357        context
358    }
359}
360
361#[crate::async_trait]
362impl<'v, T: FromForm<'v>> FromForm<'v> for Contextual<'v, T> {
363    type Context = (<T as FromForm<'v>>::Context, Context<'v>);
364
365    fn init(opts: Options) -> Self::Context {
366        (T::init(opts), Context::default())
367    }
368
369    fn push_value((ref mut val_ctxt, ctxt): &mut Self::Context, field: ValueField<'v>) {
370        ctxt.values.entry(field.name.source()).or_default().push(field.value);
371        T::push_value(val_ctxt, field);
372    }
373
374    async fn push_data((ref mut val_ctxt, ctxt): &mut Self::Context, field: DataField<'v, '_>) {
375        ctxt.data_fields.insert(field.name.source());
376        T::push_data(val_ctxt, field).await;
377    }
378
379    fn push_error((_, ref mut ctxt): &mut Self::Context, e: Error<'v>) {
380        ctxt.push_error(e);
381    }
382
383    fn finalize((val_ctxt, mut context): Self::Context) -> Result<'v, Self> {
384        let value = match T::finalize(val_ctxt) {
385            Ok(value) => Some(value),
386            Err(errors) => {
387                context.push_errors(errors);
388                None
389            }
390        };
391
392        Ok(Contextual { value, context })
393    }
394}