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/v0.5/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())?.get(0).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            .map(|e| e.iter())
183            .flatten()
184            .chain(self.form_errors.iter())
185    }
186
187    /// Returns the errors associated with the field `name`. This method is
188    /// roughly equivalent to:
189    ///
190    /// ```rust
191    /// # use rocket::form::{Context, name::Name};
192    /// # let context = Context::default();
193    /// # let name = Name::new("foo");
194    /// context.errors().filter(|e| e.is_for(name))
195    /// # ;
196    /// ```
197    ///
198    /// That is, it uses [`Error::is_for()`] to determine which errors are
199    /// associated with the field named `name`. This considers all errors whose
200    /// associated field name is a prefix of `name` to be an error for the field
201    /// named `name`. In other words, it associates parent field errors with
202    /// their children: `a.b`'s errors apply to `a.b.c`, `a.b.d` and so on but
203    /// not `a.c`.
204    ///
205    /// Lookup is case-sensitive but key-separator (`.` or `[]`) insensitive.
206    ///
207    /// # Example
208    ///
209    /// ```rust
210    /// # use rocket::post;
211    /// # type T = String;
212    /// use rocket::form::{Form, Contextual};
213    ///
214    /// #[post("/submit", data = "<form>")]
215    /// fn submit(form: Form<Contextual<'_, T>>) {
216    ///     // Get all errors for field `id`.
217    ///     let id = form.context.field_errors("id");
218    ///
219    ///     // Get all errors for `foo.bar` or `foo` if `foo` failed first.
220    ///     let foo_bar = form.context.field_errors("foo.bar");
221    /// }
222    /// ```
223    pub fn field_errors<'a, N>(&'a self, name: N) -> impl Iterator<Item = &Error<'v>> + '_
224        where N: AsRef<Name> + 'a
225    {
226        self.errors.values()
227            .map(|e| e.iter())
228            .flatten()
229            .filter(move |e| e.is_for(&name))
230    }
231
232    /// Returns the errors associated _exactly_ with the field `name`. Prefer
233    /// [`Context::field_errors()`] instead.
234    ///
235    /// This method is roughly equivalent to:
236    ///
237    /// ```rust
238    /// # use rocket::form::{Context, name::Name};
239    /// # let context = Context::default();
240    /// # let name = Name::new("foo");
241    /// context.errors().filter(|e| e.is_for_exactly(name))
242    /// # ;
243    /// ```
244    ///
245    /// That is, it uses [`Error::is_for_exactly()`] to determine which errors
246    /// are associated with the field named `name`. This considers _only_ errors
247    /// whose associated field name is _exactly_ `name` to be an error for the
248    /// field named `name`. This is _not_ what is typically desired as it
249    /// ignores errors that occur in the parent which will result in missing
250    /// errors associated with its children. Use [`Context::field_errors()`] in
251    /// almost all cases.
252    ///
253    /// Lookup is case-sensitive but key-separator (`.` or `[]`) insensitive.
254    ///
255    /// # Example
256    ///
257    /// ```rust
258    /// # use rocket::post;
259    /// # type T = String;
260    /// use rocket::form::{Form, Contextual};
261    ///
262    /// #[post("/submit", data = "<form>")]
263    /// fn submit(form: Form<Contextual<'_, T>>) {
264    ///     // Get all errors for field `id`.
265    ///     let id = form.context.exact_field_errors("id");
266    ///
267    ///     // Get all errors exactly for `foo.bar`. If `foo` failed, we will
268    ///     // this will return no errors. Use `Context::field_errors()`.
269    ///     let foo_bar = form.context.exact_field_errors("foo.bar");
270    /// }
271    /// ```
272    pub fn exact_field_errors<'a, N>(&'a self, name: N) -> impl Iterator<Item = &Error<'v>> + '_
273        where N: AsRef<Name> + 'a
274    {
275        self.errors.values()
276            .map(|e| e.iter())
277            .flatten()
278            .filter(move |e| e.is_for_exactly(&name))
279    }
280
281    /// Returns the `max` of the statuses associated with all field errors.
282    ///
283    /// See [`Error::status()`] for details on how an error status is computed.
284    ///
285    /// # Example
286    ///
287    /// ```rust
288    /// # use rocket::post;
289    /// # type T = String;
290    /// use rocket::http::Status;
291    /// use rocket::form::{Form, Contextual};
292    ///
293    /// #[post("/submit", data = "<form>")]
294    /// fn submit(form: Form<Contextual<'_, T>>) -> (Status, &'static str) {
295    ///     (form.context.status(), "Thanks!")
296    /// }
297    /// ```
298    pub fn status(&self) -> Status {
299        self.status
300    }
301
302    /// Inject a single error `error` into the context.
303    ///
304    /// # Example
305    ///
306    /// ```rust
307    /// # use rocket::post;
308    /// # type T = String;
309    /// use rocket::http::Status;
310    /// use rocket::form::{Form, Contextual, Error};
311    ///
312    /// #[post("/submit", data = "<form>")]
313    /// fn submit(mut form: Form<Contextual<'_, T>>) {
314    ///     let error = Error::validation("a good error message")
315    ///         .with_name("field_name")
316    ///         .with_value("some field value");
317    ///
318    ///     form.context.push_error(error);
319    /// }
320    /// ```
321    pub fn push_error(&mut self, error: Error<'v>) {
322        self.status = std::cmp::max(self.status, error.status());
323        match error.name {
324            Some(ref name) => match self.errors.get_mut(name) {
325                Some(errors) => errors.push(error),
326                None => { self.errors.insert(name.clone(), error.into()); },
327            }
328            None => self.form_errors.push(error)
329        }
330    }
331
332    /// Inject all of the errors in `errors` into the context.
333    ///
334    /// # Example
335    ///
336    /// ```rust
337    /// # use rocket::post;
338    /// # type T = String;
339    /// use rocket::http::Status;
340    /// use rocket::form::{Form, Contextual, Error};
341    ///
342    /// #[post("/submit", data = "<form>")]
343    /// fn submit(mut form: Form<Contextual<'_, T>>) {
344    ///     let error = Error::validation("a good error message")
345    ///         .with_name("field_name")
346    ///         .with_value("some field value");
347    ///
348    ///     form.context.push_errors(vec![error]);
349    /// }
350    /// ```
351    pub fn push_errors<E: Into<Errors<'v>>>(&mut self, errors: E) {
352        errors.into().into_iter().for_each(|e| self.push_error(e))
353    }
354}
355
356impl<'f> From<Errors<'f>> for Context<'f> {
357    fn from(errors: Errors<'f>) -> Self {
358        let mut context = Context::default();
359        context.push_errors(errors);
360        context
361    }
362}
363
364#[crate::async_trait]
365impl<'v, T: FromForm<'v>> FromForm<'v> for Contextual<'v, T> {
366    type Context = (<T as FromForm<'v>>::Context, Context<'v>);
367
368    fn init(opts: Options) -> Self::Context {
369        (T::init(opts), Context::default())
370    }
371
372    fn push_value((ref mut val_ctxt, ctxt): &mut Self::Context, field: ValueField<'v>) {
373        ctxt.values.entry(field.name.source()).or_default().push(field.value);
374        T::push_value(val_ctxt, field);
375    }
376
377    async fn push_data((ref mut val_ctxt, ctxt): &mut Self::Context, field: DataField<'v, '_>) {
378        ctxt.data_fields.insert(field.name.source());
379        T::push_data(val_ctxt, field).await;
380    }
381
382    fn push_error((_, ref mut ctxt): &mut Self::Context, e: Error<'v>) {
383        ctxt.push_error(e);
384    }
385
386    fn finalize((val_ctxt, mut context): Self::Context) -> Result<'v, Self> {
387        let value = match T::finalize(val_ctxt) {
388            Ok(value) => Some(value),
389            Err(errors) => {
390                context.push_errors(errors);
391                None
392            }
393        };
394
395        Ok(Contextual { value, context })
396    }
397}