Derive FromParam for Enum #2826

This commit is contained in:
loikki 2024-07-25 16:57:58 +02:00 committed by Matthew Pomes
parent 509a033c84
commit 38cebebbc3
No known key found for this signature in database
GPG Key ID: B8C0D93B8D8FBDB7
11 changed files with 271 additions and 0 deletions

View File

@ -0,0 +1,48 @@
use crate::exports::*;
use devise::ext::SpanDiagnosticExt;
use devise::Support;
use devise::*;
use proc_macro2::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
pub fn derive_from_param(input: proc_macro::TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'a> #_request::FromParam<'a>))
.support(Support::Enum)
.validator(ValidatorBuild::new().fields_validate(|_, fields| {
if !fields.is_empty() {
return Err(fields
.span()
.error("Only enums without data fields are supported"));
}
Ok(())
}))
.inner_mapper(MapperBuild::new().enum_map(|_, data| {
let matches = data.variants().map(|field| {
let field_name = field.ident.unraw();
quote!(
stringify!(#field_name) => Ok(Self::#field),
)
});
let names = data.variants().map(|field| {
let field_name = field.ident.unraw();
quote!(
#_Cow::Borrowed(stringify!(#field_name)),
)
});
quote! {
type Error = #_request::EnumFromParamError<'a>;
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
match param {
#(#matches)*
_ => Err(#_request::EnumFromParamError::new(
#_Cow::Borrowed(param),
#_Cow::Borrowed(&[#(#names)*]),
)),
}
}
}
}))
.to_tokens()
}

View File

@ -3,3 +3,4 @@ pub mod from_form;
pub mod from_form_field; pub mod from_form_field;
pub mod responder; pub mod responder;
pub mod uri_display; pub mod uri_display;
pub mod from_param;

View File

@ -774,6 +774,39 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream {
emit!(derive::from_form::derive_from_form(input)) emit!(derive::from_form::derive_from_form(input))
} }
/// Derive for the [`FromParam`] trait.
///
/// The [`FromParam`] derive can be applied to enums with nullary
/// (zero-length) fields. To implement FromParam, the function matches each variant
/// to its stringified field name (case sensitive):
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// #
/// use rocket::request::FromParam;
///
/// #[derive(FromParam, Debug, PartialEq)]
/// enum MyParam {
/// A,
/// B,
/// }
///
/// assert_eq!(MyParam::from_param("A").unwrap(), MyParam::A);
/// assert_eq!(MyParam::from_param("B").unwrap(), MyParam::B);
/// assert!(MyParam::from_param("a").is_err());
/// assert!(MyParam::from_param("b").is_err());
/// assert!(MyParam::from_param("c").is_err());
/// assert!(MyParam::from_param("C").is_err());
/// ```
///
/// Now `MyParam` can be used in an endpoint and will accept either `A` or `B`.
/// [`FromParam`]: ../rocket/request/trait.FromParam.html
///
#[proc_macro_derive(FromParam)]
pub fn derive_from_param(input: TokenStream) -> TokenStream {
emit!(derive::from_param::derive_from_param(input))
}
/// Derive for the [`Responder`] trait. /// Derive for the [`Responder`] trait.
/// ///
/// The [`Responder`] derive can be applied to enums and structs with named /// The [`Responder`] derive can be applied to enums and structs with named

View File

@ -0,0 +1,22 @@
use rocket::request::FromParam;
#[derive(Debug, FromParam, PartialEq)]
enum Test {
Test1,
Test2,
r#for,
}
#[test]
fn derive_from_param() {
let test1 = Test::from_param("Test1").expect("Should be valid");
assert_eq!(test1, Test::Test1);
let test2 = Test::from_param("Test2").expect("Should be valid");
assert_eq!(test2, Test::Test2);
let test2 = Test::from_param("for").expect("Should be valid");
assert_eq!(test2, Test::r#for);
let test3 = Test::from_param("not_test");
assert!(test3.is_err());
}

View File

@ -0,0 +1 @@
../ui-fail/from_param.rs

View File

@ -0,0 +1,53 @@
error: named structs are not supported
--> tests/ui-fail-nightly/from_param.rs:4:1
|
4 | / struct Foo1 {
5 | | a: String
6 | | }
| |_^
|
note: error occurred while deriving `FromParam`
--> tests/ui-fail-nightly/from_param.rs:3:10
|
3 | #[derive(FromParam)]
| ^^^^^^^^^
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
error: named structs are not supported
--> tests/ui-fail-nightly/from_param.rs:9:1
|
9 | struct Foo2 {}
| ^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromParam`
--> tests/ui-fail-nightly/from_param.rs:8:10
|
8 | #[derive(FromParam)]
| ^^^^^^^^^
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
error: Only enums without data fields are supported
--> tests/ui-fail-nightly/from_param.rs:13:6
|
13 | A(String),
| ^^^^^^^^
|
note: error occurred while deriving `FromParam`
--> tests/ui-fail-nightly/from_param.rs:11:10
|
11 | #[derive(FromParam)]
| ^^^^^^^^^
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs are not supported
--> tests/ui-fail-nightly/from_param.rs:18:1
|
18 | struct Foo4(usize);
| ^^^^^^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromParam`
--> tests/ui-fail-nightly/from_param.rs:17:10
|
17 | #[derive(FromParam)]
| ^^^^^^^^^
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -0,0 +1 @@
../ui-fail/from_param.rs

View File

@ -0,0 +1,57 @@
error: named structs are not supported
--> tests/ui-fail-stable/from_param.rs:4:1
|
4 | / struct Foo1 {
5 | | a: String
6 | | }
| |_^
error: [note] error occurred while deriving `FromParam`
--> tests/ui-fail-stable/from_param.rs:3:10
|
3 | #[derive(FromParam)]
| ^^^^^^^^^
|
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
error: named structs are not supported
--> tests/ui-fail-stable/from_param.rs:9:1
|
9 | struct Foo2 {}
| ^^^^^^^^^^^^^^
error: [note] error occurred while deriving `FromParam`
--> tests/ui-fail-stable/from_param.rs:8:10
|
8 | #[derive(FromParam)]
| ^^^^^^^^^
|
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
error: Only enums without data fields are supported
--> tests/ui-fail-stable/from_param.rs:13:6
|
13 | A(String),
| ^^^^^^^^
error: [note] error occurred while deriving `FromParam`
--> tests/ui-fail-stable/from_param.rs:11:10
|
11 | #[derive(FromParam)]
| ^^^^^^^^^
|
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs are not supported
--> tests/ui-fail-stable/from_param.rs:18:1
|
18 | struct Foo4(usize);
| ^^^^^^^^^^^^^^^^^^^
error: [note] error occurred while deriving `FromParam`
--> tests/ui-fail-stable/from_param.rs:17:10
|
17 | #[derive(FromParam)]
| ^^^^^^^^^
|
= note: this error originates in the derive macro `FromParam` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -0,0 +1,20 @@
use rocket::request::FromParam;
#[derive(FromParam)]
struct Foo1 {
a: String
}
#[derive(FromParam)]
struct Foo2 {}
#[derive(FromParam)]
enum Foo3 {
A(String),
B(String)
}
#[derive(FromParam)]
struct Foo4(usize);
fn main() {}

View File

@ -1,6 +1,10 @@
use std::borrow::Cow;
use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use std::path::PathBuf; use std::path::PathBuf;
use cookie::Display;
use crate::error::Empty; use crate::error::Empty;
use crate::either::Either; use crate::either::Either;
use crate::http::uri::{Segments, error::PathError, fmt::Path}; use crate::http::uri::{Segments, error::PathError, fmt::Path};
@ -306,6 +310,31 @@ impl<'a, T: FromParam<'a>> FromParam<'a> for Option<T> {
} }
} }
/// Error type for automatically derived `FromParam` enums
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct EnumFromParamError<'a> {
pub value: Cow<'a, str>,
pub options: Cow<'static, [Cow<'static, str>]>,
}
impl<'a> EnumFromParamError<'a> {
pub fn new(value: Cow<'a, str>, options: Cow<'static, [Cow<'static, str>]>) -> Self {
Self {
value,
options,
}
}
}
impl fmt::Display for EnumFromParamError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unexpected value {:?}, expected one of {:?}", self.value, self.options)
}
}
impl std::error::Error for EnumFromParamError<'_> {}
/// Trait to convert _many_ dynamic path segment strings to a concrete value. /// Trait to convert _many_ dynamic path segment strings to a concrete value.
/// ///
/// This is the `..` analog to [`FromParam`], and its functionality is identical /// This is the `..` analog to [`FromParam`], and its functionality is identical

View File

@ -12,6 +12,12 @@ pub use self::request::Request;
pub use self::from_request::{FromRequest, Outcome}; pub use self::from_request::{FromRequest, Outcome};
pub use self::from_param::{FromParam, FromSegments}; pub use self::from_param::{FromParam, FromSegments};
#[doc(hidden)]
pub use self::from_param::EnumFromParamError;
#[doc(hidden)]
pub use rocket_codegen::FromParam;
#[doc(inline)] #[doc(inline)]
pub use crate::response::flash::FlashMessage; pub use crate::response::flash::FlashMessage;