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}