Update docs, examples and tests

This commit is contained in:
Matthew Pomes 2024-07-24 00:29:02 -05:00 committed by Sergio Benitez
parent 872595b8fa
commit 6b9fb1aa97
14 changed files with 86 additions and 71 deletions

View File

@ -1361,13 +1361,13 @@ pub fn catchers(input: TokenStream) -> TokenStream {
/// ///
/// ```rust /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// #[get("/person/<name>")] /// // #[get("/person/<name>")]
/// fn maybe(name: Option<&str>) { } /// // fn maybe(name: Option<&str>) { }
/// ///
/// let bob1 = uri!(maybe(name = "Bob")); /// // let bob1 = uri!(maybe(name = "Bob"));
/// let bob2 = uri!(maybe("Bob Smith")); /// // let bob2 = uri!(maybe("Bob Smith"));
/// assert_eq!(bob1.to_string(), "/person/Bob"); /// // assert_eq!(bob1.to_string(), "/person/Bob");
/// assert_eq!(bob2.to_string(), "/person/Bob%20Smith"); /// // assert_eq!(bob2.to_string(), "/person/Bob%20Smith");
/// ///
/// #[get("/person/<age>")] /// #[get("/person/<age>")]
/// fn ok(age: Result<u8, std::num::ParseIntError>) { } /// fn ok(age: Result<u8, std::num::ParseIntError>) { }

View File

@ -2,8 +2,10 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use std::num::ParseIntError;
use std::path::PathBuf; use std::path::PathBuf;
use rocket::error::Empty;
use rocket::http::CookieJar; use rocket::http::CookieJar;
use rocket::http::uri::fmt::{FromUriParam, Query}; use rocket::http::uri::fmt::{FromUriParam, Query};
use rocket::form::{Form, error::{Errors, ErrorKind}}; use rocket::form::{Form, error::{Errors, ErrorKind}};
@ -474,8 +476,8 @@ struct Third<'r> {
#[post("/<foo>/<bar>?<q1>&<rest..>")] #[post("/<foo>/<bar>?<q1>&<rest..>")]
fn optionals( fn optionals(
foo: Option<usize>, foo: Result<usize, ParseIntError>,
bar: Option<String>, bar: Result<String, Empty>,
q1: Result<usize, Errors<'_>>, q1: Result<usize, Errors<'_>>,
rest: Option<Third<'_>> rest: Option<Third<'_>>
) { } ) { }
@ -547,6 +549,13 @@ fn test_optional_uri_parameters() {
q1 = _, q1 = _,
rest = _, rest = _,
)) => "/10/hi%20there", )) => "/10/hi%20there",
// uri!(optionals(
// foo = 10,
// bar = Err(Empty),
// q1 = _,
// rest = _,
// )) => "/10/hi%20there",
} }
} }

View File

@ -128,15 +128,11 @@ pub type Outcome<'r, T, E = <T as FromData<'r>>::Error>
/// ///
/// * `Option<T>` /// * `Option<T>`
/// ///
/// Forwards to `T`'s `FromData` implementation, capturing the outcome. /// Forwards to `T`'s `FromData` implementation if there is data, capturing the outcome.
/// If `T` returns an Error or Forward, the `Option` returns the same.
/// ///
/// - **Fails:** Never. /// - **None:** If the data stream is empty.
/// /// - **Some:** If `T` succeeds to parse the incoming data.
/// - **Succeeds:** Always. If `T`'s `FromData` implementation succeeds, the
/// parsed value is returned in `Some`. If its implementation forwards or
/// fails, `None` is returned.
///
/// - **Forwards:** Never.
/// ///
/// * `Result<T, T::Error>` /// * `Result<T, T::Error>`
/// ///

View File

@ -106,7 +106,7 @@ use crate::http::uncased::AsUncased;
/// |------------------------|-------------|-------------------|--------|--------|----------------------------------------------------| /// |------------------------|-------------|-------------------|--------|--------|----------------------------------------------------|
/// | [`Strict<T>`] | **strict** | if `strict` `T` | if `T` | if `T` | `T: FromForm` | /// | [`Strict<T>`] | **strict** | if `strict` `T` | if `T` | if `T` | `T: FromForm` |
/// | [`Lenient<T>`] | **lenient** | if `lenient` `T` | if `T` | if `T` | `T: FromForm` | /// | [`Lenient<T>`] | **lenient** | if `lenient` `T` | if `T` | if `T` | `T: FromForm` |
/// | `Option<T>` | **strict** | `None` | if `T` | if `T` | Infallible, `T: FromForm` | /// | `Option<T>` | **strict** | `None` | if `T` | if `T` | `T: FromForm` |
/// | [`Result<T>`] | _inherit_ | `T::finalize()` | if `T` | if `T` | Infallible, `T: FromForm` | /// | [`Result<T>`] | _inherit_ | `T::finalize()` | if `T` | if `T` | Infallible, `T: FromForm` |
/// | `Vec<T>` | _inherit_ | `vec![]` | if `T` | if `T` | `T: FromForm` | /// | `Vec<T>` | _inherit_ | `vec![]` | if `T` | if `T` | `T: FromForm` |
/// | [`HashMap<K, V>`] | _inherit_ | `HashMap::new()` | if `V` | if `V` | `K: FromForm + Eq + Hash`, `V: FromForm` | /// | [`HashMap<K, V>`] | _inherit_ | `HashMap::new()` | if `V` | if `V` | `K: FromForm + Eq + Hash`, `V: FromForm` |
@ -151,6 +151,12 @@ use crate::http::uncased::AsUncased;
/// ///
/// ## Additional Notes /// ## Additional Notes
/// ///
/// * **`Option<T>` where `T: FromForm`**
///
/// `Option` is no longer Infallible. It now checks (depending on the strictness)
/// for whether any values were passed in, and returns an error if any values were passed in,
/// and the inner `T` failed to parse.
///
/// * **`Vec<T>` where `T: FromForm`** /// * **`Vec<T>` where `T: FromForm`**
/// ///
/// Parses a sequence of `T`'s. A new `T` is created whenever the field /// Parses a sequence of `T`'s. A new `T` is created whenever the field
@ -814,7 +820,7 @@ impl<'v, K, V> FromForm<'v> for BTreeMap<K, V>
} }
} }
struct OptionFormCtx<'v, T: FromForm<'v>> { pub struct OptionFormCtx<'v, T: FromForm<'v>> {
strict: bool, strict: bool,
has_field: bool, has_field: bool,
inner: T::Context, inner: T::Context,

View File

@ -105,18 +105,20 @@ fn defaults() {
&[] => Result<'_, bool> = Ok(false), &[] => Result<'_, bool> = Ok(false),
&[] => Result<'_, Strict<bool>> = Err(error::ErrorKind::Missing.into()), &[] => Result<'_, Strict<bool>> = Err(error::ErrorKind::Missing.into()),
&["=unknown"] => Option<bool> = None, &[] => Option<Lenient<bool>> = None,
&["=unknown"] => Option<Strict<bool>> = None,
&["=unknown"] => Option<Lenient<bool>> = None,
&[] => Option<Lenient<bool>> = Some(false.into()),
&["=123"] => Option<time::Date> = None,
&["=no"] => Option<bool> = Some(false), &["=no"] => Option<bool> = Some(false),
&["=yes"] => Option<bool> = Some(true), &["=yes"] => Option<bool> = Some(true),
&["=yes"] => Option<Lenient<bool>> = Some(true.into()), &["=yes"] => Option<Lenient<bool>> = Some(true.into()),
&["=yes"] => Option<Strict<bool>> = Some(true.into()), &["=yes"] => Option<Strict<bool>> = Some(true.into()),
} }
assert_parses_fail! {
&["=unknown"] => Option<bool>,
&["=unknown"] => Option<Strict<bool>>,
&["=unknown"] => Option<Lenient<bool>>,
&["=123"] => Option<time::Date>,
}
} }
#[test] #[test]

View File

@ -50,7 +50,7 @@
//! ```rust //! ```rust
//! # use rocket::post; //! # use rocket::post;
//! # type S = Option<String>; //! # type S = Option<String>;
//! # type E = std::convert::Infallible; //! # type E = std::io::Error;
//! #[post("/", data = "<my_val>")] //! #[post("/", data = "<my_val>")]
//! fn hello(my_val: Result<S, E>) { /* ... */ } //! fn hello(my_val: Result<S, E>) { /* ... */ }
//! ``` //! ```

View File

@ -46,16 +46,9 @@ use crate::http::uri::{Segments, error::PathError, fmt::Path};
/// ///
/// Sometimes, a forward is not desired, and instead, we simply want to know /// Sometimes, a forward is not desired, and instead, we simply want to know
/// that the dynamic path segment could not be parsed into some desired type /// that the dynamic path segment could not be parsed into some desired type
/// `T`. In these cases, types of `Option<T>`, `Result<T, T::Error>`, or /// `T`. In these cases, types of `Result<T, T::Error>`, or
/// `Either<A, B>` can be used, which implement `FromParam` themselves. /// `Either<A, B>` can be used, which implement `FromParam` themselves.
/// ///
/// * **`Option<T>`** _where_ **`T: FromParam`**
///
/// Always returns successfully.
///
/// If the conversion to `T` fails, `None` is returned. If the conversion
/// succeeds, `Some(value)` is returned.
///
/// * **`Result<T, T::Error>`** _where_ **`T: FromParam`** /// * **`Result<T, T::Error>`** _where_ **`T: FromParam`**
/// ///
/// Always returns successfully. /// Always returns successfully.
@ -121,14 +114,6 @@ use crate::http::uri::{Segments, error::PathError, fmt::Path};
/// Returns the percent-decoded path segment with invalid UTF-8 byte /// Returns the percent-decoded path segment with invalid UTF-8 byte
/// sequences replaced by <20> U+FFFD. /// sequences replaced by <20> U+FFFD.
/// ///
/// * **Option&lt;T>** _where_ **T: FromParam**
///
/// _This implementation always returns successfully._
///
/// The path segment is parsed by `T`'s `FromParam` implementation. If the
/// parse succeeds, a `Some(parsed_value)` is returned. Otherwise, a `None`
/// is returned.
///
/// * **Result&lt;T, T::Error>** _where_ **T: FromParam** /// * **Result&lt;T, T::Error>** _where_ **T: FromParam**
/// ///
/// _This implementation always returns successfully._ /// _This implementation always returns successfully._
@ -329,6 +314,11 @@ impl<'a, T: FromParam<'a>> FromParam<'a> for Result<T, T::Error> {
/// any other segments that begin with "*" or "." are ignored. If a /// any other segments that begin with "*" or "." are ignored. If a
/// percent-decoded segment results in invalid UTF8, an `Err` is returned with /// percent-decoded segment results in invalid UTF8, an `Err` is returned with
/// the `Utf8Error`. /// the `Utf8Error`.
///
/// **`Option&lt;T>` where `T: FromSegments`**
///
/// Succeeds as `None` if the this segments matched nothing, otherwise invokes
/// `T`'s `FromSegments` implementation.
pub trait FromSegments<'r>: Sized { pub trait FromSegments<'r>: Sized {
/// The associated error to be returned when parsing fails. /// The associated error to be returned when parsing fails.
type Error: std::fmt::Debug; type Error: std::fmt::Debug;

View File

@ -84,8 +84,7 @@ pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
/// If the `Outcome` is [`Error`], the request will fail with the given /// If the `Outcome` is [`Error`], the request will fail with the given
/// status code and error. The designated error [`Catcher`](crate::Catcher) /// status code and error. The designated error [`Catcher`](crate::Catcher)
/// will be used to respond to the request. Note that users can request types /// will be used to respond to the request. Note that users can request types
/// of `Result<S, E>` and `Option<S>` to catch `Error`s and retrieve the /// of `Result<S, E>` to catch `Error`s and retrieve the error value.
/// error value.
/// ///
/// * **Forward**(Status) /// * **Forward**(Status)
/// ///
@ -177,9 +176,9 @@ pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
/// ///
/// The type `T` is derived from the incoming request using `T`'s /// The type `T` is derived from the incoming request using `T`'s
/// `FromRequest` implementation. If the derivation is a `Success`, the /// `FromRequest` implementation. If the derivation is a `Success`, the
/// derived value is returned in `Some`. Otherwise, a `None` is returned. /// derived value is returned in `Some`. If the derivation is a `Forward`,
/// /// the result is `None`, and if the derivation is an `Error`, the `Error`
/// _This implementation always returns successfully._ /// is preserved.
/// ///
/// * **Result&lt;T, T::Error>** _where_ **T: FromRequest** /// * **Result&lt;T, T::Error>** _where_ **T: FromRequest**
/// ///
@ -189,6 +188,14 @@ pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
/// returned in `Err`. If the derivation is a `Forward`, the request is /// returned in `Err`. If the derivation is a `Forward`, the request is
/// forwarded with the same status code as the original forward. /// forwarded with the same status code as the original forward.
/// ///
/// * **Outcome&lt;T, T::Error>** _where_ **T: FromRequest**
///
/// The type `T` is derived from the incoming request using `T`'s
/// `FromRequest` implementation. The `Outcome` is then provided to the handler,
/// reguardless of what it returned.
///
/// _This guard **always** succeeds_
///
/// [`Config`]: crate::config::Config /// [`Config`]: crate::config::Config
/// ///
/// # Example /// # Example
@ -521,12 +528,13 @@ impl<'r, T: FromRequest<'r>> FromRequest<'r> for Result<T, T::Error> {
#[crate::async_trait] #[crate::async_trait]
impl<'r, T: FromRequest<'r>> FromRequest<'r> for Option<T> { impl<'r, T: FromRequest<'r>> FromRequest<'r> for Option<T> {
type Error = Infallible; type Error = T::Error;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> { async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
match T::from_request(request).await { match T::from_request(request).await {
Success(val) => Success(Some(val)), Success(val) => Success(Some(val)),
Error(_) | Forward(_) => Success(None), Forward(_) => Success(None),
Error(e) => Error(e)
} }
} }
} }

View File

@ -11,13 +11,13 @@ fn set() -> Flash<&'static str> {
} }
#[get("/unused")] #[get("/unused")]
fn unused(flash: Option<FlashMessage<'_>>) -> Option<()> { fn unused(flash: Result<FlashMessage<'_>, ()>) -> Option<()> {
flash.map(|_| ()) flash.ok().map(|_| ())
} }
#[get("/use")] #[get("/use")]
fn used(flash: Option<FlashMessage<'_>>) -> Option<String> { fn used(flash: Result<FlashMessage<'_>, ()>) -> Option<String> {
flash.map(|f| f.message().into()) flash.ok().map(|f| f.message().into())
} }
mod flash_lazy_remove_tests { mod flash_lazy_remove_tests {

View File

@ -270,7 +270,7 @@ will be routed as follows:
You'll also find a route's rank logged in brackets during application launch: You'll also find a route's rank logged in brackets during application launch:
`GET /user/<id> [3] (user_str)`. `GET /user/<id> [3] (user_str)`.
Forwards can be _caught_ by using a `Result` or `Option` type. For example, if Forwards can be _caught_ by using a `Option` type. For example, if
the type of `id` in the `user` function was `Result<usize, &str>`, then `user` the type of `id` in the `user` function was `Result<usize, &str>`, then `user`
would never forward. An `Ok` variant would indicate that `<id>` was a valid would never forward. An `Ok` variant would indicate that `<id>` was a valid
`usize`, while an `Err` would indicate that `<id>` was not a `usize`. The `usize`, while an `Err` would indicate that `<id>` was not a `usize`. The
@ -279,7 +279,7 @@ would never forward. An `Ok` variant would indicate that `<id>` was a valid
! tip: It's not just forwards that can be caught! ! tip: It's not just forwards that can be caught!
In general, when any guard fails for any reason, including parameter guards, In general, when any guard fails for any reason, including parameter guards,
you can use an `Option` or `Result` type in its place to catch the error. you can use a `Result` type in its place to catch the error.
By the way, if you were to omit the `rank` parameter in the `user_str` or By the way, if you were to omit the `rank` parameter in the `user_str` or
`user_int` routes, Rocket would emit an error and abort launch, indicating that `user_int` routes, Rocket would emit an error and abort launch, indicating that
@ -511,8 +511,7 @@ it always succeeds. The user is redirected to a log in page.
### Fallible Guards ### Fallible Guards
A failing or forwarding guard can be "caught" in handler, preventing it from A failing or forwarding guard can be "caught" in handler, preventing it from
failing or forwarding, via the `Option<T>` and `Result<T, E>` guards. When a failing or forwarding, via `Result<T, E>` guards. If a guard `T` fails
guard `T` fails or forwards, `Option<T>` will be `None`. If a guard `T` fails
with error `E`, `Result<T, E>` will be `Err(E)`. with error `E`, `Result<T, E>` will be `Err(E)`.
As an example, for the `User` guard above, instead of allowing the guard to As an example, for the `User` guard above, instead of allowing the guard to
@ -538,7 +537,7 @@ fn admin_panel_user(user: Option<User>) -> Result<&'static str, Redirect> {
} }
``` ```
If the `User` guard forwards or fails, the `Option` will be `None`. If it If the `User` guard forwards, the `Option` will be `None`. If it
succeeds, it will be `Some(User)`. succeeds, it will be `Some(User)`.
For guards that may fail (and not just forward), the `Result<T, E>` guard allows For guards that may fail (and not just forward), the `Result<T, E>` guard allows
@ -898,14 +897,14 @@ If a `POST /todo` request arrives, the form data will automatically be parsed
into the `Task` structure. If the data that arrives isn't of the correct into the `Task` structure. If the data that arrives isn't of the correct
Content-Type, the request is forwarded. If the data doesn't parse or is simply Content-Type, the request is forwarded. If the data doesn't parse or is simply
invalid, a customizable error is returned. As before, a forward or error can invalid, a customizable error is returned. As before, a forward or error can
be caught by using the `Option` and `Result` types: be caught by using the `Result` types:
```rust ```rust
# use rocket::{post, form::Form}; # use rocket::{post, form::{Form, Errors}};
# type Task<'r> = &'r str; # type Task<'r> = &'r str;
#[post("/todo", data = "<task>")] #[post("/todo", data = "<task>")]
fn new(task: Option<Form<Task<'_>>>) { /* .. */ } fn new(task: Result<Form<Task<'_>>, Errors<'_>>) { /* .. */ }
``` ```
### Multipart ### Multipart

View File

@ -616,18 +616,20 @@ For example, given the following route:
```rust ```rust
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
# fn main() {} # fn main() {}
# use std::num::ParseIntError;
#[get("/<id>/<name>?<age>")] #[get("/<id>/<name>?<age>")]
fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ } fn person(id: Result<usize, ParseIntError>, name: &str, age: Option<u8>) { /* .. */ }
``` ```
URIs to `person` can be created as follows: URIs to `person` can be created as follows:
```rust ```rust
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
# use std::num::ParseIntError;
# #[get("/<id>/<name>?<age>")] # #[get("/<id>/<name>?<age>")]
# fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ } # fn person(id: Result<usize, ParseIntError>, name: &str, age: Option<u8>) { /* .. */ }
// with unnamed parameters, in route path declaration order // with unnamed parameters, in route path declaration order
let mike = uri!(person(101, "Mike Smith", Some(28))); let mike = uri!(person(101, "Mike Smith", Some(28)));
@ -755,9 +757,10 @@ generated.
```rust ```rust
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
# use std::num::ParseIntError;
#[get("/<id>/<name>?<age>")] #[get("/<id>/<name>?<age>")]
fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ } fn person(id: Result<usize, ParseIntError>, name: &str, age: Option<u8>) { /* .. */ }
// Note that `id` is `Option<usize>` in the route, but `id` in `uri!` _cannot_ // Note that `id` is `Option<usize>` in the route, but `id` in `uri!` _cannot_
// be an `Option`. `age`, on the other hand, _must_ be an `Option` (or `Result` // be an `Option`. `age`, on the other hand, _must_ be an `Option` (or `Result`

View File

@ -53,8 +53,8 @@ fn login(_user: User) -> Redirect {
} }
#[get("/login", rank = 2)] #[get("/login", rank = 2)]
fn login_page(flash: Option<FlashMessage<'_>>) -> Template { fn login_page(flash: Result<FlashMessage<'_>, ()>) -> Template {
Template::render("login", flash) Template::render("login", flash.ok())
} }
#[post("/login", data = "<login>")] #[post("/login", data = "<login>")]

View File

@ -1,5 +1,7 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use rocket::form::Errors;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
#[derive(FromFormField)] #[derive(FromFormField)]
@ -51,13 +53,13 @@ fn wave(name: &str, age: u8) -> String {
// http://127.0.0.1:8000/?name=Rocketeer&lang=en&emoji // http://127.0.0.1:8000/?name=Rocketeer&lang=en&emoji
// http://127.0.0.1:8000/?lang=ru&emoji&name=Rocketeer // http://127.0.0.1:8000/?lang=ru&emoji&name=Rocketeer
#[get("/?<lang>&<opt..>")] #[get("/?<lang>&<opt..>")]
fn hello(lang: Option<Lang>, opt: Options<'_>) -> String { fn hello(lang: Result<Lang, Errors<'_>>, opt: Options<'_>) -> String {
let mut greeting = String::new(); let mut greeting = String::new();
if opt.emoji { if opt.emoji {
greeting.push_str("👋 "); greeting.push_str("👋 ");
} }
match lang { match lang.ok() {
Some(Lang::Russian) => greeting.push_str("Привет"), Some(Lang::Russian) => greeting.push_str("Привет"),
Some(Lang::English) => greeting.push_str("Hello"), Some(Lang::English) => greeting.push_str("Hello"),
None => greeting.push_str("Hi"), None => greeting.push_str("Hi"),

View File

@ -86,8 +86,8 @@ async fn delete(id: i32, conn: DbConn) -> Result<Flash<Redirect>, Template> {
} }
#[get("/")] #[get("/")]
async fn index(flash: Option<FlashMessage<'_>>, conn: DbConn) -> Template { async fn index(flash: Result<FlashMessage<'_>, ()>, conn: DbConn) -> Template {
let flash = flash.map(FlashMessage::into_inner); let flash = flash.ok().map(FlashMessage::into_inner);
Template::render("index", Context::raw(&conn, flash).await) Template::render("index", Context::raw(&conn, flash).await)
} }