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}