use indexmap::IndexMap; use devise::{Spanned, ext::TypeExt}; use quote::ToTokens; use crate::syn::{self, Expr, Ident, LitStr, Path, Token, Type}; use crate::syn::parse::{self, Parse, ParseStream}; use crate::syn::punctuated::Punctuated; use crate::http::{uri, uri::Origin, ext::IntoOwned}; use crate::proc_macro2::{TokenStream, Span}; use crate::proc_macro_ext::StringLit; use crate::attribute::param::{Parameter, Dynamic}; use crate::name::Name; // TODO(diag): Use 'Diagnostic' in place of syn::Error. #[derive(Debug)] pub enum ArgExpr { Expr(Expr), Ignored(Token![_]), } #[derive(Debug)] pub enum Arg { Unnamed(ArgExpr), Named(Name, Ident, Token![=], ArgExpr), } #[derive(Debug)] pub enum Args { Unnamed(Punctuated), Named(Punctuated), } // 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 { pub mount_point: Option>, pub route_path: Path, pub arguments: Args, } #[derive(Debug)] pub struct FnArg { pub ident: Ident, pub ty: Type, } pub enum Validation<'a> { // Parameters that were ignored in a named argument setting. NamedIgnored(Vec<&'a Dynamic>), // Number expected, what we actually got. Unnamed(usize, usize), // (Missing, Extra, Duplicate) Named(Vec<&'a Name>, Vec<&'a Ident>, Vec<&'a Ident>), // Everything is okay; here are the expressions in the route decl order. Ok(Vec<&'a ArgExpr>) } // 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 (`uri_params`) 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). // // `fn_args` are the URI arguments (excluding request guards and ignored path // parts) from the original handler in the order they were declared in the URI // (`/`). `route_uri` is the URI itself. // // internal_uri!("//<_>?lang=en&", (one: ty, two: ty), $($tt)*); // ^----/----^ ^-----\-----^ ^-------/------^ ^-----| // path_params query_params fn_args uri_params // ^------ route_uri ------^ #[derive(Debug)] pub struct InternalUriParams { pub route_uri: Origin<'static>, pub mount_params: Vec, pub path_params: Vec, pub query_params: Vec, pub fn_args: Vec, pub uri_params: UriParams, } impl Parse for ArgExpr { fn parse(input: ParseStream<'_>) -> parse::Result { if input.peek(Token![_]) { return Ok(ArgExpr::Ignored(input.parse::()?)); } input.parse::().map(ArgExpr::Expr) } } impl Parse for Arg { fn parse(input: ParseStream<'_>) -> parse::Result { let has_key = input.peek2(Token![=]); if has_key { let ident = input.parse::()?; let eq_token = input.parse::()?; let expr = input.parse::()?; Ok(Arg::Named(Name::from(&ident), ident, eq_token, expr)) } else { let expr = input.parse::()?; Ok(Arg::Unnamed(expr)) } } } fn err>(span: Span, s: S) -> parse::Result { Err(parse::Error::new(span.into(), s.as_ref())) } impl Parse for UriParams { // Parses the mount point, if any, route identifier, and arguments. fn parse(input: ParseStream<'_>) -> parse::Result { 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::()?; let mount_point = Origin::parse_owned(string.value()) .map(|m| m.into_normalized()) .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`") })?; if !input.peek(Token![,]) && input.cursor().eof() { return err(string.span(), "unexpected end of input: \ expected ',' followed by route path"); } input.parse::()?; Some(mount_point) } else { None }; // Parse the route identifier, which must always exist. let route_path = input.parse::()?; // If there are no arguments, finish early. if !input.peek(Token![:]) && input.cursor().eof() { let arguments = Args::Unnamed(Punctuated::new()); return Ok(Self { mount_point, route_path, arguments }); } // Parse arguments let colon = input.parse::()?; let arguments: Punctuated = input.parse_terminated(Arg::parse)?; // A 'colon' was used but there are no arguments. if arguments.is_empty() { return err(colon.span(), "expected argument list after `:`"); } // 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 { return err(arguments.span(), "named and unnamed parameters cannot be mixed"); } // 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) }; Ok(Self { mount_point, route_path, arguments }) } } impl Parse for FnArg { fn parse(input: ParseStream<'_>) -> parse::Result { let ident = input.parse::()?; input.parse::()?; let mut ty = input.parse::()?; ty.strip_lifetimes(); Ok(FnArg { ident, ty }) } } impl Parse for InternalUriParams { fn parse(input: ParseStream<'_>) -> parse::Result { let route_uri_str = input.parse::()?; input.parse::()?; // Validation should always succeed since this macro can only be called // if the route attribute succeeded, implying a valid route URI. let route_uri_str = StringLit::new(route_uri_str.value(), route_uri_str.span()); let route_uri = Origin::parse_route(&route_uri_str) .map(|o| o.into_normalized().into_owned()) .map_err(|_| input.error("internal error: invalid route URI"))?; let content; syn::parenthesized!(content in input); let fn_args: Punctuated = content.parse_terminated(FnArg::parse)?; let fn_args = fn_args.into_iter().collect(); input.parse::()?; let uri_params = input.parse::()?; // This span isn't right...we don't have the original span. let span = route_uri_str.subspan(1..route_uri.path().len() + 1); let mount_params = match uri_params.mount_point.as_ref() { Some(mount) => Parameter::parse_many::(mount.path().as_str(), span) .map(|p| p.expect("internal error: invalid path parameter")) .collect::>(), None => vec![] }; let path_params = Parameter::parse_many::(route_uri.path().as_str(), span) .map(|p| p.expect("internal error: invalid path parameter")) .collect::>(); let query_params = match route_uri.query() { Some(query) => { let i = route_uri.path().len() + 2; let span = route_uri_str.subspan(i..(i + query.len())); Parameter::parse_many::(query.as_str(), span) .map(|p| p.expect("internal error: invalid query parameter")) .collect::>() } None => vec![] }; Ok(InternalUriParams { route_uri, mount_params, path_params, query_params, fn_args, uri_params }) } } impl InternalUriParams { pub fn fn_args_str(&self) -> String { self.fn_args.iter() .map(|FnArg { ident, ty }| { let ty = ty.with_stripped_lifetimes(); let ty_str = quote!(#ty).to_string(); let ty_str: String = ty_str.chars().filter(|c| !c.is_whitespace()).collect(); format!("{}: {}", ident, ty_str) }) .collect::>() .join(", ") } pub fn dynamic_path_params(&self) -> impl Iterator + Clone { self.path_params.iter() .filter_map(|p| p.dynamic().or_else(|| p.ignored())) } pub fn dynamic_query_params(&self) -> impl Iterator + Clone { self.query_params.iter().filter_map(|p| p.dynamic()) } pub fn validate(&self) -> Validation<'_> { let args = &self.uri_params.arguments; let all_params = self.dynamic_path_params().chain(self.dynamic_query_params()); match args { Args::Unnamed(args) => { let (expected, actual) = (all_params.count(), args.len()); let unnamed_args = args.iter().map(|arg| arg.unnamed()); match expected == actual { true => Validation::Ok(unnamed_args.collect()), false => Validation::Unnamed(expected, actual) } }, Args::Named(args) => { let ignored = all_params.clone().filter(|p| p.is_wild()); if ignored.clone().count() > 0 { return Validation::NamedIgnored(ignored.collect()); } let mut params = all_params.map(|p| (&p.name, None)) .collect::>>(); let (mut extra, mut dup) = (vec![], vec![]); let named_args = args.iter().map(|arg| arg.named()); for (name, ident, expr) in named_args { match params.get_mut(name) { Some(ref entry) if entry.is_some() => dup.push(ident), Some(entry) => *entry = Some(expr), None => extra.push(ident), } } 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) } } } } } 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, _ => false } } fn unnamed(&self) -> &ArgExpr { match self { Arg::Unnamed(expr) => expr, _ => panic!("Called Arg::unnamed() on an Arg::named!"), } } fn named(&self) -> (&Name, &Ident, &ArgExpr) { match self { Arg::Named(name, ident, _, expr) => (name, 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(), } } } impl ArgExpr { pub fn as_expr(&self) -> Option<&Expr> { match self { ArgExpr::Expr(expr) => Some(expr), _ => None } } pub fn unwrap_expr(&self) -> &Expr { match self { ArgExpr::Expr(expr) => expr, _ => panic!("Called ArgExpr::expr() on ArgExpr::Ignored!"), } } } impl ToTokens for ArgExpr { fn to_tokens(&self, tokens: &mut TokenStream) { match self { ArgExpr::Expr(e) => e.to_tokens(tokens), ArgExpr::Ignored(e) => e.to_tokens(tokens) } } } impl ToTokens for Arg { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Arg::Unnamed(e) => e.to_tokens(tokens), Arg::Named(_, ident, eq, expr) => { ident.to_tokens(tokens); eq.to_tokens(tokens); expr.to_tokens(tokens); } } } } impl ToTokens for Args { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Args::Unnamed(e) | Args::Named(e) => e.to_tokens(tokens) } } }