mirror of https://github.com/rwf2/Rocket.git
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.
This commit is contained in:
parent
15062ded09
commit
1f82d4bbcd
|
@ -1,45 +1,40 @@
|
|||
use crate::exports::*;
|
||||
use devise::ext::SpanDiagnosticExt;
|
||||
use devise::Support;
|
||||
use devise::*;
|
||||
use proc_macro2::TokenStream;
|
||||
use devise::ext::SpanDiagnosticExt;
|
||||
|
||||
use quote::quote;
|
||||
use proc_macro2::TokenStream;
|
||||
use syn::ext::IdentExt;
|
||||
|
||||
use crate::exports::*;
|
||||
|
||||
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"));
|
||||
return Err(fields.span().error("variants with data fields are not 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),
|
||||
)
|
||||
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!(stringify!(#field_name))
|
||||
});
|
||||
|
||||
quote! {
|
||||
type Error = #_request::EnumFromParamError<'a>;
|
||||
type Error = #_error::InvalidOption<'a>;
|
||||
|
||||
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
|
||||
match param {
|
||||
#(#matches)*
|
||||
_ => Err(#_request::EnumFromParamError::new(
|
||||
#_Cow::Borrowed(param),
|
||||
#_Cow::Borrowed(&[#(#names)*]),
|
||||
)),
|
||||
#(#matches,)*
|
||||
_ => Err(#_error::InvalidOption::new(param, &[#(#names),*])),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -776,32 +776,41 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream {
|
|||
|
||||
/// 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):
|
||||
/// This [`FromParam`] derive can be applied to C-like enums whose variants have
|
||||
/// no fields. The generated implementation case-sensitively matches each
|
||||
/// variant to its stringified field name. If there is no match, an error
|
||||
/// of type [`InvalidOption`] is returned.
|
||||
///
|
||||
/// [`FromParam`]: ../rocket/request/trait.FromParam.html
|
||||
/// [`InvalidOption`]: ../rocket/error/struct.InvalidOption.html
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #
|
||||
/// use rocket::request::FromParam;
|
||||
///
|
||||
/// #[derive(FromParam, Debug, PartialEq)]
|
||||
/// enum MyParam {
|
||||
/// A,
|
||||
/// B,
|
||||
/// Bob,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(MyParam::from_param("A").unwrap(), MyParam::A);
|
||||
/// assert_eq!(MyParam::from_param("B").unwrap(), MyParam::B);
|
||||
/// assert_eq!(MyParam::from_param("Bob").unwrap(), MyParam::Bob);
|
||||
/// assert!(MyParam::from_param("a").is_err());
|
||||
/// assert!(MyParam::from_param("b").is_err());
|
||||
/// assert!(MyParam::from_param("bob").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
|
||||
///
|
||||
/// // Now `MyParam` can be used in an route to accept either `A` or `B`.
|
||||
/// #[get("/<param>")]
|
||||
/// fn index(param: MyParam) -> &'static str {
|
||||
/// match param {
|
||||
/// MyParam::A => "A",
|
||||
/// MyParam::Bob => "Bob",
|
||||
/// }
|
||||
/// }
|
||||
#[proc_macro_derive(FromParam)]
|
||||
pub fn derive_from_param(input: TokenStream) -> TokenStream {
|
||||
emit!(derive::from_param::derive_from_param(input))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use rocket::request::FromParam;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, FromParam, PartialEq)]
|
||||
enum Test {
|
||||
Test1,
|
||||
|
@ -9,14 +10,16 @@ enum Test {
|
|||
|
||||
#[test]
|
||||
fn derive_from_param() {
|
||||
let test1 = Test::from_param("Test1").expect("Should be valid");
|
||||
assert_eq!(test1, Test::Test1);
|
||||
assert_eq!(Test::from_param("Test1").unwrap(), Test::Test1);
|
||||
assert_eq!(Test::from_param("Test2").unwrap(), Test::Test2);
|
||||
assert_eq!(Test::from_param("for").unwrap(), Test::r#for);
|
||||
|
||||
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 err = Test::from_param("For").unwrap_err();
|
||||
assert_eq!(err.value, "For");
|
||||
assert_eq!(err.options, &["Test1", "Test2", "for"]);
|
||||
|
||||
let err = Test::from_param("not_test").unwrap_err();
|
||||
assert_eq!(err.value, "not_test");
|
||||
assert_eq!(err.options, &["Test1", "Test2", "for"]);
|
||||
|
||||
let test3 = Test::from_param("not_test");
|
||||
assert!(test3.is_err());
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ note: error occurred while deriving `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
|
||||
error: variants with data fields are not supported
|
||||
--> tests/ui-fail-nightly/from_param.rs:13:6
|
||||
|
|
||||
13 | A(String),
|
||||
|
|
|
@ -28,7 +28,7 @@ error: [note] error occurred while deriving `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
|
||||
error: variants with data fields are not supported
|
||||
--> tests/ui-fail-stable/from_param.rs:13:6
|
||||
|
|
||||
13 | A(String),
|
||||
|
|
|
@ -89,6 +89,60 @@ pub enum ErrorKind {
|
|||
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Empty;
|
||||
|
||||
/// An error that occurs when a value doesn't match one of the expected options.
|
||||
///
|
||||
/// This error is returned by the [`FromParam`] trait implementation generated
|
||||
/// by the [`FromParam` derive](macro@rocket::FromParam) when the value of a
|
||||
/// dynamic path segment does not match one of the expected variants. The
|
||||
/// `value` field will contain the value that was provided, and `options` will
|
||||
/// contain each of possible stringified variants.
|
||||
///
|
||||
/// [`FromParam`]: trait@rocket::request::FromParam
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::error::InvalidOption;
|
||||
///
|
||||
/// #[derive(FromParam)]
|
||||
/// enum MyParam {
|
||||
/// FirstOption,
|
||||
/// SecondOption,
|
||||
/// ThirdOption,
|
||||
/// }
|
||||
///
|
||||
/// #[get("/<param>")]
|
||||
/// fn hello(param: Result<MyParam, InvalidOption<'_>>) {
|
||||
/// if let Err(e) = param {
|
||||
/// assert_eq!(e.options, &["FirstOption", "SecondOption", "ThirdOption"]);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct InvalidOption<'a> {
|
||||
/// The value that was provided.
|
||||
pub value: &'a str,
|
||||
/// The expected values: a slice of strings, one for each possible value.
|
||||
pub options: &'static [&'static str],
|
||||
}
|
||||
|
||||
impl<'a> InvalidOption<'a> {
|
||||
#[doc(hidden)]
|
||||
pub fn new(value: &'a str, options: &'static [&'static str]) -> Self {
|
||||
Self { value, options }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidOption<'_> {
|
||||
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 InvalidOption<'_> {}
|
||||
|
||||
impl Error {
|
||||
#[inline(always)]
|
||||
pub(crate) fn new(kind: ErrorKind) -> Error {
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use cookie::Display;
|
||||
|
||||
use crate::error::Empty;
|
||||
use crate::either::Either;
|
||||
use crate::http::uri::{Segments, error::PathError, fmt::Path};
|
||||
|
@ -16,6 +12,11 @@ use crate::http::uri::{Segments, error::PathError, fmt::Path};
|
|||
/// a dynamic segment `<param>` where `param` has some type `T` that implements
|
||||
/// `FromParam`, `T::from_param` will be called.
|
||||
///
|
||||
/// # Deriving
|
||||
///
|
||||
/// The `FromParam` trait can be automatically derived for C-like enums. See
|
||||
/// [`FromParam` derive](macro@rocket::FromParam) for more information.
|
||||
///
|
||||
/// # Forwarding
|
||||
///
|
||||
/// 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> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This is the `..` analog to [`FromParam`], and its functionality is identical
|
||||
|
|
|
@ -12,9 +12,6 @@ pub use self::request::Request;
|
|||
pub use self::from_request::{FromRequest, Outcome};
|
||||
pub use self::from_param::{FromParam, FromSegments};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::from_param::EnumFromParamError;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use rocket_codegen::FromParam;
|
||||
|
||||
|
|
Loading…
Reference in New Issue