2018-09-20 04:47:58 +00:00
|
|
|
use proc_macro::Span;
|
|
|
|
|
2018-10-04 09:00:04 +00:00
|
|
|
use derive_utils::{syn, Spanned};
|
|
|
|
use derive_utils::proc_macro2::TokenStream as TokenStream2;
|
2018-09-20 04:47:58 +00:00
|
|
|
use derive_utils::ext::TypeExt;
|
|
|
|
use quote::ToTokens;
|
|
|
|
|
|
|
|
use self::syn::{Expr, Ident, LitStr, Path, Token, Type};
|
|
|
|
use self::syn::parse::{self, Parse, ParseStream};
|
|
|
|
use self::syn::punctuated::Punctuated;
|
|
|
|
|
2018-10-04 10:54:46 +00:00
|
|
|
use http::{uri::Origin, ext::IntoOwned};
|
2018-09-20 04:47:58 +00:00
|
|
|
use indexmap::IndexMap;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2018-10-04 09:00:04 +00:00
|
|
|
pub enum Arg {
|
2018-09-20 04:47:58 +00:00
|
|
|
Unnamed(Expr),
|
2018-10-04 09:00:04 +00:00
|
|
|
Named(Ident, Token![=], Expr),
|
2018-09-20 04:47:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Args {
|
2018-10-04 09:00:04 +00:00
|
|
|
Unnamed(Punctuated<Arg, Token![,]>),
|
|
|
|
Named(Punctuated<Arg, Token![,]>),
|
2018-09-20 04:47:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// For an invocation that looks like:
|
|
|
|
// uri!("/mount/point", this::route: e1, e2, e3);
|
|
|
|
// ^-------------| ^----------| ^---------|
|
|
|
|
// uri_params.mount_point | uri_params.arguments
|
|
|
|
// uri_params.route_path
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct UriParams {
|
2018-10-04 10:54:46 +00:00
|
|
|
pub mount_point: Option<Origin<'static>>,
|
2018-09-20 04:47:58 +00:00
|
|
|
pub route_path: Path,
|
2018-10-04 09:00:04 +00:00
|
|
|
pub arguments: Args,
|
2018-09-20 04:47:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct FnArg {
|
|
|
|
pub ident: Ident,
|
|
|
|
pub ty: Type,
|
|
|
|
}
|
|
|
|
|
2018-10-04 09:00:04 +00:00
|
|
|
pub enum Validation<'a> {
|
2018-09-20 04:47:58 +00:00
|
|
|
// Number expected, what we actually got.
|
|
|
|
Unnamed(usize, usize),
|
|
|
|
// (Missing, Extra, Duplicate)
|
2018-10-04 09:00:04 +00:00
|
|
|
Named(Vec<&'a Ident>, Vec<&'a Ident>, Vec<&'a Ident>),
|
|
|
|
// Everything is okay; here are the expressions in the route decl order.
|
|
|
|
Ok(Vec<&'a Expr>)
|
2018-09-20 04:47:58 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 09:00:04 +00:00
|
|
|
// This is invoked by Rocket itself. The `uri!` macro expands to a call to a
|
|
|
|
// route-specific macro which in-turn expands to a call to `internal_uri!`,
|
|
|
|
// passing along the user's parameters from the original `uri!` call. This is
|
|
|
|
// necessary so that we can converge the type information in the route (from the
|
|
|
|
// route-specific macro) with the user's parameters (by forwarding them to the
|
|
|
|
// internal_uri! call).
|
|
|
|
//
|
2018-09-20 04:47:58 +00:00
|
|
|
// `fn_args` are the URI arguments (excluding guards) from the original route's
|
|
|
|
// handler in the order they were declared in the URI (`<first>/<second>`).
|
2018-10-04 09:00:04 +00:00
|
|
|
// `uri` is the full URI used in the origin route's attribute.
|
|
|
|
//
|
|
|
|
// internal_uri!("/<first>/<second>", (first: ty, second: ty), $($tt)*);
|
2018-10-04 10:54:46 +00:00
|
|
|
// ^--------|--------- ^-----------|---------| ^-----|
|
|
|
|
// route_uri fn_args uri_params
|
2018-09-20 04:47:58 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct InternalUriParams {
|
2018-10-04 10:54:46 +00:00
|
|
|
pub route_uri: Origin<'static>,
|
2018-09-20 04:47:58 +00:00
|
|
|
pub fn_args: Vec<FnArg>,
|
|
|
|
pub uri_params: UriParams,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Parse for Arg {
|
|
|
|
fn parse(input: ParseStream) -> parse::Result<Self> {
|
|
|
|
let has_key = input.peek2(Token![=]);
|
|
|
|
if has_key {
|
|
|
|
let ident = input.parse::<Ident>()?;
|
2018-10-04 09:00:04 +00:00
|
|
|
let eq_token = input.parse::<Token![=]>()?;
|
2018-09-20 04:47:58 +00:00
|
|
|
let expr = input.parse::<Expr>()?;
|
2018-10-04 09:00:04 +00:00
|
|
|
Ok(Arg::Named(ident, eq_token, expr))
|
2018-09-20 04:47:58 +00:00
|
|
|
} else {
|
|
|
|
let expr = input.parse::<Expr>()?;
|
|
|
|
Ok(Arg::Unnamed(expr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-04 09:00:04 +00:00
|
|
|
fn err<T, S: AsRef<str>>(span: Span, s: S) -> parse::Result<T> {
|
|
|
|
Err(parse::Error::new(span.into(), s.as_ref()))
|
|
|
|
}
|
|
|
|
|
2018-09-20 04:47:58 +00:00
|
|
|
impl Parse for UriParams {
|
|
|
|
// Parses the mount point, if any, route identifier, and arguments.
|
|
|
|
fn parse(input: ParseStream) -> parse::Result<Self> {
|
|
|
|
if input.is_empty() {
|
|
|
|
return Err(input.error("call to `uri!` cannot be empty"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the mount point and suffixing ',', if any.
|
|
|
|
let mount_point = if input.peek(LitStr) {
|
|
|
|
let string = input.parse::<LitStr>()?;
|
2018-10-04 10:54:46 +00:00
|
|
|
let mount_point = Origin::parse_owned(string.value()).map_err(|_| {
|
|
|
|
// TODO(proc_macro): use error, add example as a help
|
|
|
|
parse::Error::new(string.span(), "invalid mount point; \
|
|
|
|
mount points must be static, absolute URIs: `/example`")
|
|
|
|
})?;
|
2018-10-04 09:00:04 +00:00
|
|
|
|
|
|
|
if !input.peek(Token![,]) && input.cursor().eof() {
|
|
|
|
return err(string.span().unstable(), "unexpected end of input: \
|
|
|
|
expected ',' followed by route path");
|
|
|
|
}
|
|
|
|
|
2018-09-20 04:47:58 +00:00
|
|
|
input.parse::<Token![,]>()?;
|
2018-10-04 10:54:46 +00:00
|
|
|
Some(mount_point)
|
2018-09-20 04:47:58 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
// Parse the route identifier, which must always exist.
|
|
|
|
let route_path = input.parse::<Path>()?;
|
|
|
|
|
|
|
|
// If there are no arguments, finish early.
|
2018-10-04 09:00:04 +00:00
|
|
|
if !input.peek(Token![:]) && input.cursor().eof() {
|
|
|
|
let arguments = Args::Unnamed(Punctuated::new());
|
2018-09-20 04:47:58 +00:00
|
|
|
return Ok(Self { mount_point, route_path, arguments });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse arguments
|
2018-10-04 09:00:04 +00:00
|
|
|
let colon = input.parse::<Token![:]>()?;
|
2018-09-20 04:47:58 +00:00
|
|
|
let arguments: Punctuated<Arg, Token![,]> = input.parse_terminated(Arg::parse)?;
|
|
|
|
|
|
|
|
// A 'colon' was used but there are no arguments.
|
|
|
|
if arguments.is_empty() {
|
2018-10-04 09:00:04 +00:00
|
|
|
return err(colon.span(), "expected argument list after `:`");
|
2018-09-20 04:47:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that both types of arguments were not used at once.
|
|
|
|
let (mut homogeneous_args, mut prev_named) = (true, None);
|
|
|
|
for arg in &arguments {
|
|
|
|
match prev_named {
|
|
|
|
Some(prev_named) => homogeneous_args = prev_named == arg.is_named(),
|
|
|
|
None => prev_named = Some(arg.is_named()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !homogeneous_args {
|
2018-10-04 09:00:04 +00:00
|
|
|
return err(arguments.span(), "named and unnamed parameters cannot be mixed");
|
2018-09-20 04:47:58 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 09:00:04 +00:00
|
|
|
// Create the `Args` enum, which properly record one-kind-of-argument-ness.
|
|
|
|
let arguments = match prev_named {
|
|
|
|
Some(true) => Args::Named(arguments),
|
|
|
|
_ => Args::Unnamed(arguments)
|
2018-09-20 04:47:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Self { mount_point, route_path, arguments })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Parse for FnArg {
|
|
|
|
fn parse(input: ParseStream) -> parse::Result<FnArg> {
|
|
|
|
let ident = input.parse::<Ident>()?;
|
|
|
|
input.parse::<Token![:]>()?;
|
|
|
|
let mut ty = input.parse::<Type>()?;
|
|
|
|
ty.strip_lifetimes();
|
|
|
|
Ok(FnArg { ident, ty })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Parse for InternalUriParams {
|
|
|
|
fn parse(input: ParseStream) -> parse::Result<InternalUriParams> {
|
2018-10-04 10:54:46 +00:00
|
|
|
let route_uri_str = input.parse::<LitStr>()?;
|
2018-09-20 04:47:58 +00:00
|
|
|
input.parse::<Token![,]>()?;
|
|
|
|
|
2018-10-04 10:54:46 +00:00
|
|
|
// Validation should always succeed since this macro can only be called
|
|
|
|
// if the route attribute succeeded, implying a valid route URI.
|
|
|
|
let route_uri = Origin::parse_route(&route_uri_str.value())
|
|
|
|
.map(|o| o.to_normalized().into_owned())
|
|
|
|
.map_err(|_| input.error("internal error: invalid route URI"))?;
|
|
|
|
|
2018-09-20 04:47:58 +00:00
|
|
|
let content;
|
|
|
|
syn::parenthesized!(content in input);
|
|
|
|
let fn_args: Punctuated<FnArg, Token![,]> = content.parse_terminated(FnArg::parse)?;
|
|
|
|
let fn_args = fn_args.into_iter().collect();
|
|
|
|
|
|
|
|
input.parse::<Token![,]>()?;
|
|
|
|
let uri_params = input.parse::<UriParams>()?;
|
2018-10-04 10:54:46 +00:00
|
|
|
Ok(InternalUriParams { route_uri, fn_args, uri_params })
|
2018-09-20 04:47:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl InternalUriParams {
|
|
|
|
pub fn fn_args_str(&self) -> String {
|
|
|
|
self.fn_args.iter()
|
2018-10-04 09:00:04 +00:00
|
|
|
.map(|FnArg { ident, ty }| format!("{}: {}", ident, quote!(#ty).to_string().trim()))
|
2018-09-20 04:47:58 +00:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(", ")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn validate(&self) -> Validation {
|
2018-10-04 09:00:04 +00:00
|
|
|
let args = &self.uri_params.arguments;
|
|
|
|
match args {
|
|
|
|
Args::Unnamed(inner) => {
|
|
|
|
let (expected, actual) = (self.fn_args.len(), inner.len());
|
|
|
|
if expected != actual { Validation::Unnamed(expected, actual) }
|
|
|
|
else { Validation::Ok(args.unnamed().unwrap().collect()) }
|
|
|
|
},
|
|
|
|
Args::Named(_) => {
|
|
|
|
let mut params: IndexMap<&Ident, Option<&Expr>> = self.fn_args.iter()
|
|
|
|
.map(|FnArg { ident, .. }| (ident, None))
|
2018-09-20 04:47:58 +00:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
let (mut extra, mut dup) = (vec![], vec![]);
|
2018-10-04 09:00:04 +00:00
|
|
|
for (ident, expr) in args.named().unwrap() {
|
2018-09-20 04:47:58 +00:00
|
|
|
match params.get_mut(ident) {
|
2018-10-04 09:00:04 +00:00
|
|
|
Some(ref entry) if entry.is_some() => dup.push(ident),
|
|
|
|
Some(entry) => *entry = Some(expr),
|
|
|
|
None => extra.push(ident),
|
2018-09-20 04:47:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let (mut missing, mut exprs) = (vec![], vec![]);
|
|
|
|
for (name, expr) in params {
|
|
|
|
match expr {
|
|
|
|
Some(expr) => exprs.push(expr),
|
|
|
|
None => missing.push(name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (extra.len() + dup.len() + missing.len()) == 0 {
|
|
|
|
Validation::Ok(exprs)
|
|
|
|
} else {
|
|
|
|
Validation::Named(missing, extra, dup)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-04 09:00:04 +00:00
|
|
|
impl UriParams {
|
|
|
|
/// The Span to use when referring to all of the arguments.
|
|
|
|
pub fn args_span(&self) -> Span {
|
|
|
|
match self.arguments.num() {
|
|
|
|
0 => self.route_path.span(),
|
|
|
|
_ => self.arguments.span()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Arg {
|
|
|
|
fn is_named(&self) -> bool {
|
|
|
|
match *self {
|
|
|
|
Arg::Named(..) => true,
|
|
|
|
Arg::Unnamed(_) => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unnamed(&self) -> &Expr {
|
|
|
|
match self {
|
|
|
|
Arg::Unnamed(expr) => expr,
|
|
|
|
_ => panic!("Called Arg::unnamed() on an Arg::named!"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn named(&self) -> (&Ident, &Expr) {
|
|
|
|
match self {
|
|
|
|
Arg::Named(ident, _, expr) => (ident, expr),
|
|
|
|
_ => panic!("Called Arg::named() on an Arg::Unnamed!"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Args {
|
|
|
|
fn num(&self) -> usize {
|
|
|
|
match self {
|
|
|
|
Args::Named(inner) | Args::Unnamed(inner) => inner.len(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn named(&self) -> Option<impl Iterator<Item = (&Ident, &Expr)>> {
|
|
|
|
match self {
|
|
|
|
Args::Named(args) => Some(args.iter().map(|arg| arg.named())),
|
|
|
|
_ => None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unnamed(&self) -> Option<impl Iterator<Item = &Expr>> {
|
|
|
|
match self {
|
|
|
|
Args::Unnamed(args) => Some(args.iter().map(|arg| arg.unnamed())),
|
|
|
|
_ => None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl ToTokens for Arg {
|
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
|
|
match self {
|
|
|
|
Arg::Unnamed(e) => e.to_tokens(tokens),
|
|
|
|
Arg::Named(ident, eq, expr) => tokens.extend(quote!(#ident #eq #expr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToTokens for Args {
|
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
|
|
match self {
|
|
|
|
Args::Unnamed(e) | Args::Named(e) => e.to_tokens(tokens)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|