Skip to content

Commit 1f82d4b

Browse files
committed
Improve FromParam derive docs and error values.
This commit improves the docs for the `FromParam` derive macro and exposes a new `InvalidOption` error value, which is returned when the derived `FromParam` implementation fails.
1 parent 15062de commit 1f82d4b

File tree

8 files changed

+106
-72
lines changed

8 files changed

+106
-72
lines changed

core/codegen/src/derive/from_param.rs

+14-19
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,40 @@
1-
use crate::exports::*;
2-
use devise::ext::SpanDiagnosticExt;
3-
use devise::Support;
41
use devise::*;
5-
use proc_macro2::TokenStream;
2+
use devise::ext::SpanDiagnosticExt;
3+
64
use quote::quote;
5+
use proc_macro2::TokenStream;
76
use syn::ext::IdentExt;
87

8+
use crate::exports::*;
9+
910
pub fn derive_from_param(input: proc_macro::TokenStream) -> TokenStream {
1011
DeriveGenerator::build_for(input, quote!(impl<'a> #_request::FromParam<'a>))
1112
.support(Support::Enum)
1213
.validator(ValidatorBuild::new().fields_validate(|_, fields| {
1314
if !fields.is_empty() {
14-
return Err(fields
15-
.span()
16-
.error("Only enums without data fields are supported"));
15+
return Err(fields.span().error("variants with data fields are not supported"));
1716
}
17+
1818
Ok(())
1919
}))
2020
.inner_mapper(MapperBuild::new().enum_map(|_, data| {
2121
let matches = data.variants().map(|field| {
2222
let field_name = field.ident.unraw();
23-
quote!(
24-
stringify!(#field_name) => Ok(Self::#field),
25-
)
23+
quote!(stringify!(#field_name) => Ok(Self::#field))
2624
});
25+
2726
let names = data.variants().map(|field| {
2827
let field_name = field.ident.unraw();
29-
quote!(
30-
#_Cow::Borrowed(stringify!(#field_name)),
31-
)
28+
quote!(stringify!(#field_name))
3229
});
3330

3431
quote! {
35-
type Error = #_request::EnumFromParamError<'a>;
32+
type Error = #_error::InvalidOption<'a>;
33+
3634
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
3735
match param {
38-
#(#matches)*
39-
_ => Err(#_request::EnumFromParamError::new(
40-
#_Cow::Borrowed(param),
41-
#_Cow::Borrowed(&[#(#names)*]),
42-
)),
36+
#(#matches,)*
37+
_ => Err(#_error::InvalidOption::new(param, &[#(#names),*])),
4338
}
4439
}
4540
}

core/codegen/src/lib.rs

+20-11
Original file line numberDiff line numberDiff line change
@@ -776,32 +776,41 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream {
776776

777777
/// Derive for the [`FromParam`] trait.
778778
///
779-
/// The [`FromParam`] derive can be applied to enums with nullary
780-
/// (zero-length) fields. To implement FromParam, the function matches each variant
781-
/// to its stringified field name (case sensitive):
779+
/// This [`FromParam`] derive can be applied to C-like enums whose variants have
780+
/// no fields. The generated implementation case-sensitively matches each
781+
/// variant to its stringified field name. If there is no match, an error
782+
/// of type [`InvalidOption`] is returned.
783+
///
784+
/// [`FromParam`]: ../rocket/request/trait.FromParam.html
785+
/// [`InvalidOption`]: ../rocket/error/struct.InvalidOption.html
786+
///
787+
/// # Example
782788
///
783789
/// ```rust
784790
/// # #[macro_use] extern crate rocket;
785-
/// #
786791
/// use rocket::request::FromParam;
787792
///
788793
/// #[derive(FromParam, Debug, PartialEq)]
789794
/// enum MyParam {
790795
/// A,
791-
/// B,
796+
/// Bob,
792797
/// }
793798
///
794799
/// assert_eq!(MyParam::from_param("A").unwrap(), MyParam::A);
795-
/// assert_eq!(MyParam::from_param("B").unwrap(), MyParam::B);
800+
/// assert_eq!(MyParam::from_param("Bob").unwrap(), MyParam::Bob);
796801
/// assert!(MyParam::from_param("a").is_err());
797-
/// assert!(MyParam::from_param("b").is_err());
802+
/// assert!(MyParam::from_param("bob").is_err());
798803
/// assert!(MyParam::from_param("c").is_err());
799804
/// assert!(MyParam::from_param("C").is_err());
800-
/// ```
801-
///
802-
/// Now `MyParam` can be used in an endpoint and will accept either `A` or `B`.
803-
/// [`FromParam`]: ../rocket/request/trait.FromParam.html
804805
///
806+
/// // Now `MyParam` can be used in an route to accept either `A` or `B`.
807+
/// #[get("/<param>")]
808+
/// fn index(param: MyParam) -> &'static str {
809+
/// match param {
810+
/// MyParam::A => "A",
811+
/// MyParam::Bob => "Bob",
812+
/// }
813+
/// }
805814
#[proc_macro_derive(FromParam)]
806815
pub fn derive_from_param(input: TokenStream) -> TokenStream {
807816
emit!(derive::from_param::derive_from_param(input))

core/codegen/tests/from_param.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use rocket::request::FromParam;
22

3+
#[allow(non_camel_case_types)]
34
#[derive(Debug, FromParam, PartialEq)]
45
enum Test {
56
Test1,
@@ -9,14 +10,16 @@ enum Test {
910

1011
#[test]
1112
fn derive_from_param() {
12-
let test1 = Test::from_param("Test1").expect("Should be valid");
13-
assert_eq!(test1, Test::Test1);
13+
assert_eq!(Test::from_param("Test1").unwrap(), Test::Test1);
14+
assert_eq!(Test::from_param("Test2").unwrap(), Test::Test2);
15+
assert_eq!(Test::from_param("for").unwrap(), Test::r#for);
1416

15-
let test2 = Test::from_param("Test2").expect("Should be valid");
16-
assert_eq!(test2, Test::Test2);
17-
let test2 = Test::from_param("for").expect("Should be valid");
18-
assert_eq!(test2, Test::r#for);
17+
let err = Test::from_param("For").unwrap_err();
18+
assert_eq!(err.value, "For");
19+
assert_eq!(err.options, &["Test1", "Test2", "for"]);
20+
21+
let err = Test::from_param("not_test").unwrap_err();
22+
assert_eq!(err.value, "not_test");
23+
assert_eq!(err.options, &["Test1", "Test2", "for"]);
1924

20-
let test3 = Test::from_param("not_test");
21-
assert!(test3.is_err());
2225
}

core/codegen/tests/ui-fail-nightly/from_param.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ note: error occurred while deriving `FromParam`
2626
| ^^^^^^^^^
2727
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
2828

29-
error: Only enums without data fields are supported
29+
error: variants with data fields are not supported
3030
--> tests/ui-fail-nightly/from_param.rs:13:6
3131
|
3232
13 | A(String),

core/codegen/tests/ui-fail-stable/from_param.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ error: [note] error occurred while deriving `FromParam`
2828
|
2929
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
3030

31-
error: Only enums without data fields are supported
31+
error: variants with data fields are not supported
3232
--> tests/ui-fail-stable/from_param.rs:13:6
3333
|
3434
13 | A(String),

core/lib/src/error.rs

+54
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,60 @@ pub enum ErrorKind {
8989
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
9090
pub struct Empty;
9191

92+
/// An error that occurs when a value doesn't match one of the expected options.
93+
///
94+
/// This error is returned by the [`FromParam`] trait implementation generated
95+
/// by the [`FromParam` derive](macro@rocket::FromParam) when the value of a
96+
/// dynamic path segment does not match one of the expected variants. The
97+
/// `value` field will contain the value that was provided, and `options` will
98+
/// contain each of possible stringified variants.
99+
///
100+
/// [`FromParam`]: trait@rocket::request::FromParam
101+
///
102+
/// # Example
103+
///
104+
/// ```rust
105+
/// # #[macro_use] extern crate rocket;
106+
/// use rocket::error::InvalidOption;
107+
///
108+
/// #[derive(FromParam)]
109+
/// enum MyParam {
110+
/// FirstOption,
111+
/// SecondOption,
112+
/// ThirdOption,
113+
/// }
114+
///
115+
/// #[get("/<param>")]
116+
/// fn hello(param: Result<MyParam, InvalidOption<'_>>) {
117+
/// if let Err(e) = param {
118+
/// assert_eq!(e.options, &["FirstOption", "SecondOption", "ThirdOption"]);
119+
/// }
120+
/// }
121+
/// ```
122+
#[derive(Debug, Clone)]
123+
#[non_exhaustive]
124+
pub struct InvalidOption<'a> {
125+
/// The value that was provided.
126+
pub value: &'a str,
127+
/// The expected values: a slice of strings, one for each possible value.
128+
pub options: &'static [&'static str],
129+
}
130+
131+
impl<'a> InvalidOption<'a> {
132+
#[doc(hidden)]
133+
pub fn new(value: &'a str, options: &'static [&'static str]) -> Self {
134+
Self { value, options }
135+
}
136+
}
137+
138+
impl fmt::Display for InvalidOption<'_> {
139+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140+
write!(f, "unexpected value {:?}, expected one of {:?}", self.value, self.options)
141+
}
142+
}
143+
144+
impl std::error::Error for InvalidOption<'_> {}
145+
92146
impl Error {
93147
#[inline(always)]
94148
pub(crate) fn new(kind: ErrorKind) -> Error {

core/lib/src/request/from_param.rs

+5-29
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
use std::borrow::Cow;
2-
use std::fmt;
31
use std::str::FromStr;
42
use std::path::PathBuf;
53

6-
use cookie::Display;
7-
84
use crate::error::Empty;
95
use crate::either::Either;
106
use crate::http::uri::{Segments, error::PathError, fmt::Path};
@@ -16,6 +12,11 @@ use crate::http::uri::{Segments, error::PathError, fmt::Path};
1612
/// a dynamic segment `<param>` where `param` has some type `T` that implements
1713
/// `FromParam`, `T::from_param` will be called.
1814
///
15+
/// # Deriving
16+
///
17+
/// The `FromParam` trait can be automatically derived for C-like enums. See
18+
/// [`FromParam` derive](macro@rocket::FromParam) for more information.
19+
///
1920
/// # Forwarding
2021
///
2122
/// If the conversion fails, the incoming request will be forwarded to the next
@@ -310,31 +311,6 @@ impl<'a, T: FromParam<'a>> FromParam<'a> for Option<T> {
310311
}
311312
}
312313

313-
/// Error type for automatically derived `FromParam` enums
314-
#[derive(Debug, Clone)]
315-
#[non_exhaustive]
316-
pub struct EnumFromParamError<'a> {
317-
pub value: Cow<'a, str>,
318-
pub options: Cow<'static, [Cow<'static, str>]>,
319-
}
320-
321-
impl<'a> EnumFromParamError<'a> {
322-
pub fn new(value: Cow<'a, str>, options: Cow<'static, [Cow<'static, str>]>) -> Self {
323-
Self {
324-
value,
325-
options,
326-
}
327-
}
328-
}
329-
330-
impl fmt::Display for EnumFromParamError<'_> {
331-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332-
write!(f, "Unexpected value {:?}, expected one of {:?}", self.value, self.options)
333-
}
334-
}
335-
336-
impl std::error::Error for EnumFromParamError<'_> {}
337-
338314
/// Trait to convert _many_ dynamic path segment strings to a concrete value.
339315
///
340316
/// This is the `..` analog to [`FromParam`], and its functionality is identical

core/lib/src/request/mod.rs

-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ pub use self::request::Request;
1212
pub use self::from_request::{FromRequest, Outcome};
1313
pub use self::from_param::{FromParam, FromSegments};
1414

15-
#[doc(hidden)]
16-
pub use self::from_param::EnumFromParamError;
17-
1815
#[doc(hidden)]
1916
pub use rocket_codegen::FromParam;
2017

0 commit comments

Comments
 (0)