diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 07d4fc80..5cec12ca 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -1361,13 +1361,13 @@ pub fn catchers(input: TokenStream) -> TokenStream { /// /// ```rust /// # #[macro_use] extern crate rocket; -/// #[get("/person/")] -/// fn maybe(name: Option<&str>) { } +/// // #[get("/person/")] +/// // 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/")] /// fn ok(age: Result) { } diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs index 0dbcc502..5b023122 100644 --- a/core/codegen/tests/typed-uris.rs +++ b/core/codegen/tests/typed-uris.rs @@ -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("//?&")] fn optionals( - foo: Option, - bar: Option, + foo: Result, + bar: Result, q1: Result>, rest: Option> ) { } @@ -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", } } diff --git a/core/lib/src/data/from_data.rs b/core/lib/src/data/from_data.rs index cd1fd8e1..4e124332 100644 --- a/core/lib/src/data/from_data.rs +++ b/core/lib/src/data/from_data.rs @@ -128,15 +128,11 @@ pub type Outcome<'r, T, E = >::Error> /// /// * `Option` /// -/// 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` /// @@ -423,6 +419,6 @@ impl<'r, T: FromData<'r>> FromData<'r> for Option { Outcome::Success(None) } else { T::from_data(req, data).await.map(Some) - } + } } } diff --git a/core/lib/src/form/from_form.rs b/core/lib/src/form/from_form.rs index 6cb0923d..449eb666 100644 --- a/core/lib/src/form/from_form.rs +++ b/core/lib/src/form/from_form.rs @@ -106,7 +106,7 @@ use crate::http::uncased::AsUncased; /// |------------------------|-------------|-------------------|--------|--------|----------------------------------------------------| /// | [`Strict`] | **strict** | if `strict` `T` | if `T` | if `T` | `T: FromForm` | /// | [`Lenient`] | **lenient** | if `lenient` `T` | if `T` | if `T` | `T: FromForm` | -/// | `Option` | **strict** | `None` | if `T` | if `T` | Infallible, `T: FromForm` | +/// | `Option` | **strict** | `None` | if `T` | if `T` | `T: FromForm` | /// | [`Result`] | _inherit_ | `T::finalize()` | if `T` | if `T` | Infallible, `T: FromForm` | /// | `Vec` | _inherit_ | `vec![]` | if `T` | if `T` | `T: FromForm` | /// | [`HashMap`] | _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` 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` 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 } } -struct OptionFormCtx<'v, T: FromForm<'v>> { +pub struct OptionFormCtx<'v, T: FromForm<'v>> { strict: bool, has_field: bool, inner: T::Context, diff --git a/core/lib/src/form/tests.rs b/core/lib/src/form/tests.rs index 0165318a..b72d4855 100644 --- a/core/lib/src/form/tests.rs +++ b/core/lib/src/form/tests.rs @@ -105,18 +105,20 @@ fn defaults() { &[] => Result<'_, bool> = Ok(false), &[] => Result<'_, Strict> = Err(error::ErrorKind::Missing.into()), - &["=unknown"] => Option = None, - &["=unknown"] => Option> = None, - &["=unknown"] => Option> = None, - - &[] => Option> = Some(false.into()), - &["=123"] => Option = None, + &[] => Option> = None, &["=no"] => Option = Some(false), &["=yes"] => Option = Some(true), &["=yes"] => Option> = Some(true.into()), &["=yes"] => Option> = Some(true.into()), } + + assert_parses_fail! { + &["=unknown"] => Option, + &["=unknown"] => Option>, + &["=unknown"] => Option>, + &["=123"] => Option, + } } #[test] diff --git a/core/lib/src/outcome.rs b/core/lib/src/outcome.rs index 35521aa3..54fe46ef 100644 --- a/core/lib/src/outcome.rs +++ b/core/lib/src/outcome.rs @@ -50,7 +50,7 @@ //! ```rust //! # use rocket::post; //! # type S = Option; -//! # type E = std::convert::Infallible; +//! # type E = std::io::Error; //! #[post("/", data = "")] //! fn hello(my_val: Result) { /* ... */ } //! ``` diff --git a/core/lib/src/request/from_param.rs b/core/lib/src/request/from_param.rs index e0d62252..7d2d2703 100644 --- a/core/lib/src/request/from_param.rs +++ b/core/lib/src/request/from_param.rs @@ -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`, `Result`, or +/// `T`. In these cases, types of `Result`, or /// `Either` can be used, which implement `FromParam` themselves. /// -/// * **`Option`** _where_ **`T: FromParam`** -/// -/// Always returns successfully. -/// -/// If the conversion to `T` fails, `None` is returned. If the conversion -/// succeeds, `Some(value)` is returned. -/// /// * **`Result`** _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 � U+FFFD. /// -/// * **Option<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<T, T::Error>** _where_ **T: FromParam** /// /// _This implementation always returns successfully._ @@ -329,6 +314,11 @@ impl<'a, T: FromParam<'a>> FromParam<'a> for Result { /// 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<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; diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs index f4677db3..04241129 100644 --- a/core/lib/src/request/from_request.rs +++ b/core/lib/src/request/from_request.rs @@ -84,8 +84,7 @@ pub type Outcome = outcome::Outcome; /// 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` and `Option` to catch `Error`s and retrieve the -/// error value. +/// of `Result` to catch `Error`s and retrieve the error value. /// /// * **Forward**(Status) /// @@ -177,9 +176,9 @@ pub type Outcome = outcome::Outcome; /// /// 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<T, T::Error>** _where_ **T: FromRequest** /// @@ -189,6 +188,14 @@ pub type Outcome = outcome::Outcome; /// returned in `Err`. If the derivation is a `Forward`, the request is /// forwarded with the same status code as the original forward. /// +/// * **Outcome<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 { #[crate::async_trait] impl<'r, T: FromRequest<'r>> FromRequest<'r> for Option { - type Error = Infallible; + type Error = T::Error; - async fn from_request(request: &'r Request<'_>) -> Outcome { + async fn from_request(request: &'r Request<'_>) -> Outcome { match T::from_request(request).await { Success(val) => Success(Some(val)), - Error(_) | Forward(_) => Success(None), + Forward(_) => Success(None), + Error(e) => Error(e) } } } diff --git a/core/lib/tests/flash-lazy-removes-issue-466.rs b/core/lib/tests/flash-lazy-removes-issue-466.rs index ff30b83a..e45a7be7 100644 --- a/core/lib/tests/flash-lazy-removes-issue-466.rs +++ b/core/lib/tests/flash-lazy-removes-issue-466.rs @@ -11,13 +11,13 @@ fn set() -> Flash<&'static str> { } #[get("/unused")] -fn unused(flash: Option>) -> Option<()> { - flash.map(|_| ()) +fn unused(flash: Result, ()>) -> Option<()> { + flash.ok().map(|_| ()) } #[get("/use")] -fn used(flash: Option>) -> Option { - flash.map(|f| f.message().into()) +fn used(flash: Result, ()>) -> Option { + flash.ok().map(|f| f.message().into()) } mod flash_lazy_remove_tests { diff --git a/docs/guide/05-requests.md b/docs/guide/05-requests.md index 08a0329d..84595ace 100644 --- a/docs/guide/05-requests.md +++ b/docs/guide/05-requests.md @@ -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/ [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`, then `user` would never forward. An `Ok` variant would indicate that `` was a valid `usize`, while an `Err` would indicate that `` was not a `usize`. The @@ -279,7 +279,7 @@ would never forward. An `Ok` variant would indicate that `` 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` and `Result` guards. When a -guard `T` fails or forwards, `Option` will be `None`. If a guard `T` fails +failing or forwarding, via `Result` guards. If a guard `T` fails with error `E`, `Result` 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) -> 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` 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 = "")] -fn new(task: Option>>) { /* .. */ } +fn new(task: Result>, Errors<'_>>) { /* .. */ } ``` ### Multipart diff --git a/docs/guide/06-responses.md b/docs/guide/06-responses.md index 31a10d32..36b6cd2f 100644 --- a/docs/guide/06-responses.md +++ b/docs/guide/06-responses.md @@ -616,18 +616,20 @@ For example, given the following route: ```rust # #[macro_use] extern crate rocket; # fn main() {} +# use std::num::ParseIntError; #[get("//?")] -fn person(id: Option, name: &str, age: Option) { /* .. */ } +fn person(id: Result, name: &str, age: Option) { /* .. */ } ``` URIs to `person` can be created as follows: ```rust # #[macro_use] extern crate rocket; +# use std::num::ParseIntError; # #[get("//?")] -# fn person(id: Option, name: &str, age: Option) { /* .. */ } +# fn person(id: Result, name: &str, age: Option) { /* .. */ } // 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("//?")] -fn person(id: Option, name: &str, age: Option) { /* .. */ } +fn person(id: Result, name: &str, age: Option) { /* .. */ } // Note that `id` is `Option` in the route, but `id` in `uri!` _cannot_ // be an `Option`. `age`, on the other hand, _must_ be an `Option` (or `Result` diff --git a/examples/cookies/src/session.rs b/examples/cookies/src/session.rs index 31d0fc61..f909d87b 100644 --- a/examples/cookies/src/session.rs +++ b/examples/cookies/src/session.rs @@ -53,8 +53,8 @@ fn login(_user: User) -> Redirect { } #[get("/login", rank = 2)] -fn login_page(flash: Option>) -> Template { - Template::render("login", flash) +fn login_page(flash: Result, ()>) -> Template { + Template::render("login", flash.ok()) } #[post("/login", data = "")] diff --git a/examples/hello/src/main.rs b/examples/hello/src/main.rs index 0f8c55cb..35e19879 100644 --- a/examples/hello/src/main.rs +++ b/examples/hello/src/main.rs @@ -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("/?&")] -fn hello(lang: Option, opt: Options<'_>) -> String { +fn hello(lang: Result>, 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"), diff --git a/examples/todo/src/main.rs b/examples/todo/src/main.rs index b6411624..8c965ee2 100644 --- a/examples/todo/src/main.rs +++ b/examples/todo/src/main.rs @@ -86,8 +86,8 @@ async fn delete(id: i32, conn: DbConn) -> Result, Template> { } #[get("/")] -async fn index(flash: Option>, conn: DbConn) -> Template { - let flash = flash.map(FlashMessage::into_inner); +async fn index(flash: Result, ()>, conn: DbConn) -> Template { + let flash = flash.ok().map(FlashMessage::into_inner); Template::render("index", Context::raw(&conn, flash).await) }