mirror of https://github.com/rwf2/Rocket.git
Derive FromParam for Enum #2826
This commit is contained in:
parent
509a033c84
commit
38cebebbc3
|
@ -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()
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
../ui-fail/from_param.rs
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
||||||
|
../ui-fail/from_param.rs
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue