Revamp codegen, fixing inconscpicuous bugs.

This commit completely revamps the way that codegen handles route URI
"parameters". The changes are largely internal. In summary, codegen code
is better organized, better written, and less subject to error.

There are three breaking changes:
  * `path` is now `uri` in `route` attribute: `#[route(GET, path = "..")]`
    becomes `#[route(GET, uri = "..")]`.
  * the order of execution for path and query guards relative to
    each-other is now unspecified
  * URI normalization now normalizes the query part as well.

Several error messages were improved. A couple of bugs were fixed:
  * Prior to this commit, Rocket would optimistically try to parse every
    segment of a URI as an ident, in case one was needed in the future.
    A bug in rustc results in codegen "panicking" if the segment
    couldn't _lex_ as an ident. This panic didn't manifest until far
    after expansion, unfortunately. This wasn't a problem before as we
    only allowed ident-like segments (ASCII), but now that we allow any
    UTF-8, the bug surfaced. This was fixed by never attempting to parse
    non-idents as idents.
  * Prior to this commit, it was impossible to generate typed URIs for
    paths that ignored path parameters via the recently added syntax
    `<_>`: the macro would panic. This was fixed by, well, handling
    these ignored parameters.

Some minor additions:
  * Added `RawStr::find()`, expanding its `Pattern`-based API.
  * Added an internal mechanism to dynamically determine if a `UriPart`
    is `Path` or `Query`.
This commit is contained in:
Sergio Benitez 2021-03-02 04:01:33 -08:00
parent 63a14525d8
commit 78e2f8a3c9
57 changed files with 2350 additions and 1991 deletions

View File

@ -19,6 +19,7 @@ indexmap = "1.0"
quote = "1.0"
rocket_http = { version = "0.5.0-dev", path = "../http/" }
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "bd221a4" }
unicode-xid = "0.2"
glob = "0.3"
[dev-dependencies]

View File

@ -1,141 +0,0 @@
use devise::{syn, Diagnostic, Spanned, Result};
use devise::ext::SpanDiagnosticExt;
use devise::proc_macro2::{TokenStream, Span};
trait EntryAttr {
/// Whether the attribute requires the attributed function to be `async`.
const REQUIRES_ASYNC: bool;
/// Return a new or rewritten function, using block as the main execution.
fn function(f: &mut syn::ItemFn) -> Result<TokenStream>;
}
struct Main;
impl EntryAttr for Main {
const REQUIRES_ASYNC: bool = true;
fn function(f: &mut syn::ItemFn) -> Result<TokenStream> {
let (attrs, vis, block, sig) = (&f.attrs, &f.vis, &f.block, &mut f.sig);
if sig.ident != "main" {
// FIXME(diag): warning!
Span::call_site()
.warning("attribute is typically applied to `main` function")
.span_note(sig.ident.span(), "this function is not `main`")
.emit_as_item_tokens();
}
sig.asyncness = None;
Ok(quote_spanned!(block.span().into() => #(#attrs)* #vis #sig {
::rocket::async_main(async move #block)
}))
}
}
struct Test;
impl EntryAttr for Test {
const REQUIRES_ASYNC: bool = true;
fn function(f: &mut syn::ItemFn) -> Result<TokenStream> {
let (attrs, vis, block, sig) = (&f.attrs, &f.vis, &f.block, &mut f.sig);
sig.asyncness = None;
Ok(quote_spanned!(block.span().into() => #(#attrs)* #[test] #vis #sig {
::rocket::async_test(async move #block)
}))
}
}
struct Launch;
impl EntryAttr for Launch {
const REQUIRES_ASYNC: bool = false;
fn function(f: &mut syn::ItemFn) -> Result<TokenStream> {
if f.sig.ident == "main" {
return Err(Span::call_site()
.error("attribute cannot be applied to `main` function")
.note("this attribute generates a `main` function")
.span_note(f.sig.ident.span(), "this function cannot be `main`"));
}
// Always infer the type as `::rocket::Rocket`.
if let syn::ReturnType::Type(_, ref mut ty) = &mut f.sig.output {
if let syn::Type::Infer(_) = &mut **ty {
let new = quote_spanned!(ty.span() => ::rocket::Rocket);
*ty = syn::parse2(new).expect("path is type");
}
}
let ty = match &f.sig.output {
syn::ReturnType::Type(_, ty) => ty,
_ => return Err(Span::call_site()
.error("attribute can only be applied to functions that return a value")
.span_note(f.sig.span(), "this function must return a value"))
};
let block = &f.block;
let rocket = quote_spanned!(ty.span().into() => {
let ___rocket: #ty = #block;
let ___rocket: ::rocket::Rocket = ___rocket;
___rocket
});
let (vis, mut sig) = (&f.vis, f.sig.clone());
sig.ident = syn::Ident::new("main", sig.ident.span());
sig.output = syn::ReturnType::Default;
sig.asyncness = None;
Ok(quote_spanned!(block.span().into() =>
#[allow(dead_code)] #f
#vis #sig {
::rocket::async_main(async move { let _ = #rocket.launch().await; })
}
))
}
}
fn parse_input<A: EntryAttr>(input: proc_macro::TokenStream) -> Result<syn::ItemFn> {
let function: syn::ItemFn = syn::parse(input)
.map_err(Diagnostic::from)
.map_err(|d| d.help("attribute can only be applied to functions"))?;
if A::REQUIRES_ASYNC && function.sig.asyncness.is_none() {
return Err(Span::call_site()
.error("attribute can only be applied to `async` functions")
.span_note(function.sig.span(), "this function must be `async`"));
}
if !function.sig.inputs.is_empty() {
return Err(Span::call_site()
.error("attribute can only be applied to functions without arguments")
.span_note(function.sig.span(), "this function must take no arguments"));
}
Ok(function)
}
fn _async_entry<A: EntryAttr>(
_args: proc_macro::TokenStream,
input: proc_macro::TokenStream
) -> Result<TokenStream> {
let mut function = parse_input::<A>(input)?;
A::function(&mut function).map(|t| t.into())
}
macro_rules! async_entry {
($name:ident, $kind:ty, $default:expr) => (
pub fn $name(a: proc_macro::TokenStream, i: proc_macro::TokenStream) -> TokenStream {
_async_entry::<$kind>(a, i).unwrap_or_else(|d| {
let d = d.emit_as_item_tokens();
let default = $default;
quote!(#d #default)
})
}
)
}
async_entry!(async_test_attribute, Test, quote!());
async_entry!(main_attribute, Main, quote!(fn main() {}));
async_entry!(launch_attribute, Launch, quote!(fn main() {}));

View File

@ -1,79 +1,25 @@
use devise::ext::SpanDiagnosticExt;
use devise::{syn, MetaItem, Spanned, Result, FromMeta, Diagnostic};
mod parse;
use crate::http_codegen::{self, Optional};
use devise::ext::SpanDiagnosticExt;
use devise::{syn, Spanned, Result};
use crate::http_codegen::Optional;
use crate::proc_macro2::{TokenStream, Span};
use crate::syn_ext::ReturnTypeExt;
/// The raw, parsed `#[catch(code)]` attribute.
#[derive(Debug, FromMeta)]
struct CatchAttribute {
#[meta(naked)]
status: CatcherCode
}
/// `Some` if there's a code, `None` if it's `default`.
#[derive(Debug)]
struct CatcherCode(Option<http_codegen::Status>);
impl FromMeta for CatcherCode {
fn from_meta(meta: &MetaItem) -> Result<Self> {
if usize::from_meta(meta).is_ok() {
let status = http_codegen::Status::from_meta(meta)?;
Ok(CatcherCode(Some(status)))
} else if let MetaItem::Path(path) = meta {
if path.is_ident("default") {
Ok(CatcherCode(None))
} else {
Err(meta.span().error("expected `default`"))
}
} else {
let msg = format!("expected integer or identifier, found {}", meta.description());
Err(meta.span().error(msg))
}
}
}
/// This structure represents the parsed `catch` attribute and associated items.
struct CatchParams {
/// The status associated with the code in the `#[catch(code)]` attribute.
status: Option<http_codegen::Status>,
/// The function that was decorated with the `catch` attribute.
function: syn::ItemFn,
}
fn parse_params(
args: TokenStream,
input: proc_macro::TokenStream
) -> Result<CatchParams> {
let function: syn::ItemFn = syn::parse(input)
.map_err(Diagnostic::from)
.map_err(|diag| diag.help("`#[catch]` can only be used on functions"))?;
let attribute = CatchAttribute::from_meta(&syn::parse2(quote!(catch(#args)))?)
.map_err(|diag| diag.help("`#[catch]` expects a status code int or `default`: \
`#[catch(404)]` or `#[catch(default)]`"))?;
Ok(CatchParams { status: attribute.status.0, function })
}
use crate::exports::*;
pub fn _catch(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream
) -> Result<TokenStream> {
// Parse and validate all of the user's input.
let catch = parse_params(args.into(), input)?;
let catch = parse::Attribute::parse(args.into(), input)?;
// Gather everything we'll need to generate the catcher.
let user_catcher_fn = &catch.function;
let user_catcher_fn_name = catch.function.sig.ident.clone();
let (vis, catcher_status) = (&catch.function.vis, &catch.status);
let status_code = Optional(catcher_status.as_ref().map(|s| s.0.code));
// Variables names we'll use and reuse.
define_spanned_export!(catch.function.span().into() =>
__req, __status, _Box, Request, Response, StaticCatcherInfo, Catcher,
ErrorHandlerFuture, Status);
let user_catcher_fn_name = &catch.function.sig.ident;
let vis = &catch.function.vis;
let status_code = Optional(catch.status.map(|s| s.code));
// Determine the number of parameters that will be passed in.
if catch.function.sig.inputs.len() > 2 {

View File

@ -0,0 +1,58 @@
use devise::ext::SpanDiagnosticExt;
use devise::{syn, MetaItem, Spanned, Result, FromMeta, Diagnostic};
use crate::{http, http_codegen};
use crate::proc_macro2::TokenStream;
/// This structure represents the parsed `catch` attribute and associated items.
pub struct Attribute {
/// The status associated with the code in the `#[catch(code)]` attribute.
pub status: Option<http::Status>,
/// The function that was decorated with the `catch` attribute.
pub function: syn::ItemFn,
}
/// We generate a full parser for the meta-item for great error messages.
#[derive(FromMeta)]
struct Meta {
#[meta(naked)]
code: Code,
}
/// `Some` if there's a code, `None` if it's `default`.
#[derive(Debug)]
struct Code(Option<http::Status>);
impl FromMeta for Code {
fn from_meta(meta: &MetaItem) -> Result<Self> {
if usize::from_meta(meta).is_ok() {
let status = http_codegen::Status::from_meta(meta)?;
Ok(Code(Some(status.0)))
} else if let MetaItem::Path(path) = meta {
if path.is_ident("default") {
Ok(Code(None))
} else {
Err(meta.span().error("expected `default`"))
}
} else {
let msg = format!("expected integer or `default`, found {}", meta.description());
Err(meta.span().error(msg))
}
}
}
impl Attribute {
pub fn parse(args: TokenStream, input: proc_macro::TokenStream) -> Result<Self> {
let function: syn::ItemFn = syn::parse(input)
.map_err(Diagnostic::from)
.map_err(|diag| diag.help("`#[catch]` can only be used on functions"))?;
let attr: MetaItem = syn::parse2(quote!(catch(#args)))?;
let status = Meta::from_meta(&attr)
.map(|meta| meta.code.0)
.map_err(|diag| diag.help("`#[catch]` expects a status code int or `default`: \
`#[catch(404)]` or `#[catch(default)]`"))?;
Ok(Attribute { status, function })
}
}

View File

@ -0,0 +1,58 @@
use super::EntryAttr;
use devise::{syn, Spanned, Result};
use devise::ext::SpanDiagnosticExt;
use devise::proc_macro2::{TokenStream, Span};
/// `#[rocket::launch]`: generates a `main` function that calls the attributed
/// function to generate a `Rocket` instance. Then calls `.launch()` on the
/// returned instance inside of an `rocket::async_main`.
pub struct Launch;
impl EntryAttr for Launch {
const REQUIRES_ASYNC: bool = false;
fn function(f: &mut syn::ItemFn) -> Result<TokenStream> {
if f.sig.ident == "main" {
return Err(Span::call_site()
.error("attribute cannot be applied to `main` function")
.note("this attribute generates a `main` function")
.span_note(f.sig.ident.span(), "this function cannot be `main`"));
}
// Always infer the type as `::rocket::Rocket`.
if let syn::ReturnType::Type(_, ref mut ty) = &mut f.sig.output {
if let syn::Type::Infer(_) = &mut **ty {
let new = quote_spanned!(ty.span() => ::rocket::Rocket);
*ty = syn::parse2(new).expect("path is type");
}
}
let ty = match &f.sig.output {
syn::ReturnType::Type(_, ty) => ty,
_ => return Err(Span::call_site()
.error("attribute can only be applied to functions that return a value")
.span_note(f.sig.span(), "this function must return a value"))
};
let block = &f.block;
let rocket = quote_spanned!(ty.span().into() => {
let ___rocket: #ty = #block;
let ___rocket: ::rocket::Rocket = ___rocket;
___rocket
});
let (vis, mut sig) = (&f.vis, f.sig.clone());
sig.ident = syn::Ident::new("main", sig.ident.span());
sig.output = syn::ReturnType::Default;
sig.asyncness = None;
Ok(quote_spanned!(block.span().into() =>
#[allow(dead_code)] #f
#vis #sig {
::rocket::async_main(async move { let _ = #rocket.launch().await; })
}
))
}
}

View File

@ -0,0 +1,28 @@
use super::EntryAttr;
use devise::{syn, Spanned, Result};
use devise::ext::SpanDiagnosticExt;
use devise::proc_macro2::{TokenStream, Span};
/// `#[rocket::async_main]`: calls the attributed fn inside `rocket::async_main`
pub struct Main;
impl EntryAttr for Main {
const REQUIRES_ASYNC: bool = true;
fn function(f: &mut syn::ItemFn) -> Result<TokenStream> {
let (attrs, vis, block, sig) = (&f.attrs, &f.vis, &f.block, &mut f.sig);
if sig.ident != "main" {
// FIXME(diag): warning!
Span::call_site()
.warning("attribute is typically applied to `main` function")
.span_note(sig.ident.span(), "this function is not `main`")
.emit_as_item_tokens();
}
sig.asyncness = None;
Ok(quote_spanned!(block.span().into() => #(#attrs)* #vis #sig {
::rocket::async_main(async move #block)
}))
}
}

View File

@ -0,0 +1,55 @@
mod main;
mod launch;
mod test;
use devise::{syn, Diagnostic, Spanned, Result};
use devise::ext::SpanDiagnosticExt;
use devise::proc_macro2::{TokenStream, Span};
// Common trait implemented by `async` entry generating attributes.
trait EntryAttr {
/// Whether the attribute requires the attributed function to be `async`.
const REQUIRES_ASYNC: bool;
/// Return a new or rewritten function, using block as the main execution.
fn function(f: &mut syn::ItemFn) -> Result<TokenStream>;
}
fn _async_entry<A: EntryAttr>(
_args: proc_macro::TokenStream,
input: proc_macro::TokenStream
) -> Result<TokenStream> {
let mut function: syn::ItemFn = syn::parse(input)
.map_err(Diagnostic::from)
.map_err(|d| d.help("attribute can only be applied to functions"))?;
if A::REQUIRES_ASYNC && function.sig.asyncness.is_none() {
return Err(Span::call_site()
.error("attribute can only be applied to `async` functions")
.span_note(function.sig.span(), "this function must be `async`"));
}
if !function.sig.inputs.is_empty() {
return Err(Span::call_site()
.error("attribute can only be applied to functions without arguments")
.span_note(function.sig.span(), "this function must take no arguments"));
}
A::function(&mut function).map(|t| t.into())
}
macro_rules! async_entry {
($name:ident, $kind:ty, $default:expr) => (
pub fn $name(a: proc_macro::TokenStream, i: proc_macro::TokenStream) -> TokenStream {
_async_entry::<$kind>(a, i).unwrap_or_else(|d| {
let d = d.emit_as_item_tokens();
let default = $default;
quote!(#d #default)
})
}
)
}
async_entry!(async_test_attribute, test::Test, quote!());
async_entry!(main_attribute, main::Main, quote!(fn main() {}));
async_entry!(launch_attribute, launch::Launch, quote!(fn main() {}));

View File

@ -0,0 +1,19 @@
use super::EntryAttr;
use devise::{syn, Spanned, Result};
use devise::proc_macro2::TokenStream;
/// `#[rocket::async_test]`: calls the attributed fn inside `rocket::async_test`
pub struct Test;
impl EntryAttr for Test {
const REQUIRES_ASYNC: bool = true;
fn function(f: &mut syn::ItemFn) -> Result<TokenStream> {
let (attrs, vis, block, sig) = (&f.attrs, &f.vis, &f.block, &mut f.sig);
sig.asyncness = None;
Ok(quote_spanned!(block.span().into() => #(#attrs)* #[test] #vis #sig {
::rocket::async_test(async move #block)
}))
}
}

View File

@ -1,4 +1,4 @@
pub mod async_entry;
pub mod entry;
pub mod catch;
pub mod route;
pub mod segments;
pub mod param;

View File

@ -0,0 +1,39 @@
use std::hash::{Hash, Hasher};
use devise::{syn, FromMeta, MetaItem, Result};
use crate::name::Name;
use crate::proc_macro2::Span;
use crate::proc_macro_ext::StringLit;
use crate::http::uri;
impl Dynamic {
pub fn is_wild(&self) -> bool {
self.value == "_"
}
}
impl FromMeta for Dynamic {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let string = StringLit::from_meta(meta)?;
let span = string.subspan(1..string.len() + 1);
// We don't allow `_`. We abuse `uri::Query` to enforce this.
Ok(Dynamic::parse::<uri::Query>(&string, span)?)
}
}
impl PartialEq for Dynamic {
fn eq(&self, other: &Dynamic) -> bool {
self.value == other.value
}
}
impl Eq for Dynamic {}
impl Hash for Dynamic {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state)
}
}

View File

@ -0,0 +1,118 @@
mod parse;
use std::ops::Deref;
use std::hash::Hash;
use crate::syn;
use crate::name::Name;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Parameter {
Static(Name),
Ignored(Dynamic),
Dynamic(Dynamic),
Guard(Guard),
}
#[derive(Debug, Clone)]
pub struct Dynamic {
pub name: Name,
pub index: usize,
pub trailing: bool,
}
#[derive(Debug, Clone)]
pub struct Guard {
pub source: Dynamic,
pub fn_ident: syn::Ident,
pub ty: syn::Type,
}
impl Parameter {
pub fn r#static(&self) -> Option<&Name> {
match self {
Parameter::Static(s) => Some(s),
_ => None
}
}
pub fn ignored(&self) -> Option<&Dynamic> {
match self {
Parameter::Ignored(d) => Some(d),
_ => None
}
}
pub fn take_dynamic(self) -> Option<Dynamic> {
match self {
Parameter::Dynamic(d) => Some(d),
Parameter::Guard(g) => Some(g.source),
_ => None
}
}
pub fn dynamic(&self) -> Option<&Dynamic> {
match self {
Parameter::Dynamic(d) => Some(d),
Parameter::Guard(g) => Some(&g.source),
_ => None
}
}
pub fn dynamic_mut(&mut self) -> Option<&mut Dynamic> {
match self {
Parameter::Dynamic(d) => Some(d),
Parameter::Guard(g) => Some(&mut g.source),
_ => None
}
}
pub fn guard(&self) -> Option<&Guard> {
match self {
Parameter::Guard(g) => Some(g),
_ => None
}
}
}
impl Dynamic {
// This isn't public since this `Dynamic` should always be an `Ignored`.
pub fn is_wild(&self) -> bool {
self.name == "_"
}
}
impl Guard {
pub fn from(source: Dynamic, fn_ident: syn::Ident, ty: syn::Type) -> Self {
Guard { source, fn_ident, ty }
}
}
macro_rules! impl_derived {
($T:ty => $U:ty = $v:ident) => (
impl Deref for $T {
type Target = $U;
fn deref(&self) -> &Self::Target {
&self.$v
}
}
impl PartialEq for $T {
fn eq(&self, other: &Self) -> bool {
self.$v == other.$v
}
}
impl Eq for $T { }
impl Hash for $T {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.$v.hash(state)
}
}
)
}
impl_derived!(Dynamic => Name = name);
impl_derived!(Guard => Dynamic = source);

View File

@ -0,0 +1,245 @@
use unicode_xid::UnicodeXID;
use devise::{Diagnostic, ext::SpanDiagnosticExt};
use crate::name::Name;
use crate::http::uri::{self, UriPart};
use crate::proc_macro_ext::StringLit;
use crate::proc_macro2::Span;
use crate::attribute::param::{Parameter, Dynamic};
#[derive(Debug)]
pub struct Error<'a> {
segment: &'a str,
span: Span,
source: &'a str,
source_span: Span,
kind: ErrorKind,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum ErrorKind {
Empty,
BadIdent,
Ignored,
EarlyTrailing,
NoTrailing,
Static,
}
impl Dynamic {
pub fn parse<P: UriPart>(
segment: &str,
span: Span,
) -> Result<Self, Error<'_>> {
match Parameter::parse::<P>(&segment, span)? {
Parameter::Dynamic(d) | Parameter::Ignored(d) => Ok(d),
Parameter::Guard(g) => Ok(g.source),
Parameter::Static(_) => Err(Error::new(segment, span, ErrorKind::Static)),
}
}
}
impl Parameter {
pub fn parse<P: UriPart>(
segment: &str,
source_span: Span,
) -> Result<Self, Error<'_>> {
let mut trailing = false;
// Check if this is a dynamic param. If so, check its well-formedness.
if segment.starts_with('<') && segment.ends_with('>') {
let mut name = &segment[1..(segment.len() - 1)];
if name.ends_with("..") {
trailing = true;
name = &name[..(name.len() - 2)];
}
let span = subspan(name, segment, source_span);
if name.is_empty() {
return Err(Error::new(name, source_span, ErrorKind::Empty));
} else if !is_valid_ident(name) {
return Err(Error::new(name, span, ErrorKind::BadIdent));
}
let dynamic = Dynamic { name: Name::new(name, span), trailing, index: 0 };
if dynamic.is_wild() && P::KIND != uri::Kind::Path {
return Err(Error::new(name, span, ErrorKind::Ignored));
} else if dynamic.is_wild() {
return Ok(Parameter::Ignored(dynamic));
} else {
return Ok(Parameter::Dynamic(dynamic));
}
} else if segment.is_empty() {
return Err(Error::new(segment, source_span, ErrorKind::Empty));
} else if segment.starts_with('<') {
let candidate = candidate_from_malformed(segment);
source_span.warning("`segment` starts with `<` but does not end with `>`")
.help(format!("perhaps you meant the dynamic parameter `<{}>`?", candidate))
.emit_as_item_tokens();
} else if segment.contains('>') || segment.contains('<') {
source_span.warning("`segment` contains `<` or `>` but is not a dynamic parameter")
.emit_as_item_tokens();
}
Ok(Parameter::Static(Name::new(segment, source_span)))
}
pub fn parse_many<P: uri::UriPart>(
source: &str,
source_span: Span,
) -> impl Iterator<Item = Result<Self, Error<'_>>> {
let mut trailing: Option<(&str, Span)> = None;
// We check for empty segments when we parse an `Origin` in `FromMeta`.
source.split(P::DELIMITER)
.filter(|s| !s.is_empty())
.enumerate()
.map(move |(i, segment)| {
if let Some((trail, span)) = trailing {
let error = Error::new(trail, span, ErrorKind::EarlyTrailing)
.source(source, source_span);
return Err(error);
}
let segment_span = subspan(segment, source, source_span);
let mut parsed = Self::parse::<P>(segment, segment_span)
.map_err(|e| e.source(source, source_span))?;
if let Some(ref mut d) = parsed.dynamic_mut() {
if d.trailing {
trailing = Some((segment, segment_span));
}
d.index = i;
}
Ok(parsed)
})
}
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::Empty => "parameters cannot be empty".fmt(f),
ErrorKind::BadIdent => "invalid identifier".fmt(f),
ErrorKind::Ignored => "parameter must be named".fmt(f),
ErrorKind::NoTrailing => "parameter cannot be trailing".fmt(f),
ErrorKind::EarlyTrailing => "unexpected text after trailing parameter".fmt(f),
ErrorKind::Static => "unexpected static parameter".fmt(f),
}
}
}
impl<'a> Error<'a> {
pub fn new(segment: &str, span: Span, kind: ErrorKind) -> Error<'_> {
Error { segment, source: segment, span, source_span: span, kind }
}
pub fn source(mut self, source: &'a str, span: Span) -> Self {
self.source = source;
self.source_span = span;
self
}
}
impl From<Error<'_>> for Diagnostic {
fn from(error: Error<'_>) -> Self {
match error.kind {
ErrorKind::Empty => error.span.error(error.kind.to_string()),
ErrorKind::BadIdent => {
let candidate = candidate_from_malformed(error.segment);
error.span.error(format!("{}: `{}`", error.kind, error.segment))
.help("dynamic parameters must be valid identifiers")
.help(format!("did you mean `<{}>`?", candidate))
}
ErrorKind::Ignored => {
error.span.error(error.kind.to_string())
.help("use a name such as `_guard` or `_param`")
}
ErrorKind::EarlyTrailing => {
trailspan(error.segment, error.source, error.source_span)
.error(error.kind.to_string())
.help("a trailing parameter must be the final component")
.span_note(error.span, "trailing param is here")
}
ErrorKind::NoTrailing => {
let candidate = candidate_from_malformed(error.segment);
error.span.error(error.kind.to_string())
.help(format!("did you mean `<{}>`?", candidate))
}
ErrorKind::Static => {
let candidate = candidate_from_malformed(error.segment);
error.span.error(error.kind.to_string())
.help(format!("parameter must be dynamic: `<{}>`", candidate))
}
}
}
}
impl devise::FromMeta for Dynamic {
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
let string = StringLit::from_meta(meta)?;
let span = string.subspan(1..string.len() + 1);
let param = Dynamic::parse::<uri::Path>(&string, span)?;
if param.is_wild() {
return Err(Error::new(&string, span, ErrorKind::Ignored).into());
} else if param.trailing {
return Err(Error::new(&string, span, ErrorKind::NoTrailing).into());
} else {
Ok(param)
}
}
}
fn subspan(needle: &str, haystack: &str, span: Span) -> Span {
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
StringLit::new(haystack, span).subspan(index..index + needle.len())
}
fn trailspan(needle: &str, haystack: &str, span: Span) -> Span {
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
let lit = StringLit::new(haystack, span);
if needle.as_ptr() as usize > haystack.as_ptr() as usize {
lit.subspan((index - 1)..)
} else {
lit.subspan(index..)
}
}
fn candidate_from_malformed(segment: &str) -> String {
let candidate = segment.chars().enumerate()
.filter(|(i, c)| *i == 0 && is_ident_start(*c) || *i != 0 && is_ident_continue(*c))
.map(|(_, c)| c)
.collect::<String>();
if candidate.is_empty() {
"param".into()
} else {
candidate
}
}
#[inline]
fn is_ident_start(c: char) -> bool {
c.is_ascii_alphabetic()
|| c == '_'
|| (c > '\x7f' && UnicodeXID::is_xid_start(c))
}
#[inline]
fn is_ident_continue(c: char) -> bool {
c.is_ascii_alphanumeric()
|| c == '_'
|| (c > '\x7f' && UnicodeXID::is_xid_continue(c))
}
fn is_valid_ident(string: &str) -> bool {
let mut chars = string.chars();
match chars.next() {
Some(c) => is_ident_start(c) && chars.all(is_ident_continue),
None => false
}
}

View File

@ -1,498 +0,0 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use devise::{syn, Spanned, SpanWrapped, Result, FromMeta, Diagnostic};
use devise::ext::{SpanDiagnosticExt, TypeExt};
use indexmap::IndexSet;
use crate::proc_macro_ext::{Diagnostics, StringLit};
use crate::syn_ext::{IdentExt, NameSource};
use crate::proc_macro2::{TokenStream, Span};
use crate::http_codegen::{Method, MediaType, RoutePath, DataSegment, Optional};
use crate::attribute::segments::{Source, Kind, Segment};
use crate::{URI_MACRO_PREFIX, ROCKET_PARAM_PREFIX};
/// The raw, parsed `#[route]` attribute.
#[derive(Debug, FromMeta)]
struct RouteAttribute {
#[meta(naked)]
method: SpanWrapped<Method>,
path: RoutePath,
data: Option<SpanWrapped<DataSegment>>,
format: Option<MediaType>,
rank: Option<isize>,
}
/// The raw, parsed `#[method]` (e.g, `get`, `put`, `post`, etc.) attribute.
#[derive(Debug, FromMeta)]
struct MethodRouteAttribute {
#[meta(naked)]
path: RoutePath,
data: Option<SpanWrapped<DataSegment>>,
format: Option<MediaType>,
rank: Option<isize>,
}
/// This structure represents the parsed `route` attribute and associated items.
#[derive(Debug)]
struct Route {
/// The attribute: `#[get(path, ...)]`.
attribute: RouteAttribute,
/// The function the attribute decorated, i.e, the handler.
function: syn::ItemFn,
/// The non-static parameters declared in the route segments.
segments: IndexSet<Segment>,
/// The parsed inputs to the user's function. The name is the param as the
/// user wrote it, while the ident is the identifier that should be used
/// during code generation, the `rocket_ident`.
inputs: Vec<(NameSource, syn::Ident, syn::Type)>,
}
impl Route {
fn find_input<T>(&self, name: &T) -> Option<&(NameSource, syn::Ident, syn::Type)>
where T: PartialEq<NameSource>
{
self.inputs.iter().find(|(n, ..)| name == n)
}
}
fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
// Gather diagnostics as we proceed.
let mut diags = Diagnostics::new();
// Emit a warning if a `data` param was supplied for non-payload methods.
if let Some(ref data) = attr.data {
if !attr.method.0.supports_payload() {
let msg = format!("'{}' does not typically support payloads", attr.method.0);
// FIXME(diag: warning)
data.full_span.warning("`data` used with non-payload-supporting method")
.span_note(attr.method.span, msg)
.emit_as_item_tokens();
}
}
// Collect non-wild dynamic segments in an `IndexSet`, checking for dups.
let mut segments: IndexSet<Segment> = IndexSet::new();
fn dup_check<'a, I>(set: &mut IndexSet<Segment>, iter: I, diags: &mut Diagnostics)
where I: Iterator<Item = &'a Segment>
{
for segment in iter.filter(|s| s.is_dynamic()) {
let span = segment.span;
if let Some(previous) = set.replace(segment.clone()) {
diags.push(span.error(format!("duplicate parameter: `{}`", previous.name))
.span_note(previous.span, "previous parameter with the same name here"))
}
}
}
dup_check(&mut segments, attr.path.path.iter().filter(|s| !s.is_wild()), &mut diags);
attr.path.query.as_ref().map(|q| dup_check(&mut segments, q.iter(), &mut diags));
dup_check(&mut segments, attr.data.as_ref().map(|s| &s.value.0).into_iter(), &mut diags);
// Check the validity of function arguments.
let mut inputs = vec![];
let mut fn_segments: IndexSet<Segment> = IndexSet::new();
for input in &function.sig.inputs {
let help = "all handler arguments must be of the form: `ident: Type`";
let span = input.span();
let (ident, ty) = match input {
syn::FnArg::Typed(arg) => match *arg.pat {
syn::Pat::Ident(ref pat) => (&pat.ident, &arg.ty),
syn::Pat::Wild(_) => {
diags.push(span.error("handler arguments cannot be ignored").help(help));
continue;
}
_ => {
diags.push(span.error("invalid use of pattern").help(help));
continue;
}
}
// Other cases shouldn't happen since we parsed an `ItemFn`.
_ => {
diags.push(span.error("invalid handler argument").help(help));
continue;
}
};
let rocket_ident = ident.prepend(ROCKET_PARAM_PREFIX);
inputs.push((ident.clone().into(), rocket_ident, ty.with_stripped_lifetimes()));
fn_segments.insert(ident.into());
}
// Check that all of the declared parameters are function inputs.
let span = function.sig.paren_token.span;
for missing in segments.difference(&fn_segments) {
diags.push(missing.span.error("unused dynamic parameter")
.span_note(span, format!("expected argument named `{}` here", missing.name)))
}
diags.head_err_or(Route { attribute: attr, function, inputs, segments })
}
fn param_expr(seg: &Segment, ident: &syn::Ident, ty: &syn::Type) -> TokenStream {
let i = seg.index.expect("dynamic parameters must be indexed");
let span = ident.span().join(ty.span()).unwrap_or_else(|| ty.span());
let name = ident.to_string();
define_spanned_export!(span =>
__req, __data, _log, _request, _None, _Some, _Ok, _Err, Outcome
);
// All dynamic parameter should be found if this function is being called;
// that's the point of statically checking the URI parameters.
let internal_error = quote!({
#_log::error("Internal invariant error: expected dynamic parameter not found.");
#_log::error("Please report this error to the Rocket issue tracker.");
#Outcome::Forward(#__data)
});
// Returned when a dynamic parameter fails to parse.
let parse_error = quote!({
#_log::warn_(&format!("Failed to parse '{}': {:?}", #name, __error));
#Outcome::Forward(#__data)
});
let expr = match seg.kind {
Kind::Single => quote_spanned! { span =>
match #__req.routed_segment(#i) {
#_Some(__s) => match <#ty as #_request::FromParam>::from_param(__s) {
#_Ok(__v) => __v,
#_Err(__error) => return #parse_error,
},
#_None => return #internal_error
}
},
Kind::Multi => quote_spanned! { span =>
match <#ty as #_request::FromSegments>::from_segments(#__req.routed_segments(#i..)) {
#_Ok(__v) => __v,
#_Err(__error) => return #parse_error,
}
},
Kind::Static => return quote!()
};
quote! {
let #ident: #ty = #expr;
}
}
fn data_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream {
let span = ident.span().join(ty.span()).unwrap_or_else(|| ty.span());
define_spanned_export!(span => __req, __data, FromData, Outcome);
quote_spanned! { span =>
let __outcome = <#ty as #FromData>::from_data(#__req, #__data).await;
let #ident: #ty = match __outcome {
#Outcome::Success(__d) => __d,
#Outcome::Forward(__d) => return #Outcome::Forward(__d),
#Outcome::Failure((__c, _)) => return #Outcome::Failure(__c),
};
}
}
fn query_exprs(route: &Route) -> Option<TokenStream> {
use devise::ext::{Split2, Split6};
define_spanned_export!(Span::call_site() =>
__req, __data, _log, _form, Outcome, _Ok, _Err, _Some, _None
);
let query_segments = route.attribute.path.query.as_ref()?;
// Record all of the static parameters for later filtering.
let (raw_name, raw_value) = query_segments.iter()
.filter(|s| !s.is_dynamic())
.map(|s| {
let name = s.name.name();
match name.find('=') {
Some(i) => (&name[..i], &name[i + 1..]),
None => (name, "")
}
})
.split2();
// Now record all of the dynamic parameters.
let (name, matcher, ident, init_expr, push_expr, finalize_expr) = query_segments.iter()
.filter(|s| s.is_dynamic())
.map(|s| (s, s.name.name(), route.find_input(&s.name).expect("dynamic has input")))
.map(|(seg, name, (_, ident, ty))| {
let matcher = match seg.kind {
Kind::Multi => quote_spanned!(seg.span => _),
_ => quote_spanned!(seg.span => #name)
};
let span = ty.span();
define_spanned_export!(span => FromForm, _form);
let ty = quote_spanned!(span => <#ty as #FromForm>);
let i = ident.clone().with_span(span);
let init = quote_spanned!(span => #ty::init(#_form::Options::Lenient));
let finalize = quote_spanned!(span => #ty::finalize(#i));
let push = match seg.kind {
Kind::Multi => quote_spanned!(span => #ty::push_value(&mut #i, _f)),
_ => quote_spanned!(span => #ty::push_value(&mut #i, _f.shift())),
};
(name, matcher, ident, init, push, finalize)
})
.split6();
#[allow(non_snake_case)]
Some(quote! {
let mut _e = #_form::Errors::new();
#(let mut #ident = #init_expr;)*
for _f in #__req.query_fields() {
let _raw = (_f.name.source().as_str(), _f.value);
let _key = _f.name.key_lossy().as_str();
match (_raw, _key) {
// Skip static parameters so <param..> doesn't see them.
#(((#raw_name, #raw_value), _) => { /* skip */ },)*
#((_, #matcher) => #push_expr,)*
_ => { /* in case we have no trailing, ignore all else */ },
}
}
#(
let #ident = match #finalize_expr {
#_Ok(_v) => #_Some(_v),
#_Err(_err) => {
_e.extend(_err.with_name(#_form::NameView::new(#name)));
#_None
},
};
)*
if !_e.is_empty() {
#_log::warn_("query string failed to match declared route");
for _err in _e { #_log::warn_(_err); }
return #Outcome::Forward(#__data);
}
#(let #ident = #ident.unwrap();)*
})
}
fn request_guard_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream {
let span = ident.span().join(ty.span()).unwrap_or_else(|| ty.span());
define_spanned_export!(span => __req, __data, _request, Outcome);
quote_spanned! { span =>
let #ident: #ty = match <#ty as #_request::FromRequest>::from_request(#__req).await {
#Outcome::Success(__v) => __v,
#Outcome::Forward(_) => return #Outcome::Forward(#__data),
#Outcome::Failure((__c, _)) => return #Outcome::Failure(__c),
};
}
}
fn generate_internal_uri_macro(route: &Route) -> TokenStream {
// Keep a global counter (+ thread ID later) to generate unique ids.
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let dynamic_args = route.segments.iter()
.filter(|seg| seg.source == Source::Path || seg.source == Source::Query)
.filter(|seg| seg.kind != Kind::Static)
.map(|seg| &seg.name)
.map(|seg_name| route.find_input(seg_name).unwrap())
.map(|(name, _, ty)| (name.ident(), ty))
.map(|(ident, ty)| quote!(#ident: #ty));
let mut hasher = DefaultHasher::new();
route.function.sig.ident.hash(&mut hasher);
route.attribute.path.origin.0.path().hash(&mut hasher);
std::process::id().hash(&mut hasher);
std::thread::current().id().hash(&mut hasher);
COUNTER.fetch_add(1, Ordering::AcqRel).hash(&mut hasher);
let generated_macro_name = route.function.sig.ident.prepend(URI_MACRO_PREFIX);
let inner_generated_macro_name = generated_macro_name.append(&hasher.finish().to_string());
let route_uri = route.attribute.path.origin.0.to_string();
quote_spanned! { Span::call_site() =>
#[doc(hidden)]
#[macro_export]
macro_rules! #inner_generated_macro_name {
($($token:tt)*) => {{
extern crate std;
extern crate rocket;
rocket::rocket_internal_uri!(#route_uri, (#(#dynamic_args),*), $($token)*)
}};
}
#[doc(hidden)]
pub use #inner_generated_macro_name as #generated_macro_name;
}
}
fn generate_respond_expr(route: &Route) -> TokenStream {
let ret_span = match route.function.sig.output {
syn::ReturnType::Default => route.function.sig.ident.span(),
syn::ReturnType::Type(_, ref ty) => ty.span().into()
};
define_spanned_export!(ret_span => __req, _handler);
let user_handler_fn_name = &route.function.sig.ident;
let parameter_names = route.inputs.iter()
.map(|(_, rocket_ident, _)| rocket_ident);
let _await = route.function.sig.asyncness
.map(|a| quote_spanned!(a.span().into() => .await));
let responder_stmt = quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*) #_await;
};
quote_spanned! { ret_span =>
#responder_stmt
#_handler::Outcome::from(#__req, ___responder)
}
}
fn codegen_route(route: Route) -> Result<TokenStream> {
// Generate the declarations for path, data, and request guard parameters.
let mut data_stmt = None;
let mut req_guard_definitions = vec![];
let mut parameter_definitions = vec![];
for (name, rocket_ident, ty) in &route.inputs {
let fn_segment: Segment = name.ident().into();
match route.segments.get(&fn_segment) {
Some(seg) if seg.source == Source::Path => {
parameter_definitions.push(param_expr(seg, rocket_ident, &ty));
}
Some(seg) if seg.source == Source::Data => {
// the data statement needs to come last, so record it specially
data_stmt = Some(data_expr(rocket_ident, &ty));
}
Some(_) => continue, // handle query parameters later
None => {
req_guard_definitions.push(request_guard_expr(rocket_ident, &ty));
}
};
}
// Generate the declarations for query parameters.
if let Some(exprs) = query_exprs(&route) {
parameter_definitions.push(exprs);
}
// Gather everything we need.
use crate::exports::{
__req, __data, _Box, Request, Data, Route, StaticRouteInfo, HandlerFuture
};
let (vis, user_handler_fn) = (&route.function.vis, &route.function);
let user_handler_fn_name = &user_handler_fn.sig.ident;
let generated_internal_uri_macro = generate_internal_uri_macro(&route);
let generated_respond_expr = generate_respond_expr(&route);
let method = route.attribute.method;
let path = route.attribute.path.origin.0.to_string();
let rank = Optional(route.attribute.rank);
let format = Optional(route.attribute.format);
Ok(quote! {
#user_handler_fn
#[doc(hidden)]
#[allow(non_camel_case_types)]
/// Rocket code generated proxy structure.
#vis struct #user_handler_fn_name { }
/// Rocket code generated proxy static conversion implementation.
impl From<#user_handler_fn_name> for #StaticRouteInfo {
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
fn from(_: #user_handler_fn_name) -> #StaticRouteInfo {
fn monomorphized_function<'_b>(
#__req: &'_b #Request<'_>,
#__data: #Data
) -> #HandlerFuture<'_b> {
#_Box::pin(async move {
#(#req_guard_definitions)*
#(#parameter_definitions)*
#data_stmt
#generated_respond_expr
})
}
#StaticRouteInfo {
name: stringify!(#user_handler_fn_name),
method: #method,
path: #path,
handler: monomorphized_function,
format: #format,
rank: #rank,
}
}
}
/// Rocket code generated proxy conversion implementation.
impl From<#user_handler_fn_name> for #Route {
#[inline]
fn from(_: #user_handler_fn_name) -> #Route {
#StaticRouteInfo::from(#user_handler_fn_name {}).into()
}
}
/// Rocket code generated wrapping URI macro.
#generated_internal_uri_macro
}.into())
}
fn complete_route(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
let function: syn::ItemFn = syn::parse2(input)
.map_err(|e| Diagnostic::from(e))
.map_err(|diag| diag.help("`#[route]` can only be used on functions"))?;
let attr_tokens = quote!(route(#args));
let attribute = RouteAttribute::from_meta(&syn::parse2(attr_tokens)?)?;
codegen_route(parse_route(attribute, function)?)
}
fn incomplete_route(
method: crate::http::Method,
args: TokenStream,
input: TokenStream
) -> Result<TokenStream> {
let method_str = method.to_string().to_lowercase();
// FIXME(proc_macro): there should be a way to get this `Span`.
let method_span = StringLit::new(format!("#[{}]", method), Span::call_site())
.subspan(2..2 + method_str.len());
let method_ident = syn::Ident::new(&method_str, method_span.into());
let function: syn::ItemFn = syn::parse2(input)
.map_err(|e| Diagnostic::from(e))
.map_err(|d| d.help(format!("#[{}] can only be used on functions", method_str)))?;
let full_attr = quote!(#method_ident(#args));
let method_attribute = MethodRouteAttribute::from_meta(&syn::parse2(full_attr)?)?;
let attribute = RouteAttribute {
method: SpanWrapped {
full_span: method_span, span: method_span, value: Method(method)
},
path: method_attribute.path,
data: method_attribute.data,
format: method_attribute.format,
rank: method_attribute.rank,
};
codegen_route(parse_route(attribute, function)?)
}
pub fn route_attribute<M: Into<Option<crate::http::Method>>>(
method: M,
args: proc_macro::TokenStream,
input: proc_macro::TokenStream
) -> TokenStream {
let result = match method.into() {
Some(method) => incomplete_route(method, args.into(), input.into()),
None => complete_route(args.into(), input.into())
};
result.unwrap_or_else(|diag| diag.emit_as_item_tokens())
}

View File

@ -0,0 +1,353 @@
mod parse;
use proc_macro2::{TokenStream, Span};
use devise::{Spanned, SpanWrapped, Result, FromMeta, Diagnostic};
use crate::{proc_macro2, syn};
use crate::proc_macro_ext::StringLit;
use crate::syn_ext::IdentExt;
use crate::http_codegen::{Method, Optional};
use crate::attribute::param::Guard;
use parse::{Route, Attribute, MethodAttribute};
impl Route {
pub fn param_guards(&self) -> impl Iterator<Item = &Guard> {
self.path_params.iter().filter_map(|p| p.guard())
}
pub fn query_guards(&self) -> impl Iterator<Item = &Guard> {
self.query_params.iter().filter_map(|p| p.guard())
}
}
fn query_decls(route: &Route) -> Option<TokenStream> {
use devise::ext::{Split2, Split6};
define_spanned_export!(Span::call_site() =>
__req, __data, _log, _form, Outcome, _Ok, _Err, _Some, _None
);
// Record all of the static parameters for later filtering.
let (raw_name, raw_value) = route.query_params.iter()
.filter_map(|s| s.r#static())
.map(|name| match name.find('=') {
Some(i) => (&name[..i], &name[i + 1..]),
None => (name.as_str(), "")
})
.split2();
// Now record all of the dynamic parameters.
let (name, matcher, ident, init_expr, push_expr, finalize_expr) = route.query_guards()
.map(|guard| {
let (name, ty) = (&guard.name, &guard.ty);
let ident = guard.fn_ident.rocketized().with_span(ty.span());
let matcher = match guard.trailing {
true => quote_spanned!(name.span() => _),
_ => quote!(#name)
};
define_spanned_export!(ty.span() => FromForm, _form);
let ty = quote_spanned!(ty.span() => <#ty as #FromForm>);
let init = quote_spanned!(ty.span() => #ty::init(#_form::Options::Lenient));
let finalize = quote_spanned!(ty.span() => #ty::finalize(#ident));
let push = match guard.trailing {
true => quote_spanned!(ty.span() => #ty::push_value(&mut #ident, _f)),
_ => quote_spanned!(ty.span() => #ty::push_value(&mut #ident, _f.shift())),
};
(name, matcher, ident, init, push, finalize)
})
.split6();
#[allow(non_snake_case)]
Some(quote! {
let mut _e = #_form::Errors::new();
#(let mut #ident = #init_expr;)*
for _f in #__req.query_fields() {
let _raw = (_f.name.source().as_str(), _f.value);
let _key = _f.name.key_lossy().as_str();
match (_raw, _key) {
// Skip static parameters so <param..> doesn't see them.
#(((#raw_name, #raw_value), _) => { /* skip */ },)*
#((_, #matcher) => #push_expr,)*
_ => { /* in case we have no trailing, ignore all else */ },
}
}
#(
let #ident = match #finalize_expr {
#_Ok(_v) => #_Some(_v),
#_Err(_err) => {
_e.extend(_err.with_name(#_form::NameView::new(#name)));
#_None
},
};
)*
if !_e.is_empty() {
#_log::warn_("query string failed to match declared route");
for _err in _e { #_log::warn_(_err); }
return #Outcome::Forward(#__data);
}
#(let #ident = #ident.unwrap();)*
})
}
fn request_guard_decl(guard: &Guard) -> TokenStream {
let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty);
define_spanned_export!(ty.span() => __req, __data, _request, FromRequest, Outcome);
quote_spanned! { ty.span() =>
let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await {
#Outcome::Success(__v) => __v,
#Outcome::Forward(_) => return #Outcome::Forward(#__data),
#Outcome::Failure((__c, _)) => return #Outcome::Failure(__c),
};
}
}
fn param_guard_decl(guard: &Guard) -> TokenStream {
let (i, name, ty) = (guard.index, &guard.name, &guard.ty);
define_spanned_export!(ty.span() =>
__req, __data, _log, _None, _Some, _Ok, _Err,
Outcome, FromSegments, FromParam
);
// Returned when a dynamic parameter fails to parse.
let parse_error = quote!({
#_log::warn_(&format!("Failed to parse '{}': {:?}", #name, __error));
#Outcome::Forward(#__data)
});
// All dynamic parameters should be found if this function is being called;
// that's the point of statically checking the URI parameters.
let expr = match guard.trailing {
false => quote_spanned! { ty.span() =>
match #__req.routed_segment(#i) {
#_Some(__s) => match <#ty as #FromParam>::from_param(__s) {
#_Ok(__v) => __v,
#_Err(__error) => return #parse_error,
},
#_None => {
#_log::error("Internal invariant: dynamic parameter not found.");
#_log::error("Please report this error to the Rocket issue tracker.");
return #Outcome::Forward(#__data);
}
}
},
true => quote_spanned! { ty.span() =>
match <#ty as #FromSegments>::from_segments(#__req.routed_segments(#i..)) {
#_Ok(__v) => __v,
#_Err(__error) => return #parse_error,
}
},
};
let ident = guard.fn_ident.rocketized();
quote!(let #ident: #ty = #expr;)
}
fn data_guard_decl(guard: &Guard) -> TokenStream {
let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty);
define_spanned_export!(ty.span() => __req, __data, FromData, Outcome);
quote_spanned! { ty.span() =>
let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await {
#Outcome::Success(__d) => __d,
#Outcome::Forward(__d) => return #Outcome::Forward(__d),
#Outcome::Failure((__c, _)) => return #Outcome::Failure(__c),
};
}
}
fn internal_uri_macro_decl(route: &Route) -> TokenStream {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
// Keep a global counter (+ thread ID later) to generate unique ids.
static COUNTER: AtomicUsize = AtomicUsize::new(0);
// FIXME: Is this the right order? Does order matter?
let uri_args = route.param_guards()
.chain(route.query_guards())
.map(|guard| (&guard.fn_ident, &guard.ty))
.map(|(ident, ty)| quote!(#ident: #ty));
// Generate entropy based on the route's metadata.
let mut hasher = DefaultHasher::new();
route.handler.sig.ident.hash(&mut hasher);
route.attr.uri.path().hash(&mut hasher);
route.attr.uri.query().hash(&mut hasher);
std::process::id().hash(&mut hasher);
std::thread::current().id().hash(&mut hasher);
COUNTER.fetch_add(1, Ordering::AcqRel).hash(&mut hasher);
let macro_name = route.handler.sig.ident.prepend(crate::URI_MACRO_PREFIX);
let inner_macro_name = macro_name.append(&hasher.finish().to_string());
let route_uri = route.attr.uri.to_string();
quote_spanned! { Span::call_site() =>
#[doc(hidden)]
#[macro_export]
/// Rocket generated URI macro.
macro_rules! #inner_macro_name {
($($token:tt)*) => {{
extern crate std;
extern crate rocket;
rocket::rocket_internal_uri!(#route_uri, (#(#uri_args),*), $($token)*)
}};
}
#[doc(hidden)]
pub use #inner_macro_name as #macro_name;
}
}
fn responder_outcome_expr(route: &Route) -> TokenStream {
let ret_span = match route.handler.sig.output {
syn::ReturnType::Default => route.handler.sig.ident.span(),
syn::ReturnType::Type(_, ref ty) => ty.span().into()
};
let user_handler_fn_name = &route.handler.sig.ident;
let parameter_names = route.arguments.map.values()
.map(|(ident, _)| ident.rocketized());
let _await = route.handler.sig.asyncness
.map(|a| quote_spanned!(a.span().into() => .await));
define_spanned_export!(ret_span => __req, _handler);
quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*) #_await;
#_handler::Outcome::from(#__req, ___responder)
}
}
fn codegen_route(route: Route) -> Result<TokenStream> {
use crate::exports::*;
// Generate the declarations for all of the guards.
let request_guards = route.request_guards.iter().map(request_guard_decl);
let param_guards = route.param_guards().map(param_guard_decl);
let query_guards = query_decls(&route);
let data_guard = route.data_guard.as_ref().map(data_guard_decl);
// Gather info about the function.
let (vis, handler_fn) = (&route.handler.vis, &route.handler);
let handler_fn_name = &handler_fn.sig.ident;
let internal_uri_macro = internal_uri_macro_decl(&route);
let responder_outcome = responder_outcome_expr(&route);
let method = route.attr.method;
let path = route.attr.uri.to_string();
let rank = Optional(route.attr.rank);
let format = Optional(route.attr.format.as_ref());
Ok(quote! {
#handler_fn
#[doc(hidden)]
#[allow(non_camel_case_types)]
/// Rocket code generated proxy structure.
#vis struct #handler_fn_name { }
/// Rocket code generated proxy static conversion implementation.
impl From<#handler_fn_name> for #StaticRouteInfo {
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
fn from(_: #handler_fn_name) -> #StaticRouteInfo {
fn monomorphized_function<'_b>(
#__req: &'_b #Request,
#__data: #Data
) -> #HandlerFuture<'_b> {
#_Box::pin(async move {
#(#request_guards)*
#(#param_guards)*
#query_guards
#data_guard
#responder_outcome
})
}
#StaticRouteInfo {
name: stringify!(#handler_fn_name),
method: #method,
path: #path,
handler: monomorphized_function,
format: #format,
rank: #rank,
}
}
}
/// Rocket code generated proxy conversion implementation.
impl From<#handler_fn_name> for #Route {
#[inline]
fn from(_: #handler_fn_name) -> #Route {
#StaticRouteInfo::from(#handler_fn_name {}).into()
}
}
/// Rocket code generated wrapping URI macro.
#internal_uri_macro
}.into())
}
fn complete_route(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
let function: syn::ItemFn = syn::parse2(input)
.map_err(|e| Diagnostic::from(e))
.map_err(|diag| diag.help("`#[route]` can only be used on functions"))?;
let attr_tokens = quote!(route(#args));
let attribute = Attribute::from_meta(&syn::parse2(attr_tokens)?)?;
codegen_route(Route::from(attribute, function)?)
}
fn incomplete_route(
method: crate::http::Method,
args: TokenStream,
input: TokenStream
) -> Result<TokenStream> {
let method_str = method.to_string().to_lowercase();
// FIXME(proc_macro): there should be a way to get this `Span`.
let method_span = StringLit::new(format!("#[{}]", method), Span::call_site())
.subspan(2..2 + method_str.len());
let method_ident = syn::Ident::new(&method_str, method_span.into());
let function: syn::ItemFn = syn::parse2(input)
.map_err(|e| Diagnostic::from(e))
.map_err(|d| d.help(format!("#[{}] can only be used on functions", method_str)))?;
let full_attr = quote!(#method_ident(#args));
let method_attribute = MethodAttribute::from_meta(&syn::parse2(full_attr)?)?;
let attribute = Attribute {
method: SpanWrapped {
full_span: method_span, span: method_span, value: Method(method)
},
uri: method_attribute.uri,
data: method_attribute.data,
format: method_attribute.format,
rank: method_attribute.rank,
};
codegen_route(Route::from(attribute, function)?)
}
pub fn route_attribute<M: Into<Option<crate::http::Method>>>(
method: M,
args: proc_macro::TokenStream,
input: proc_macro::TokenStream
) -> TokenStream {
let result = match method.into() {
Some(method) => incomplete_route(method, args.into(), input.into()),
None => complete_route(args.into(), input.into())
};
result.unwrap_or_else(|diag| diag.emit_as_item_tokens())
}

View File

@ -0,0 +1,226 @@
use devise::{syn, Spanned, SpanWrapped, Result, FromMeta};
use devise::ext::{SpanDiagnosticExt, TypeExt};
use indexmap::{IndexSet, IndexMap};
use crate::proc_macro_ext::Diagnostics;
use crate::http_codegen::{Method, MediaType};
use crate::attribute::param::{Parameter, Dynamic, Guard};
use crate::syn_ext::FnArgExt;
use crate::name::Name;
use crate::proc_macro2::Span;
use crate::http::ext::IntoOwned;
use crate::http::uri::{self, Origin};
/// This structure represents the parsed `route` attribute and associated items.
#[derive(Debug)]
pub struct Route {
/// The attribute: `#[get(path, ...)]`.
pub attr: Attribute,
/// The static and dynamic path parameters.
pub path_params: Vec<Parameter>,
/// The static and dynamic query parameters.
pub query_params: Vec<Parameter>,
/// The data guard, if any.
pub data_guard: Option<Guard>,
/// The request guards.
pub request_guards: Vec<Guard>,
/// The decorated function: the handler.
pub handler: syn::ItemFn,
/// The parsed arguments to the user's function.
pub arguments: Arguments,
}
type ArgumentMap = IndexMap<Name, (syn::Ident, syn::Type)>;
#[derive(Debug)]
pub struct Arguments {
pub span: Span,
pub map: ArgumentMap
}
/// The parsed `#[route(..)]` attribute.
#[derive(Debug, FromMeta)]
pub struct Attribute {
#[meta(naked)]
pub method: SpanWrapped<Method>,
pub uri: RouteUri,
pub data: Option<SpanWrapped<Dynamic>>,
pub format: Option<MediaType>,
pub rank: Option<isize>,
}
/// The parsed `#[method(..)]` (e.g, `get`, `put`, etc.) attribute.
#[derive(Debug, FromMeta)]
pub struct MethodAttribute {
#[meta(naked)]
pub uri: RouteUri,
pub data: Option<SpanWrapped<Dynamic>>,
pub format: Option<MediaType>,
pub rank: Option<isize>,
}
#[derive(Debug)]
pub struct RouteUri {
origin: Origin<'static>,
path_span: Span,
query_span: Option<Span>,
}
impl FromMeta for RouteUri {
fn from_meta(meta: &devise::MetaItem) -> Result<Self> {
let string = crate::proc_macro_ext::StringLit::from_meta(meta)?;
let origin = Origin::parse_route(&string)
.map_err(|e| {
let span = string.subspan(e.index() + 1..(e.index() + 2));
span.error(format!("invalid route URI: {}", e))
.help("expected URI in origin form: \"/path/<param>\"")
})?;
if !origin.is_normalized() {
let normalized = origin.clone().into_normalized();
let span = origin.path().find("//")
.or_else(|| origin.query()
.and_then(|q| q.find("&&"))
.map(|i| origin.path().len() + 1 + i))
.map(|i| string.subspan((1 + i)..(1 + i + 2)))
.unwrap_or(string.span());
return Err(span.error("route URIs cannot contain empty segments")
.note(format!("expected \"{}\", found \"{}\"", normalized, origin)));
}
let path_span = string.subspan(1..origin.path().len() + 1);
let query_span = origin.query().map(|q| {
let len_to_q = 1 + origin.path().len() + 1;
let end_of_q = len_to_q + q.len();
string.subspan(len_to_q..end_of_q)
});
Ok(RouteUri { origin: origin.into_owned(), path_span, query_span })
}
}
impl Route {
pub fn upgrade_param(param: Parameter, args: &Arguments) -> Result<Parameter> {
if !param.dynamic().is_some() {
return Ok(param);
}
let param = param.take_dynamic().expect("dynamic() => take_dynamic()");
Route::upgrade_dynamic(param, args).map(Parameter::Guard)
}
pub fn upgrade_dynamic(param: Dynamic, args: &Arguments) -> Result<Guard> {
if let Some((ident, ty)) = args.map.get(&param.name) {
Ok(Guard::from(param, ident.clone(), ty.clone()))
} else {
let msg = format!("expected argument named `{}` here", param.name);
let diag = param.span().error("unused parameter").span_note(args.span, msg);
Err(diag)
}
}
pub fn from(attr: Attribute, handler: syn::ItemFn) -> Result<Route> {
// Collect diagnostics as we proceed.
let mut diags = Diagnostics::new();
// Emit a warning if a `data` param was supplied for non-payload methods.
if let Some(ref data) = attr.data {
if !attr.method.0.supports_payload() {
let msg = format!("'{}' does not typically support payloads", attr.method.0);
// FIXME(diag: warning)
data.full_span.warning("`data` used with non-payload-supporting method")
.span_note(attr.method.span, msg)
.emit_as_item_tokens();
}
}
// Check the validity of function arguments.
let span = handler.sig.paren_token.span;
let mut arguments = Arguments { map: ArgumentMap::new(), span };
for arg in &handler.sig.inputs {
if let Some((ident, ty)) = arg.typed() {
let value = (ident.clone(), ty.with_stripped_lifetimes());
arguments.map.insert(Name::from(ident), value);
} else {
let error = match arg.wild() {
Some(_) => "handler arguments cannot be ignored",
None => "handler arguments must be of the form `ident: Type`"
};
let diag = arg.span()
.error(error)
.note("handler arguments must be of the form: `ident: Type`");
diags.push(diag);
}
}
// Parse and collect the path parameters.
let (source, span) = (attr.uri.path(), attr.uri.path_span);
let path_params = Parameter::parse_many::<uri::Path>(source.as_str(), span)
.map(|p| Route::upgrade_param(p?, &arguments))
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
.collect::<Vec<_>>();
// Parse and collect the query parameters.
let query_params = match (attr.uri.query(), attr.uri.query_span) {
(Some(r), Some(span)) => Parameter::parse_many::<uri::Query>(r.as_str(), span)
.map(|p| Route::upgrade_param(p?, &arguments))
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
.collect::<Vec<_>>(),
_ => vec![]
};
// Remove the `SpanWrapped` layer and upgrade to a guard.
let data_guard = attr.data.clone()
.map(|p| Route::upgrade_dynamic(p.value, &arguments))
.and_then(|p| p.map_err(|e| diags.push(e.into())).ok());
// Collect all of the declared dynamic route parameters.
let all_dyn_params = path_params.iter().filter_map(|p| p.dynamic())
.chain(query_params.iter().filter_map(|p| p.dynamic()))
.chain(data_guard.as_ref().map(|g| &g.source).into_iter());
// Check for any duplicates in the dynamic route parameters.
let mut dyn_params: IndexSet<&Dynamic> = IndexSet::new();
for p in all_dyn_params {
if let Some(prev) = dyn_params.replace(p) {
diags.push(p.span().error(format!("duplicate parameter: `{}`", p.name))
.span_note(prev.span(), "previous parameter with the same name here"))
}
}
// Collect the request guards: all the arguments not already a guard.
let request_guards = arguments.map.iter()
.filter(|(name, _)| {
let mut all_other_guards = path_params.iter().filter_map(|p| p.guard())
.chain(query_params.iter().filter_map(|p| p.guard()))
.chain(data_guard.as_ref().into_iter());
all_other_guards.all(|g| g.name != name)
})
.enumerate()
.map(|(index, (name, (ident, ty)))| Guard {
source: Dynamic { index, name: name.clone(), trailing: false },
fn_ident: ident.clone(),
ty: ty.clone(),
})
.collect();
diags.head_err_or(Route {
attr, path_params, query_params, data_guard, request_guards,
handler, arguments,
})
}
}
impl std::ops::Deref for RouteUri {
type Target = Origin<'static>;
fn deref(&self) -> &Self::Target {
&self.origin
}
}

View File

@ -1,171 +0,0 @@
use std::hash::{Hash, Hasher};
use devise::{syn, Diagnostic, ext::SpanDiagnosticExt};
use crate::proc_macro2::Span;
use crate::http::RawStr;
use crate::http::uri::{self, UriPart};
use crate::http::route::RouteSegment;
use crate::proc_macro_ext::{Diagnostics, StringLit, PResult, DResult};
use crate::syn_ext::NameSource;
pub use crate::http::route::{Error, Kind};
#[derive(Debug, Clone)]
pub struct Segment {
pub span: Span,
pub kind: Kind,
pub source: Source,
pub name: NameSource,
pub index: Option<usize>,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Source {
Path,
Query,
Data,
Unknown,
}
impl Segment {
fn from<P: UriPart>(segment: RouteSegment<'_, P>, span: Span) -> Segment {
let source = match P::DELIMITER {
'/' => Source::Path,
'&' => Source::Query,
_ => unreachable!("only paths and queries")
};
let (kind, index) = (segment.kind, segment.index);
Segment { span, kind, source, index, name: NameSource::new(&segment.name, span) }
}
pub fn is_wild(&self) -> bool {
self.name == "_"
}
pub fn is_dynamic(&self) -> bool {
match self.kind {
Kind::Static => false,
Kind::Single | Kind::Multi => true,
}
}
}
impl From<&syn::Ident> for Segment {
fn from(ident: &syn::Ident) -> Segment {
Segment {
kind: Kind::Static,
source: Source::Unknown,
span: ident.span(),
name: ident.clone().into(),
index: None,
}
}
}
impl PartialEq for Segment {
fn eq(&self, other: &Segment) -> bool {
self.name == other.name
}
}
impl Eq for Segment { }
impl Hash for Segment {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
fn subspan(needle: &str, haystack: &str, span: Span) -> Span {
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
StringLit::new(haystack, span).subspan(index..index + needle.len())
}
fn trailspan(needle: &str, haystack: &str, span: Span) -> Span {
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
let lit = StringLit::new(haystack, span);
if needle.as_ptr() as usize > haystack.as_ptr() as usize {
lit.subspan((index - 1)..)
} else {
lit.subspan(index..)
}
}
fn into_diagnostic(
segment: &str, // The segment that failed.
source: &str, // The haystack where `segment` can be found.
span: Span, // The `Span` of `Source`.
error: &Error<'_>, // The error.
) -> Diagnostic {
let seg_span = subspan(segment, source, span);
match error {
Error::Empty => seg_span.error(error.to_string()),
Error::Ident(_) => {
seg_span.error(error.to_string())
.help("parameter names must be valid identifiers")
}
Error::Ignored => {
seg_span.error(error.to_string())
.help("use a name such as `_guard` or `_param`")
}
Error::MissingClose => {
seg_span.error(error.to_string())
.help(format!("did you mean '{}>'?", segment))
}
Error::Malformed => {
seg_span.error(error.to_string())
.help("parameters must be of the form '<param>'")
.help("identifiers cannot contain '<' or '>'")
}
Error::Uri => {
seg_span.error(error.to_string())
.note("components cannot contain reserved characters")
.help("reserved characters include: '%', '+', '&', etc.")
}
Error::Trailing(multi) => {
let multi_span = subspan(multi, source, span);
trailspan(segment, source, span)
.error(error.to_string())
.help("a multi-segment param must be the final component")
.span_note(multi_span, "multi-segment param is here")
}
}
}
pub fn parse_data_segment(segment: &str, span: Span) -> PResult<Segment> {
<RouteSegment<'_, uri::Query>>::parse_one(segment)
.map(|segment| {
let mut seg = Segment::from(segment, span);
seg.source = Source::Data;
seg.index = Some(0);
seg
})
.map_err(|e| into_diagnostic(segment, segment, span, &e))
}
pub fn parse_segments<P: UriPart>(
string: &RawStr,
span: Span
) -> DResult<Vec<Segment>> {
let mut segments = vec![];
let mut diags = Diagnostics::new();
for result in <RouteSegment<'_, P>>::parse_many(string) {
match result {
Ok(segment) => {
let seg_span = subspan(&segment.string, string.as_str(), span);
segments.push(Segment::from(segment, seg_span));
},
Err((segment_string, error)) => {
diags.push(into_diagnostic(segment_string, string.as_str(), span, &error));
if let Error::Trailing(..) = error {
break;
}
}
}
}
diags.err_or(segments)
}

View File

@ -3,15 +3,14 @@ use std::fmt::Display;
use devise::{syn, Result};
use devise::ext::{SpanDiagnosticExt, quote_respanned};
use crate::http::{uri::{Origin, Path, Query}, ext::IntoOwned};
use crate::http::route::{RouteSegment, Kind};
use crate::attribute::segments::Source;
use crate::http::uri;
use crate::syn::{Expr, Ident, Type, spanned::Spanned};
use crate::http_codegen::Optional;
use crate::syn_ext::IdentExt;
use crate::bang::uri_parsing::*;
use crate::proc_macro2::TokenStream;
use crate::attribute::param::Parameter;
use crate::exports::_uri;
use crate::URI_MACRO_PREFIX;
@ -40,42 +39,41 @@ pub fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
}
fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
impl Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>,
impl Iterator<Item = (&'a Ident, &'a Type, &'a ArgExpr)>,
impl Iterator<Item = &'a Expr>, // path exprs
impl Iterator<Item = &'a ArgExpr>, // query exprs
impl Iterator<Item = (&'a Ident, &'a Type)>, // types for both path || query
)>
{
let route_name = &internal.uri_params.route_path;
match internal.validate() {
Validation::Ok(exprs) => {
let path_param_count = internal.route_uri.path().as_str().matches('<').count();
let path_params = internal.dynamic_path_params();
let path_param_count = path_params.clone().count();
for expr in exprs.iter().take(path_param_count) {
if !expr.as_expr().is_some() {
return Err(expr.span().error("path parameters cannot be ignored"));
}
}
// Create an iterator over all `ident`, `ty`, and `expr` triples.
let arguments = internal.fn_args.iter()
.zip(exprs.into_iter())
.map(|(FnArg { ident, ty }, expr)| (ident, ty, expr));
let query_exprs = exprs.clone().into_iter().skip(path_param_count);
let path_exprs = exprs.into_iter().map(|e| e.unwrap_expr()).take(path_param_count);
let types = internal.fn_args.iter().map(|a| (&a.ident, &a.ty));
Ok((path_exprs, query_exprs, types))
}
Validation::NamedIgnored(_) => {
let diag = internal.uri_params.args_span()
.error("expected unnamed arguments due to ignored parameters")
.note(format!("uri for route `{}` ignores path parameters: \"{}\"",
quote!(#route_name), internal.route_uri));
// Create iterators for just the path and query parts.
let path_params = arguments.clone()
.take(path_param_count)
.map(|(i, t, e)| (i, t, e.unwrap_expr()));
let query_params = arguments.skip(path_param_count);
Ok((path_params, query_params))
Err(diag)
}
Validation::Unnamed(expected, actual) => {
let mut diag = internal.uri_params.args_span().error(
format!("`{}` route uri expects {} but {} supplied", quote!(#route_name),
p!(expected, "parameter"), p!(actual, "was")));
if expected > 0 {
let ps = p!("parameter", expected);
diag = diag.note(format!("expected {}: {}", ps, internal.fn_args_str()));
}
let diag = internal.uri_params.args_span()
.error(format!("expected {} but {} supplied",
p!(expected, "parameter"), p!(actual, "was")))
.note(format!("route `{}` has uri \"{}\"",
quote!(#route_name), internal.route_uri));
Err(diag)
}
@ -112,12 +110,11 @@ fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
}
}
fn add_binding(to: &mut Vec<TokenStream>, ident: &Ident, ty: &Type, expr: &Expr, source: Source) {
fn add_binding<P: uri::UriPart>(to: &mut Vec<TokenStream>, ident: &Ident, ty: &Type, expr: &Expr) {
let span = expr.span();
define_spanned_export!(span => _uri);
let part = match source {
Source::Query => quote_spanned!(span => #_uri::Query),
_ => quote_spanned!(span => #_uri::Path),
let part = match P::KIND {
uri::Kind::Path => quote_spanned!(span => #_uri::Path),
uri::Kind::Query => quote_spanned!(span => #_uri::Query),
};
let tmp_ident = ident.clone().with_span(expr.span());
@ -129,107 +126,117 @@ fn add_binding(to: &mut Vec<TokenStream>, ident: &Ident, ty: &Type, expr: &Expr,
));
}
fn explode_path<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>>(
uri: &Origin<'_>,
fn explode_path<'a>(
internal: &InternalUriParams,
bindings: &mut Vec<TokenStream>,
mut items: I
mut exprs: impl Iterator<Item = &'a Expr>,
mut args: impl Iterator<Item = (&'a Ident, &'a Type)>,
) -> TokenStream {
let (uri_mod, path) = (quote!(rocket::http::uri), uri.path().as_str());
if !path.contains('<') {
return quote!(#uri_mod::UriArgumentsKind::Static(#path));
if internal.dynamic_path_params().count() == 0 {
let route_uri = &internal.route_uri;
if let Some(ref mount) = internal.uri_params.mount_point {
let full_uri = route_uri.map_path(|p| format!("{}/{}", mount, p))
.expect("origin from path")
.into_normalized();
let path = full_uri.path().as_str();
return quote!(#_uri::UriArgumentsKind::Static(#path));
} else {
let path = route_uri.path().as_str();
return quote!(#_uri::UriArgumentsKind::Static(#path));
}
}
let uri_display = quote!(#uri_mod::UriDisplay<#uri_mod::Path>);
let dyn_exprs = <RouteSegment<'_, Path>>::parse(uri).map(|segment| {
let segment = segment.expect("segment okay; prechecked on parse");
match segment.kind {
Kind::Static => {
let string = &segment.string;
quote!(&#string as &dyn #uri_display)
}
Kind::Single | Kind::Multi => {
let (ident, ty, expr) = items.next().expect("one item for each dyn");
add_binding(bindings, &ident, &ty, &expr, Source::Path);
let uri_display = quote!(#_uri::UriDisplay<#_uri::Path>);
let all_path_params = internal.mount_params.iter().chain(internal.path_params.iter());
let dyn_exprs = all_path_params.map(|param| {
match param {
Parameter::Static(name) => {
quote!(&#name as &dyn #uri_display)
},
Parameter::Dynamic(_) | Parameter::Guard(_) => {
let (ident, ty) = args.next().expect("ident/ty for non-ignored");
let expr = exprs.next().expect("one expr per dynamic arg");
add_binding::<uri::Path>(bindings, &ident, &ty, &expr);
quote_spanned!(expr.span() => &#ident as &dyn #uri_display)
}
Parameter::Ignored(_) => {
let expr = exprs.next().expect("one expr per dynamic arg");
quote_spanned!(expr.span() => &#expr as &dyn #uri_display)
}
}
});
quote!(#uri_mod::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))
quote!(#_uri::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))
}
fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a ArgExpr)>>(
uri: &Origin<'_>,
fn explode_query<'a>(
internal: &InternalUriParams,
bindings: &mut Vec<TokenStream>,
mut items: I
mut arg_exprs: impl Iterator<Item = &'a ArgExpr>,
mut args: impl Iterator<Item = (&'a Ident, &'a Type)>,
) -> Option<TokenStream> {
let (uri_mod, query) = (quote!(rocket::http::uri), uri.query()?.as_str());
if !query.contains('<') {
return Some(quote!(#uri_mod::UriArgumentsKind::Static(#query)));
let query = internal.route_uri.query()?.as_str();
if internal.dynamic_query_params().count() == 0 {
return Some(quote!(#_uri::UriArgumentsKind::Static(#query)));
}
let query_arg = quote!(#uri_mod::UriQueryArgument);
let uri_display = quote!(#uri_mod::UriDisplay<#uri_mod::Query>);
let dyn_exprs = <RouteSegment<'_, Query>>::parse(uri)?.filter_map(|segment| {
let segment = segment.expect("segment okay; prechecked on parse");
if segment.kind == Kind::Static {
let string = &segment.string;
return Some(quote!(#query_arg::Raw(#string)));
let query_arg = quote!(#_uri::UriQueryArgument);
let uri_display = quote!(#_uri::UriDisplay<#_uri::Query>);
let dyn_exprs = internal.query_params.iter().filter_map(|param| {
if let Parameter::Static(source) = param {
return Some(quote!(#query_arg::Raw(#source)));
}
let (ident, ty, arg_expr) = items.next().expect("one item for each dyn");
let dynamic = match param {
Parameter::Static(source) => {
return Some(quote!(#query_arg::Raw(#source)));
},
Parameter::Dynamic(ref seg) => seg,
Parameter::Guard(ref seg) => seg,
Parameter::Ignored(_) => unreachable!("invariant: unignorable q")
};
let (ident, ty) = args.next().expect("ident/ty for query");
let arg_expr = arg_exprs.next().expect("one expr per query");
let expr = match arg_expr.as_expr() {
Some(expr) => expr,
None => {
// Force a typecheck for the `Ignoreable` trait. Note that write
// out the path to `is_ignorable` to get the right span.
bindings.push(quote_respanned! { arg_expr.span() =>
rocket::http::uri::assert_ignorable::<#uri_mod::Query, #ty>();
rocket::http::uri::assert_ignorable::<#_uri::Query, #ty>();
});
return None;
}
};
let name = &segment.name;
add_binding(bindings, &ident, &ty, &expr, Source::Query);
Some(match segment.kind {
Kind::Single => quote_spanned! { expr.span() =>
let name = &dynamic.name;
add_binding::<uri::Query>(bindings, &ident, &ty, &expr);
Some(match dynamic.trailing {
false => quote_spanned! { expr.span() =>
#query_arg::NameValue(#name, &#ident as &dyn #uri_display)
},
Kind::Multi => quote_spanned! { expr.span() =>
true => quote_spanned! { expr.span() =>
#query_arg::Value(&#ident as &dyn #uri_display)
},
Kind::Static => unreachable!("Kind::Static returns early")
})
});
Some(quote!(#uri_mod::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*])))
}
// Returns an Origin URI with the mount point and route path concatenated. The
// query string is mangled by replacing single dynamic parameters in query parts
// (`<param>`) with `param=<param>`.
fn build_origin(internal: &InternalUriParams) -> Origin<'static> {
let mount_point = internal.uri_params.mount_point.as_ref()
.map(|origin| origin.path().as_str())
.unwrap_or("");
let path = format!("{}/{}", mount_point, internal.route_uri.path());
let query = internal.route_uri.query().map(|q| q.as_str());
Origin::new(path, query).into_normalized().into_owned()
Some(quote!(#_uri::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*])))
}
pub fn _uri_internal_macro(input: TokenStream) -> Result<TokenStream> {
// Parse the internal invocation and the user's URI param expressions.
let internal = syn::parse2::<InternalUriParams>(input)?;
let (path_params, query_params) = extract_exprs(&internal)?;
let (path_exprs, query_exprs, mut fn_args) = extract_exprs(&internal)?;
let mut bindings = vec![];
let uri = build_origin(&internal);
let uri_mod = quote!(rocket::http::uri);
let path = explode_path(&uri, &mut bindings, path_params);
let query = Optional(explode_query(&uri, &mut bindings, query_params));
let path = explode_path(&internal, &mut bindings, path_exprs, &mut fn_args);
let query = explode_query(&internal, &mut bindings, query_exprs, fn_args);
let query = Optional(query);
Ok(quote!({
#(#bindings)*

View File

@ -6,9 +6,11 @@ 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::Origin, ext::IntoOwned};
use crate::http::{uri, uri::Origin, ext::IntoOwned};
use crate::proc_macro2::{TokenStream, Span};
use crate::syn_ext::NameSource;
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.
@ -21,7 +23,7 @@ pub enum ArgExpr {
#[derive(Debug)]
pub enum Arg {
Unnamed(ArgExpr),
Named(NameSource, Token![=], ArgExpr),
Named(Name, Ident, Token![=], ArgExpr),
}
#[derive(Debug)]
@ -49,31 +51,37 @@ pub struct FnArg {
}
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<NameSource>, Vec<&'a Ident>, Vec<&'a Ident>),
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 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).
// 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 guards) from the original route's
// handler in the order they were declared in the URI (`<first>/<second>`).
// `uri` is the full URI used in the origin route's attribute.
// `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
// (`<first>/<second>`). `route_uri` is the URI itself.
//
// internal_uri!("/<first>/<second>", (first: ty, second: ty), $($tt)*);
// ^--------|--------- ^-----------|---------| ^-----|
// route_uri fn_args uri_params
// internal_uri!("/<one>/<_>?lang=en&<two>", (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<Parameter>,
pub path_params: Vec<Parameter>,
pub query_params: Vec<Parameter>,
pub fn_args: Vec<FnArg>,
pub uri_params: UriParams,
}
@ -95,7 +103,7 @@ impl Parse for Arg {
let ident = input.parse::<Ident>()?;
let eq_token = input.parse::<Token![=]>()?;
let expr = input.parse::<ArgExpr>()?;
Ok(Arg::Named(ident.into(), eq_token, expr))
Ok(Arg::Named(Name::from(&ident), ident, eq_token, expr))
} else {
let expr = input.parse::<ArgExpr>()?;
Ok(Arg::Unnamed(expr))
@ -117,11 +125,13 @@ impl Parse for UriParams {
// Parse the mount point and suffixing ',', if any.
let mount_point = if input.peek(LitStr) {
let string = input.parse::<LitStr>()?;
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; \
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: \
@ -192,7 +202,8 @@ impl Parse for InternalUriParams {
// 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())
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"))?;
@ -203,37 +214,85 @@ impl Parse for InternalUriParams {
input.parse::<Token![,]>()?;
let uri_params = input.parse::<UriParams>()?;
Ok(InternalUriParams { route_uri, fn_args, uri_params })
// 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::<uri::Path>(mount.path().as_str(), span)
.map(|p| p.expect("internal error: invalid path parameter"))
.collect::<Vec<_>>(),
None => vec![]
};
let path_params = Parameter::parse_many::<uri::Path>(route_uri.path().as_str(), span)
.map(|p| p.expect("internal error: invalid path parameter"))
.collect::<Vec<_>>();
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::<uri::Query>(query.as_str(), span)
.map(|p| p.expect("internal error: invalid query parameter"))
.collect::<Vec<_>>()
}
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 }| format!("{}: {}", ident, quote!(#ty).to_string().trim()))
.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::<Vec<_>>()
.join(", ")
}
pub fn dynamic_path_params(&self) -> impl Iterator<Item = &Dynamic> + Clone {
self.path_params.iter()
.filter_map(|p| p.dynamic().or_else(|| p.ignored()))
}
pub fn dynamic_query_params(&self) -> impl Iterator<Item = &Dynamic> + 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(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::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(_) => {
let mut params: IndexMap<NameSource, Option<&ArgExpr>> = self.fn_args.iter()
.map(|FnArg { ident, .. }| (ident.clone().into(), None))
.collect();
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::<IndexMap<&Name, Option<&ArgExpr>>>();
let (mut extra, mut dup) = (vec![], vec![]);
for (name, expr) in args.named().unwrap() {
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(name.ident()),
Some(ref entry) if entry.is_some() => dup.push(ident),
Some(entry) => *entry = Some(expr),
None => extra.push(name.ident()),
None => extra.push(ident),
}
}
@ -280,9 +339,9 @@ impl Arg {
}
}
fn named(&self) -> (&NameSource, &ArgExpr) {
fn named(&self) -> (&Name, &Ident, &ArgExpr) {
match self {
Arg::Named(name, _, expr) => (name, expr),
Arg::Named(name, ident, _, expr) => (name, ident, expr),
_ => panic!("Called Arg::named() on an Arg::Unnamed!"),
}
}
@ -294,20 +353,6 @@ impl Args {
Args::Named(inner) | Args::Unnamed(inner) => inner.len(),
}
}
fn named(&self) -> Option<impl Iterator<Item = (&NameSource, &ArgExpr)>> {
match self {
Args::Named(args) => Some(args.iter().map(|arg| arg.named())),
_ => None
}
}
fn unnamed(&self) -> Option<impl Iterator<Item = &ArgExpr>> {
match self {
Args::Unnamed(args) => Some(args.iter().map(|arg| arg.unnamed())),
_ => None
}
}
}
impl ArgExpr {
@ -339,9 +384,10 @@ impl ToTokens for Arg {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Arg::Unnamed(e) => e.to_tokens(tokens),
Arg::Named(name, eq, expr) => {
let ident = name.ident();
tokens.extend(quote!(#ident #eq #expr))
Arg::Named(_, ident, eq, expr) => {
ident.to_tokens(tokens);
eq.to_tokens(tokens);
expr.to_tokens(tokens);
}
}
}

View File

@ -2,11 +2,11 @@ use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
use crate::exports::*;
use crate::proc_macro2::Span;
use crate::syn_ext::NameSource;
use crate::name::Name;
pub struct FormField {
pub span: Span,
pub name: NameSource,
pub name: Name,
}
#[derive(FromMeta)]
@ -41,8 +41,8 @@ impl FromMeta for FormField {
s.chars().all(|c| c.is_ascii_graphic() && !CONTROL_CHARS.contains(&c))
}
let name = NameSource::from_meta(meta)?;
if !is_valid_field_name(name.name()) {
let name = Name::from_meta(meta)?;
if !is_valid_field_name(name.as_str()) {
let chars = CONTROL_CHARS.iter()
.map(|c| format!("{:?}", c))
.collect::<Vec<_>>()
@ -69,7 +69,7 @@ impl FieldExt for Field<'_> {
let name = fields.next()
.map(|f| f.name)
.unwrap_or_else(|| NameSource::from(self.ident().clone()));
.unwrap_or_else(|| Name::from(self.ident().clone()));
if let Some(field) = fields.next() {
return Err(field.span

View File

@ -249,20 +249,5 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
}})
})
)
// .inner_mapper(MapperBuild::new()
// .with_output(|_, output| quote! {
// fn default() -> #_Option<Self> {
// Some(Self { #output })
// }
// })
// .try_fields_map(|m, f| mapper::fields_null(m, f))
// .field_map(|_, field| {
// let ident = field.ident.as_ref().expect("named");
// let ty = field.ty.with_stripped_lifetimes();
// quote_spanned!(ty.span() =>
// #ident: <#ty as #_form::FromForm<'__f>>::default()?,
// )
// })
// )
.to_tokens()
}

View File

@ -2,11 +2,11 @@ use devise::{*, ext::SpanDiagnosticExt};
use crate::exports::*;
use crate::proc_macro2::TokenStream;
use crate::syn_ext::NameSource;
use crate::name::Name;
#[derive(FromMeta)]
pub struct FieldAttr {
value: NameSource,
value: Name,
}
pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream {
@ -36,10 +36,10 @@ pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream {
.map(|v| FieldAttr::one_from_attrs("field", &v.attrs).map(|o| {
o.map(|f| f.value).unwrap_or_else(|| v.ident.clone().into())
}))
.collect::<Result<Vec<NameSource>>>()?;
.collect::<Result<Vec<Name>>>()?;
let variant_name = variant_name_sources.iter()
.map(|n| n.name())
.map(|n| n.as_str())
.collect::<Vec<_>>();
let builder = data.variants()

View File

@ -86,7 +86,10 @@ define_exported_paths! {
BorrowMut => ::std::borrow::BorrowMut,
Outcome => rocket::outcome::Outcome,
FromForm => rocket::form::FromForm,
FromRequest => rocket::request::FromRequest,
FromData => rocket::data::FromData,
FromSegments => rocket::request::FromSegments,
FromParam => rocket::request::FromParam,
Request => rocket::request::Request,
Response => rocket::response::Response,
Data => rocket::data::Data,

View File

@ -2,11 +2,7 @@ use quote::ToTokens;
use devise::{FromMeta, MetaItem, Result, ext::{Split2, PathExt, SpanDiagnosticExt}};
use crate::proc_macro2::TokenStream;
use crate::http::{self, ext::IntoOwned};
use crate::http::uri::{Path, Query};
use crate::attribute::segments::{parse_segments, parse_data_segment, Segment, Kind};
use crate::proc_macro_ext::StringLit;
use crate::http;
#[derive(Debug)]
pub struct ContentType(pub http::ContentType);
@ -17,31 +13,12 @@ pub struct Status(pub http::Status);
#[derive(Debug)]
pub struct MediaType(pub http::MediaType);
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub struct Method(pub http::Method);
#[derive(Debug)]
pub struct Origin(pub http::uri::Origin<'static>);
#[derive(Clone, Debug)]
pub struct DataSegment(pub Segment);
#[derive(Clone, Debug)]
pub struct Optional<T>(pub Option<T>);
impl FromMeta for StringLit {
fn from_meta(meta: &MetaItem) -> Result<Self> {
Ok(StringLit::new(String::from_meta(meta)?, meta.value_span()))
}
}
#[derive(Debug)]
pub struct RoutePath {
pub origin: Origin,
pub path: Vec<Segment>,
pub query: Option<Vec<Segment>>,
}
impl FromMeta for Status {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let num = usize::from_meta(meta)?;
@ -155,69 +132,6 @@ impl ToTokens for Method {
}
}
impl FromMeta for Origin {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let string = StringLit::from_meta(meta)?;
let uri = http::uri::Origin::parse_route(&string)
.map_err(|e| {
let span = string.subspan(e.index() + 1..(e.index() + 2));
span.error(format!("invalid route URI: {}", e))
.help("expected path in origin form: \"/path/<param>\"")
})?;
if !uri.is_normalized() {
let normalized = uri.clone().into_normalized();
return Err(string.span().error("paths cannot contain empty segments")
.note(format!("expected '{}', found '{}'", normalized, uri)));
}
Ok(Origin(uri.into_owned()))
}
}
impl FromMeta for DataSegment {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let string = StringLit::from_meta(meta)?;
let span = string.subspan(1..(string.len() + 1));
let segment = parse_data_segment(&string, span)?;
if segment.kind != Kind::Single {
return Err(span.error("malformed parameter")
.help("parameter must be of the form '<param>'"));
}
Ok(DataSegment(segment))
}
}
impl FromMeta for RoutePath {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let (origin, string) = (Origin::from_meta(meta)?, StringLit::from_meta(meta)?);
let path_span = string.subspan(1..origin.0.path().len() + 1);
let path = parse_segments::<Path>(origin.0.path(), path_span);
let query = origin.0.query()
.map(|q| {
let len_to_q = 1 + origin.0.path().len() + 1;
let end_of_q = len_to_q + q.len();
let query_span = string.subspan(len_to_q..end_of_q);
if q.starts_with('&') || q.contains("&&") || q.ends_with('&') {
// TODO: Show a help message with what's expected.
Err(query_span.error("query cannot contain empty segments").into())
} else {
parse_segments::<Query>(q, query_span)
}
}).transpose();
match (path, query) {
(Ok(path), Ok(query)) => Ok(RoutePath { origin, path, query }),
(Err(diag), Ok(_)) | (Ok(_), Err(diag)) => Err(diag.emit_head()),
(Err(d1), Err(d2)) => Err(d1.join(d2).emit_head())
}
}
}
impl<T: ToTokens> ToTokens for Optional<T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
use crate::exports::{_Some, _None};

View File

@ -66,13 +66,14 @@ mod attribute;
mod bang;
mod http_codegen;
mod syn_ext;
mod name;
use crate::http::Method;
use proc_macro::TokenStream;
use devise::{proc_macro2, syn};
static URI_MACRO_PREFIX: &str = "rocket_uri_macro_";
static ROCKET_PARAM_PREFIX: &str = "__rocket_param_";
static ROCKET_IDENT_PREFIX: &str = "__rocket_";
macro_rules! emit {
($tokens:expr) => ({
@ -118,13 +119,13 @@ macro_rules! route_attribute {
/// * [`options`] - `OPTIONS` specific route
/// * [`patch`] - `PATCH` specific route
///
/// Additionally, [`route`] allows the method and path to be explicitly
/// Additionally, [`route`] allows the method and uri to be explicitly
/// specified:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// #
/// #[route(GET, path = "/")]
/// #[route(GET, uri = "/")]
/// fn index() -> &'static str {
/// "Hello, world!"
/// }
@ -144,22 +145,22 @@ macro_rules! route_attribute {
/// The grammar for all method-specific route attributes is defined as:
///
/// ```text
/// route := '"' path ('?' query)? '"' (',' parameter)*
/// route := '"' uri ('?' query)? '"' (',' parameter)*
///
/// path := ('/' segment)*
/// uri := ('/' segment)*
///
/// query := segment ('&' segment)*
///
/// segment := URI_SEG
/// | SINGLE_PARAM
/// | MULTI_PARAM
/// | TRAILING_PARAM
///
/// parameter := 'rank' '=' INTEGER
/// | 'format' '=' '"' MEDIA_TYPE '"'
/// | 'data' '=' '"' SINGLE_PARAM '"'
///
/// SINGLE_PARAM := '<' IDENT '>'
/// MULTI_PARAM := '<' IDENT '..>'
/// TRAILING_PARAM := '<' IDENT '..>'
///
/// URI_SEG := valid, non-percent-encoded HTTP URI segment
/// MEDIA_TYPE := valid HTTP media type or known shorthand
@ -171,13 +172,13 @@ macro_rules! route_attribute {
/// The generic route attribute is defined as:
///
/// ```text
/// generic-route := METHOD ',' 'path' '=' route
/// generic-route := METHOD ',' 'uri' '=' route
/// ```
///
/// # Typing Requirements
///
/// Every identifier that appears in a dynamic parameter (`SINGLE_PARAM`
/// or `MULTI_PARAM`) must appear as an argument to the function. For
/// or `TRAILING_PARAM`) must appear as an argument to the function. For
/// example, the following route requires the decorated function to have
/// the arguments `foo`, `baz`, `msg`, `rest`, and `form`:
///
@ -193,7 +194,7 @@ macro_rules! route_attribute {
/// The type of each function argument corresponding to a dynamic
/// parameter is required to implement one of Rocket's guard traits. The
/// exact trait that is required to be implemented depends on the kind
/// of dynamic parameter (`SINGLE` or `MULTI`) and where in the route
/// of dynamic parameter (`SINGLE` or `TRAILING`) and where in the route
/// attribute the parameter appears. The table below summarizes trait
/// requirements:
///
@ -201,7 +202,7 @@ macro_rules! route_attribute {
/// |----------|-------------|-------------------|
/// | path | `<ident>` | [`FromParam`] |
/// | path | `<ident..>` | [`FromSegments`] |
/// | query | `<ident>` | [`FromFormField`] |
/// | query | `<ident>` | [`FromForm`] |
/// | query | `<ident..>` | [`FromFrom`] |
/// | data | `<ident>` | [`FromData`] |
///
@ -238,13 +239,10 @@ macro_rules! route_attribute {
/// `Failure`. See [`FromRequest` Outcomes] for further
/// detail.
///
/// 2. Path and query parameters from left to right as declared
/// in the function argument list.
/// 2. Path and query guards in an unspecified order. If a path
/// or query guard fails, the request is forwarded.
///
/// If a path or query parameter guard fails, the request is
/// forwarded.
///
/// 3. Data parameter, if any.
/// 3. Data guard, if any.
///
/// If a data guard fails, the request is forwarded if the
/// [`Outcome`] is `Forward` or failed if the [`Outcome`] is
@ -359,17 +357,17 @@ pub fn catch(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn async_test(args: TokenStream, input: TokenStream) -> TokenStream {
emit!(attribute::async_entry::async_test_attribute(args, input))
emit!(attribute::entry::async_test_attribute(args, input))
}
#[proc_macro_attribute]
pub fn main(args: TokenStream, input: TokenStream) -> TokenStream {
emit!(attribute::async_entry::main_attribute(args, input))
emit!(attribute::entry::main_attribute(args, input))
}
#[proc_macro_attribute]
pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream {
emit!(attribute::async_entry::launch_attribute(args, input))
emit!(attribute::entry::launch_attribute(args, input))
}
/// Derive for the [`FromFormField`] trait.

101
core/codegen/src/name.rs Normal file
View File

@ -0,0 +1,101 @@
use crate::syn::{self, Ident, ext::IdentExt};
use crate::proc_macro2::Span;
/// A "name" read by codegen, which may or may not be a valid identifier. A
/// `Name` is typically constructed indirectly via FromMeta, or From<Ident> or
/// directly from a string via `Name::new()`.
///
/// Some "names" in Rocket include:
/// * Dynamic parameter: `name` in `<name>`
/// * Renamed fields: `foo` in #[field(name = "foo")].
///
/// `Name` implements Hash, PartialEq, and Eq, and additionally PartialEq<S> for
/// all types `S: AsStr<str>`. These implementations all compare the value of
/// `name()` only.
#[derive(Debug, Clone)]
pub struct Name {
value: String,
span: Span,
}
impl Name {
/// Creates a new `Name` from the string `name` and span `span`. If
/// `name` is a valid ident, the ident is stored as well.
pub fn new<S: Into<String>>(name: S, span: Span) -> Self {
Name { value: name.into(), span }
}
/// Returns the name as a string. Notably, if `self` was constructed from an
/// Ident this method returns a name *without* an `r#` prefix.
pub fn as_str(&self) -> &str {
&self.value
}
pub fn span(&self) -> Span {
self.span
}
}
impl devise::FromMeta for Name {
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
use devise::ext::SpanDiagnosticExt;
if let syn::Lit::Str(s) = meta.lit()? {
return Ok(Name::new(s.value(), s.span()));
}
Err(meta.value_span().error("invalid value: expected string literal"))
}
}
impl quote::ToTokens for Name {
fn to_tokens(&self, tokens: &mut devise::proc_macro2::TokenStream) {
self.as_str().to_tokens(tokens)
}
}
impl From<&Ident> for Name {
fn from(ident: &Ident) -> Self {
Name::new(ident.unraw().to_string(), ident.span())
}
}
impl From<Ident> for Name {
fn from(ident: Ident) -> Self {
Name::new(ident.unraw().to_string(), ident.span())
}
}
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::hash::Hash for Name {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
self.as_str().hash(hasher)
}
}
impl std::ops::Deref for Name {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Eq for Name { }
impl<S: AsRef<str> + ?Sized> PartialEq<S> for Name {
fn eq(&self, other: &S) -> bool {
self.as_str() == other.as_ref()
}
}
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}

View File

@ -4,10 +4,6 @@ use devise::Diagnostic;
use crate::proc_macro2::{Span, Literal};
pub type PResult<T> = std::result::Result<T, Diagnostic>;
pub type DResult<T> = std::result::Result<T, Diagnostics>;
// An experiment.
pub struct Diagnostics(Vec<Diagnostic>);
@ -20,11 +16,6 @@ impl Diagnostics {
self.0.push(diag);
}
pub fn join(mut self, mut diags: Diagnostics) -> Self {
self.0.append(&mut diags.0);
self
}
pub fn emit_head(self) -> Diagnostic {
let mut iter = self.0.into_iter();
let mut last = iter.next().expect("Diagnostic::emit_head empty");
@ -37,19 +28,12 @@ impl Diagnostics {
last
}
pub fn head_err_or<T>(self, ok: T) -> PResult<T> {
pub fn head_err_or<T>(self, ok: T) -> devise::Result<T> {
match self.0.is_empty() {
true => Ok(ok),
false => Err(self.emit_head())
}
}
pub fn err_or<T>(self, ok: T) -> DResult<T> {
match self.0.is_empty() {
true => Ok(ok),
false => Err(self)
}
}
}
impl From<Diagnostic> for Diagnostics {
@ -64,18 +48,8 @@ impl From<Vec<Diagnostic>> for Diagnostics {
}
}
use std::ops::Deref;
pub struct StringLit(pub String, pub Literal);
impl Deref for StringLit {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl StringLit {
pub fn new<S: Into<String>>(string: S, span: Span) -> Self {
let string = string.into();
@ -95,3 +69,17 @@ impl StringLit {
self.1.subspan(range).unwrap_or_else(|| self.span())
}
}
impl devise::FromMeta for StringLit {
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
Ok(StringLit::new(String::from_meta(meta)?, meta.value_span()))
}
}
impl std::ops::Deref for StringLit {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}

View File

@ -1,7 +1,5 @@
//! Extensions to `syn` types.
use devise::ext::SpanDiagnosticExt;
use crate::syn::{self, Ident, ext::IdentExt as _};
use crate::proc_macro2::Span;
@ -9,6 +7,20 @@ pub trait IdentExt {
fn prepend(&self, string: &str) -> syn::Ident;
fn append(&self, string: &str) -> syn::Ident;
fn with_span(self, span: Span) -> syn::Ident;
fn rocketized(&self) -> syn::Ident;
}
pub trait ReturnTypeExt {
fn ty(&self) -> Option<&syn::Type>;
}
pub trait TokenStreamExt {
fn respanned(&self, span: crate::proc_macro2::Span) -> Self;
}
pub trait FnArgExt {
fn typed(&self) -> Option<(&syn::Ident, &syn::Type)>;
fn wild(&self) -> Option<&syn::PatWild>;
}
impl IdentExt for syn::Ident {
@ -24,10 +36,10 @@ impl IdentExt for syn::Ident {
self.set_span(span);
self
}
}
pub trait ReturnTypeExt {
fn ty(&self) -> Option<&syn::Type>;
fn rocketized(&self) -> syn::Ident {
self.prepend(crate::ROCKET_IDENT_PREFIX)
}
}
impl ReturnTypeExt for syn::ReturnType {
@ -39,10 +51,6 @@ impl ReturnTypeExt for syn::ReturnType {
}
}
pub trait TokenStreamExt {
fn respanned(&self, span: crate::proc_macro2::Span) -> Self;
}
impl TokenStreamExt for crate::proc_macro2::TokenStream {
fn respanned(&self, span: crate::proc_macro2::Span) -> Self {
self.clone().into_iter().map(|mut token| {
@ -52,82 +60,24 @@ impl TokenStreamExt for crate::proc_macro2::TokenStream {
}
}
/// Represents the source of a name read by codegen, which may or may not be a
/// valid identifier. A `NameSource` is typically constructed indirectly via
/// FromMeta, or From<Ident> or directly from a string via `NameSource::new()`.
///
/// NameSource implements Hash, PartialEq, and Eq, and additionally PartialEq<S>
/// for all types `S: AsStr<str>`. These implementations all compare the value
/// of `name()` only.
#[derive(Debug, Clone)]
pub struct NameSource {
name: String,
ident: Option<Ident>,
}
impl NameSource {
/// Creates a new `NameSource` from the string `name` and span `span`. If
/// `name` is a valid ident, the ident is stored as well.
pub fn new<S: AsRef<str>>(name: S, span: crate::proc_macro2::Span) -> Self {
let name = name.as_ref();
syn::parse_str::<Ident>(name)
.map(|mut ident| { ident.set_span(span); ident })
.map(|ident| NameSource::from(ident))
.unwrap_or_else(|_| NameSource { name: name.into(), ident: None })
}
/// Returns the name as a string. Notably, if `self` was constructed from an
/// Ident this method returns a name *without* an `r#` prefix.
pub fn name(&self) -> &str {
&self.name
}
/// Returns the Ident corresponding to `self`, if any, otherwise panics. If
/// `self` was constructed from an `Ident`, this never panics. Otherwise,
/// panics if the string `self` was constructed from was not a valid ident.
pub fn ident(&self) -> &Ident {
self.ident.as_ref().expect("ident from namesource")
}
}
impl devise::FromMeta for NameSource {
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
if let syn::Lit::Str(s) = meta.lit()? {
return Ok(NameSource::new(s.value(), s.span()));
impl FnArgExt for syn::FnArg {
fn typed(&self) -> Option<(&Ident, &syn::Type)> {
match self {
syn::FnArg::Typed(arg) => match *arg.pat {
syn::Pat::Ident(ref pat) => Some((&pat.ident, &arg.ty)),
_ => None
}
_ => None,
}
}
Err(meta.value_span().error("invalid value: expected string literal"))
}
}
impl From<Ident> for NameSource {
fn from(ident: Ident) -> Self {
Self { name: ident.unraw().to_string(), ident: Some(ident), }
}
}
impl std::hash::Hash for NameSource {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
self.name().hash(hasher)
}
}
impl AsRef<str> for NameSource {
fn as_ref(&self) -> &str {
self.name()
}
}
impl Eq for NameSource { }
impl<S: AsRef<str>> PartialEq<S> for NameSource {
fn eq(&self, other: &S) -> bool {
self.name == other.as_ref()
}
}
impl std::fmt::Display for NameSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.name().fmt(f)
fn wild(&self) -> Option<&syn::PatWild> {
match self {
syn::FnArg::Typed(arg) => match *arg.pat {
syn::Pat::Wild(ref pat) => Some(pat),
_ => None
}
_ => None,
}
}
}

View File

@ -55,7 +55,7 @@ fn post1(
#[route(
POST,
path = "/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>",
uri = "/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>",
format = "json",
data = "<simple>",
rank = 138

View File

@ -8,6 +8,18 @@ use rocket::http::CookieJar;
use rocket::http::uri::{FromUriParam, Query};
use rocket::form::{Form, error::{Errors, ErrorKind}};
macro_rules! assert_uri_eq {
($($uri:expr => $expected:expr,)+) => {
$(
let actual = $uri;
let expected = rocket::http::uri::Origin::parse($expected).expect("valid origin URI");
if actual != expected {
panic!("URI mismatch: got {}, expected {}", actual, expected);
}
)+
};
}
#[derive(FromForm, UriDisplayQuery)]
struct User<'a> {
name: &'a str,
@ -62,13 +74,13 @@ fn guard_3(id: i32, name: String, cookies: &CookieJar<'_>) { }
#[post("/<id>", data = "<form>")]
fn no_uri_display_okay(id: i32, form: Form<Second>) { }
#[post("/name/<name>?<foo>&bar=10&<bar>&<query..>", data = "<user>", rank = 2)]
#[post("/name/<name>?<foo>&type=10&<type>&<query..>", data = "<user>", rank = 2)]
fn complex<'r>(
foo: usize,
name: &str,
query: User<'r>,
user: Form<User<'r>>,
bar: &str,
r#type: &str,
cookies: &CookieJar<'_>
) { }
@ -81,12 +93,6 @@ fn param_and_segments(path: PathBuf, id: usize) { }
#[post("/a/<id>/then/<path..>")]
fn guarded_segments(cookies: &CookieJar<'_>, path: PathBuf, id: usize) { }
macro_rules! assert_uri_eq {
($($uri:expr => $expected:expr,)+) => {
$(assert_eq!($uri, rocket::http::uri::Origin::parse($expected).expect("valid origin URI"));)+
};
}
#[test]
fn check_simple_unnamed() {
assert_uri_eq! {
@ -236,42 +242,42 @@ fn check_with_segments() {
fn check_complex() {
assert_uri_eq! {
uri!(complex: "no idea", 10, "high", ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: "Bob", 248, "?", User { name: "Robert".into(), nickname: "Bob".into() }) =>
"/name/Bob?foo=248&bar=10&bar=%3F&name=Robert&nickname=Bob",
"/name/Bob?foo=248&type=10&type=%3F&name=Robert&nickname=Bob",
uri!(complex: "Bob", 248, "a a", &User { name: "Robert".into(), nickname: "B".into() }) =>
"/name/Bob?foo=248&bar=10&bar=a%20a&name=Robert&nickname=B",
"/name/Bob?foo=248&type=10&type=a%20a&name=Robert&nickname=B",
uri!(complex: "no idea", 248, "", &User { name: "A B".into(), nickname: "A".into() }) =>
"/name/no%20idea?foo=248&bar=10&bar=&name=A%20B&nickname=A",
"/name/no%20idea?foo=248&type=10&type=&name=A%20B&nickname=A",
uri!(complex: "hi", 3, "b", &User { name: "A B C".into(), nickname: "a b".into() }) =>
"/name/hi?foo=3&bar=10&bar=b&name=A%20B%20C&nickname=a%20b",
uri!(complex: name = "no idea", foo = 10, bar = "high", query = ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: foo = 10, name = "no idea", bar = "high", query = ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", bar = "high", ) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", bar = "high") =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = *&("A B C", "a c"), foo = 10, name = "no idea", bar = "high") =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: foo = 3, name = "hi", bar = "b",
"/name/hi?foo=3&type=10&type=b&name=A%20B%20C&nickname=a%20b",
uri!(complex: name = "no idea", foo = 10, r#type = "high", query = ("A B C", "a c")) =>
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: foo = 10, name = "no idea", r#type = "high", query = ("A B C", "a c")) =>
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", r#type = "high", ) =>
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", r#type = "high") =>
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = *&("A B C", "a c"), foo = 10, name = "no idea", r#type = "high") =>
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: foo = 3, name = "hi", r#type = "b",
query = &User { name: "A B C".into(), nickname: "a b".into() }) =>
"/name/hi?foo=3&bar=10&bar=b&name=A%20B%20C&nickname=a%20b",
"/name/hi?foo=3&type=10&type=b&name=A%20B%20C&nickname=a%20b",
uri!(complex: query = &User { name: "A B C".into(), nickname: "a b".into() },
foo = 3, name = "hi", bar = "b") =>
"/name/hi?foo=3&bar=10&bar=b&name=A%20B%20C&nickname=a%20b",
foo = 3, name = "hi", r#type = "b") =>
"/name/hi?foo=3&type=10&type=b&name=A%20B%20C&nickname=a%20b",
}
// Ensure variables are correctly processed.
let user = User { name: "Robert".into(), nickname: "Bob".into() };
assert_uri_eq! {
uri!(complex: "complex", 0, "high", &user) =>
"/name/complex?foo=0&bar=10&bar=high&name=Robert&nickname=Bob",
"/name/complex?foo=0&type=10&type=high&name=Robert&nickname=Bob",
uri!(complex: "complex", 0, "high", &user) =>
"/name/complex?foo=0&bar=10&bar=high&name=Robert&nickname=Bob",
"/name/complex?foo=0&type=10&type=high&name=Robert&nickname=Bob",
uri!(complex: "complex", 0, "high", user) =>
"/name/complex?foo=0&bar=10&bar=high&name=Robert&nickname=Bob",
"/name/complex?foo=0&type=10&type=high&name=Robert&nickname=Bob",
}
}
@ -434,3 +440,39 @@ fn test_optional_uri_parameters() {
) => "/10/hi%20there",
}
}
#[test]
fn test_simple_ignored() {
#[get("/<_>")] fn ignore_one() { }
assert_uri_eq! {
uri!(ignore_one: 100) => "/100",
uri!(ignore_one: "hello") => "/hello",
uri!(ignore_one: "cats r us") => "/cats%20r%20us",
}
#[get("/<_>/<_>")] fn ignore_two() { }
assert_uri_eq! {
uri!(ignore_two: 100, "boop") => "/100/boop",
uri!(ignore_two: &"hi", "bop") => "/hi/bop",
}
#[get("/<_>/foo/<_>")] fn ignore_inner_two() { }
#[get("/hi/<_>/foo")] fn ignore_inner_one_a() { }
#[get("/hey/hi/<_>/foo/<_>")] fn ignore_inner_two_b() { }
assert_uri_eq! {
uri!(ignore_inner_two: 100, "boop") => "/100/foo/boop",
uri!(ignore_inner_one_a: "!?") => "/hi/!%3F/foo",
uri!(ignore_inner_two_b: &mut 5, "boo") => "/hey/hi/5/foo/boo",
}
#[get("/<_>/foo/<_>?hi")] fn ignore_with_q() { }
#[get("/hi/<_>/foo/<_>?hi&<hey>")] fn ignore_with_q2(hey: Option<usize>) { }
#[get("/hi/<_>/foo/<_>?<hi>&<hey>")] fn ignore_with_q3(hi: &str, hey: &str) { }
assert_uri_eq! {
uri!(ignore_with_q: 100, "boop") => "/100/foo/boop?hi",
uri!(ignore_with_q2: "!?", "bop", Some(3usize)) => "/hi/!%3F/foo/bop?hi&hey=3",
uri!(ignore_with_q3: &mut 5, "boo", "hi b", "ho") => "/hi/5/foo/boo?hi=hi%20b&hey=ho",
}
}

View File

@ -1,8 +1,8 @@
error: parameter must be named
--> $DIR/bad-ignored-segments.rs:6:11
--> $DIR/bad-ignored-segments.rs:6:12
|
6 | #[get("/c?<_>")]
| ^^^
| ^
|
= help: use a name such as `_guard` or `_param`

View File

@ -14,7 +14,7 @@ error: expected `fn`
|
= help: `#[catch]` can only be used on functions
error: expected integer or identifier, found string literal
error: expected integer or `default`, found string literal
--> $DIR/catch.rs:11:9
|
11 | #[catch("404")]

View File

@ -1,4 +1,4 @@
error: missing expected parameter: `path`
error: missing expected parameter: `uri`
--> $DIR/route-attribute-general-syntax.rs:4:1
|
4 | #[get()]
@ -74,7 +74,7 @@ error: handler arguments cannot be ignored
39 | fn c1(_: usize) {}
| ^^^^^^^^
|
= help: all handler arguments must be of the form: `ident: Type`
= note: handler arguments must be of the form: `ident: Type`
error: invalid value: expected string literal
--> $DIR/route-attribute-general-syntax.rs:43:7

View File

@ -4,7 +4,7 @@ error: invalid route URI: expected token '/' but found 'a' at index 0
5 | #[get("a")]
| ^
|
= help: expected path in origin form: "/path/<param>"
= help: expected URI in origin form: "/path/<param>"
error: invalid route URI: unexpected EOF: expected token '/' at index 0
--> $DIR/route-path-bad-syntax.rs:8:8
@ -12,7 +12,7 @@ error: invalid route URI: unexpected EOF: expected token '/' at index 0
8 | #[get("")]
| ^
|
= help: expected path in origin form: "/path/<param>"
= help: expected URI in origin form: "/path/<param>"
error: invalid route URI: expected token '/' but found 'a' at index 0
--> $DIR/route-path-bad-syntax.rs:11:8
@ -20,41 +20,45 @@ error: invalid route URI: expected token '/' but found 'a' at index 0
11 | #[get("a/b/c")]
| ^
|
= help: expected path in origin form: "/path/<param>"
= help: expected URI in origin form: "/path/<param>"
error: paths cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:14:7
error: route URIs cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:14:10
|
14 | #[get("/a///b")]
| ^^^^^^^^
| ^^
|
= note: expected '/a/b', found '/a///b'
= note: expected "/a/b", found "/a///b"
error: query cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:17:10
error: route URIs cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:17:13
|
17 | #[get("/?bat&&")]
| ^^^^^
| ^^
|
= note: expected "/?bat", found "/?bat&&"
error: query cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:20:10
error: route URIs cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:20:13
|
20 | #[get("/?bat&&")]
| ^^^^^
| ^^
|
= note: expected "/?bat", found "/?bat&&"
error: paths cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:23:7
error: route URIs cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:23:12
|
23 | #[get("/a/b//")]
| ^^^^^^^^
| ^^
|
= note: expected '/a/b', found '/a/b//'
= note: expected "/a/b", found "/a/b//"
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:42:9
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:42:10
|
42 | #[get("/<name>")]
| ^^^^^^
| ^^^^
|
note: expected argument named `name` here
--> $DIR/route-path-bad-syntax.rs:43:6
@ -62,11 +66,11 @@ note: expected argument named `name` here
43 | fn h0(_name: usize) {}
| ^^^^^^^^^^^^^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:45:11
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:45:12
|
45 | #[get("/a?<r>")]
| ^^^
| ^
|
note: expected argument named `r` here
--> $DIR/route-path-bad-syntax.rs:46:6
@ -74,11 +78,11 @@ note: expected argument named `r` here
46 | fn h1() {}
| ^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:48:22
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:48:23
|
48 | #[post("/a", data = "<test>")]
| ^^^^^^
| ^^^^
|
note: expected argument named `test` here
--> $DIR/route-path-bad-syntax.rs:49:6
@ -86,11 +90,11 @@ note: expected argument named `test` here
49 | fn h2() {}
| ^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:51:9
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:51:10
|
51 | #[get("/<_r>")]
| ^^^^
| ^^
|
note: expected argument named `_r` here
--> $DIR/route-path-bad-syntax.rs:52:6
@ -98,11 +102,11 @@ note: expected argument named `_r` here
52 | fn h3() {}
| ^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:54:9
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:54:10
|
54 | #[get("/<_r>/<b>")]
| ^^^^
| ^^
|
note: expected argument named `_r` here
--> $DIR/route-path-bad-syntax.rs:55:6
@ -110,11 +114,11 @@ note: expected argument named `_r` here
55 | fn h4() {}
| ^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:54:14
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:54:15
|
54 | #[get("/<_r>/<b>")]
| ^^^
| ^
|
note: expected argument named `b` here
--> $DIR/route-path-bad-syntax.rs:55:6
@ -122,69 +126,82 @@ note: expected argument named `b` here
55 | fn h4() {}
| ^^
error: `foo_.` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:60:9
error: invalid identifier: `foo_.`
--> $DIR/route-path-bad-syntax.rs:60:10
|
60 | #[get("/<foo_.>")]
| ^^^^^^^
| ^^^^^
|
= help: parameter names must be valid identifiers
= help: dynamic parameters must be valid identifiers
= help: did you mean `<foo_>`?
error: `foo*` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:63:9
error: invalid identifier: `foo*`
--> $DIR/route-path-bad-syntax.rs:63:10
|
63 | #[get("/<foo*>")]
| ^^^^^^
| ^^^^
|
= help: parameter names must be valid identifiers
= help: dynamic parameters must be valid identifiers
= help: did you mean `<foo>`?
error: `!` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:66:9
error: invalid identifier: `!`
--> $DIR/route-path-bad-syntax.rs:66:10
|
66 | #[get("/<!>")]
| ^^^
| ^
|
= help: parameter names must be valid identifiers
= help: dynamic parameters must be valid identifiers
= help: did you mean `<param>`?
error: `name>:<id` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:69:9
error: invalid identifier: `name>:<id`
--> $DIR/route-path-bad-syntax.rs:69:10
|
69 | #[get("/<name>:<id>")]
| ^^^^^^^^^^^
| ^^^^^^^^^
|
= help: parameter names must be valid identifiers
= help: dynamic parameters must be valid identifiers
= help: did you mean `<nameid>`?
error: malformed parameter
error: unexpected static parameter
--> $DIR/route-path-bad-syntax.rs:74:20
|
74 | #[get("/", data = "foo")]
| ^^^
|
= help: parameter must be of the form '<param>'
= help: parameter must be dynamic: `<foo>`
error: malformed parameter
error: parameter cannot be trailing
--> $DIR/route-path-bad-syntax.rs:77:20
|
77 | #[get("/", data = "<foo..>")]
| ^^^^^^^
|
= help: parameter must be of the form '<param>'
= help: did you mean `<foo>`?
error: parameter is missing a closing bracket
warning: `segment` starts with `<` but does not end with `>`
--> $DIR/route-path-bad-syntax.rs:80:20
|
80 | #[get("/", data = "<foo")]
| ^^^^
|
= help: did you mean '<foo>'?
= help: perhaps you meant the dynamic parameter `<foo>`?
error: `test ` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:83:20
error: unexpected static parameter
--> $DIR/route-path-bad-syntax.rs:80:20
|
80 | #[get("/", data = "<foo")]
| ^^^^
|
= help: parameter must be dynamic: `<foo>`
error: invalid identifier: `test `
--> $DIR/route-path-bad-syntax.rs:83:21
|
83 | #[get("/", data = "<test >")]
| ^^^^^^^
| ^^^^^
|
= help: parameter names must be valid identifiers
= help: dynamic parameters must be valid identifiers
= help: did you mean `<test>`?
error: handler arguments cannot be ignored
--> $DIR/route-path-bad-syntax.rs:89:7
@ -192,37 +209,34 @@ error: handler arguments cannot be ignored
89 | fn k0(_: usize) {}
| ^^^^^^^^
|
= help: all handler arguments must be of the form: `ident: Type`
= note: handler arguments must be of the form: `ident: Type`
error: parameter names cannot be empty
error: parameters cannot be empty
--> $DIR/route-path-bad-syntax.rs:93:9
|
93 | #[get("/<>")]
| ^^
error: malformed parameter or identifier
warning: `segment` starts with `<` but does not end with `>`
--> $DIR/route-path-bad-syntax.rs:96:9
|
96 | #[get("/<id><")]
| ^^^^^
|
= help: parameters must be of the form '<param>'
= help: identifiers cannot contain '<' or '>'
= help: perhaps you meant the dynamic parameter `<id>`?
error: malformed parameter or identifier
warning: `segment` starts with `<` but does not end with `>`
--> $DIR/route-path-bad-syntax.rs:99:9
|
99 | #[get("/<<<<id><")]
| ^^^^^^^^
|
= help: parameters must be of the form '<param>'
= help: identifiers cannot contain '<' or '>'
= help: perhaps you meant the dynamic parameter `<id>`?
error: malformed parameter or identifier
warning: `segment` starts with `<` but does not end with `>`
--> $DIR/route-path-bad-syntax.rs:102:9
|
102 | #[get("/<>name><")]
| ^^^^^^^^
|
= help: parameters must be of the form '<param>'
= help: identifiers cannot contain '<' or '>'
= help: perhaps you meant the dynamic parameter `<name>`?

View File

@ -1,16 +1,16 @@
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:6:7
--> $DIR/route-type-errors.rs:6:12
|
6 | fn f0(foo: Q) {}
| ^^^^^^ the trait `FromParam<'_>` is not implemented for `Q`
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
= note: required by `from_param`
error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied
--> $DIR/route-type-errors.rs:9:7
--> $DIR/route-type-errors.rs:9:12
|
9 | fn f1(foo: Q) {}
| ^^^^^^ the trait `FromSegments<'_>` is not implemented for `Q`
| ^ the trait `FromSegments<'_>` is not implemented for `Q`
|
= note: required by `from_segments`
@ -31,10 +31,10 @@ error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied
= note: required because of the requirements on the impl of `FromForm<'_>` for `Q`
error[E0277]: the trait bound `Q: FromData<'_>` is not satisfied
--> $DIR/route-type-errors.rs:18:7
--> $DIR/route-type-errors.rs:18:12
|
18 | fn f4(foo: Q) {}
| ^^^^^^ the trait `FromData<'_>` is not implemented for `Q`
| ^ the trait `FromData<'_>` is not implemented for `Q`
|
::: $WORKSPACE/core/lib/src/data/from_data.rs
|
@ -42,10 +42,10 @@ error[E0277]: the trait bound `Q: FromData<'_>` is not satisfied
| -- required by this bound in `rocket::data::FromData::from_data`
error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
--> $DIR/route-type-errors.rs:21:7
--> $DIR/route-type-errors.rs:21:10
|
21 | fn f5(a: Q, foo: Q) {}
| ^^^^ the trait `FromRequest<'_, '_>` is not implemented for `Q`
| ^ the trait `FromRequest<'_, '_>` is not implemented for `Q`
|
::: $WORKSPACE/core/lib/src/request/from_request.rs
|
@ -53,18 +53,18 @@ error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
| -- required by this bound in `from_request`
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:21:13
--> $DIR/route-type-errors.rs:21:18
|
21 | fn f5(a: Q, foo: Q) {}
| ^^^^^^ the trait `FromParam<'_>` is not implemented for `Q`
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
= note: required by `from_param`
error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
--> $DIR/route-type-errors.rs:24:7
--> $DIR/route-type-errors.rs:24:10
|
24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {}
| ^^^^ the trait `FromRequest<'_, '_>` is not implemented for `Q`
| ^ the trait `FromRequest<'_, '_>` is not implemented for `Q`
|
::: $WORKSPACE/core/lib/src/request/from_request.rs
|
@ -72,17 +72,17 @@ error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
| -- required by this bound in `from_request`
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:24:13
--> $DIR/route-type-errors.rs:24:18
|
24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {}
| ^^^^^^ the trait `FromParam<'_>` is not implemented for `Q`
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
= note: required by `from_param`
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:24:34
--> $DIR/route-type-errors.rs:24:39
|
24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {}
| ^^^^^^ the trait `FromParam<'_>` is not implemented for `Q`
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
= note: required by `from_param`

View File

@ -70,11 +70,11 @@ error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::u
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, Result<_, _>>` for `Result<std::string::String, &str>`
= note: required by `from_uri_param`
error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfied
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:58:20
|
58 | uri!(simple_q: "hi");
| ^^^^ the trait `FromUriParam<Query, &str>` is not implemented for `isize`
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
= help: the following implementations were found:
<isize as FromUriParam<P, &'x isize>>
@ -82,11 +82,11 @@ error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfie
<isize as FromUriParam<P, isize>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfied
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:60:25
|
60 | uri!(simple_q: id = "hi");
| ^^^^ the trait `FromUriParam<Query, &str>` is not implemented for `isize`
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
= help: the following implementations were found:
<isize as FromUriParam<P, &'x isize>>
@ -94,48 +94,48 @@ error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfie
<isize as FromUriParam<P, isize>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:62:24
|
62 | uri!(other_q: 100, S);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:64:26
|
64 | uri!(other_q: rest = S, id = 100);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: Ignorable<Query>` is not satisfied
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:66:26
|
66 | uri!(other_q: rest = _, id = 100);
| ^ the trait `Ignorable<Query>` is not implemented for `S`
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `S`
|
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
| ------------ required by this bound in `assert_ignorable`
error[E0277]: the trait bound `usize: Ignorable<Query>` is not satisfied
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:68:34
|
68 | uri!(other_q: rest = S, id = _);
| ^ the trait `Ignorable<Query>` is not implemented for `usize`
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `usize`
|
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
| ------------ required by this bound in `assert_ignorable`
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:68:26
|
68 | uri!(other_q: rest = S, id = _);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`

View File

@ -1,238 +1,274 @@
error: path parameters cannot be ignored
--> $DIR/typed-uris-bad-params.rs:55:37
error: expected identifier
--> $DIR/typed-uris-bad-params.rs:62:19
|
55 | uri!(optionals: id = 10, name = _);
62 | uri!(ignored: _ = 10);
| ^
error: expected 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:68:19
|
68 | uri!(ignored: 10, "10");
| ^^^^^^^^
|
= note: route `ignored` has uri "/<_>"
error: expected unnamed arguments due to ignored parameters
--> $DIR/typed-uris-bad-params.rs:66:19
|
66 | uri!(ignored: num = 10);
| ^^^^^^^^
|
= note: uri for route `ignored` ignores path parameters: "/<_>"
error: expected 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:64:19
|
64 | uri!(ignored: 10, 20);
| ^^^^^^
|
= note: route `ignored` has uri "/<_>"
error: path parameters cannot be ignored
--> $DIR/typed-uris-bad-params.rs:60:19
|
60 | uri!(ignored: _);
| ^
error: path parameters cannot be ignored
--> $DIR/typed-uris-bad-params.rs:58:37
|
58 | uri!(optionals: id = 10, name = _);
| ^
error: path parameters cannot be ignored
--> $DIR/typed-uris-bad-params.rs:53:26
--> $DIR/typed-uris-bad-params.rs:56:26
|
53 | uri!(optionals: id = _, name = "bob".into());
56 | uri!(optionals: id = _, name = "bob".into());
| ^
error: invalid parameters for `has_two` route uri
--> $DIR/typed-uris-bad-params.rs:51:19
--> $DIR/typed-uris-bad-params.rs:54:19
|
51 | uri!(has_two: id = 100, cookies = "hi");
54 | uri!(has_two: id = 100, cookies = "hi");
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32, name: String
= help: missing parameter: `name`
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:51:29
--> $DIR/typed-uris-bad-params.rs:54:29
|
51 | uri!(has_two: id = 100, cookies = "hi");
54 | uri!(has_two: id = 100, cookies = "hi");
| ^^^^^^^
error: invalid parameters for `has_two` route uri
--> $DIR/typed-uris-bad-params.rs:49:19
--> $DIR/typed-uris-bad-params.rs:52:19
|
49 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32, name: String
= help: missing parameter: `name`
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:49:19
--> $DIR/typed-uris-bad-params.rs:52:19
|
49 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
| ^^^^^^^
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:49:45
--> $DIR/typed-uris-bad-params.rs:52:45
|
49 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
| ^^ ^^
error: invalid parameters for `has_two` route uri
--> $DIR/typed-uris-bad-params.rs:47:19
--> $DIR/typed-uris-bad-params.rs:50:19
|
47 | uri!(has_two: name = "hi");
50 | uri!(has_two: name = "hi");
| ^^^^^^^^^^^
|
= note: uri parameters are: id: i32, name: String
= help: missing parameter: `id`
error: invalid parameters for `has_two` route uri
--> $DIR/typed-uris-bad-params.rs:45:19
--> $DIR/typed-uris-bad-params.rs:48:19
|
45 | uri!(has_two: id = 100, id = 100, );
48 | uri!(has_two: id = 100, id = 100, );
| ^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32, name: String
= help: missing parameter: `name`
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:45:29
--> $DIR/typed-uris-bad-params.rs:48:29
|
45 | uri!(has_two: id = 100, id = 100, );
48 | uri!(has_two: id = 100, id = 100, );
| ^^
error: invalid parameters for `has_one_guarded` route uri
--> $DIR/typed-uris-bad-params.rs:43:27
--> $DIR/typed-uris-bad-params.rs:46:27
|
43 | uri!(has_one_guarded: id = 100, cookies = "hi");
46 | uri!(has_one_guarded: id = 100, cookies = "hi");
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:43:37
--> $DIR/typed-uris-bad-params.rs:46:37
|
43 | uri!(has_one_guarded: id = 100, cookies = "hi");
46 | uri!(has_one_guarded: id = 100, cookies = "hi");
| ^^^^^^^
error: invalid parameters for `has_one_guarded` route uri
--> $DIR/typed-uris-bad-params.rs:41:27
--> $DIR/typed-uris-bad-params.rs:44:27
|
41 | uri!(has_one_guarded: cookies = "hi", id = 100);
44 | uri!(has_one_guarded: cookies = "hi", id = 100);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:41:27
--> $DIR/typed-uris-bad-params.rs:44:27
|
41 | uri!(has_one_guarded: cookies = "hi", id = 100);
44 | uri!(has_one_guarded: cookies = "hi", id = 100);
| ^^^^^^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:39:19
--> $DIR/typed-uris-bad-params.rs:42:19
|
39 | uri!(has_one: name = "hi");
42 | uri!(has_one: name = "hi");
| ^^^^^^^^^^^
|
= note: uri parameters are: id: i32
= help: missing parameter: `id`
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:39:19
--> $DIR/typed-uris-bad-params.rs:42:19
|
39 | uri!(has_one: name = "hi");
42 | uri!(has_one: name = "hi");
| ^^^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:37:19
--> $DIR/typed-uris-bad-params.rs:40:19
|
37 | uri!(has_one: id = 100, id = 100, );
40 | uri!(has_one: id = 100, id = 100, );
| ^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:37:29
--> $DIR/typed-uris-bad-params.rs:40:29
|
37 | uri!(has_one: id = 100, id = 100, );
40 | uri!(has_one: id = 100, id = 100, );
| ^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:35:19
--> $DIR/typed-uris-bad-params.rs:38:19
|
35 | uri!(has_one: id = 100, id = 100);
38 | uri!(has_one: id = 100, id = 100);
| ^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:35:29
--> $DIR/typed-uris-bad-params.rs:38:29
|
35 | uri!(has_one: id = 100, id = 100);
38 | uri!(has_one: id = 100, id = 100);
| ^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:33:19
--> $DIR/typed-uris-bad-params.rs:36:19
|
33 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameters: `name`, `age`
--> $DIR/typed-uris-bad-params.rs:33:19
--> $DIR/typed-uris-bad-params.rs:36:19
|
33 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
| ^^^^ ^^^
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:33:51
--> $DIR/typed-uris-bad-params.rs:36:51
|
33 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
| ^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:31:19
--> $DIR/typed-uris-bad-params.rs:34:19
|
31 | uri!(has_one: name = 100, age = 50, id = 100);
34 | uri!(has_one: name = 100, age = 50, id = 100);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameters: `name`, `age`
--> $DIR/typed-uris-bad-params.rs:31:19
--> $DIR/typed-uris-bad-params.rs:34:19
|
31 | uri!(has_one: name = 100, age = 50, id = 100);
34 | uri!(has_one: name = 100, age = 50, id = 100);
| ^^^^ ^^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:29:19
--> $DIR/typed-uris-bad-params.rs:32:19
|
29 | uri!(has_one: name = 100, id = 100);
32 | uri!(has_one: name = 100, id = 100);
| ^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:29:19
--> $DIR/typed-uris-bad-params.rs:32:19
|
29 | uri!(has_one: name = 100, id = 100);
32 | uri!(has_one: name = 100, id = 100);
| ^^^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:27:19
--> $DIR/typed-uris-bad-params.rs:30:19
|
27 | uri!(has_one: id = 100, name = "hi");
30 | uri!(has_one: id = 100, name = "hi");
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:27:29
--> $DIR/typed-uris-bad-params.rs:30:29
|
27 | uri!(has_one: id = 100, name = "hi");
30 | uri!(has_one: id = 100, name = "hi");
| ^^^^
error: `has_two` route uri expects 2 parameters but 1 was supplied
--> $DIR/typed-uris-bad-params.rs:25:19
error: expected 2 parameters but 1 was supplied
--> $DIR/typed-uris-bad-params.rs:28:19
|
25 | uri!(has_two: 10);
28 | uri!(has_two: 10);
| ^^
|
= note: expected parameters: id: i32, name: String
= note: route `has_two` has uri "/<id>?<name>"
error: `has_two` route uri expects 2 parameters but 3 were supplied
--> $DIR/typed-uris-bad-params.rs:24:19
error: expected 2 parameters but 3 were supplied
--> $DIR/typed-uris-bad-params.rs:27:19
|
24 | uri!(has_two: 10, "hi", "there");
27 | uri!(has_two: 10, "hi", "there");
| ^^^^^^^^^^^^^^^^^
|
= note: expected parameters: id: i32, name: String
= note: route `has_two` has uri "/<id>?<name>"
error: `has_one_guarded` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:22:27
error: expected 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:25:27
|
22 | uri!(has_one_guarded: "hi", 100);
25 | uri!(has_one_guarded: "hi", 100);
| ^^^^^^^^^
|
= note: expected parameter: id: i32
= note: route `has_one_guarded` has uri "/<id>"
error: `has_one` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:21:19
error: expected 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:24:19
|
21 | uri!(has_one: "Hello", 23, );
24 | uri!(has_one: "Hello", 23, );
| ^^^^^^^^^^^^
|
= note: expected parameter: id: i32
= note: route `has_one` has uri "/<id>"
error: `has_one` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:20:19
error: expected 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:23:19
|
20 | uri!(has_one: 1, 23);
23 | uri!(has_one: 1, 23);
| ^^^^^
|
= note: expected parameter: id: i32
= note: route `has_one` has uri "/<id>"
error: `has_one` route uri expects 1 parameter but 0 were supplied
--> $DIR/typed-uris-bad-params.rs:18:10
error: expected 1 parameter but 0 were supplied
--> $DIR/typed-uris-bad-params.rs:21:10
|
18 | uri!(has_one);
21 | uri!(has_one);
| ^^^^^^^
|
= note: expected parameter: id: i32
= note: route `has_one` has uri "/<id>"

View File

@ -1,56 +1,56 @@
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:6:13
|
6 | struct Bar1(BadType);
| ^^^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:10:5
|
10 | field: BadType,
| ^^^^^^^^^^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:16:5
|
16 | bad: BadType,
| ^^^^^^^^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:21:11
|
21 | Inner(BadType),
| ^^^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
= note: 1 redundant requirements hidden
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:27:9
|
27 | field: BadType,
| ^^^^^^^^^^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
= note: 1 redundant requirements hidden
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:35:9
|
35 | other: BadType,
| ^^^^^^^^^^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
= note: 1 redundant requirements hidden
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Path>` is not satisfied
--> $DIR/uri_display_type_errors.rs:40:12

View File

@ -12,7 +12,7 @@ error: expected `fn`
9 | const CATCH: &str = "Catcher";
| ^^^^^
error: expected integer or identifier, found string literal
error: expected integer or `default`, found string literal
--- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
--> $DIR/catch.rs:11:9
|

View File

@ -1,4 +1,4 @@
error: missing expected parameter: `path`
error: missing expected parameter: `uri`
--> $DIR/route-attribute-general-syntax.rs:4:1
|
4 | #[get()]
@ -65,7 +65,7 @@ error: expected key/value `key = value`
| ^^^
error: handler arguments cannot be ignored
--- help: all handler arguments must be of the form: `ident: Type`
--- note: handler arguments must be of the form: `ident: Type`
--> $DIR/route-attribute-general-syntax.rs:39:7
|
39 | fn c1(_: usize) {}

View File

@ -1,51 +1,53 @@
error: invalid route URI: expected token '/' but found 'a' at index 0
--- help: expected path in origin form: "/path/<param>"
--- help: expected URI in origin form: "/path/<param>"
--> $DIR/route-path-bad-syntax.rs:5:7
|
5 | #[get("a")]
| ^^^
error: invalid route URI: unexpected EOF: expected token '/' at index 0
--- help: expected path in origin form: "/path/<param>"
--- help: expected URI in origin form: "/path/<param>"
--> $DIR/route-path-bad-syntax.rs:8:7
|
8 | #[get("")]
| ^^
error: invalid route URI: expected token '/' but found 'a' at index 0
--- help: expected path in origin form: "/path/<param>"
--- help: expected URI in origin form: "/path/<param>"
--> $DIR/route-path-bad-syntax.rs:11:7
|
11 | #[get("a/b/c")]
| ^^^^^^^
error: paths cannot contain empty segments
--- note: expected '/a/b', found '/a///b'
error: route URIs cannot contain empty segments
--- note: expected "/a/b", found "/a///b"
--> $DIR/route-path-bad-syntax.rs:14:7
|
14 | #[get("/a///b")]
| ^^^^^^^^
error: query cannot contain empty segments
error: route URIs cannot contain empty segments
--- note: expected "/?bat", found "/?bat&&"
--> $DIR/route-path-bad-syntax.rs:17:7
|
17 | #[get("/?bat&&")]
| ^^^^^^^^^
error: query cannot contain empty segments
error: route URIs cannot contain empty segments
--- note: expected "/?bat", found "/?bat&&"
--> $DIR/route-path-bad-syntax.rs:20:7
|
20 | #[get("/?bat&&")]
| ^^^^^^^^^
error: paths cannot contain empty segments
--- note: expected '/a/b', found '/a/b//'
error: route URIs cannot contain empty segments
--- note: expected "/a/b", found "/a/b//"
--> $DIR/route-path-bad-syntax.rs:23:7
|
23 | #[get("/a/b//")]
| ^^^^^^^^
error: unused dynamic parameter
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:42:7
|
42 | #[get("/<name>")]
@ -57,7 +59,7 @@ error: [note] expected argument named `name` here
43 | fn h0(_name: usize) {}
| ^^^^^^^^^^^^^^
error: unused dynamic parameter
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:45:7
|
45 | #[get("/a?<r>")]
@ -69,7 +71,7 @@ error: [note] expected argument named `r` here
46 | fn h1() {}
| ^^
error: unused dynamic parameter
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:48:21
|
48 | #[post("/a", data = "<test>")]
@ -81,7 +83,7 @@ error: [note] expected argument named `test` here
49 | fn h2() {}
| ^^
error: unused dynamic parameter
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:51:7
|
51 | #[get("/<_r>")]
@ -93,7 +95,7 @@ error: [note] expected argument named `_r` here
52 | fn h3() {}
| ^^
error: unused dynamic parameter
error: unused parameter
--> $DIR/route-path-bad-syntax.rs:54:7
|
54 | #[get("/<_r>/<b>")]
@ -105,95 +107,76 @@ error: [note] expected argument named `b` here
55 | fn h4() {}
| ^^
error: `foo_.` is not a valid identifier
--- help: parameter names must be valid identifiers
error: invalid identifier: `foo_.`
--- help: dynamic parameters must be valid identifiers
--- help: did you mean `<foo_>`?
--> $DIR/route-path-bad-syntax.rs:60:7
|
60 | #[get("/<foo_.>")]
| ^^^^^^^^^^
error: `foo*` is not a valid identifier
--- help: parameter names must be valid identifiers
error: invalid identifier: `foo*`
--- help: dynamic parameters must be valid identifiers
--- help: did you mean `<foo>`?
--> $DIR/route-path-bad-syntax.rs:63:7
|
63 | #[get("/<foo*>")]
| ^^^^^^^^^
error: `!` is not a valid identifier
--- help: parameter names must be valid identifiers
error: invalid identifier: `!`
--- help: dynamic parameters must be valid identifiers
--- help: did you mean `<param>`?
--> $DIR/route-path-bad-syntax.rs:66:7
|
66 | #[get("/<!>")]
| ^^^^^^
error: `name>:<id` is not a valid identifier
--- help: parameter names must be valid identifiers
error: invalid identifier: `name>:<id`
--- help: dynamic parameters must be valid identifiers
--- help: did you mean `<nameid>`?
--> $DIR/route-path-bad-syntax.rs:69:7
|
69 | #[get("/<name>:<id>")]
| ^^^^^^^^^^^^^^
error: malformed parameter
--- help: parameter must be of the form '<param>'
error: unexpected static parameter
--- help: parameter must be dynamic: `<foo>`
--> $DIR/route-path-bad-syntax.rs:74:19
|
74 | #[get("/", data = "foo")]
| ^^^^^
error: malformed parameter
--- help: parameter must be of the form '<param>'
error: parameter cannot be trailing
--- help: did you mean `<foo>`?
--> $DIR/route-path-bad-syntax.rs:77:19
|
77 | #[get("/", data = "<foo..>")]
| ^^^^^^^^^
error: parameter is missing a closing bracket
--- help: did you mean '<foo>'?
error: unexpected static parameter
--- help: parameter must be dynamic: `<foo>`
--> $DIR/route-path-bad-syntax.rs:80:19
|
80 | #[get("/", data = "<foo")]
| ^^^^^^
error: `test ` is not a valid identifier
--- help: parameter names must be valid identifiers
error: invalid identifier: `test `
--- help: dynamic parameters must be valid identifiers
--- help: did you mean `<test>`?
--> $DIR/route-path-bad-syntax.rs:83:19
|
83 | #[get("/", data = "<test >")]
| ^^^^^^^^^
error: handler arguments cannot be ignored
--- help: all handler arguments must be of the form: `ident: Type`
--- note: handler arguments must be of the form: `ident: Type`
--> $DIR/route-path-bad-syntax.rs:89:7
|
89 | fn k0(_: usize) {}
| ^
error: parameter names cannot be empty
error: parameters cannot be empty
--> $DIR/route-path-bad-syntax.rs:93:7
|
93 | #[get("/<>")]
| ^^^^^
error: malformed parameter or identifier
--- help: parameters must be of the form '<param>'
--- help: identifiers cannot contain '<' or '>'
--> $DIR/route-path-bad-syntax.rs:96:7
|
96 | #[get("/<id><")]
| ^^^^^^^^
error: malformed parameter or identifier
--- help: parameters must be of the form '<param>'
--- help: identifiers cannot contain '<' or '>'
--> $DIR/route-path-bad-syntax.rs:99:7
|
99 | #[get("/<<<<id><")]
| ^^^^^^^^^^^
error: malformed parameter or identifier
--- help: parameters must be of the form '<param>'
--- help: identifiers cannot contain '<' or '>'
--> $DIR/route-path-bad-syntax.rs:102:7
|
102 | #[get("/<>name><")]
| ^^^^^^^^^^^

View File

@ -70,11 +70,11 @@ error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::u
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` for `std::result::Result<std::string::String, &str>`
= note: required by `from_uri_param`
error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfied
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:58:20
|
58 | uri!(simple_q: "hi");
| ^^^^ the trait `FromUriParam<Query, &str>` is not implemented for `isize`
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
= help: the following implementations were found:
<isize as FromUriParam<P, &'x isize>>
@ -82,11 +82,11 @@ error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfie
<isize as FromUriParam<P, isize>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfied
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:60:25
|
60 | uri!(simple_q: id = "hi");
| ^^^^ the trait `FromUriParam<Query, &str>` is not implemented for `isize`
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
= help: the following implementations were found:
<isize as FromUriParam<P, &'x isize>>
@ -94,48 +94,48 @@ error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfie
<isize as FromUriParam<P, isize>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:62:24
|
62 | uri!(other_q: 100, S);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:64:26
|
64 | uri!(other_q: rest = S, id = 100);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: Ignorable<Query>` is not satisfied
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:66:26
|
66 | uri!(other_q: rest = _, id = 100);
| ^ the trait `Ignorable<Query>` is not implemented for `S`
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `S`
|
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
| ------------ required by this bound in `assert_ignorable`
error[E0277]: the trait bound `usize: Ignorable<Query>` is not satisfied
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:68:34
|
68 | uri!(other_q: rest = S, id = _);
| ^ the trait `Ignorable<Query>` is not implemented for `usize`
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `usize`
|
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
| ------------ required by this bound in `assert_ignorable`
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:68:26
|
68 | uri!(other_q: rest = S, id = _);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`

View File

@ -1,233 +1,266 @@
error: path parameters cannot be ignored
--> $DIR/typed-uris-bad-params.rs:55:37
error: expected identifier
--> $DIR/typed-uris-bad-params.rs:62:19
|
55 | uri!(optionals: id = 10, name = _);
62 | uri!(ignored: _ = 10);
| ^
error: expected 1 parameter but 2 were supplied
--- note: route `ignored` has uri "/<_>"
--> $DIR/typed-uris-bad-params.rs:68:19
|
68 | uri!(ignored: 10, "10");
| ^^
error: expected unnamed arguments due to ignored parameters
--- note: uri for route `ignored` ignores path parameters: "/<_>"
--> $DIR/typed-uris-bad-params.rs:66:19
|
66 | uri!(ignored: num = 10);
| ^^^
error: expected 1 parameter but 2 were supplied
--- note: route `ignored` has uri "/<_>"
--> $DIR/typed-uris-bad-params.rs:64:19
|
64 | uri!(ignored: 10, 20);
| ^^
error: path parameters cannot be ignored
--> $DIR/typed-uris-bad-params.rs:60:19
|
60 | uri!(ignored: _);
| ^
error: path parameters cannot be ignored
--> $DIR/typed-uris-bad-params.rs:58:37
|
58 | uri!(optionals: id = 10, name = _);
| ^
error: path parameters cannot be ignored
--> $DIR/typed-uris-bad-params.rs:53:26
--> $DIR/typed-uris-bad-params.rs:56:26
|
53 | uri!(optionals: id = _, name = "bob".into());
56 | uri!(optionals: id = _, name = "bob".into());
| ^
error: invalid parameters for `has_two` route uri
--- note: uri parameters are: id: i32, name: String
--- help: missing parameter: `name`
--> $DIR/typed-uris-bad-params.rs:51:19
--> $DIR/typed-uris-bad-params.rs:54:19
|
51 | uri!(has_two: id = 100, cookies = "hi");
54 | uri!(has_two: id = 100, cookies = "hi");
| ^^
error: [help] unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:51:29
--> $DIR/typed-uris-bad-params.rs:54:29
|
51 | uri!(has_two: id = 100, cookies = "hi");
54 | uri!(has_two: id = 100, cookies = "hi");
| ^^^^^^^
error: invalid parameters for `has_two` route uri
--- note: uri parameters are: id: i32, name: String
--- help: missing parameter: `name`
--> $DIR/typed-uris-bad-params.rs:49:19
--> $DIR/typed-uris-bad-params.rs:52:19
|
49 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
| ^^^^^^^
error: [help] unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:49:19
--> $DIR/typed-uris-bad-params.rs:52:19
|
49 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
| ^^^^^^^
error: [help] duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:49:45
--> $DIR/typed-uris-bad-params.rs:52:45
|
49 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
| ^^
error: invalid parameters for `has_two` route uri
--- note: uri parameters are: id: i32, name: String
--- help: missing parameter: `id`
--> $DIR/typed-uris-bad-params.rs:47:19
--> $DIR/typed-uris-bad-params.rs:50:19
|
47 | uri!(has_two: name = "hi");
50 | uri!(has_two: name = "hi");
| ^^^^
error: invalid parameters for `has_two` route uri
--- note: uri parameters are: id: i32, name: String
--- help: missing parameter: `name`
--> $DIR/typed-uris-bad-params.rs:45:19
--> $DIR/typed-uris-bad-params.rs:48:19
|
45 | uri!(has_two: id = 100, id = 100, );
48 | uri!(has_two: id = 100, id = 100, );
| ^^
error: [help] duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:45:29
--> $DIR/typed-uris-bad-params.rs:48:29
|
45 | uri!(has_two: id = 100, id = 100, );
48 | uri!(has_two: id = 100, id = 100, );
| ^^
error: invalid parameters for `has_one_guarded` route uri
--- note: uri parameters are: id: i32
--> $DIR/typed-uris-bad-params.rs:43:27
--> $DIR/typed-uris-bad-params.rs:46:27
|
43 | uri!(has_one_guarded: id = 100, cookies = "hi");
46 | uri!(has_one_guarded: id = 100, cookies = "hi");
| ^^
error: [help] unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:43:37
--> $DIR/typed-uris-bad-params.rs:46:37
|
43 | uri!(has_one_guarded: id = 100, cookies = "hi");
46 | uri!(has_one_guarded: id = 100, cookies = "hi");
| ^^^^^^^
error: invalid parameters for `has_one_guarded` route uri
--- note: uri parameters are: id: i32
--> $DIR/typed-uris-bad-params.rs:41:27
--> $DIR/typed-uris-bad-params.rs:44:27
|
41 | uri!(has_one_guarded: cookies = "hi", id = 100);
44 | uri!(has_one_guarded: cookies = "hi", id = 100);
| ^^^^^^^
error: [help] unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:41:27
--> $DIR/typed-uris-bad-params.rs:44:27
|
41 | uri!(has_one_guarded: cookies = "hi", id = 100);
44 | uri!(has_one_guarded: cookies = "hi", id = 100);
| ^^^^^^^
error: invalid parameters for `has_one` route uri
--- note: uri parameters are: id: i32
--- help: missing parameter: `id`
--> $DIR/typed-uris-bad-params.rs:39:19
--> $DIR/typed-uris-bad-params.rs:42:19
|
39 | uri!(has_one: name = "hi");
42 | uri!(has_one: name = "hi");
| ^^^^
error: [help] unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:39:19
--> $DIR/typed-uris-bad-params.rs:42:19
|
39 | uri!(has_one: name = "hi");
42 | uri!(has_one: name = "hi");
| ^^^^
error: invalid parameters for `has_one` route uri
--- note: uri parameters are: id: i32
--> $DIR/typed-uris-bad-params.rs:37:19
--> $DIR/typed-uris-bad-params.rs:40:19
|
37 | uri!(has_one: id = 100, id = 100, );
40 | uri!(has_one: id = 100, id = 100, );
| ^^
error: [help] duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:37:29
--> $DIR/typed-uris-bad-params.rs:40:29
|
37 | uri!(has_one: id = 100, id = 100, );
40 | uri!(has_one: id = 100, id = 100, );
| ^^
error: invalid parameters for `has_one` route uri
--- note: uri parameters are: id: i32
--> $DIR/typed-uris-bad-params.rs:35:19
--> $DIR/typed-uris-bad-params.rs:38:19
|
35 | uri!(has_one: id = 100, id = 100);
38 | uri!(has_one: id = 100, id = 100);
| ^^
error: [help] duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:35:29
--> $DIR/typed-uris-bad-params.rs:38:29
|
35 | uri!(has_one: id = 100, id = 100);
38 | uri!(has_one: id = 100, id = 100);
| ^^
error: invalid parameters for `has_one` route uri
--- note: uri parameters are: id: i32
--> $DIR/typed-uris-bad-params.rs:33:19
--> $DIR/typed-uris-bad-params.rs:36:19
|
33 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
| ^^^^
error: [help] unknown parameters: `name`, `age`
--> $DIR/typed-uris-bad-params.rs:33:19
--> $DIR/typed-uris-bad-params.rs:36:19
|
33 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
| ^^^^
error: [help] duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:33:51
--> $DIR/typed-uris-bad-params.rs:36:51
|
33 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
| ^^
error: invalid parameters for `has_one` route uri
--- note: uri parameters are: id: i32
--> $DIR/typed-uris-bad-params.rs:31:19
--> $DIR/typed-uris-bad-params.rs:34:19
|
31 | uri!(has_one: name = 100, age = 50, id = 100);
34 | uri!(has_one: name = 100, age = 50, id = 100);
| ^^^^
error: [help] unknown parameters: `name`, `age`
--> $DIR/typed-uris-bad-params.rs:31:19
--> $DIR/typed-uris-bad-params.rs:34:19
|
31 | uri!(has_one: name = 100, age = 50, id = 100);
34 | uri!(has_one: name = 100, age = 50, id = 100);
| ^^^^
error: invalid parameters for `has_one` route uri
--- note: uri parameters are: id: i32
--> $DIR/typed-uris-bad-params.rs:29:19
--> $DIR/typed-uris-bad-params.rs:32:19
|
29 | uri!(has_one: name = 100, id = 100);
32 | uri!(has_one: name = 100, id = 100);
| ^^^^
error: [help] unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:29:19
--> $DIR/typed-uris-bad-params.rs:32:19
|
29 | uri!(has_one: name = 100, id = 100);
32 | uri!(has_one: name = 100, id = 100);
| ^^^^
error: invalid parameters for `has_one` route uri
--- note: uri parameters are: id: i32
--> $DIR/typed-uris-bad-params.rs:27:19
--> $DIR/typed-uris-bad-params.rs:30:19
|
27 | uri!(has_one: id = 100, name = "hi");
30 | uri!(has_one: id = 100, name = "hi");
| ^^
error: [help] unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:27:29
--> $DIR/typed-uris-bad-params.rs:30:29
|
27 | uri!(has_one: id = 100, name = "hi");
30 | uri!(has_one: id = 100, name = "hi");
| ^^^^
error: `has_two` route uri expects 2 parameters but 1 was supplied
--- note: expected parameters: id: i32, name: String
--> $DIR/typed-uris-bad-params.rs:25:19
error: expected 2 parameters but 1 was supplied
--- note: route `has_two` has uri "/<id>?<name>"
--> $DIR/typed-uris-bad-params.rs:28:19
|
25 | uri!(has_two: 10);
28 | uri!(has_two: 10);
| ^^
error: `has_two` route uri expects 2 parameters but 3 were supplied
--- note: expected parameters: id: i32, name: String
--> $DIR/typed-uris-bad-params.rs:24:19
error: expected 2 parameters but 3 were supplied
--- note: route `has_two` has uri "/<id>?<name>"
--> $DIR/typed-uris-bad-params.rs:27:19
|
24 | uri!(has_two: 10, "hi", "there");
27 | uri!(has_two: 10, "hi", "there");
| ^^
error: `has_one_guarded` route uri expects 1 parameter but 2 were supplied
--- note: expected parameter: id: i32
--> $DIR/typed-uris-bad-params.rs:22:27
error: expected 1 parameter but 2 were supplied
--- note: route `has_one_guarded` has uri "/<id>"
--> $DIR/typed-uris-bad-params.rs:25:27
|
22 | uri!(has_one_guarded: "hi", 100);
25 | uri!(has_one_guarded: "hi", 100);
| ^^^^
error: `has_one` route uri expects 1 parameter but 2 were supplied
--- note: expected parameter: id: i32
--> $DIR/typed-uris-bad-params.rs:21:19
error: expected 1 parameter but 2 were supplied
--- note: route `has_one` has uri "/<id>"
--> $DIR/typed-uris-bad-params.rs:24:19
|
21 | uri!(has_one: "Hello", 23, );
24 | uri!(has_one: "Hello", 23, );
| ^^^^^^^
error: `has_one` route uri expects 1 parameter but 2 were supplied
--- note: expected parameter: id: i32
--> $DIR/typed-uris-bad-params.rs:20:19
error: expected 1 parameter but 2 were supplied
--- note: route `has_one` has uri "/<id>"
--> $DIR/typed-uris-bad-params.rs:23:19
|
20 | uri!(has_one: 1, 23);
23 | uri!(has_one: 1, 23);
| ^
error: `has_one` route uri expects 1 parameter but 0 were supplied
--- note: expected parameter: id: i32
--> $DIR/typed-uris-bad-params.rs:18:10
error: expected 1 parameter but 0 were supplied
--- note: route `has_one` has uri "/<id>"
--> $DIR/typed-uris-bad-params.rs:21:10
|
18 | uri!(has_one);
21 | uri!(has_one);
| ^^^^^^^

View File

@ -1,56 +1,56 @@
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:6:13
|
6 | struct Bar1(BadType);
| ^^^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:10:5
|
10 | field: BadType,
| ^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:16:5
|
16 | bad: BadType,
| ^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:21:11
|
21 | Inner(BadType),
| ^^^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
= note: 1 redundant requirements hidden
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:27:9
|
27 | field: BadType,
| ^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
= note: 1 redundant requirements hidden
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<Query>` is not satisfied
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
--> $DIR/uri_display_type_errors.rs:35:9
|
35 | other: BadType,
| ^^^^^ the trait `UriDisplay<Query>` is not implemented for `BadType`
| ^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
= note: 1 redundant requirements hidden
= note: required because of the requirements on the impl of `UriDisplay<Query>` for `&&BadType`
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Path>` is not satisfied
--> $DIR/uri_display_type_errors.rs:40:12

View File

@ -14,6 +14,9 @@ fn has_two(cookies: &CookieJar<'_>, id: i32, name: String) { }
#[post("/<id>/<name>")]
fn optionals(id: Option<i32>, name: Result<String, &str>) { }
#[post("/<_>")]
fn ignored() { }
fn main() {
uri!(has_one);
@ -53,4 +56,14 @@ fn main() {
uri!(optionals: id = _, name = "bob".into());
uri!(optionals: id = 10, name = _);
uri!(ignored: _);
uri!(ignored: _ = 10);
uri!(ignored: 10, 20);
uri!(ignored: num = 10);
uri!(ignored: 10, "10");
}

View File

@ -30,7 +30,6 @@ time = "0.2.11"
indexmap = { version = "1.5.2", features = ["std"] }
tokio-rustls = { version = "0.22.0", optional = true }
tokio = { version = "1.0", features = ["net", "sync", "time"] }
unicode-xid = "0.2"
log = "0.4"
ref-cast = "1.0"
uncased = "0.9.4"

View File

@ -27,9 +27,6 @@ mod docify;
#[cfg(feature = "tls")]
pub mod tls;
#[doc(hidden)]
pub mod route;
#[macro_use]
mod header;
mod cookies;

View File

@ -5,7 +5,7 @@ use std::str::Utf8Error;
use std::fmt;
use ref_cast::RefCast;
use stable_pattern::{Pattern, ReverseSearcher, Split, SplitInternal};
use stable_pattern::{Pattern, Searcher, ReverseSearcher, Split, SplitInternal};
use crate::uncased::UncasedStr;
@ -540,6 +540,34 @@ impl RawStr {
pat.is_suffix_of(self.as_str())
}
/// Returns the byte index of the first character of this string slice that
/// matches the pattern.
///
/// Returns [`None`] if the pattern doesn't match.
///
/// The pattern can be a `&str`, [`char`], a slice of [`char`]s, or a
/// function or closure that determines if a character matches.
///
/// [`char`]: prim@char
///
/// # Example
///
/// ```
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let s = RawStr::new("Löwe 老虎 Léopard Gepardi");
///
/// assert_eq!(s.find('L'), Some(0));
/// assert_eq!(s.find('é'), Some(14));
/// assert_eq!(s.find("pard"), Some(17));
/// ```
#[inline]
pub fn find<'a, P: Pattern<'a>>(&'a self, pat: P) -> Option<usize> {
pat.into_searcher(self.as_str()).next_match().map(|(i, _)| i)
}
/// An iterator over substrings of this string slice, separated by
/// characters matched by a pattern.
///

View File

@ -1,167 +0,0 @@
use std::borrow::Cow;
use std::marker::PhantomData;
use unicode_xid::UnicodeXID;
use crate::ext::IntoOwned;
use crate::uri::{Origin, UriPart, Path, Query};
use self::Error::*;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Kind {
Static,
Single,
Multi,
}
#[derive(Debug, Clone)]
pub struct RouteSegment<'a, P: UriPart> {
pub string: Cow<'a, str>,
pub kind: Kind,
pub name: Cow<'a, str>,
pub index: Option<usize>,
_part: PhantomData<P>,
}
impl<P: UriPart + 'static> IntoOwned for RouteSegment<'_, P> {
type Owned = RouteSegment<'static, P>;
#[inline]
fn into_owned(self) -> Self::Owned {
RouteSegment {
string: IntoOwned::into_owned(self.string),
kind: self.kind,
name: IntoOwned::into_owned(self.name),
index: self.index,
_part: PhantomData
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Error<'a> {
Empty,
Ident(&'a str),
Ignored,
MissingClose,
Malformed,
Uri,
Trailing(&'a str)
}
impl std::fmt::Display for Error<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Empty => "parameter names cannot be empty".fmt(f),
Ident(name) => write!(f, "`{}` is not a valid identifier", name),
Ignored => "parameter must be named".fmt(f),
MissingClose => "parameter is missing a closing bracket".fmt(f),
Malformed => "malformed parameter or identifier".fmt(f),
Uri => "segment contains invalid URI characters".fmt(f),
Trailing(i) => write!(f, "unexpected trailing text after `{}`", i)
}
}
}
pub type SResult<'a, P> = Result<RouteSegment<'a, P>, (&'a str, Error<'a>)>;
#[inline]
fn is_ident_start(c: char) -> bool {
c.is_ascii_alphabetic()
|| c == '_'
|| (c > '\x7f' && UnicodeXID::is_xid_start(c))
}
#[inline]
fn is_ident_continue(c: char) -> bool {
c.is_ascii_alphanumeric()
|| c == '_'
|| (c > '\x7f' && UnicodeXID::is_xid_continue(c))
}
fn is_valid_ident(string: &str) -> bool {
let mut chars = string.chars();
match chars.next() {
Some(c) => is_ident_start(c) && chars.all(is_ident_continue),
None => false
}
}
impl<'a, P: UriPart> RouteSegment<'a, P> {
pub fn parse_one(segment: &'a str) -> Result<Self, Error<'_>> {
let (string, index) = (segment.into(), None);
// Check if this is a dynamic param. If so, check its well-formedness.
if segment.starts_with('<') && segment.ends_with('>') {
let mut kind = Kind::Single;
let mut name = &segment[1..(segment.len() - 1)];
if name.ends_with("..") {
kind = Kind::Multi;
name = &name[..(name.len() - 2)];
}
if name.is_empty() {
return Err(Empty);
} else if !is_valid_ident(name) {
return Err(Ident(name));
} else if name == "_" && P::DELIMITER != '/' {
// Only path segments may be ignored.
return Err(Ignored);
}
let name = name.into();
return Ok(RouteSegment { string, name, kind, index, _part: PhantomData });
} else if segment.is_empty() {
return Err(Empty);
} else if segment.starts_with('<') && segment.len() > 1
&& !segment[1..].contains('<') && !segment[1..].contains('>') {
return Err(MissingClose);
} else if segment.contains('>') || segment.contains('<') {
return Err(Malformed);
}
Ok(RouteSegment {
string, index,
name: segment.into(),
kind: Kind::Static,
_part: PhantomData
})
}
pub fn parse_many<S: AsRef<str> + ?Sized> (
string: &'a S,
) -> impl Iterator<Item = SResult<'_, P>> {
let mut last_multi_seg: Option<&str> = None;
// We check for empty segments when we parse an `Origin` in `FromMeta`.
string.as_ref()
.split(P::DELIMITER)
.filter(|s| !s.is_empty())
.enumerate()
.map(move |(i, seg)| {
if let Some(multi_seg) = last_multi_seg {
return Err((seg, Trailing(multi_seg)));
}
let mut parsed = Self::parse_one(seg).map_err(|e| (seg, e))?;
if parsed.kind == Kind::Multi {
last_multi_seg = Some(seg);
}
parsed.index = Some(i);
Ok(parsed)
})
}
}
impl<'a> RouteSegment<'a, Path> {
pub fn parse(uri: &'a Origin<'_>) -> impl Iterator<Item = SResult<'a, Path>> {
Self::parse_many(uri.path().as_str())
}
}
impl<'a> RouteSegment<'a, Query> {
pub fn parse(uri: &'a Origin<'_>) -> Option<impl Iterator<Item = SResult<'a, Query>>> {
uri.query().map(|q| Self::parse_many(q.as_str()))
}
}

View File

@ -53,12 +53,21 @@ mod private {
/// [`UriDisplay`]: crate::uri::UriDisplay
/// [`Formatter`]: crate::uri::Formatter
pub trait UriPart: private::Sealed {
/// The dynamic version of `Self`.
#[doc(hidden)]
const KIND: Kind;
/// The delimiter used to separate components of this URI part.
/// Specifically, `/` for `Path` and `&` for `Query`.
#[doc(hidden)]
const DELIMITER: char;
}
/// Dynamic version of the `Path` and `Query` parts.
#[doc(hidden)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Kind { Path, Query }
/// Marker type indicating use of a type for the path [`UriPart`] of a URI.
///
/// In route URIs, this corresponds to all of the text before a `?`, if any, or
@ -87,9 +96,11 @@ pub enum Path { }
pub enum Query { }
impl UriPart for Path {
const KIND: Kind = Kind::Path;
const DELIMITER: char = '/';
}
impl UriPart for Query {
const KIND: Kind = Kind::Query;
const DELIMITER: char = '&';
}

View File

@ -42,7 +42,8 @@ use state::Storage;
/// "/",
/// "/a/b/c",
/// "/a/b/c?q",
/// "/some%20thing"
/// "/hello?lang=en",
/// "/some%20thing?q=foo&lang=fr",
/// # ];
/// # for uri in &valid_uris {
/// # assert!(Origin::parse(uri).unwrap().is_normalized());
@ -57,7 +58,9 @@ use state::Storage;
/// # let invalid = [
/// "//", // one empty segment
/// "/a/b/", // trailing empty segment
/// "/a/ab//c//d" // two empty segments
/// "/a/ab//c//d", // two empty segments
/// "/?a&&b", // empty query segment
/// "/?foo&", // trailing empty query segment
/// # ];
/// # for uri in &invalid {
/// # assert!(!Origin::parse(uri).unwrap().is_normalized());
@ -72,11 +75,10 @@ use state::Storage;
/// # use rocket::http::uri::Origin;
/// # let invalid = [
/// // abnormal versions
/// "//", "/a/b/", "/a/ab//c//d"
/// # ,
/// "//", "/a/b/", "/a/ab//c//d", "/a?a&&b&",
///
/// // normalized versions
/// "/", "/a/b", "/a/ab/c/d"
/// "/", "/a/b", "/a/ab/c/d", "/a?a&b",
/// # ];
/// # for i in 0..(invalid.len() / 2) {
/// # let abnormal = Origin::parse(invalid[i]).unwrap();
@ -285,14 +287,19 @@ impl<'a> Origin<'a> {
/// let normal = Origin::parse("/a/b/c").unwrap();
/// assert!(normal.is_normalized());
///
/// let normal = Origin::parse("/a/b/c?a=b&c").unwrap();
/// assert!(normal.is_normalized());
///
/// let abnormal = Origin::parse("/a/b/c//d").unwrap();
/// assert!(!abnormal.is_normalized());
///
/// let abnormal = Origin::parse("/a?q&&b").unwrap();
/// assert!(!abnormal.is_normalized());
/// ```
pub fn is_normalized(&self) -> bool {
let path_str = self.path().as_str();
path_str.starts_with('/') &&
!path_str.contains("//") &&
!(path_str.len() > 1 && path_str.ends_with('/'))
self.path().starts_with('/')
&& self.raw_path_segments().all(|s| !s.is_empty())
&& self.raw_query_segments().all(|s| !s.is_empty())
}
/// Normalizes `self`.
@ -314,21 +321,37 @@ impl<'a> Origin<'a> {
/// assert_eq!(normalized, Origin::parse("/a/b/c/d").unwrap());
/// ```
pub fn into_normalized(mut self) -> Self {
use std::fmt::Write;
if self.is_normalized() {
self
} else {
let mut new_path = String::with_capacity(self.path().len());
for segment in self.raw_path_segments() {
use std::fmt::Write;
let _ = write!(new_path, "/{}", segment);
for seg in self.raw_path_segments().filter(|s| !s.is_empty()) {
let _ = write!(new_path, "/{}", seg);
}
if new_path.is_empty() {
new_path.push('/');
}
// Note: normalization preserves segmments!
self.path = Indexed::from(Cow::Owned(new_path));
if let Some(q) = self.query() {
let mut new_query = String::with_capacity(q.len());
let raw_segments = self.raw_query_segments()
.filter(|s| !s.is_empty())
.enumerate();
for (i, seg) in raw_segments {
if i != 0 { new_query.push('&'); }
let _ = write!(new_query, "{}", seg);
}
self.query = Some(Indexed::from(Cow::Owned(new_query)));
}
// Note: normalization preserves segments!
self
}
}
@ -467,6 +490,7 @@ impl<'a> Origin<'a> {
let cached = self.decoded_path_segs.get_or_set(|| {
let (indexed, path) = (&self.path, self.path());
self.raw_path_segments()
.filter(|r| !r.is_empty())
.map(|s| decode_to_indexed_str::<Path>(s, (indexed, path)))
.collect()
});
@ -500,6 +524,8 @@ impl<'a> Origin<'a> {
};
self.raw_query_segments()
.filter(|s| !s.is_empty())
.map(|s| s.split_at_byte(b'='))
.map(|(name, val)| {
let name = decode_to_indexed_str::<Query>(name, (indexed, query));
let val = decode_to_indexed_str::<Query>(val, (indexed, query));
@ -512,58 +538,46 @@ impl<'a> Origin<'a> {
}
/// Returns an iterator over the raw, undecoded segments of the path in this
/// URI.
/// URI. Segments may be empty.
///
/// ### Examples
///
/// A valid URI with only non-empty segments:
/// ### Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/a/b/c?a=true").unwrap();
/// # let segments: Vec<_> = uri.raw_path_segments().collect();
/// # assert_eq!(segments, &["a", "b", "c"]);
/// let uri = Origin::parse("/").unwrap();
/// let segments: Vec<_> = uri.raw_path_segments().collect();
/// assert!(segments.is_empty());
///
/// for (i, segment) in uri.raw_path_segments().enumerate() {
/// match i {
/// 0 => assert_eq!(segment, "a"),
/// 1 => assert_eq!(segment, "b"),
/// 2 => assert_eq!(segment, "c"),
/// _ => unreachable!("only three segments")
/// }
/// }
/// ```
/// let uri = Origin::parse("//").unwrap();
/// let segments: Vec<_> = uri.raw_path_segments().collect();
/// assert_eq!(segments, &["", ""]);
///
/// A URI with empty segments:
/// let uri = Origin::parse("/a").unwrap();
/// let segments: Vec<_> = uri.raw_path_segments().collect();
/// assert_eq!(segments, &["a"]);
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("///a//b///c////d?query&param").unwrap();
/// # let segments: Vec<_> = uri.raw_path_segments().collect();
/// # assert_eq!(segments, &["a", "b", "c", "d"]);
///
/// for (i, segment) in uri.raw_path_segments().enumerate() {
/// match i {
/// 0 => assert_eq!(segment, "a"),
/// 1 => assert_eq!(segment, "b"),
/// 2 => assert_eq!(segment, "c"),
/// 3 => assert_eq!(segment, "d"),
/// _ => unreachable!("only four segments")
/// }
/// }
/// let uri = Origin::parse("/a//b///c/d?query&param").unwrap();
/// let segments: Vec<_> = uri.raw_path_segments().collect();
/// assert_eq!(segments, &["a", "", "b", "", "", "c", "d"]);
/// ```
#[inline(always)]
pub fn raw_path_segments(&self) -> impl Iterator<Item = &RawStr> {
self.path().split(Path::DELIMITER).filter(|s| !s.is_empty())
let path = match self.path() {
p if p == "/" => None,
p if p.starts_with('/') => Some(&p[1..]),
p => Some(p)
};
path.map(|p| p.split(Path::DELIMITER))
.into_iter()
.flatten()
}
/// Returns a (smart) iterator over the non-empty, non-url-decoded `(name,
/// value)` pairs of the query of this URI. If there is no query, the
/// iterator is empty.
/// Returns an iterator over the non-empty, non-url-decoded `(name, value)`
/// pairs of the query of this URI. If there is no query, the iterator is
/// empty. Segments may be empty.
///
/// # Example
///
@ -572,23 +586,25 @@ impl<'a> Origin<'a> {
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/").unwrap();
/// assert!(uri.raw_query_segments().next().is_none());
///
/// let uri = Origin::parse("/?a=b&dog").unwrap();
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
/// assert!(query_segs.is_empty());
/// assert_eq!(query_segs, &["a=b", "dog"]);
///
/// let uri = Origin::parse("/?&").unwrap();
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
/// assert_eq!(query_segs, &["", ""]);
///
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
/// let query_segs: Vec<_> = uri.raw_query_segments()
/// .map(|(name, val)| (name.as_str(), val.as_str()))
/// .collect();
///
/// assert_eq!(query_segs, &[("a+b%2F", "some+one%40gmail.com"), ("%26%3D2", "")]);
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
/// assert_eq!(query_segs, &["a+b%2F=some+one%40gmail.com", "", "%26%3D2"]);
/// ```
#[inline(always)]
pub fn raw_query_segments(&self) -> impl Iterator<Item = (&RawStr, &RawStr)> {
self.query().into_iter().flat_map(|q| {
q.split(Query::DELIMITER)
.filter(|s| !s.is_empty())
.map(|q| q.split_at_byte(b'='))
})
#[inline]
pub fn raw_query_segments(&self) -> impl Iterator<Item = &RawStr> {
self.query()
.into_iter()
.flat_map(|q| q.split(Query::DELIMITER))
}
}

View File

@ -205,7 +205,6 @@ impl Drop for Error {
use crate::http::uri;
use crate::http::ext::IntoOwned;
use crate::http::route::Error as SegmentError;
/// Error returned by [`Route::map_base()`] on invalid URIs.
#[derive(Debug)]
@ -218,12 +217,6 @@ pub enum RouteUriError {
DynamicBase,
}
impl<'a> From<(&'a str, SegmentError<'a>)> for RouteUriError {
fn from((seg, err): (&'a str, SegmentError<'a>)) -> Self {
RouteUriError::Segment(seg.into(), err.to_string())
}
}
impl<'a> From<uri::Error<'a>> for RouteUriError {
fn from(error: uri::Error<'a>) -> Self {
RouteUriError::Uri(error.into_owned())

View File

@ -1,8 +1,6 @@
use super::Route;
use crate::http::MediaType;
use crate::http::route::Kind;
use crate::form::ValueField;
use crate::request::Request;
impl Route {
@ -48,15 +46,15 @@ impl Route {
}
fn paths_collide(route: &Route, other: &Route) -> bool {
let a_segments = &route.metadata.path_segments;
let b_segments = &other.metadata.path_segments;
let a_segments = &route.metadata.path_segs;
let b_segments = &other.metadata.path_segs;
for (seg_a, seg_b) in a_segments.iter().zip(b_segments.iter()) {
if seg_a.kind == Kind::Multi || seg_b.kind == Kind::Multi {
if seg_a.trailing || seg_b.trailing {
return true;
}
if seg_a.kind == Kind::Static && seg_b.kind == Kind::Static {
if seg_a.string != seg_b.string {
if !seg_a.dynamic && !seg_b.dynamic {
if seg_a.value != seg_b.value {
return false;
}
}
@ -66,17 +64,23 @@ fn paths_collide(route: &Route, other: &Route) -> bool {
}
fn paths_match(route: &Route, req: &Request<'_>) -> bool {
let route_segments = &route.metadata.path_segments;
let route_segments = &route.metadata.path_segs;
let req_segments = req.routed_segments(0..);
if route_segments.len() > req_segments.len() {
return false;
}
if route.metadata.wild_path {
return true;
}
for (route_seg, req_seg) in route_segments.iter().zip(req_segments) {
match route_seg.kind {
Kind::Multi => return true,
Kind::Static if route_seg.string != req_seg => return false,
_ => continue,
if route_seg.trailing {
return true;
}
if !route_seg.dynamic && route_seg.value != req_seg {
return false;
}
}
@ -84,21 +88,16 @@ fn paths_match(route: &Route, req: &Request<'_>) -> bool {
}
fn queries_match(route: &Route, req: &Request<'_>) -> bool {
if route.metadata.fully_dynamic_query {
if route.metadata.wild_query {
return true;
}
let route_segments = match route.metadata.query_segments {
Some(ref segments) => segments.iter(),
None => return true
};
let route_query_fields = route.metadata.static_query_fields.iter()
.map(|(k, v)| (k.as_str(), v.as_str()));
for seg in route_segments.filter(|s| s.kind == Kind::Static) {
if !req.query_fields().any(|f| f == ValueField::parse(&seg.string)) {
trace_!("route {} missing static query {}", route, seg.string);
for f in req.query_fields() {
trace_!("field: {:?}", f);
}
for route_seg in route_query_fields {
if !req.uri().query_segments().any(|req_seg| req_seg == route_seg) {
trace_!("request {} missing static query {:?}", req, route_seg);
return false;
}
}

View File

@ -1,5 +1,6 @@
mod collider;
mod route;
mod segment;
use std::collections::HashMap;
@ -8,6 +9,7 @@ use crate::http::Method;
use crate::handler::dummy;
pub use self::route::Route;
pub use self::segment::Segment;
// type Selector = (Method, usize);
type Selector = Method;

View File

@ -6,10 +6,11 @@ use yansi::Paint;
use crate::codegen::StaticRouteInfo;
use crate::handler::Handler;
use crate::http::{Method, MediaType};
use crate::http::route::{RouteSegment, Kind};
use crate::error::RouteUriError;
use crate::http::ext::IntoOwned;
use crate::http::uri::{Origin, Path, Query};
use crate::http::uri::Origin;
use crate::router::Segment;
use crate::form::ValueField;
/// A route: a method, its handler, path, rank, and format/media type.
#[derive(Clone)]
@ -37,43 +38,24 @@ pub struct Route {
#[derive(Debug, Default, Clone)]
pub(crate) struct Metadata {
pub path_segments: Vec<RouteSegment<'static, Path>>,
pub query_segments: Option<Vec<RouteSegment<'static, Query>>>,
pub fully_dynamic_query: bool,
}
impl Metadata {
fn from(route: &Route) -> Result<Metadata, RouteUriError> {
let path_segments = <RouteSegment<'_, Path>>::parse(&route.uri)
.map(|res| res.map(|s| s.into_owned()))
.collect::<Result<Vec<_>, _>>()?;
let (query_segments, is_dyn) = match <RouteSegment<'_, Query>>::parse(&route.uri) {
Some(results) => {
let segments = results.map(|res| res.map(|s| s.into_owned()))
.collect::<Result<Vec<_>, _>>()?;
let dynamic = !segments.iter().any(|s| s.kind == Kind::Static);
(Some(segments), dynamic)
}
None => (None, true)
};
Ok(Metadata { path_segments, query_segments, fully_dynamic_query: is_dyn })
}
pub path_segs: Vec<Segment>,
pub query_segs: Vec<Segment>,
pub static_query_fields: Vec<(String, String)>,
pub static_path: bool,
pub wild_path: bool,
pub wild_query: bool,
}
#[inline(always)]
fn default_rank(route: &Route) -> isize {
let static_path = route.metadata.path_segments.iter().all(|s| s.kind == Kind::Static);
let partly_static_query = route.uri.query().map(|_| !route.metadata.fully_dynamic_query);
match (static_path, partly_static_query) {
(true, Some(true)) => -6, // static path, partly static query
(true, Some(false)) => -5, // static path, fully dynamic query
let static_path = route.metadata.static_path;
let wild_query = route.uri.query().map(|_| route.metadata.wild_query);
match (static_path, wild_query) {
(true, Some(false)) => -6, // static path, partly static query
(true, Some(true)) => -5, // static path, fully dynamic query
(true, None) => -4, // static path, no query
(false, Some(true)) => -3, // dynamic path, partly static query
(false, Some(false)) => -2, // dynamic path, fully dynamic query
(false, Some(false)) => -3, // dynamic path, partly static query
(false, Some(true)) => -2, // dynamic path, fully dynamic query
(false, None) => -1, // dynamic path, no query
}
}
@ -186,16 +168,37 @@ impl Route {
method, rank,
};
route.update_metadata().unwrap_or_else(|e| panic(path, e));
route.update_metadata();
route
}
fn metadata(&self) -> Metadata {
let path_segs = self.uri.raw_path_segments()
.map(Segment::from)
.collect::<Vec<_>>();
let query_segs = self.uri.raw_query_segments()
.map(Segment::from)
.collect::<Vec<_>>();
Metadata {
static_path: path_segs.iter().all(|s| !s.dynamic),
wild_path: path_segs.iter().all(|s| s.dynamic)
&& path_segs.last().map_or(false, |p| p.trailing),
wild_query: query_segs.iter().all(|s| s.dynamic),
static_query_fields: query_segs.iter().filter(|s| !s.dynamic)
.map(|s| ValueField::parse(&s.value))
.map(|f| (f.name.source().to_string(), f.value.to_string()))
.collect(),
path_segs,
query_segs,
}
}
/// Updates the cached routing metadata. MUST be called whenver the route's
/// URI is set or changes.
fn update_metadata(&mut self) -> Result<(), RouteUriError> {
let new_metadata = Metadata::from(&*self)?;
self.metadata = new_metadata;
Ok(())
fn update_metadata(&mut self) {
self.metadata = self.metadata();
}
/// Retrieves the path of the base mount point of this route as an `&str`.
@ -272,7 +275,7 @@ impl Route {
let new_uri = format!("{}{}", self.base, self.path);
self.uri = Origin::parse_route(&new_uri)?.into_owned().into_normalized();
self.update_metadata()?;
self.update_metadata();
Ok(self)
}
}

View File

@ -0,0 +1,28 @@
use crate::http::RawStr;
#[derive(Debug, Clone)]
pub struct Segment {
pub value: String,
pub dynamic: bool,
pub trailing: bool,
}
impl Segment {
pub fn from(segment: &RawStr) -> Self {
let mut value = segment;
let mut dynamic = false;
let mut trailing = false;
if segment.starts_with('<') && segment.ends_with('>') {
dynamic = true;
value = &segment[1..(segment.len() - 1)];
if value.ends_with("..") {
trailing = true;
value = &value[..(value.len() - 2)];
}
}
Segment { value: value.to_string(), dynamic, trailing }
}
}

View File

@ -1515,8 +1515,7 @@ segment in its query string.
For example, the route below will match requests with path `/` and _at least_
the query segments `hello` and `cat=♥`:
```rust,ignore
# FIXME: https://github.com/rust-lang/rust/issues/82583
```rust
# #[macro_use] extern crate rocket;
#[get("/?hello&cat=♥")]
@ -1524,17 +1523,17 @@ fn cats() -> &'static str {
"Hello, kittens!"
}
// The following GET requests match `cats`.
// The following GET requests match `cats`. `%E2%99%A5` is encoded `♥`.
# let status = rocket_guide_tests::client(routes![cats]).get(
"/?cat%3D%E2%99%A5%26hello"
"/?cat=%E2%99%A5&hello"
# ).dispatch().status();
# assert_eq!(status, rocket::http::Status::Ok);
# let status = rocket_guide_tests::client(routes![cats]).get(
"/?hello&cat%3D%E2%99%A5%26"
"/?hello&cat=%E2%99%A5"
# ).dispatch().status();
# assert_eq!(status, rocket::http::Status::Ok);
# let status = rocket_guide_tests::client(routes![cats]).get(
"/?dogs=amazing&hello&there&cat%3D%E2%99%A5%26"
"/?dogs=amazing&hello&there&cat=%E2%99%A5"
# ).dispatch().status();
# assert_eq!(status, rocket::http::Status::Ok);
```

View File

@ -50,6 +50,8 @@ macro_rules! assert_form_parses_ok {
}
pub fn client(routes: Vec<rocket::Route>) -> rocket::local::blocking::Client {
let rocket = rocket::custom(rocket::Config::debug_default()).mount("/", routes);
let mut config = rocket::Config::debug_default();
config.log_level = rocket::config::LogLevel::Debug;
let rocket = rocket::custom(config).mount("/", routes);
rocket::local::blocking::Client::tracked(rocket).unwrap()
}