mirror of https://github.com/rwf2/Rocket.git
Document the 'Contextual' form guard.
This commit is contained in:
parent
ab13d73b30
commit
8a9000a9cb
|
@ -4,50 +4,155 @@ use indexmap::{IndexMap, IndexSet};
|
||||||
use crate::form::prelude::*;
|
use crate::form::prelude::*;
|
||||||
use crate::http::Status;
|
use crate::http::Status;
|
||||||
|
|
||||||
/// An infallible form guard that records form fields while parsing any form
|
/// An infallible form guard that records form fields and errors during parsing.
|
||||||
/// type.
|
|
||||||
///
|
///
|
||||||
/// See the [forms guide](https://rocket.rs/master/guide/requests/#context) for
|
/// This form guard _never fails_. It should be use _only_ when the form
|
||||||
/// usage details.
|
/// [`Context`] is required. In all other cases, prefer to use `T` directly.
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
///
|
||||||
|
/// `Contextual` acts as a proxy for any form type, recording all submitted form
|
||||||
|
/// values and produced errors and associating them with their corresponding
|
||||||
|
/// field name. `Contextual` is particularly useful for rendering forms with
|
||||||
|
/// previously submitted values and errors associated with form input.
|
||||||
|
///
|
||||||
|
/// To retrieve the context for a form, use `Form<Contextual<'_, T>>` as a data
|
||||||
|
/// guard, where `T` implements `FromForm`. The `context` field contains the
|
||||||
|
/// form's [`Context`]:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::post;
|
||||||
|
/// # type T = String;
|
||||||
|
/// use rocket::form::{Form, Contextual};
|
||||||
|
///
|
||||||
|
/// #[post("/submit", data = "<form>")]
|
||||||
|
/// fn submit(form: Form<Contextual<'_, T>>) {
|
||||||
|
/// if let Some(ref value) = form.value {
|
||||||
|
/// // The form parsed successfully. `value` is the `T`.
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // We can retrieve raw field values and errors.
|
||||||
|
/// let raw_id_value = form.context.field_value("id");
|
||||||
|
/// let id_errors = form.context.field_errors("id");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// `Context` serializes as a map, so it can be rendered in templates that
|
||||||
|
/// require `Serialize` types. See the [forms guide] for further usage details.
|
||||||
|
///
|
||||||
|
/// [forms guide]: https://rocket.rs/master/guide/requests/#context
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Contextual<'v, T> {
|
pub struct Contextual<'v, T> {
|
||||||
/// The value, if it could be successfully parsed.
|
/// The value, if it was successfully parsed, or `None` otherwise.
|
||||||
pub value: Option<T>,
|
pub value: Option<T>,
|
||||||
/// The context containig all received fields, values, and errors.
|
/// The context with all submitted fields and associated values and errors.
|
||||||
pub context: Context<'v>
|
pub context: Context<'v>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A form context containing received fields, values, and encountered errors.
|
/// A form context containing received fields, values, and encountered errors.
|
||||||
///
|
///
|
||||||
|
/// A value of this type is produced by the [`Contextual`] form guard in its
|
||||||
|
/// [`context`](Contextual::context) field. `Context` contains an entry for
|
||||||
|
/// every form field submitted by the client regardless of whether the field
|
||||||
|
/// parsed or validated successfully.
|
||||||
|
///
|
||||||
|
/// # Field Values
|
||||||
|
///
|
||||||
|
/// The original, submitted field value(s) for a _value_ field can be retrieved
|
||||||
|
/// via [`Context::field_value()`] or [`Context::field_values()`]. Data fields do not have
|
||||||
|
/// their values recorded. All submitted field names, including data field
|
||||||
|
/// names, can be retrieved via [`Context::fields()`].
|
||||||
|
///
|
||||||
|
/// # Field Errors
|
||||||
|
///
|
||||||
/// # Serialization
|
/// # Serialization
|
||||||
///
|
///
|
||||||
/// When a value of this type is serialized, a `struct` or map with the
|
/// When a value of this type is serialized, a `struct` or map with the
|
||||||
/// following fields is emitted:
|
/// following fields is emitted:
|
||||||
///
|
///
|
||||||
/// | field | type | description |
|
/// | field | type | description |
|
||||||
/// |---------------|-------------------|------------------------------------------------|
|
/// |---------------|------------------------------------|--------------------------------------|
|
||||||
/// | `errors` | &str => &[Error] | map from a field name to errors it encountered |
|
/// | `errors` | map: string to array of [`Error`]s | maps a field name to its errors |
|
||||||
/// | `values` | &str => &[&str] | map from a field name to its submitted values |
|
/// | `values` | map: string to array of strings | maps a field name to its form values |
|
||||||
/// | `data_values` | &[&str] | field names of all data fields received |
|
/// | `data_fields` | array of strings | field names of all form data fields |
|
||||||
/// | `form_errors` | &[Error] | errors not corresponding to specific fields |
|
/// | `form_errors` | array of [`Error`]s | errors not associated with a field |
|
||||||
///
|
///
|
||||||
/// See [`Error`] for details on how an `Error` is serialized.
|
/// See [`Error`](Error#serialization) for `Error` serialization details.
|
||||||
#[derive(Debug, Default, Serialize)]
|
#[derive(Debug, Default, Serialize)]
|
||||||
pub struct Context<'v> {
|
pub struct Context<'v> {
|
||||||
errors: IndexMap<NameBuf<'v>, Errors<'v>>,
|
errors: IndexMap<NameBuf<'v>, Errors<'v>>,
|
||||||
values: IndexMap<&'v Name, Vec<&'v str>>,
|
values: IndexMap<&'v Name, Vec<&'v str>>,
|
||||||
data_values: IndexSet<&'v Name>,
|
data_fields: IndexSet<&'v Name>,
|
||||||
form_errors: Errors<'v>,
|
form_errors: Errors<'v>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
status: Status,
|
status: Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'v> Context<'v> {
|
impl<'v> Context<'v> {
|
||||||
pub fn value<N: AsRef<Name>>(&self, name: N) -> Option<&'v str> {
|
/// Returns the names of all submitted form fields, both _value_ and _data_
|
||||||
|
/// fields.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::post;
|
||||||
|
/// # type T = String;
|
||||||
|
/// use rocket::form::{Form, Contextual};
|
||||||
|
///
|
||||||
|
/// #[post("/submit", data = "<form>")]
|
||||||
|
/// fn submit(form: Form<Contextual<'_, T>>) {
|
||||||
|
/// let field_names = form.context.fields();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn fields(&self) -> impl Iterator<Item = &'v Name> + '_ {
|
||||||
|
self.values.iter()
|
||||||
|
.map(|(name, _)| *name)
|
||||||
|
.chain(self.data_fields.iter().copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the _first_ value, if any, submitted for the _value_ field named
|
||||||
|
/// `name`.
|
||||||
|
///
|
||||||
|
/// The type of `name` may be `&Name`, `&str`, or `&RawStr`. Lookup is
|
||||||
|
/// case-sensitive but key-seperator (`.` or `[]`) insensitive.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::post;
|
||||||
|
/// # type T = String;
|
||||||
|
/// use rocket::form::{Form, Contextual};
|
||||||
|
///
|
||||||
|
/// #[post("/submit", data = "<form>")]
|
||||||
|
/// fn submit(form: Form<Contextual<'_, T>>) {
|
||||||
|
/// let first_value_for_id = form.context.field_value("id");
|
||||||
|
/// let first_value_for_foo_bar = form.context.field_value("foo.bar");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn field_value<N: AsRef<Name>>(&self, name: N) -> Option<&'v str> {
|
||||||
self.values.get(name.as_ref())?.get(0).cloned()
|
self.values.get(name.as_ref())?.get(0).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn values<'a, N>(&'a self, name: N) -> impl Iterator<Item = &'v str> + 'a
|
/// Returns the values, if any, submitted for the _value_ field named
|
||||||
|
/// `name`.
|
||||||
|
///
|
||||||
|
/// The type of `name` may be `&Name`, `&str`, or `&RawStr`. Lookup is
|
||||||
|
/// case-sensitive but key-seperator (`.` or `[]`) insensitive.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::post;
|
||||||
|
/// # type T = String;
|
||||||
|
/// use rocket::form::{Form, Contextual};
|
||||||
|
///
|
||||||
|
/// #[post("/submit", data = "<form>")]
|
||||||
|
/// fn submit(form: Form<Contextual<'_, T>>) {
|
||||||
|
/// let values_for_id = form.context.field_values("id");
|
||||||
|
/// let values_for_foo_bar = form.context.field_values("foo.bar");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn field_values<N>(&self, name: N) -> impl Iterator<Item = &'v str> + '_
|
||||||
where N: AsRef<Name>
|
where N: AsRef<Name>
|
||||||
{
|
{
|
||||||
self.values
|
self.values
|
||||||
|
@ -57,27 +162,139 @@ impl<'v> Context<'v> {
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_error<N: AsRef<Name>>(&self, name: &N) -> bool {
|
/// Returns an iterator over all of the errors in the context, including
|
||||||
self.errors(name).next().is_some()
|
/// those not associated with any field.
|
||||||
}
|
///
|
||||||
|
/// # Example
|
||||||
pub fn errors<'a, N>(&'a self, name: &'a N) -> impl Iterator<Item = &Error<'v>> + 'a
|
///
|
||||||
where N: AsRef<Name> + ?Sized
|
/// ```rust
|
||||||
{
|
/// # use rocket::post;
|
||||||
let name = name.as_ref();
|
/// # type T = String;
|
||||||
name.prefixes()
|
/// use rocket::form::{Form, Contextual};
|
||||||
.filter_map(move |name| self.errors.get(name))
|
///
|
||||||
.map(|e| e.iter())
|
/// #[post("/submit", data = "<form>")]
|
||||||
.flatten()
|
/// fn submit(form: Form<Contextual<'_, T>>) {
|
||||||
}
|
/// let errors = form.context.errors();
|
||||||
|
/// }
|
||||||
pub fn all_errors(&self) -> impl Iterator<Item = &Error<'v>> {
|
/// ```
|
||||||
|
pub fn errors(&self) -> impl Iterator<Item = &Error<'v>> {
|
||||||
self.errors.values()
|
self.errors.values()
|
||||||
.map(|e| e.iter())
|
.map(|e| e.iter())
|
||||||
.flatten()
|
.flatten()
|
||||||
.chain(self.form_errors.iter())
|
.chain(self.form_errors.iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the errors associated with the field `name`. This method is
|
||||||
|
/// roughly equivalent to:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::form::{Context, name::Name};
|
||||||
|
/// # let context = Context::default();
|
||||||
|
/// # let name = Name::new("foo");
|
||||||
|
/// context.errors().filter(|e| e.is_for(name))
|
||||||
|
/// # ;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// That is, it uses [`Error::is_for()`] to determine which errors are
|
||||||
|
/// associated with the field named `name`. This considers all errors whose
|
||||||
|
/// associated field name is a prefix of `name` to be an error for the field
|
||||||
|
/// named `name`. In other words, it associates parent field errors with
|
||||||
|
/// their children: `a.b`'s errors apply to `a.b.c`, `a.b.d` and so on but
|
||||||
|
/// not `a.c`.
|
||||||
|
///
|
||||||
|
/// Lookup is case-sensitive but key-seperator (`.` or `[]`) insensitive.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::post;
|
||||||
|
/// # type T = String;
|
||||||
|
/// use rocket::form::{Form, Contextual};
|
||||||
|
///
|
||||||
|
/// #[post("/submit", data = "<form>")]
|
||||||
|
/// fn submit(form: Form<Contextual<'_, T>>) {
|
||||||
|
/// // Get all errors for field `id`.
|
||||||
|
/// let id = form.context.field_errors("id");
|
||||||
|
///
|
||||||
|
/// // Get all errors for `foo.bar` or `foo` if `foo` failed first.
|
||||||
|
/// let foo_bar = form.context.field_errors("foo.bar");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn field_errors<'a, N>(&'a self, name: N) -> impl Iterator<Item = &Error<'v>> + '_
|
||||||
|
where N: AsRef<Name> + 'a
|
||||||
|
{
|
||||||
|
self.errors.values()
|
||||||
|
.map(|e| e.iter())
|
||||||
|
.flatten()
|
||||||
|
.filter(move |e| e.is_for(&name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the errors associated _exactly_ with the field `name`. Prefer
|
||||||
|
/// [`Context::field_errors()`] instead.
|
||||||
|
///
|
||||||
|
/// This method is roughly equivalent to:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::form::{Context, name::Name};
|
||||||
|
/// # let context = Context::default();
|
||||||
|
/// # let name = Name::new("foo");
|
||||||
|
/// context.errors().filter(|e| e.is_for_exactly(name))
|
||||||
|
/// # ;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// That is, it uses [`Error::is_for_exactly()`] to determine which errors
|
||||||
|
/// are associated with the field named `name`. This considers _only_ errors
|
||||||
|
/// whose associated field name is _exactly_ `name` to be an error for the
|
||||||
|
/// field named `name`. This is _not_ what is typically desired as it
|
||||||
|
/// ignores errors that occur in the parent which will result in missing
|
||||||
|
/// errors associated with its chilren. Use [`Context::field_errors()`] in
|
||||||
|
/// almost all cases.
|
||||||
|
///
|
||||||
|
/// Lookup is case-sensitive but key-seperator (`.` or `[]`) insensitive.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::post;
|
||||||
|
/// # type T = String;
|
||||||
|
/// use rocket::form::{Form, Contextual};
|
||||||
|
///
|
||||||
|
/// #[post("/submit", data = "<form>")]
|
||||||
|
/// fn submit(form: Form<Contextual<'_, T>>) {
|
||||||
|
/// // Get all errors for field `id`.
|
||||||
|
/// let id = form.context.exact_field_errors("id");
|
||||||
|
///
|
||||||
|
/// // Get all errors exactly for `foo.bar`. If `foo` failed, we will
|
||||||
|
/// // this will return no erorrs. Use `Context::field_errors()`.
|
||||||
|
/// let foo_bar = form.context.exact_field_errors("foo.bar");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn exact_field_errors<'a, N>(&'a self, name: N) -> impl Iterator<Item = &Error<'v>> + '_
|
||||||
|
where N: AsRef<Name> + 'a
|
||||||
|
{
|
||||||
|
self.errors.values()
|
||||||
|
.map(|e| e.iter())
|
||||||
|
.flatten()
|
||||||
|
.filter(move |e| e.is_for_exactly(&name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `max` of the statuses associated with all field errors.
|
||||||
|
///
|
||||||
|
/// See [`Error::status()`] for details on how an error status is computed.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::post;
|
||||||
|
/// # type T = String;
|
||||||
|
/// use rocket::http::Status;
|
||||||
|
/// use rocket::form::{Form, Contextual};
|
||||||
|
///
|
||||||
|
/// #[post("/submit", data = "<form>")]
|
||||||
|
/// fn submit(form: Form<Contextual<'_, T>>) -> (Status, &'static str) {
|
||||||
|
/// (form.context.status(), "Thanks!")
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn status(&self) -> Status {
|
pub fn status(&self) -> Status {
|
||||||
self.status
|
self.status
|
||||||
}
|
}
|
||||||
|
@ -119,11 +336,8 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Contextual<'v, T> {
|
||||||
T::push_value(val_ctxt, field);
|
T::push_value(val_ctxt, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn push_data(
|
async fn push_data((ref mut val_ctxt, ctxt): &mut Self::Context, field: DataField<'v, '_>) {
|
||||||
(ref mut val_ctxt, ctxt): &mut Self::Context,
|
ctxt.data_fields.insert(field.name.source());
|
||||||
field: DataField<'v, '_>
|
|
||||||
) {
|
|
||||||
ctxt.data_values.insert(field.name.source());
|
|
||||||
T::push_data(val_ctxt, field).await;
|
T::push_data(val_ctxt, field).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use indexmap::IndexMap;
|
||||||
use crate::form::prelude::*;
|
use crate::form::prelude::*;
|
||||||
use crate::http::uncased::AsUncased;
|
use crate::http::uncased::AsUncased;
|
||||||
|
|
||||||
/// Trait for implementing form guards: types parseable from HTTP form fields.
|
/// Trait implemented by form guards: types parseable from HTTP forms.
|
||||||
///
|
///
|
||||||
/// Only form guards that are _collections_, that is, collect more than one form
|
/// Only form guards that are _collections_, that is, collect more than one form
|
||||||
/// field while parsing, should implement `FromForm`. All other types should
|
/// field while parsing, should implement `FromForm`. All other types should
|
||||||
|
|
|
@ -178,12 +178,6 @@ impl<N: AsRef<Name> + ?Sized> PartialEq<N> for NameBuf<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<Name> for NameBuf<'_> {
|
|
||||||
fn eq(&self, other: &Name) -> bool {
|
|
||||||
self.keys().eq(other.keys())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<NameBuf<'_>> for Name {
|
impl PartialEq<NameBuf<'_>> for Name {
|
||||||
fn eq(&self, other: &NameBuf<'_>) -> bool {
|
fn eq(&self, other: &NameBuf<'_>) -> bool {
|
||||||
self.keys().eq(other.keys())
|
self.keys().eq(other.keys())
|
||||||
|
|
|
@ -210,6 +210,12 @@ impl AsRef<Name> for RawStr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<Name> for Name {
|
||||||
|
fn as_ref(&self) -> &Name {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Eq for Name { }
|
impl Eq for Name { }
|
||||||
|
|
||||||
impl std::hash::Hash for Name {
|
impl std::hash::Hash for Name {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#![cfg_attr(nightly, feature(decl_macro))]
|
#![cfg_attr(nightly, feature(decl_macro))]
|
||||||
|
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
//! # Rocket - Core API Documentation
|
//! # Rocket - Core API Documentation
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -67,6 +67,9 @@ fn index<'r>() -> Template {
|
||||||
Template::render("index", &Context::default())
|
Template::render("index", &Context::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: We use `Contextual` here because we want to collect all submitted form
|
||||||
|
// fields to re-render forms with submitted values on error. If you have no such
|
||||||
|
// need, do not use `Contextual`. Use the equivalent of `Form<Submit<'_>>`.
|
||||||
#[post("/", data = "<form>")]
|
#[post("/", data = "<form>")]
|
||||||
fn submit<'r>(form: Form<Contextual<'r, Submit<'r>>>) -> (Status, Template) {
|
fn submit<'r>(form: Form<Contextual<'r, Submit<'r>>>) -> (Status, Template) {
|
||||||
let template = match form.value {
|
let template = match form.value {
|
||||||
|
|
|
@ -1521,10 +1521,11 @@ map! {
|
||||||
|
|
||||||
### Context
|
### Context
|
||||||
|
|
||||||
The [`Contextual`] type acts as a proxy for any form type, recording all of the
|
The [`Contextual`] form guard acts as a proxy for any other form guard,
|
||||||
submitted form values and produced errors and associating them with their
|
recording all submitted form values and produced errors and associating them
|
||||||
corresponding field name. `Contextual` is particularly useful to render a form
|
with their corresponding field name. `Contextual` is particularly useful for
|
||||||
with previously submitted values and render errors associated with a form input.
|
rendering forms with previously submitted values and errors associated with form
|
||||||
|
input.
|
||||||
|
|
||||||
To retrieve the context for a form, use `Form<Contextual<'_, T>>` as a data
|
To retrieve the context for a form, use `Form<Contextual<'_, T>>` as a data
|
||||||
guard, where `T` implements `FromForm`. The `context` field contains the form's
|
guard, where `T` implements `FromForm`. The `context` field contains the form's
|
||||||
|
@ -1542,18 +1543,22 @@ fn submit(form: Form<Contextual<'_, T>>) {
|
||||||
// The form parsed successfully. `value` is the `T`.
|
// The form parsed successfully. `value` is the `T`.
|
||||||
}
|
}
|
||||||
|
|
||||||
// In all cases, `form.context` contains the `Context`.
|
|
||||||
// We can retrieve raw field values and errors.
|
// We can retrieve raw field values and errors.
|
||||||
let raw_id_value = form.context.value("id");
|
let raw_id_value = form.context.field_value("id");
|
||||||
let id_errors = form.context.errors("id");
|
let id_errors = form.context.field_errors("id");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`Context` is nesting-aware for errors. When `Context` is queried for errors for
|
||||||
|
a field named `foo.bar`, it returns errors for fields that are a prefix of
|
||||||
|
`foo.bar`, namely `foo` and `foo.bar`. Similarly, if queried for errors for a
|
||||||
|
field named `foo.bar.baz`, errors for field `foo`, `foo.bar`, and `foo.bar.baz`
|
||||||
|
will be returned.
|
||||||
|
|
||||||
`Context` serializes as a map, so it can be rendered in templates that require
|
`Context` serializes as a map, so it can be rendered in templates that require
|
||||||
`Serialize` types. See
|
`Serialize` types. See [`Context`] for details about its serialization format.
|
||||||
[`Context`](@api/rocket/form/struct.Context.html#Serialization) for details
|
The [forms example], too, makes use of form contexts, as well as every other
|
||||||
about its serialization format. The [forms example], too, makes use of form
|
forms feature.
|
||||||
contexts, as well as every other forms feature.
|
|
||||||
|
|
||||||
[`Contextual`]: @api/rocket/form/struct.Contextual.html
|
[`Contextual`]: @api/rocket/form/struct.Contextual.html
|
||||||
[`Context`]: @api/rocket/form/struct.Context.html
|
[`Context`]: @api/rocket/form/struct.Context.html
|
||||||
|
|
Loading…
Reference in New Issue