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
/// # #[macro_use] extern crate rocket;
/// #[get("/person/<name>")]
/// fn maybe(name: Option<&str>) { }
/// // #[get("/person/<name>")]
/// // fn maybe(name: Option<&str>) { }
///
/// let bob1 = uri!(maybe(name = "Bob"));
/// let bob2 = uri!(maybe("Bob Smith"));
/// assert_eq!(bob1.to_string(), "/person/Bob");
/// assert_eq!(bob2.to_string(), "/person/Bob%20Smith");
/// // let bob1 = uri!(maybe(name = "Bob"));
/// // let bob2 = uri!(maybe("Bob Smith"));
/// // assert_eq!(bob1.to_string(), "/person/Bob");
/// // assert_eq!(bob2.to_string(), "/person/Bob%20Smith");
///
/// #[get("/person/<age>")]
/// fn ok(age: Result<u8, std::num::ParseIntError>) { }

View File

@ -2,8 +2,10 @@
#[macro_use] extern crate rocket;
use std::num::ParseIntError;
use std::path::PathBuf;
use rocket::error::Empty;
use rocket::http::CookieJar;
use rocket::http::uri::fmt::{FromUriParam, Query};
use rocket::form::{Form, error::{Errors, ErrorKind}};
@ -474,8 +476,8 @@ struct Third<'r> {
#[post("/<foo>/<bar>?<q1>&<rest..>")]
fn optionals(
foo: Option<usize>,
bar: Option<String>,
foo: Result<usize, ParseIntError>,
bar: Result<String, Empty>,
q1: Result<usize, Errors<'_>>,
rest: Option<Third<'_>>
) { }
@ -547,6 +549,13 @@ fn test_optional_uri_parameters() {
q1 = _,
rest = _,
)) => "/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>`
///
/// 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.
///
/// - **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.
/// - **None:** If the data stream is empty.
/// - **Some:** If `T` succeeds to parse the incoming data.
///
/// * `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` |
/// | [`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` |
/// | `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` |
@ -151,6 +151,12 @@ use crate::http::uncased::AsUncased;
///
/// ## 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`**
///
/// 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,
has_field: bool,
inner: T::Context,

View File

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

View File

@ -50,7 +50,7 @@
//! ```rust
//! # use rocket::post;
//! # type S = Option<String>;
//! # type E = std::convert::Infallible;
//! # type E = std::io::Error;
//! #[post("/", data = "<my_val>")]
//! 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
/// 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.
///
/// * **`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`**
///
/// 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
/// 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**
///
/// _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
/// percent-decoded segment results in invalid UTF8, an `Err` is returned with
/// 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 {
/// The associated error to be returned when parsing fails.
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
/// status code and error. The designated error [`Catcher`](crate::Catcher)
/// 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
/// error value.
/// of `Result<S, E>` to catch `Error`s and retrieve the error value.
///
/// * **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
/// `FromRequest` implementation. If the derivation is a `Success`, the
/// derived value is returned in `Some`. Otherwise, a `None` is returned.
///
/// _This implementation always returns successfully._
/// 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`
/// is preserved.
///
/// * **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
/// 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
///
/// # Example
@ -521,12 +528,13 @@ impl<'r, T: FromRequest<'r>> FromRequest<'r> for Result<T, T::Error> {
#[crate::async_trait]
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 {
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")]
fn unused(flash: Option<FlashMessage<'_>>) -> Option<()> {
flash.map(|_| ())
fn unused(flash: Result<FlashMessage<'_>, ()>) -> Option<()> {
flash.ok().map(|_| ())
}
#[get("/use")]
fn used(flash: Option<FlashMessage<'_>>) -> Option<String> {
flash.map(|f| f.message().into())
fn used(flash: Result<FlashMessage<'_>, ()>) -> Option<String> {
flash.ok().map(|f| f.message().into())
}
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:
`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`
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
@ -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!
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
`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
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
guard `T` fails or forwards, `Option<T>` will be `None`. If a guard `T` fails
failing or forwarding, via `Result<T, E>` guards. If a guard `T` fails
with error `E`, `Result<T, E>` will be `Err(E)`.
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)`.
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
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
be caught by using the `Option` and `Result` types:
be caught by using the `Result` types:
```rust
# use rocket::{post, form::Form};
# use rocket::{post, form::{Form, Errors}};
# type Task<'r> = &'r str;
#[post("/todo", data = "<task>")]
fn new(task: Option<Form<Task<'_>>>) { /* .. */ }
fn new(task: Result<Form<Task<'_>>, Errors<'_>>) { /* .. */ }
```
### Multipart

View File

@ -616,18 +616,20 @@ For example, given the following route:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use std::num::ParseIntError;
#[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:
```rust
# #[macro_use] extern crate rocket;
# use std::num::ParseIntError;
# #[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
let mike = uri!(person(101, "Mike Smith", Some(28)));
@ -755,9 +757,10 @@ generated.
```rust
# #[macro_use] extern crate rocket;
# use std::num::ParseIntError;
#[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_
// 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)]
fn login_page(flash: Option<FlashMessage<'_>>) -> Template {
Template::render("login", flash)
fn login_page(flash: Result<FlashMessage<'_>, ()>) -> Template {
Template::render("login", flash.ok())
}
#[post("/login", data = "<login>")]

View File

@ -1,5 +1,7 @@
#[macro_use] extern crate rocket;
use rocket::form::Errors;
#[cfg(test)] mod tests;
#[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/?lang=ru&emoji&name=Rocketeer
#[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();
if opt.emoji {
greeting.push_str("👋 ");
}
match lang {
match lang.ok() {
Some(Lang::Russian) => greeting.push_str("Привет"),
Some(Lang::English) => greeting.push_str("Hello"),
None => greeting.push_str("Hi"),

View File

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