mirror of https://github.com/rwf2/Rocket.git
Tidy 'uri!' proc-macro. Improve error reporting.
This commit is contained in:
parent
7624aaf3e4
commit
3eb873d89d
|
@ -18,7 +18,7 @@ error: `simple` route uri expects 1 parameter but 2 were supplied
|
||||||
--> $DIR/typed-uris-bad-params.rs:20:18
|
--> $DIR/typed-uris-bad-params.rs:20:18
|
||||||
|
|
|
|
||||||
20 | uri!(simple: "Hello", 23, );
|
20 | uri!(simple: "Hello", 23, );
|
||||||
| ^^^^^^^^^^^
|
| ^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= note: expected parameter: id: i32
|
= note: expected parameter: id: i32
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ error: invalid parameters for `simple` route uri
|
||||||
--> $DIR/typed-uris-bad-params.rs:26:18
|
--> $DIR/typed-uris-bad-params.rs:26:18
|
||||||
|
|
|
|
||||||
26 | uri!(simple: id = 100, id = 100, );
|
26 | uri!(simple: id = 100, id = 100, );
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= note: uri parameters are: id: i32
|
= note: uri parameters are: id: i32
|
||||||
help: duplicate parameter: `id`
|
help: duplicate parameter: `id`
|
||||||
|
|
|
@ -10,11 +10,11 @@ error: named and unnamed parameters cannot be mixed
|
||||||
12 | uri!(simple: "Hello", id = 100);
|
12 | uri!(simple: "Hello", id = 100);
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: expected one of `::`, `:`, or `<eof>`, found `,`
|
error: expected `:`
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:13:16
|
--> $DIR/typed-uris-invalid-syntax.rs:13:16
|
||||||
|
|
|
|
||||||
13 | uri!(simple,);
|
13 | uri!(simple,);
|
||||||
| ^ expected one of `::`, `:`, or `<eof>` here
|
| ^
|
||||||
|
|
||||||
error: expected argument list after `:`
|
error: expected argument list after `:`
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:14:16
|
--> $DIR/typed-uris-invalid-syntax.rs:14:16
|
||||||
|
@ -22,45 +22,41 @@ error: expected argument list after `:`
|
||||||
14 | uri!(simple:);
|
14 | uri!(simple:);
|
||||||
| ^
|
| ^
|
||||||
|
|
||||||
error: expected `,`, found `<eof>`
|
error: unexpected end of input: expected ',' followed by route path
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:15:10
|
--> $DIR/typed-uris-invalid-syntax.rs:15:10
|
||||||
|
|
|
|
||||||
15 | uri!("/mount");
|
15 | uri!("/mount");
|
||||||
| ^^^^^^^^ expected `,`
|
| ^^^^^^^^
|
||||||
|
|
||||||
error: expected identifier, found `<eof>`
|
error: unexpected end of input, expected identifier
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:16:18
|
--> $DIR/typed-uris-invalid-syntax.rs:16:5
|
||||||
|
|
|
|
||||||
16 | uri!("/mount",);
|
16 | uri!("/mount",);
|
||||||
| ^ expected identifier
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: invalid mount point
|
error: invalid mount point; mount points must be static, absolute URIs: `/example`
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:17:10
|
--> $DIR/typed-uris-invalid-syntax.rs:17:10
|
||||||
|
|
|
|
||||||
17 | uri!("mount", simple);
|
17 | uri!("mount", simple);
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
|
||||||
= help: mount points must be static, absolute URIs: `/example`
|
|
||||||
|
|
||||||
error: invalid mount point
|
error: invalid mount point; mount points must be static, absolute URIs: `/example`
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:18:10
|
--> $DIR/typed-uris-invalid-syntax.rs:18:10
|
||||||
|
|
|
|
||||||
18 | uri!("/mount/<id>", simple);
|
18 | uri!("/mount/<id>", simple);
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
= help: mount points must be static, absolute URIs: `/example`
|
|
||||||
|
|
||||||
error: call to `uri!` cannot be empty
|
error: unexpected end of input, call to `uri!` cannot be empty
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:19:5
|
--> $DIR/typed-uris-invalid-syntax.rs:19:5
|
||||||
|
|
|
|
||||||
19 | uri!();
|
19 | uri!();
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
error: expected expression, found `<eof>`
|
error: unexpected end of input, expected expression
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:20:21
|
--> $DIR/typed-uris-invalid-syntax.rs:20:5
|
||||||
|
|
|
|
||||||
20 | uri!(simple: id = );
|
20 | uri!(simple: id = );
|
||||||
| ^ expected expression
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: aborting due to 10 previous errors
|
error: aborting due to 10 previous errors
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,12 @@ use syn_ext::{IdentExt, syn_to_diag};
|
||||||
mod uri;
|
mod uri;
|
||||||
mod uri_parsing;
|
mod uri_parsing;
|
||||||
|
|
||||||
|
|
||||||
|
crate fn prefix_last_segment(path: &mut Path, prefix: &str) {
|
||||||
|
let mut last_seg = path.segments.last_mut().expect("syn::Path has segments");
|
||||||
|
last_seg.value_mut().ident = last_seg.value().ident.prepend(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
fn _prefixed_vec(prefix: &str, input: TokenStream, ty: &TokenStream2) -> Result<TokenStream2> {
|
fn _prefixed_vec(prefix: &str, input: TokenStream, ty: &TokenStream2) -> Result<TokenStream2> {
|
||||||
// Parse a comma-separated list of paths.
|
// Parse a comma-separated list of paths.
|
||||||
let mut paths = <Punctuated<Path, Comma>>::parse_terminated
|
let mut paths = <Punctuated<Path, Comma>>::parse_terminated
|
||||||
|
@ -15,10 +21,7 @@ fn _prefixed_vec(prefix: &str, input: TokenStream, ty: &TokenStream2) -> Result<
|
||||||
.map_err(syn_to_diag)?;
|
.map_err(syn_to_diag)?;
|
||||||
|
|
||||||
// Prefix the last segment in each path with `prefix`.
|
// Prefix the last segment in each path with `prefix`.
|
||||||
for path in paths.iter_mut() {
|
paths.iter_mut().for_each(|p| prefix_last_segment(p, prefix));
|
||||||
let mut last_seg = path.segments.last_mut().expect("last path segment");
|
|
||||||
last_seg.value_mut().ident = last_seg.value().ident.prepend(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a `vec!` of the prefixed, mapped paths.
|
// Return a `vec!` of the prefixed, mapped paths.
|
||||||
let prefixed_mapped_paths = paths.iter()
|
let prefixed_mapped_paths = paths.iter()
|
||||||
|
|
|
@ -3,60 +3,43 @@ use proc_macro2::TokenStream as TokenStream2;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use derive_utils::{syn, Result};
|
use derive_utils::{syn, Result};
|
||||||
use quote::ToTokens;
|
|
||||||
use syn_ext::{IdentExt, syn_to_diag};
|
use syn_ext::{IdentExt, syn_to_diag};
|
||||||
|
|
||||||
use self::syn::{Expr, Ident, Type};
|
use self::syn::{Expr, Ident, Type};
|
||||||
use self::syn::spanned::Spanned as SynSpanned;
|
use self::syn::spanned::Spanned as SynSpanned;
|
||||||
use super::uri_parsing::*;
|
use bang::{prefix_last_segment, uri_parsing::*};
|
||||||
|
|
||||||
use rocket_http::uri::Origin;
|
use rocket_http::{uri::Origin, ext::IntoOwned};
|
||||||
use rocket_http::ext::IntoOwned;
|
|
||||||
|
|
||||||
const URI_INFO_MACRO_PREFIX: &str = "rocket_uri_for_";
|
const URI_INFO_MACRO_PREFIX: &str = "rocket_uri_for_";
|
||||||
|
|
||||||
crate fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
|
|
||||||
let args: TokenStream2 = input.clone().into();
|
|
||||||
|
|
||||||
let params = match syn::parse::<UriParams>(input) {
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => return Err(syn_to_diag(e)),
|
|
||||||
};
|
|
||||||
let mut path = params.route_path;
|
|
||||||
{
|
|
||||||
let mut last_seg = path.segments.last_mut().expect("last path segment");
|
|
||||||
last_seg.value_mut().ident = last_seg.value().ident.prepend(URI_INFO_MACRO_PREFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's incredibly important we use this span as the Span for the generated
|
|
||||||
// code so that errors from the `internal` call show up on the user's code.
|
|
||||||
Ok(quote_spanned!(args.span().into() => {
|
|
||||||
#path!(#args)
|
|
||||||
}).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! p {
|
macro_rules! p {
|
||||||
("parameter", $num:expr) => (
|
(@go $num:expr, $singular:expr, $plural:expr) => (
|
||||||
if $num == 1 { "parameter" } else { "parameters" }
|
if $num == 1 { $singular.into() } else { $plural }
|
||||||
);
|
);
|
||||||
|
|
||||||
($num:expr, "was") => (
|
("parameter", $n:expr) => (p!(@go $n, "parameter", "parameters"));
|
||||||
if $num == 1 { "1 was".into() } else { format!("{} were", $num) }
|
($n:expr, "was") => (p!(@go $n, "1 was", format!("{} were", $n)));
|
||||||
);
|
($n:expr, "parameter") => (p!(@go $n, "1 parameter", format!("{} parameters", $n)));
|
||||||
|
|
||||||
($num:expr, "parameter") => (
|
|
||||||
if $num == 1 { "1 parameter".into() } else { format!("{} parameters", $num) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_exprs(internal: &InternalUriParams) -> Result<Vec<Expr>> {
|
crate fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
|
||||||
|
let input2: TokenStream2 = input.clone().into();
|
||||||
|
let mut params = syn::parse::<UriParams>(input).map_err(syn_to_diag)?;
|
||||||
|
prefix_last_segment(&mut params.route_path, URI_INFO_MACRO_PREFIX);
|
||||||
|
|
||||||
|
let path = ¶ms.route_path;
|
||||||
|
Ok(quote!(#path!(#input2)).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_exprs(internal: &InternalUriParams) -> Result<Vec<&Expr>> {
|
||||||
let route_name = &internal.uri_params.route_path;
|
let route_name = &internal.uri_params.route_path;
|
||||||
match internal.validate() {
|
match internal.validate() {
|
||||||
Validation::Ok(exprs) => Ok(exprs),
|
Validation::Ok(exprs) => Ok(exprs),
|
||||||
Validation::Unnamed(expected, actual) => {
|
Validation::Unnamed(expected, actual) => {
|
||||||
let mut diag = internal.uri_params.args_span().error(
|
let mut diag = internal.uri_params.args_span().error(
|
||||||
format!("`{}` route uri expects {} but {} supplied",
|
format!("`{}` route uri expects {} but {} supplied", quote!(#route_name),
|
||||||
route_name.clone().into_token_stream(), p!(expected, "parameter"), p!(actual, "was")));
|
p!(expected, "parameter"), p!(actual, "was")));
|
||||||
|
|
||||||
if expected > 0 {
|
if expected > 0 {
|
||||||
let ps = p!("parameter", expected);
|
let ps = p!("parameter", expected);
|
||||||
|
@ -66,9 +49,9 @@ fn extract_exprs(internal: &InternalUriParams) -> Result<Vec<Expr>> {
|
||||||
Err(diag)
|
Err(diag)
|
||||||
}
|
}
|
||||||
Validation::Named(missing, extra, dup) => {
|
Validation::Named(missing, extra, dup) => {
|
||||||
let e = format!("invalid parameters for `{}` route uri", route_name.clone().into_token_stream());
|
let e = format!("invalid parameters for `{}` route uri", quote!(#route_name));
|
||||||
let mut diag = internal.uri_params.args_span().error(e);
|
let mut diag = internal.uri_params.args_span().error(e)
|
||||||
diag = diag.note(format!("uri parameters are: {}", internal.fn_args_str()));
|
.note(format!("uri parameters are: {}", internal.fn_args_str()));
|
||||||
|
|
||||||
fn join<S: Display, T: Iterator<Item = S>>(iter: T) -> (&'static str, String) {
|
fn join<S: Display, T: Iterator<Item = S>>(iter: T) -> (&'static str, String) {
|
||||||
let items: Vec<_> = iter.map(|i| format!("`{}`", i)).collect();
|
let items: Vec<_> = iter.map(|i| format!("`{}`", i)).collect();
|
||||||
|
@ -114,8 +97,8 @@ fn extract_origin(internal: &InternalUriParams) -> Result<Origin<'static>> {
|
||||||
.map_err(|_| internal.uri.span().unstable().error("invalid route URI"))
|
.map_err(|_| internal.uri.span().unstable().error("invalid route URI"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn explode<I>(route_str: &str, items: I) -> TokenStream2
|
fn explode<'a, I>(route_str: &str, items: I) -> TokenStream2
|
||||||
where I: Iterator<Item = (Ident, Type, Expr)>
|
where I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>
|
||||||
{
|
{
|
||||||
// Generate the statements to typecheck each parameter.
|
// Generate the statements to typecheck each parameter.
|
||||||
// Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e).
|
// Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e).
|
||||||
|
@ -123,16 +106,18 @@ fn explode<I>(route_str: &str, items: I) -> TokenStream2
|
||||||
let mut fmt_exprs = vec![];
|
let mut fmt_exprs = vec![];
|
||||||
|
|
||||||
for (mut ident, ty, expr) in items {
|
for (mut ident, ty, expr) in items {
|
||||||
let (span, mut expr) = (expr.span(), expr.clone());
|
let (span, expr) = (expr.span(), expr);
|
||||||
ident.set_span(span);
|
let ident_tmp = ident.prepend("tmp_");
|
||||||
let ident_tmp = ident.prepend("tmp");
|
|
||||||
|
|
||||||
let_bindings.push(quote_spanned!(span =>
|
let_bindings.push(quote_spanned!(span =>
|
||||||
let #ident_tmp = #expr; let #ident = <#ty as ::rocket::http::uri::FromUriParam<_>>::from_uri_param(#ident_tmp);
|
let #ident_tmp = #expr;
|
||||||
|
let #ident = <#ty as ::rocket::http::uri::FromUriParam<_>>::from_uri_param(#ident_tmp);
|
||||||
));
|
));
|
||||||
|
|
||||||
// generating: arg tokens for format string
|
// generating: arg tokens for format string
|
||||||
fmt_exprs.push(quote_spanned!(span => { &#ident as &::rocket::http::uri::UriDisplay }));
|
fmt_exprs.push(quote_spanned! { span =>
|
||||||
|
&#ident as &::rocket::http::uri::UriDisplay
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert all of the '<...>' into '{}'.
|
// Convert all of the '<...>' into '{}'.
|
||||||
|
@ -164,13 +149,15 @@ crate fn _uri_internal_macro(input: TokenStream) -> Result<TokenStream> {
|
||||||
let path_param_count = origin.path().matches('<').count();
|
let path_param_count = origin.path().matches('<').count();
|
||||||
|
|
||||||
// Create an iterator over the `ident`, `ty`, and `expr` triple.
|
// Create an iterator over the `ident`, `ty`, and `expr` triple.
|
||||||
let mut arguments = internal.fn_args
|
let mut arguments = internal.fn_args.iter()
|
||||||
.into_iter()
|
.zip(exprs.iter())
|
||||||
.zip(exprs.into_iter())
|
.map(|(FnArg { ident, ty }, &expr)| (ident, ty, expr));
|
||||||
.map(|(FnArg { ident, ty }, expr)| (ident, ty, expr));
|
|
||||||
|
|
||||||
// Generate an expression for both the path and query.
|
// Generate an expression for both the path and query.
|
||||||
let path = explode(origin.path(), arguments.by_ref().take(path_param_count));
|
let path = explode(origin.path(), arguments.by_ref().take(path_param_count));
|
||||||
|
|
||||||
|
// FIXME: Use Optional.
|
||||||
|
// let query = Optional(origin.query().map(|q| explode(q, arguments)));
|
||||||
let query = if let Some(expr) = origin.query().map(|q| explode(q, arguments)) {
|
let query = if let Some(expr) = origin.query().map(|q| explode(q, arguments)) {
|
||||||
quote!({ Some(#expr) })
|
quote!({ Some(#expr) })
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
use proc_macro::Span;
|
use proc_macro::Span;
|
||||||
|
|
||||||
use derive_utils::syn;
|
use derive_utils::{syn, Spanned};
|
||||||
|
use derive_utils::proc_macro2::TokenStream as TokenStream2;
|
||||||
use derive_utils::ext::TypeExt;
|
use derive_utils::ext::TypeExt;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
|
|
||||||
use self::syn::{Expr, Ident, LitStr, Path, Token, Type};
|
use self::syn::{Expr, Ident, LitStr, Path, Token, Type};
|
||||||
use self::syn::spanned::Spanned as SynSpanned;
|
|
||||||
use self::syn::parse::{self, Parse, ParseStream};
|
use self::syn::parse::{self, Parse, ParseStream};
|
||||||
use self::syn::punctuated::Punctuated;
|
use self::syn::punctuated::Punctuated;
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Arg {
|
pub enum Arg {
|
||||||
Unnamed(Expr),
|
Unnamed(Expr),
|
||||||
Named(Ident, Expr),
|
Named(Ident, Token![=], Expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Args {
|
pub enum Args {
|
||||||
Unnamed(Vec<Expr>),
|
Unnamed(Punctuated<Arg, Token![,]>),
|
||||||
Named(Vec<(Ident, Expr)>),
|
Named(Punctuated<Arg, Token![,]>),
|
||||||
}
|
}
|
||||||
|
|
||||||
// For an invocation that looks like:
|
// For an invocation that looks like:
|
||||||
|
@ -28,12 +28,11 @@ pub enum Args {
|
||||||
// ^-------------| ^----------| ^---------|
|
// ^-------------| ^----------| ^---------|
|
||||||
// uri_params.mount_point | uri_params.arguments
|
// uri_params.mount_point | uri_params.arguments
|
||||||
// uri_params.route_path
|
// uri_params.route_path
|
||||||
//
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UriParams {
|
pub struct UriParams {
|
||||||
pub mount_point: Option<LitStr>,
|
pub mount_point: Option<LitStr>,
|
||||||
pub route_path: Path,
|
pub route_path: Path,
|
||||||
pub arguments: Option<Args>,
|
pub arguments: Args,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -42,18 +41,29 @@ pub struct FnArg {
|
||||||
pub ty: Type,
|
pub ty: Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Validation {
|
pub enum Validation<'a> {
|
||||||
// Number expected, what we actually got.
|
// Number expected, what we actually got.
|
||||||
Unnamed(usize, usize),
|
Unnamed(usize, usize),
|
||||||
// (Missing, Extra, Duplicate)
|
// (Missing, Extra, Duplicate)
|
||||||
Named(Vec<Ident>, Vec<Ident>, Vec<Ident>),
|
Named(Vec<&'a Ident>, Vec<&'a Ident>, Vec<&'a Ident>),
|
||||||
// Everything is okay.
|
// Everything is okay; here are the expressions in the route decl order.
|
||||||
Ok(Vec<Expr>)
|
Ok(Vec<&'a Expr>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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).
|
||||||
|
//
|
||||||
// `fn_args` are the URI arguments (excluding guards) from the original route's
|
// `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>`).
|
// handler in the order they were declared in the URI (`<first>/<second>`).
|
||||||
// `uri` is the full URI used in the origin route's attribute
|
// `uri` is the full URI used in the origin route's attribute.
|
||||||
|
//
|
||||||
|
// internal_uri!("/<first>/<second>", (first: ty, second: ty), $($tt)*);
|
||||||
|
// ^-----------------| ^-----------|---------| ^-----|
|
||||||
|
// uri fn_args uri_params
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InternalUriParams {
|
pub struct InternalUriParams {
|
||||||
pub uri: String,
|
pub uri: String,
|
||||||
|
@ -61,63 +71,14 @@ pub struct InternalUriParams {
|
||||||
pub uri_params: UriParams,
|
pub uri_params: UriParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 UriParams {
|
|
||||||
/// The Span to use when referring to all of the arguments.
|
|
||||||
pub fn args_span(&self) -> Span {
|
|
||||||
match self.arguments {
|
|
||||||
Some(ref args) => {
|
|
||||||
let (first, last) = match args {
|
|
||||||
Args::Unnamed(ref exprs) => {
|
|
||||||
(
|
|
||||||
exprs.first().unwrap().span().unstable(),
|
|
||||||
exprs.last().unwrap().span().unstable()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
Args::Named(ref pairs) => {
|
|
||||||
(
|
|
||||||
pairs.first().unwrap().0.span().unstable(),
|
|
||||||
pairs.last().unwrap().1.span().unstable()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
first.join(last).expect("join spans")
|
|
||||||
},
|
|
||||||
None => self.route_path.span().unstable(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Arg {
|
impl Parse for Arg {
|
||||||
fn parse(input: ParseStream) -> parse::Result<Self> {
|
fn parse(input: ParseStream) -> parse::Result<Self> {
|
||||||
let has_key = input.peek2(Token![=]);
|
let has_key = input.peek2(Token![=]);
|
||||||
if has_key {
|
if has_key {
|
||||||
let ident = input.parse::<Ident>()?;
|
let ident = input.parse::<Ident>()?;
|
||||||
input.parse::<Token![=]>()?;
|
let eq_token = input.parse::<Token![=]>()?;
|
||||||
let expr = input.parse::<Expr>()?;
|
let expr = input.parse::<Expr>()?;
|
||||||
Ok(Arg::Named(ident, expr))
|
Ok(Arg::Named(ident, eq_token, expr))
|
||||||
} else {
|
} else {
|
||||||
let expr = input.parse::<Expr>()?;
|
let expr = input.parse::<Expr>()?;
|
||||||
Ok(Arg::Unnamed(expr))
|
Ok(Arg::Unnamed(expr))
|
||||||
|
@ -125,6 +86,10 @@ impl Parse for Arg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn err<T, S: AsRef<str>>(span: Span, s: S) -> parse::Result<T> {
|
||||||
|
Err(parse::Error::new(span.into(), s.as_ref()))
|
||||||
|
}
|
||||||
|
|
||||||
impl Parse for UriParams {
|
impl Parse for UriParams {
|
||||||
// Parses the mount point, if any, route identifier, and arguments.
|
// Parses the mount point, if any, route identifier, and arguments.
|
||||||
fn parse(input: ParseStream) -> parse::Result<Self> {
|
fn parse(input: ParseStream) -> parse::Result<Self> {
|
||||||
|
@ -137,8 +102,16 @@ impl Parse for UriParams {
|
||||||
let string = input.parse::<LitStr>()?;
|
let string = input.parse::<LitStr>()?;
|
||||||
let value = string.value();
|
let value = string.value();
|
||||||
if value.contains('<') || !value.starts_with('/') {
|
if value.contains('<') || !value.starts_with('/') {
|
||||||
return Err(parse::Error::new(string.span(), "invalid mount point; mount points must be static, absolute URIs: `/example`"));
|
// TODO(proc_macro): add example as a help, not in error
|
||||||
|
return err(string.span().unstable(), "invalid mount point; \
|
||||||
|
mount points must be static, absolute URIs: `/example`");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !input.peek(Token![,]) && input.cursor().eof() {
|
||||||
|
return err(string.span().unstable(), "unexpected end of input: \
|
||||||
|
expected ',' followed by route path");
|
||||||
|
}
|
||||||
|
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
Some(string)
|
Some(string)
|
||||||
} else {
|
} else {
|
||||||
|
@ -149,20 +122,18 @@ impl Parse for UriParams {
|
||||||
let route_path = input.parse::<Path>()?;
|
let route_path = input.parse::<Path>()?;
|
||||||
|
|
||||||
// If there are no arguments, finish early.
|
// If there are no arguments, finish early.
|
||||||
if !input.peek(Token![:]) {
|
if !input.peek(Token![:]) && input.cursor().eof() {
|
||||||
let arguments = None;
|
let arguments = Args::Unnamed(Punctuated::new());
|
||||||
return Ok(Self { mount_point, route_path, arguments });
|
return Ok(Self { mount_point, route_path, arguments });
|
||||||
}
|
}
|
||||||
|
|
||||||
let colon = input.parse::<Token![:]>()?;
|
|
||||||
|
|
||||||
// Parse arguments
|
// Parse arguments
|
||||||
let args_start = input.cursor();
|
let colon = input.parse::<Token![:]>()?;
|
||||||
let arguments: Punctuated<Arg, Token![,]> = input.parse_terminated(Arg::parse)?;
|
let arguments: Punctuated<Arg, Token![,]> = input.parse_terminated(Arg::parse)?;
|
||||||
|
|
||||||
// A 'colon' was used but there are no arguments.
|
// A 'colon' was used but there are no arguments.
|
||||||
if arguments.is_empty() {
|
if arguments.is_empty() {
|
||||||
return Err(parse::Error::new(colon.span(), "expected argument list after `:`"));
|
return err(colon.span(), "expected argument list after `:`");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that both types of arguments were not used at once.
|
// Ensure that both types of arguments were not used at once.
|
||||||
|
@ -175,18 +146,15 @@ impl Parse for UriParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !homogeneous_args {
|
if !homogeneous_args {
|
||||||
// TODO: This error isn't showing up with the right span.
|
return err(arguments.span(), "named and unnamed parameters cannot be mixed");
|
||||||
return Err(parse::Error::new(args_start.token_stream().span(), "named and unnamed parameters cannot be mixed"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the `Args` enum, which properly types one-kind-of-argument-ness.
|
// Create the `Args` enum, which properly record one-kind-of-argument-ness.
|
||||||
let args = if prev_named.unwrap() {
|
let arguments = match prev_named {
|
||||||
Args::Named(arguments.into_iter().map(|arg| arg.named()).collect())
|
Some(true) => Args::Named(arguments),
|
||||||
} else {
|
_ => Args::Unnamed(arguments)
|
||||||
Args::Unnamed(arguments.into_iter().map(|arg| arg.unnamed()).collect())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let arguments = Some(args);
|
|
||||||
Ok(Self { mount_point, route_path, arguments })
|
Ok(Self { mount_point, route_path, arguments })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,7 +172,6 @@ impl Parse for FnArg {
|
||||||
impl Parse for InternalUriParams {
|
impl Parse for InternalUriParams {
|
||||||
fn parse(input: ParseStream) -> parse::Result<InternalUriParams> {
|
fn parse(input: ParseStream) -> parse::Result<InternalUriParams> {
|
||||||
let uri = input.parse::<LitStr>()?.value();
|
let uri = input.parse::<LitStr>()?.value();
|
||||||
//let uri = parser.prev_span.wrap(uri_str);
|
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
@ -221,32 +188,30 @@ impl Parse for InternalUriParams {
|
||||||
impl InternalUriParams {
|
impl InternalUriParams {
|
||||||
pub fn fn_args_str(&self) -> String {
|
pub fn fn_args_str(&self) -> String {
|
||||||
self.fn_args.iter()
|
self.fn_args.iter()
|
||||||
.map(|&FnArg { ref ident, ref ty }| format!("{}: {}", ident, ty.clone().into_token_stream().to_string().trim()))
|
.map(|FnArg { ident, ty }| format!("{}: {}", ident, quote!(#ty).to_string().trim()))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self) -> Validation {
|
pub fn validate(&self) -> Validation {
|
||||||
let unnamed = |args: &Vec<Expr>| -> Validation {
|
let args = &self.uri_params.arguments;
|
||||||
let (expected, actual) = (self.fn_args.len(), args.len());
|
match args {
|
||||||
if expected != actual { Validation::Unnamed(expected, actual) }
|
Args::Unnamed(inner) => {
|
||||||
else { Validation::Ok(args.clone()) }
|
let (expected, actual) = (self.fn_args.len(), inner.len());
|
||||||
};
|
if expected != actual { Validation::Unnamed(expected, actual) }
|
||||||
|
else { Validation::Ok(args.unnamed().unwrap().collect()) }
|
||||||
match self.uri_params.arguments {
|
},
|
||||||
None => unnamed(&vec![]),
|
Args::Named(_) => {
|
||||||
Some(Args::Unnamed(ref args)) => unnamed(args),
|
let mut params: IndexMap<&Ident, Option<&Expr>> = self.fn_args.iter()
|
||||||
Some(Args::Named(ref args)) => {
|
.map(|FnArg { ident, .. }| (ident, None))
|
||||||
let mut params: IndexMap<Ident, Option<Expr>> = self.fn_args.iter()
|
|
||||||
.map(|&FnArg { ref ident, .. }| (ident.clone(), None))
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let (mut extra, mut dup) = (vec![], vec![]);
|
let (mut extra, mut dup) = (vec![], vec![]);
|
||||||
for &(ref ident, ref expr) in args {
|
for (ident, expr) in args.named().unwrap() {
|
||||||
match params.get_mut(ident) {
|
match params.get_mut(ident) {
|
||||||
Some(ref entry) if entry.is_some() => dup.push(ident.clone()),
|
Some(ref entry) if entry.is_some() => dup.push(ident),
|
||||||
Some(entry) => *entry = Some(expr.clone()),
|
Some(entry) => *entry = Some(expr),
|
||||||
None => extra.push(ident.clone()),
|
None => extra.push(ident),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,3 +233,75 @@ impl InternalUriParams {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -105,9 +105,9 @@ use self::priv_encode_set::PATH_ENCODE_SET;
|
||||||
/// dynamic parameter type.
|
/// dynamic parameter type.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(plugin, proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #![plugin(rocket_codegen)]
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # fn main() { }
|
/// # fn main() { }
|
||||||
/// use rocket::http::RawStr;
|
/// use rocket::http::RawStr;
|
||||||
/// use rocket::request::FromParam;
|
/// use rocket::request::FromParam;
|
||||||
|
|
Loading…
Reference in New Issue