rocket_community/form/
context.rs

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