diff --git a/core/codegen/src/bang/export.rs b/core/codegen/src/bang/export.rs new file mode 100644 index 0000000000..b48dd483bb --- /dev/null +++ b/core/codegen/src/bang/export.rs @@ -0,0 +1,56 @@ +use std::hash::Hash; + +use devise::Spanned; +use devise::ext::SpanDiagnosticExt; +use devise::proc_macro2::{TokenStream, TokenTree, Punct}; +use devise::syn; + +use crate::syn_ext::IdentExt; + +pub fn _macro(input: proc_macro::TokenStream) -> devise::Result { + let mac: syn::ItemMacro = syn::parse(input)?; + let macro_name = match mac.ident { + Some(ident) => ident, + None => return Err(mac.span().error("expected `macro_rules!`")), + }; + + // We rename the actual `macro_export` macro so we don't accidentally use it + // internally from the auto-imported crate root macro namespace. + let (attrs, def) = (mac.attrs, mac.mac); + let internal_name = macro_name.prepend("___internal_"); + let mod_name = macro_name.uniqueify_with(|mut hasher| def.hash(&mut hasher)); + + let macro_rules_tokens = def.tokens.clone(); + let decl_macro_tokens: TokenStream = def.tokens.into_iter() + .map(|t| match t { + TokenTree::Punct(p) if p.as_char() == ';' => { + let mut token = Punct::new(',', p.spacing()); + token.set_span(p.span()); + TokenTree::Punct(token) + }, + _ => t, + }) + .collect(); + + Ok(quote! { + #[allow(non_snake_case)] + mod #mod_name { + #[doc(hidden)] + #[macro_export] + macro_rules! #internal_name { + #macro_rules_tokens + } + + pub use #internal_name; + } + + #(#attrs)* + #[cfg(all(nightly, doc))] + pub macro #macro_name { + #decl_macro_tokens + } + + #[cfg(not(all(nightly, doc)))] + pub use #mod_name::#internal_name as #macro_name; + }) +} diff --git a/core/codegen/src/bang/mod.rs b/core/codegen/src/bang/mod.rs index 4f7dabfd15..756f75279f 100644 --- a/core/codegen/src/bang/mod.rs +++ b/core/codegen/src/bang/mod.rs @@ -7,6 +7,7 @@ use crate::syn::spanned::Spanned; mod uri; mod uri_parsing; mod test_guide; +mod export; fn struct_maker_vec( input: proc_macro::TokenStream, @@ -65,3 +66,8 @@ pub fn guide_tests_internal(input: proc_macro::TokenStream) -> TokenStream { test_guide::_macro(input) .unwrap_or_else(|diag| diag.emit_as_item_tokens()) } + +pub fn export_internal(input: proc_macro::TokenStream) -> TokenStream { + export::_macro(input) + .unwrap_or_else(|diag| diag.emit_as_item_tokens()) +} diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index f99daba6a1..682c15c95c 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -1062,3 +1062,10 @@ pub fn rocket_internal_uri(input: TokenStream) -> TokenStream { pub fn internal_guide_tests(input: TokenStream) -> TokenStream { emit!(bang::guide_tests_internal(input)) } + +#[doc(hidden)] +#[proc_macro] +/// Private Rocket internal macro: `export!`. +pub fn export(input: TokenStream) -> TokenStream { + emit!(bang::export_internal(input)) +} diff --git a/core/lib/src/data/from_data.rs b/core/lib/src/data/from_data.rs index c73cd2f819..aa7558d2aa 100644 --- a/core/lib/src/data/from_data.rs +++ b/core/lib/src/data/from_data.rs @@ -1,7 +1,7 @@ use crate::http::{RawStr, Status}; use crate::request::{Request, local_cache}; use crate::data::{Data, Limits}; -use crate::outcome::{self, IntoOutcome, Outcome::*}; +use crate::outcome::{self, IntoOutcome, try_outcome, Outcome::*}; /// Type alias for the `Outcome` of [`FromData`]. /// diff --git a/core/lib/src/form/form.rs b/core/lib/src/form/form.rs index 8861a7767a..25367e8338 100644 --- a/core/lib/src/form/form.rs +++ b/core/lib/src/form/form.rs @@ -1,6 +1,7 @@ use std::ops::{Deref, DerefMut}; -use crate::request::Request; +use crate::Request; +use crate::outcome::try_outcome; use crate::data::{Data, FromData, Outcome}; use crate::http::{RawStr, ext::IntoOwned}; use crate::form::parser::{Parser, RawStrParser, Buffer}; diff --git a/core/lib/src/form/validate.rs b/core/lib/src/form/validate.rs index 00790c0ffd..69a8f84825 100644 --- a/core/lib/src/form/validate.rs +++ b/core/lib/src/form/validate.rs @@ -88,52 +88,76 @@ use rocket_http::ContentType; use crate::{data::TempFile, form::{Result, Error}}; -/// A helper macro for custom validation error messages. -/// -/// The macro works identically to [`std::format!`] except it does not allocate -/// when the expression is a string literal. It returns a function (a closure) -/// that takes one parameter and evaluates to an `Err` of validation [`Error`] -/// with the formatted message. While useful in other contexts, it is designed -/// to be chained to validation results via `.or_else()` and `.and_then()`. -/// -/// Note that the macro never needs to be imported when used with a `FromForm` -/// derive; all items in [`form::validate`](crate::form::validate) are already -/// in scope. -/// -/// # Example -/// -/// ```rust -/// use rocket::form::FromForm; -/// -/// #[derive(FromForm)] -/// struct Person<'r> { -/// #[field(validate = len(3..).or_else(msg!("that's a short name...")))] -/// name: &'r str, -/// #[field(validate = contains('f').and_then(msg!("please, no `f`!")))] -/// non_f_name: &'r str, -/// } -/// ``` -/// -/// See the [top-level docs](crate::form::validate) for more examples. -#[macro_export] -macro_rules! msg { - ($e:expr) => ( - |_| { - Err($crate::form::Errors::from($crate::form::Error::validation($e))) - as $crate::form::Result<()> - } - ); - ($($arg:tt)*) => ( - |_| { - Err($crate::form::Errors::from($crate::form::Error::validation(format!($($arg)*)))) - as $crate::form::Result<()> - } - ); +crate::export! { + /// A helper macro for custom validation error messages. + /// + /// The macro works similar to [`std::format!`]. It generates a form + /// [`Validation`] error message. While useful in other contexts, it is + /// designed to be chained to validation results in derived `FromForm` + /// `#[field]` attributes via `.or_else()` and `.and_then()`. + /// + /// [`Validation`]: crate::form::error::ErrorKind::Validation + /// [`form::validate`]: crate::form::validate + /// + /// # Example + /// + /// ```rust + /// use rocket::form::FromForm; + /// + /// #[derive(FromForm)] + /// struct Person<'r> { + /// #[field(validate = len(3..).or_else(msg!("that's a short name...")))] + /// name: &'r str, + /// #[field(validate = contains('f').and_then(msg!("please, no `f`!")))] + /// non_f_name: &'r str, + /// } + /// ``` + /// + /// _**Note:** this macro _never_ needs to be imported when used with a + /// `FromForm` derive; all items in [`form::validate`] are automatically in + /// scope in `FromForm` derive attributes._ + /// + /// See the [top-level docs](crate::form::validate) for more examples. + /// + /// # Syntax + /// + /// The macro has the following "signatures": + /// + /// ## Variant 1 + /// + /// ```rust + /// # use rocket::form; + /// # trait Expr {} + /// fn msg<'a, T, P, E: Expr>(expr: E) -> impl Fn(P) -> form::Result<'a, T> + /// # { |_| unimplemented!() } + /// ``` + /// + /// Takes any expression and returns a function that takes any argument type + /// and evaluates to a [`form::Result`](crate::form::Result) with an `Ok` of + /// any type. The `Result` is guaranteed to be an `Err` of kind + /// [`Validation`] with `expr` as the message. + /// + /// ## Variant 2 + /// + /// ``` + /// # use rocket::form; + /// # trait Format {} + /// # trait Args {} + /// fn msg<'a, T, P, A: Args>(fmt: &str, args: A) -> impl Fn(P) -> form::Result<'a, T> + /// # { |_| unimplemented!() } + /// ``` + /// + /// Invokes the first variant as `msg!(format!(fmt, args))`. + macro_rules! msg { + ($e:expr) => (|_| { + Err($crate::form::Errors::from( + $crate::form::Error::validation($e) + )) as $crate::form::Result<()> + }); + ($($arg:tt)*) => ($crate::form::validate::msg!(format!($($arg)*))); + } } -#[doc(inline)] -pub use msg; - /// Equality validator: succeeds exactly when `a` == `b`, using [`PartialEq`]. /// /// On failure, returns a validation error with the following message: diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index f64e6c1a04..c5c9b2e69b 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -4,6 +4,7 @@ #![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")] #![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")] #![cfg_attr(nightly, feature(doc_cfg))] +#![cfg_attr(nightly, feature(decl_macro))] #![warn(rust_2018_idioms)] diff --git a/core/lib/src/outcome.rs b/core/lib/src/outcome.rs index 9b86db0c3a..59a38f5ef7 100644 --- a/core/lib/src/outcome.rs +++ b/core/lib/src/outcome.rs @@ -612,79 +612,89 @@ impl<'a, S: Send + 'a, E: Send + 'a, F: Send + 'a> Outcome { } } -/// Unwraps a [`Success`](Outcome::Success) or propagates a `Forward` or -/// `Failure`. -/// -/// This is just like `?` (or previously, `try!`), but for `Outcome`. In the -/// case of a `Forward` or `Failure` variant, the inner type is passed to -/// [`From`](std::convert::From), allowing for the conversion between specific -/// and more general types. The resulting forward/error is immediately returned. -/// -/// Because of the early return, `try_outcome!` can only be used in methods that -/// return [`Outcome`]. -/// -/// [`Outcome`]: crate::outcome::Outcome -/// -/// ## Example -/// -/// ```rust,no_run -/// # #[macro_use] extern crate rocket; -/// use std::sync::atomic::{AtomicUsize, Ordering}; -/// -/// use rocket::State; -/// use rocket::request::{self, Request, FromRequest}; -/// use rocket::outcome::Outcome::*; -/// -/// #[derive(Default)] -/// struct Atomics { -/// uncached: AtomicUsize, -/// cached: AtomicUsize, -/// } -/// -/// struct Guard1; -/// struct Guard2; -/// -/// #[rocket::async_trait] -/// impl<'r> FromRequest<'r> for Guard1 { -/// type Error = (); -/// -/// async fn from_request(req: &'r Request<'_>) -> request::Outcome { -/// // Attempt to fetch the guard, passing through any error or forward. -/// let atomics = try_outcome!(req.guard::>().await); -/// atomics.uncached.fetch_add(1, Ordering::Relaxed); -/// req.local_cache(|| atomics.cached.fetch_add(1, Ordering::Relaxed)); -/// -/// Success(Guard1) -/// } -/// } -/// -/// #[rocket::async_trait] -/// impl<'r> FromRequest<'r> for Guard2 { -/// type Error = (); -/// -/// async fn from_request(req: &'r Request<'_>) -> request::Outcome { -/// // Attempt to fetch the guard, passing through any error or forward. -/// let guard1: Guard1 = try_outcome!(req.guard::().await); -/// Success(Guard2) -/// } -/// } -/// ``` -#[macro_export] -macro_rules! try_outcome { - ($expr:expr $(,)?) => (match $expr { - $crate::outcome::Outcome::Success(val) => val, - $crate::outcome::Outcome::Failure(e) => { - return $crate::outcome::Outcome::Failure(::std::convert::From::from(e)) - }, - $crate::outcome::Outcome::Forward(f) => { - return $crate::outcome::Outcome::Forward(::std::convert::From::from(f)) - }, - }); +crate::export! { + /// Unwraps a [`Success`](Outcome::Success) or propagates a `Forward` or + /// `Failure`. + /// + /// # Syntax + /// + /// The macro has the following "signature": + /// + /// ```rust + /// use rocket::outcome::Outcome; + /// + /// // Returns the inner `S` if `outcome` is `Outcome::Success`. Otherwise + /// // returns from the caller with `Outcome, impl From>`. + /// fn try_outcome(outcome: Outcome) -> S + /// # { unimplemented!() } + /// ``` + /// + /// This is just like `?` (or previously, `try!`), but for `Outcome`. In the + /// case of a `Forward` or `Failure` variant, the inner type is passed to + /// [`From`](std::convert::From), allowing for the conversion between + /// specific and more general types. The resulting forward/error is + /// immediately returned. Because of the early return, `try_outcome!` can + /// only be used in methods that return [`Outcome`]. + /// + /// [`Outcome`]: crate::outcome::Outcome + /// + /// ## Example + /// + /// ```rust,no_run + /// # #[macro_use] extern crate rocket; + /// use std::sync::atomic::{AtomicUsize, Ordering}; + /// + /// use rocket::State; + /// use rocket::request::{self, Request, FromRequest}; + /// use rocket::outcome::{try_outcome, Outcome::*}; + /// + /// #[derive(Default)] + /// struct Atomics { + /// uncached: AtomicUsize, + /// cached: AtomicUsize, + /// } + /// + /// struct Guard1; + /// struct Guard2; + /// + /// #[rocket::async_trait] + /// impl<'r> FromRequest<'r> for Guard1 { + /// type Error = (); + /// + /// async fn from_request(req: &'r Request<'_>) -> request::Outcome { + /// // Attempt to fetch the guard, passing through any error or forward. + /// let atomics = try_outcome!(req.guard::>().await); + /// atomics.uncached.fetch_add(1, Ordering::Relaxed); + /// req.local_cache(|| atomics.cached.fetch_add(1, Ordering::Relaxed)); + /// + /// Success(Guard1) + /// } + /// } + /// + /// #[rocket::async_trait] + /// impl<'r> FromRequest<'r> for Guard2 { + /// type Error = (); + /// + /// async fn from_request(req: &'r Request<'_>) -> request::Outcome { + /// // Attempt to fetch the guard, passing through any error or forward. + /// let guard1: Guard1 = try_outcome!(req.guard::().await); + /// Success(Guard2) + /// } + /// } + /// ``` + macro_rules! try_outcome { + ($expr:expr $(,)?) => (match $expr { + $crate::outcome::Outcome::Success(val) => val, + $crate::outcome::Outcome::Failure(e) => { + return $crate::outcome::Outcome::Failure(::std::convert::From::from(e)) + }, + $crate::outcome::Outcome::Forward(f) => { + return $crate::outcome::Outcome::Forward(::std::convert::From::from(f)) + }, + }); + } } -#[doc(inline)] -pub use try_outcome; - impl fmt::Debug for Outcome { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Outcome::{}", self.formatting().1) diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs index baa5346ee9..21b2ded8f9 100644 --- a/core/lib/src/request/from_request.rs +++ b/core/lib/src/request/from_request.rs @@ -247,7 +247,7 @@ impl IntoOutcome for Result { /// ```rust /// # #[macro_use] extern crate rocket; /// # #[cfg(feature = "secrets")] mod wrapper { -/// # use rocket::outcome::IntoOutcome; +/// # use rocket::outcome::{IntoOutcome, try_outcome}; /// # use rocket::request::{self, Outcome, FromRequest, Request}; /// # struct User { id: String, is_admin: bool } /// # struct Database; @@ -311,7 +311,7 @@ impl IntoOutcome for Result { /// ```rust /// # #[macro_use] extern crate rocket; /// # #[cfg(feature = "secrets")] mod wrapper { -/// # use rocket::outcome::IntoOutcome; +/// # use rocket::outcome::{IntoOutcome, try_outcome}; /// # use rocket::request::{self, Outcome, FromRequest, Request}; /// # struct User { id: String, is_admin: bool } /// # struct Database; diff --git a/core/lib/src/request/mod.rs b/core/lib/src/request/mod.rs index 2bbb71c555..e5d8e0bcc4 100644 --- a/core/lib/src/request/mod.rs +++ b/core/lib/src/request/mod.rs @@ -14,35 +14,33 @@ pub use self::from_param::{FromParam, FromSegments}; #[doc(inline)] pub use crate::response::flash::FlashMessage; -/// Store and immediately retrieve a value `$v` in `$request`'s local cache -/// using a locally generated anonymous type to avoid type conflicts. -/// -/// # Example -/// -/// ```rust -/// use rocket::request::local_cache; -/// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap(); -/// # let request = c.get("/"); -/// -/// // The first store into local cache for a given type wins. -/// assert_eq!(request.local_cache(|| String::from("hello")), "hello"); -/// -/// // The following returns the cached, previously stored value for the type. -/// assert_eq!(request.local_cache(|| String::from("goodbye")), "hello"); -/// -/// // This shows that we cannot cache different values of the same type; we -/// // _must_ use a proxy type. To avoid the need to write these manually, use -/// // `local_cache!`, which generates one of the fly. -/// assert_eq!(local_cache!(request, String::from("hello")), "hello"); -/// assert_eq!(local_cache!(request, String::from("goodbye")), "goodbye"); -/// ``` -#[macro_export] -macro_rules! local_cache { - ($request:expr, $v:expr) => ({ - struct Local(T); - &$request.local_cache(move || Local($v)).0 - }) +crate::export! { + /// Store and immediately retrieve a value `$v` in `$request`'s local cache + /// using a locally generated anonymous type to avoid type conflicts. + /// + /// # Example + /// + /// ```rust + /// use rocket::request::local_cache; + /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap(); + /// # let request = c.get("/"); + /// + /// // The first store into local cache for a given type wins. + /// assert_eq!(request.local_cache(|| String::from("hello")), "hello"); + /// + /// // The following returns the cached, previously stored value for the type. + /// assert_eq!(request.local_cache(|| String::from("goodbye")), "hello"); + /// + /// // This shows that we cannot cache different values of the same type; we + /// // _must_ use a proxy type. To avoid the need to write these manually, use + /// // `local_cache!`, which generates one of the fly. + /// assert_eq!(local_cache!(request, String::from("hello")), "hello"); + /// assert_eq!(local_cache!(request, String::from("goodbye")), "goodbye"); + /// ``` + macro_rules! local_cache { + ($request:expr, $v:expr $(,)?) => ({ + struct Local(T); + &$request.local_cache(move || Local($v)).0 + }) + } } - -#[doc(inline)] -pub use local_cache; diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index b20fb8cf6a..8424f63d54 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -571,8 +571,9 @@ impl<'r> Request<'r> { /// Different values of the same type _cannot_ be cached without using a /// proxy, wrapper type. To avoid the need to write these manually, or for /// libraries wishing to store values of public types, use the - /// [`local_cache!`] macro to generate a locally anonymous wrapper type, - /// store, and retrieve the wrapped value from request-local cache. + /// [`local_cache!`](crate::request::local_cache) macro to generate a + /// locally anonymous wrapper type, store, and retrieve the wrapped value + /// from request-local cache. /// /// # Example /// diff --git a/examples/state/src/request_local.rs b/examples/state/src/request_local.rs index e2dce32696..afe2254a61 100644 --- a/examples/state/src/request_local.rs +++ b/examples/state/src/request_local.rs @@ -1,7 +1,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use rocket::State; -use rocket::outcome::Outcome; +use rocket::outcome::{Outcome, try_outcome}; use rocket::request::{self, FromRequest, Request}; use rocket::fairing::AdHoc; diff --git a/site/guide/6-state.md b/site/guide/6-state.md index 64d2a28199..7f25b1c58f 100644 --- a/site/guide/6-state.md +++ b/site/guide/6-state.md @@ -122,6 +122,7 @@ implementation. To do so, simply invoke `State` as a guard using the use rocket::State; use rocket::request::{self, Request, FromRequest}; +use rocket::outcome::try_outcome; # use std::sync::atomic::{AtomicUsize, Ordering}; # struct T;