Skip to content

Commit 38cebeb

Browse files
loikkithe10thWiz
loikki
authored andcommitted
Derive FromParam for Enum rwf2#2826
1 parent 509a033 commit 38cebeb

File tree

11 files changed

+271
-0
lines changed

11 files changed

+271
-0
lines changed

core/codegen/src/derive/from_param.rs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use crate::exports::*;
2+
use devise::ext::SpanDiagnosticExt;
3+
use devise::Support;
4+
use devise::*;
5+
use proc_macro2::TokenStream;
6+
use quote::quote;
7+
use syn::ext::IdentExt;
8+
9+
pub fn derive_from_param(input: proc_macro::TokenStream) -> TokenStream {
10+
DeriveGenerator::build_for(input, quote!(impl<'a> #_request::FromParam<'a>))
11+
.support(Support::Enum)
12+
.validator(ValidatorBuild::new().fields_validate(|_, fields| {
13+
if !fields.is_empty() {
14+
return Err(fields
15+
.span()
16+
.error("Only enums without data fields are supported"));
17+
}
18+
Ok(())
19+
}))
20+
.inner_mapper(MapperBuild::new().enum_map(|_, data| {
21+
let matches = data.variants().map(|field| {
22+
let field_name = field.ident.unraw();
23+
quote!(
24+
stringify!(#field_name) => Ok(Self::#field),
25+
)
26+
});
27+
let names = data.variants().map(|field| {
28+
let field_name = field.ident.unraw();
29+
quote!(
30+
#_Cow::Borrowed(stringify!(#field_name)),
31+
)
32+
});
33+
34+
quote! {
35+
type Error = #_request::EnumFromParamError<'a>;
36+
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
37+
match param {
38+
#(#matches)*
39+
_ => Err(#_request::EnumFromParamError::new(
40+
#_Cow::Borrowed(param),
41+
#_Cow::Borrowed(&[#(#names)*]),
42+
)),
43+
}
44+
}
45+
}
46+
}))
47+
.to_tokens()
48+
}

core/codegen/src/derive/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod from_form;
33
pub mod from_form_field;
44
pub mod responder;
55
pub mod uri_display;
6+
pub mod from_param;

core/codegen/src/lib.rs

+33
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,39 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream {
774774
emit!(derive::from_form::derive_from_form(input))
775775
}
776776

777+
/// Derive for the [`FromParam`] trait.
778+
///
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):
782+
///
783+
/// ```rust
784+
/// # #[macro_use] extern crate rocket;
785+
/// #
786+
/// use rocket::request::FromParam;
787+
///
788+
/// #[derive(FromParam, Debug, PartialEq)]
789+
/// enum MyParam {
790+
/// A,
791+
/// B,
792+
/// }
793+
///
794+
/// assert_eq!(MyParam::from_param("A").unwrap(), MyParam::A);
795+
/// assert_eq!(MyParam::from_param("B").unwrap(), MyParam::B);
796+
/// assert!(MyParam::from_param("a").is_err());
797+
/// assert!(MyParam::from_param("b").is_err());
798+
/// assert!(MyParam::from_param("c").is_err());
799+
/// 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
804+
///
805+
#[proc_macro_derive(FromParam)]
806+
pub fn derive_from_param(input: TokenStream) -> TokenStream {
807+
emit!(derive::from_param::derive_from_param(input))
808+
}
809+
777810
/// Derive for the [`Responder`] trait.
778811
///
779812
/// The [`Responder`] derive can be applied to enums and structs with named

core/codegen/tests/from_param.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use rocket::request::FromParam;
2+
3+
#[derive(Debug, FromParam, PartialEq)]
4+
enum Test {
5+
Test1,
6+
Test2,
7+
r#for,
8+
}
9+
10+
#[test]
11+
fn derive_from_param() {
12+
let test1 = Test::from_param("Test1").expect("Should be valid");
13+
assert_eq!(test1, Test::Test1);
14+
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);
19+
20+
let test3 = Test::from_param("not_test");
21+
assert!(test3.is_err());
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-fail/from_param.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
error: named structs are not supported
2+
--> tests/ui-fail-nightly/from_param.rs:4:1
3+
|
4+
4 | / struct Foo1 {
5+
5 | | a: String
6+
6 | | }
7+
| |_^
8+
|
9+
note: error occurred while deriving `FromParam`
10+
--> tests/ui-fail-nightly/from_param.rs:3:10
11+
|
12+
3 | #[derive(FromParam)]
13+
| ^^^^^^^^^
14+
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
15+
16+
error: named structs are not supported
17+
--> tests/ui-fail-nightly/from_param.rs:9:1
18+
|
19+
9 | struct Foo2 {}
20+
| ^^^^^^^^^^^^^^
21+
|
22+
note: error occurred while deriving `FromParam`
23+
--> tests/ui-fail-nightly/from_param.rs:8:10
24+
|
25+
8 | #[derive(FromParam)]
26+
| ^^^^^^^^^
27+
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
28+
29+
error: Only enums without data fields are supported
30+
--> tests/ui-fail-nightly/from_param.rs:13:6
31+
|
32+
13 | A(String),
33+
| ^^^^^^^^
34+
|
35+
note: error occurred while deriving `FromParam`
36+
--> tests/ui-fail-nightly/from_param.rs:11:10
37+
|
38+
11 | #[derive(FromParam)]
39+
| ^^^^^^^^^
40+
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
41+
42+
error: tuple structs are not supported
43+
--> tests/ui-fail-nightly/from_param.rs:18:1
44+
|
45+
18 | struct Foo4(usize);
46+
| ^^^^^^^^^^^^^^^^^^^
47+
|
48+
note: error occurred while deriving `FromParam`
49+
--> tests/ui-fail-nightly/from_param.rs:17:10
50+
|
51+
17 | #[derive(FromParam)]
52+
| ^^^^^^^^^
53+
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-fail/from_param.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
error: named structs are not supported
2+
--> tests/ui-fail-stable/from_param.rs:4:1
3+
|
4+
4 | / struct Foo1 {
5+
5 | | a: String
6+
6 | | }
7+
| |_^
8+
9+
error: [note] error occurred while deriving `FromParam`
10+
--> tests/ui-fail-stable/from_param.rs:3:10
11+
|
12+
3 | #[derive(FromParam)]
13+
| ^^^^^^^^^
14+
|
15+
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
17+
error: named structs are not supported
18+
--> tests/ui-fail-stable/from_param.rs:9:1
19+
|
20+
9 | struct Foo2 {}
21+
| ^^^^^^^^^^^^^^
22+
23+
error: [note] error occurred while deriving `FromParam`
24+
--> tests/ui-fail-stable/from_param.rs:8:10
25+
|
26+
8 | #[derive(FromParam)]
27+
| ^^^^^^^^^
28+
|
29+
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
30+
31+
error: Only enums without data fields are supported
32+
--> tests/ui-fail-stable/from_param.rs:13:6
33+
|
34+
13 | A(String),
35+
| ^^^^^^^^
36+
37+
error: [note] error occurred while deriving `FromParam`
38+
--> tests/ui-fail-stable/from_param.rs:11:10
39+
|
40+
11 | #[derive(FromParam)]
41+
| ^^^^^^^^^
42+
|
43+
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
44+
45+
error: tuple structs are not supported
46+
--> tests/ui-fail-stable/from_param.rs:18:1
47+
|
48+
18 | struct Foo4(usize);
49+
| ^^^^^^^^^^^^^^^^^^^
50+
51+
error: [note] error occurred while deriving `FromParam`
52+
--> tests/ui-fail-stable/from_param.rs:17:10
53+
|
54+
17 | #[derive(FromParam)]
55+
| ^^^^^^^^^
56+
|
57+
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use rocket::request::FromParam;
2+
3+
#[derive(FromParam)]
4+
struct Foo1 {
5+
a: String
6+
}
7+
8+
#[derive(FromParam)]
9+
struct Foo2 {}
10+
11+
#[derive(FromParam)]
12+
enum Foo3 {
13+
A(String),
14+
B(String)
15+
}
16+
17+
#[derive(FromParam)]
18+
struct Foo4(usize);
19+
20+
fn main() {}

core/lib/src/request/from_param.rs

+29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
use std::borrow::Cow;
2+
use std::fmt;
13
use std::str::FromStr;
24
use std::path::PathBuf;
35

6+
use cookie::Display;
7+
48
use crate::error::Empty;
59
use crate::either::Either;
610
use crate::http::uri::{Segments, error::PathError, fmt::Path};
@@ -306,6 +310,31 @@ impl<'a, T: FromParam<'a>> FromParam<'a> for Option<T> {
306310
}
307311
}
308312

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+
309338
/// Trait to convert _many_ dynamic path segment strings to a concrete value.
310339
///
311340
/// This is the `..` analog to [`FromParam`], and its functionality is identical

core/lib/src/request/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ 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+
18+
#[doc(hidden)]
19+
pub use rocket_codegen::FromParam;
20+
1521
#[doc(inline)]
1622
pub use crate::response::flash::FlashMessage;
1723

0 commit comments

Comments
 (0)