Skip to content

Commit

Permalink
Mod-export 'msg!', 'local_cache!', 'try_outcome!'.
Browse files Browse the repository at this point in the history
This removes the export of each of these macros from the root, limiting
their export-scope to their respective module. This is accomplished
using a new internal macro, 'export!', which does some "magic" to work
around rustdoc deficiencies.
  • Loading branch information
SergioBenitez committed Apr 28, 2021
1 parent 41018a5 commit bab3b1c
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 153 deletions.
56 changes: 56 additions & 0 deletions core/codegen/src/bang/export.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
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;
})
}
6 changes: 6 additions & 0 deletions core/codegen/src/bang/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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())
}
7 changes: 7 additions & 0 deletions core/codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
2 changes: 1 addition & 1 deletion core/lib/src/data/from_data.rs
Original file line number Diff line number Diff line change
@@ -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`].
///
Expand Down
3 changes: 2 additions & 1 deletion core/lib/src/form/form.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
112 changes: 68 additions & 44 deletions core/lib/src/form/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions core/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]

Expand Down
152 changes: 81 additions & 71 deletions core/lib/src/outcome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,79 +612,89 @@ impl<'a, S: Send + 'a, E: Send + 'a, F: Send + 'a> Outcome<S, E, F> {
}
}

/// 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<Self, ()> {
/// // Attempt to fetch the guard, passing through any error or forward.
/// let atomics = try_outcome!(req.guard::<State<'_, Atomics>>().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<Self, ()> {
/// // Attempt to fetch the guard, passing through any error or forward.
/// let guard1: Guard1 = try_outcome!(req.guard::<Guard1>().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<E>, impl From<F>>`.
/// fn try_outcome<S, E, F>(outcome: Outcome<S, E, F>) -> 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<Self, ()> {
/// // Attempt to fetch the guard, passing through any error or forward.
/// let atomics = try_outcome!(req.guard::<State<'_, Atomics>>().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<Self, ()> {
/// // Attempt to fetch the guard, passing through any error or forward.
/// let guard1: Guard1 = try_outcome!(req.guard::<Guard1>().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<S, E, F> fmt::Debug for Outcome<S, E, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Outcome::{}", self.formatting().1)
Expand Down
Loading

0 comments on commit bab3b1c

Please sign in to comment.