mirror of https://github.com/rwf2/Rocket.git
Fix typed URI generation for query reform.
This commit is contained in:
parent
61f107f550
commit
b9bf1ee37d
|
@ -21,7 +21,7 @@ proc-macro = true
|
||||||
|
|
||||||
[dependencies.derive_utils]
|
[dependencies.derive_utils]
|
||||||
git = "https://github.com/SergioBenitez/derive-utils"
|
git = "https://github.com/SergioBenitez/derive-utils"
|
||||||
rev = "87ad56ba"
|
rev = "f14fb4bc855"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = "0.6"
|
quote = "0.6"
|
||||||
|
|
|
@ -11,12 +11,7 @@ keywords = ["rocket", "web", "framework", "code", "generation"]
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[lib]
|
|
||||||
plugin = true
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket_http = { version = "0.4.0-dev", path = "../http" }
|
|
||||||
indexmap = "1.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
compiletest_rs = "0.3.14"
|
compiletest_rs = "0.3.14"
|
||||||
|
|
|
@ -1,387 +0,0 @@
|
||||||
#![crate_type = "dylib"]
|
|
||||||
|
|
||||||
// TODO: Version URLs.
|
|
||||||
#![doc(html_root_url = "https://api.rocket.rs")]
|
|
||||||
|
|
||||||
//! # Rocket - Code Generation
|
|
||||||
//!
|
|
||||||
//! This crate implements the code generation portions of Rocket. This includes
|
|
||||||
//! custom derives, custom attributes, and procedural macros. The documentation
|
|
||||||
//! here is purely technical. The code generation facilities are documented
|
|
||||||
//! thoroughly in the [Rocket programming guide](https://rocket.rs/guide).
|
|
||||||
//!
|
|
||||||
//! ## **Table of Contents**
|
|
||||||
//!
|
|
||||||
//! 1. [Custom Attributes](#custom-attributes)
|
|
||||||
//! 2. [Custom Derives](#custom-derives)
|
|
||||||
//! * [`FromForm`](#fromform)
|
|
||||||
//! * [`FromFormValue`](#fromformvalue)
|
|
||||||
//! * [`Responder`](#responder)
|
|
||||||
//! 3. [Procedural Macros](#procedural-macros)
|
|
||||||
//! 4. [Debugging Generated Code](#debugging-codegen)
|
|
||||||
//!
|
|
||||||
//! ## Custom Attributes
|
|
||||||
//!
|
|
||||||
//! This crate implements the following custom attributes:
|
|
||||||
//!
|
|
||||||
//! * **route**
|
|
||||||
//! * **get**
|
|
||||||
//! * **put**
|
|
||||||
//! * **post**
|
|
||||||
//! * **delete**
|
|
||||||
//! * **head**
|
|
||||||
//! * **patch**
|
|
||||||
//! * **options**
|
|
||||||
//! * **catch**
|
|
||||||
//!
|
|
||||||
//! The grammar for all _route_ attributes, including **route**, **get**,
|
|
||||||
//! **put**, **post**, **delete**, **head**, **patch**, and **options** is
|
|
||||||
//! defined as:
|
|
||||||
//!
|
|
||||||
//! <pre>
|
|
||||||
//! route := METHOD? '(' ('path' '=')? path (',' kv_param)* ')'
|
|
||||||
//!
|
|
||||||
//! path := URI_SEG
|
|
||||||
//! | DYNAMIC_PARAM
|
|
||||||
//! | '?' DYNAMIC_PARAM
|
|
||||||
//! | path '/' path
|
|
||||||
//! (string literal)
|
|
||||||
//!
|
|
||||||
//! kv_param := 'rank' '=' INTEGER
|
|
||||||
//! | 'format' '=' STRING
|
|
||||||
//! | 'data' '=' DYNAMIC_PARAM
|
|
||||||
//!
|
|
||||||
//! INTEGER := isize, as defined by Rust
|
|
||||||
//! STRING := UTF-8 string literal, as defined by Rust
|
|
||||||
//! IDENT := valid identifier, as defined by Rust
|
|
||||||
//!
|
|
||||||
//! URI_SEG := valid HTTP URI Segment
|
|
||||||
//! DYNAMIC_PARAM := '<' IDENT '..'? '>' (string literal)
|
|
||||||
//! </pre>
|
|
||||||
//!
|
|
||||||
//! Note that the **route** attribute takes a method as its first argument,
|
|
||||||
//! while the remaining do not. That is, **route** looks like:
|
|
||||||
//!
|
|
||||||
//! #[route(GET, path = "/hello")]
|
|
||||||
//!
|
|
||||||
//! while the equivalent using **get** looks like:
|
|
||||||
//!
|
|
||||||
//! #[get("/hello")]
|
|
||||||
//!
|
|
||||||
//! The syntax for the **catch** attribute is:
|
|
||||||
//!
|
|
||||||
//! <pre>
|
|
||||||
//! catch := INTEGER
|
|
||||||
//! </pre>
|
|
||||||
//!
|
|
||||||
//! A use of the `catch` attribute looks like:
|
|
||||||
//!
|
|
||||||
//! #[catch(404)]
|
|
||||||
//!
|
|
||||||
//! ## Custom Derives
|
|
||||||
//!
|
|
||||||
//! This crate* implements the following custom derives:
|
|
||||||
//!
|
|
||||||
//! * **FromForm**
|
|
||||||
//! * **FromFormValue**
|
|
||||||
//! * **Responder**
|
|
||||||
//!
|
|
||||||
//! <small>* In reality, all of these custom derives are currently implemented
|
|
||||||
//! by the `rocket_codegen_next` crate. Nonetheless, they are documented
|
|
||||||
//! here.</small>
|
|
||||||
//! ### `FromForm`
|
|
||||||
//!
|
|
||||||
//! The [`FromForm`] derive can be applied to structures with named fields:
|
|
||||||
//!
|
|
||||||
//! #[derive(FromForm)]
|
|
||||||
//! struct MyStruct {
|
|
||||||
//! field: usize,
|
|
||||||
//! other: String
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! Each field's type is required to implement [`FromFormValue`].
|
|
||||||
//!
|
|
||||||
//! The derive accepts one field attribute: `form`, with the following syntax:
|
|
||||||
//!
|
|
||||||
//! <pre>
|
|
||||||
//! form := 'field' '=' '"' IDENT '"'
|
|
||||||
//!
|
|
||||||
//! IDENT := valid identifier, as defined by Rust
|
|
||||||
//! </pre>
|
|
||||||
//!
|
|
||||||
//! When applied, the attribute looks as follows:
|
|
||||||
//!
|
|
||||||
//! #[derive(FromForm)]
|
|
||||||
//! struct MyStruct {
|
|
||||||
//! field: usize,
|
|
||||||
//! #[form(field = "renamed_field")]
|
|
||||||
//! other: String
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! The derive generates an implementation for the [`FromForm`] trait. The
|
|
||||||
//! implementation parses a form whose field names match the field names of the
|
|
||||||
//! structure on which the derive was applied. Each field's value is parsed with
|
|
||||||
//! the [`FromFormValue`] implementation of the field's type. The `FromForm`
|
|
||||||
//! implementation succeeds only when all of the field parses succeed.
|
|
||||||
//!
|
|
||||||
//! The `form` field attribute can be used to direct that a different incoming
|
|
||||||
//! field name is expected. In this case, the `field` name in the attribute is
|
|
||||||
//! used instead of the structure's actual field name when parsing a form.
|
|
||||||
//!
|
|
||||||
//! [`FromForm`]: /rocket/request/trait.FromForm.html
|
|
||||||
//! [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
|
|
||||||
//!
|
|
||||||
//! ### `FromFormValue`
|
|
||||||
//!
|
|
||||||
//! The [`FromFormValue`] derive can be applied to enums with nullary
|
|
||||||
//! (zero-length) fields:
|
|
||||||
//!
|
|
||||||
//! #[derive(FromFormValue)]
|
|
||||||
//! enum MyValue {
|
|
||||||
//! First,
|
|
||||||
//! Second,
|
|
||||||
//! Third,
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! The derive generates an implementation of the [`FromFormValue`] trait for
|
|
||||||
//! the decorated `enum`. The implementation returns successfully when the form
|
|
||||||
//! value matches, case insensitively, the stringified version of a variant's
|
|
||||||
//! name, returning an instance of said variant.
|
|
||||||
//!
|
|
||||||
//! As an example, for the `enum` above, the form values `"first"`, `"FIRST"`,
|
|
||||||
//! `"fiRSt"`, and so on would parse as `MyValue::First`, while `"second"` and
|
|
||||||
//! `"third"` would parse as `MyValue::Second` and `MyValue::Third`,
|
|
||||||
//! respectively.
|
|
||||||
//!
|
|
||||||
//! The `form` field attribute can be used to change the string that is compared
|
|
||||||
//! against for a given variant:
|
|
||||||
//!
|
|
||||||
//! #[derive(FromFormValue)]
|
|
||||||
//! enum MyValue {
|
|
||||||
//! First,
|
|
||||||
//! Second,
|
|
||||||
//! #[form(value = "fourth")]
|
|
||||||
//! Third,
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! The attribute's grammar is:
|
|
||||||
//!
|
|
||||||
//! <pre>
|
|
||||||
//! form := 'field' '=' STRING_LIT
|
|
||||||
//!
|
|
||||||
//! STRING_LIT := any valid string literal, as defined by Rust
|
|
||||||
//! </pre>
|
|
||||||
//!
|
|
||||||
//! The attribute accepts a single string parameter of name `value`
|
|
||||||
//! corresponding to the string to use to match against for the decorated
|
|
||||||
//! variant. In the example above, the the strings `"fourth"`, `"FOUrth"` and so
|
|
||||||
//! on would parse as `MyValue::Third`.
|
|
||||||
//!
|
|
||||||
//! ## `Responder`
|
|
||||||
//!
|
|
||||||
//! The [`Responder`] derive can be applied to enums and named structs. When
|
|
||||||
//! applied to enums, variants must have at least one field. When applied to
|
|
||||||
//! structs, the struct must have at least one field.
|
|
||||||
//!
|
|
||||||
//! #[derive(Responder)]
|
|
||||||
//! enum MyResponder {
|
|
||||||
//! A(String),
|
|
||||||
//! B(OtherResponse, ContentType),
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! #[derive(Responder)]
|
|
||||||
//! struct MyResponder {
|
|
||||||
//! inner: OtherResponder,
|
|
||||||
//! header: ContentType,
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! The derive generates an implementation of the [`Responder`] trait for the
|
|
||||||
//! decorated enum or structure. The derive uses the _first_ field of a variant
|
|
||||||
//! or structure to generate a `Response`. As such, the type of the first field
|
|
||||||
//! must implement [`Responder`]. The remaining fields of a variant or structure
|
|
||||||
//! are set as headers in the produced [`Response`] using
|
|
||||||
//! [`Response::set_header()`]. As such, every other field (unless explicitly
|
|
||||||
//! ignored, explained next) must implement `Into<Header>`.
|
|
||||||
//!
|
|
||||||
//! Except for the first field, fields decorated with `#[response(ignore)]` are
|
|
||||||
//! ignored by the derive:
|
|
||||||
//!
|
|
||||||
//! #[derive(Responder)]
|
|
||||||
//! enum MyResponder {
|
|
||||||
//! A(String),
|
|
||||||
//! B(OtherResponse, ContentType, #[response(ignore)] Other),
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! #[derive(Responder)]
|
|
||||||
//! struct MyResponder {
|
|
||||||
//! inner: InnerResponder,
|
|
||||||
//! header: ContentType,
|
|
||||||
//! #[response(ignore)]
|
|
||||||
//! other: Other,
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! Decorating the first field with `#[response(ignore)]` has no effect.
|
|
||||||
//!
|
|
||||||
//! Additionally, the `response` attribute can be used on named structures and
|
|
||||||
//! enum variants to override the status and/or content-type of the [`Response`]
|
|
||||||
//! produced by the generated implementation. The `response` attribute used in
|
|
||||||
//! these positions has the following grammar:
|
|
||||||
//!
|
|
||||||
//! <pre>
|
|
||||||
//! response := parameter (',' parameter)?
|
|
||||||
//!
|
|
||||||
//! parameter := 'status' '=' STATUS
|
|
||||||
//! | 'content_type' '=' CONTENT_TYPE
|
|
||||||
//!
|
|
||||||
//! STATUS := unsigned integer >= 100 and < 600
|
|
||||||
//! CONTENT_TYPE := string literal, as defined by Rust, identifying a valid
|
|
||||||
//! Content-Type, as defined by Rocket
|
|
||||||
//! </pre>
|
|
||||||
//!
|
|
||||||
//! It can be used as follows:
|
|
||||||
//!
|
|
||||||
//! #[derive(Responder)]
|
|
||||||
//! enum Error {
|
|
||||||
//! #[response(status = 500, content_type = "json")]
|
|
||||||
//! A(String),
|
|
||||||
//! #[response(status = 404)]
|
|
||||||
//! B(OtherResponse, ContentType),
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! #[derive(Responder)]
|
|
||||||
//! #[response(status = 400)]
|
|
||||||
//! struct MyResponder {
|
|
||||||
//! inner: InnerResponder,
|
|
||||||
//! header: ContentType,
|
|
||||||
//! #[response(ignore)]
|
|
||||||
//! other: Other,
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! The attribute accepts two key/value pairs: `status` and `content_type`. The
|
|
||||||
//! value of `status` must be an unsigned integer representing a valid status
|
|
||||||
//! code. The [`Response`] produced from the generated implementation will have
|
|
||||||
//! its status overriden to this value.
|
|
||||||
//!
|
|
||||||
//! The value of `content_type` must be a valid media-type in `top/sub` form or
|
|
||||||
//! `shorthand` form. Examples include:
|
|
||||||
//!
|
|
||||||
//! * `"text/html"`
|
|
||||||
//! * `"application/x-custom"`
|
|
||||||
//! * `"html"`
|
|
||||||
//! * `"json"`
|
|
||||||
//! * `"plain"`
|
|
||||||
//! * `"binary"`
|
|
||||||
//!
|
|
||||||
//! The [`Response`] produced from the generated implementation will have its
|
|
||||||
//! content-type overriden to this value.
|
|
||||||
//!
|
|
||||||
//! [`Responder`]: /rocket/response/trait.Responder.html
|
|
||||||
//! [`Response`]: /rocket/struct.Response.html
|
|
||||||
//! [`Response::set_header()`]: /rocket/struct.Response.html#method.set_header
|
|
||||||
//!
|
|
||||||
//! ## Procedural Macros
|
|
||||||
//!
|
|
||||||
//! This crate implements the following procedural macros:
|
|
||||||
//!
|
|
||||||
//! * **routes**
|
|
||||||
//! * **catchers**
|
|
||||||
//! * **uri**
|
|
||||||
//!
|
|
||||||
//! The syntax for `routes!` and `catchers!` is defined as:
|
|
||||||
//!
|
|
||||||
//! <pre>
|
|
||||||
//! macro := PATH (',' PATH)*
|
|
||||||
//!
|
|
||||||
//! PATH := a path, as defined by Rust
|
|
||||||
//! </pre>
|
|
||||||
//!
|
|
||||||
//! ### Typed URIs: `uri!`
|
|
||||||
//!
|
|
||||||
//! The `uri!` macro creates a type-safe URI given a route and values for the
|
|
||||||
//! route's URI parameters. The inputs to the macro are the path to a route, a
|
|
||||||
//! colon, and one argument for each dynamic parameter (parameters in `<>`) in
|
|
||||||
//! the route's path.
|
|
||||||
//!
|
|
||||||
//! For example, for the following route:
|
|
||||||
//!
|
|
||||||
//! ```rust,ignore
|
|
||||||
//! #[get("/person/<name>/<age>")]
|
|
||||||
//! fn person(name: String, age: u8) -> String {
|
|
||||||
//! format!("Hello {}! You're {} years old.", name, age)
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! A URI can be created as follows:
|
|
||||||
//!
|
|
||||||
//! ```rust,ignore
|
|
||||||
//! // with unnamed parameters, in route path declaration order
|
|
||||||
//! let mike = uri!(person: "Mike", 28);
|
|
||||||
//!
|
|
||||||
//! // with named parameters, order irrelevant
|
|
||||||
//! let mike = uri!(person: name = "Mike", age = 28);
|
|
||||||
//! let mike = uri!(person: age = 28, name = "Mike");
|
|
||||||
//!
|
|
||||||
//! // with a specific mount-point
|
|
||||||
//! let mike = uri!("/api", person: name = "Mike", age = 28);
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! #### Grammar
|
|
||||||
//!
|
|
||||||
//! The grammar for the `uri!` macro is as follows:
|
|
||||||
//!
|
|
||||||
//! <pre>
|
|
||||||
//! uri := (mount ',')? PATH (':' params)?
|
|
||||||
//!
|
|
||||||
//! mount = STRING
|
|
||||||
//! params := unnamed | named
|
|
||||||
//! unnamed := EXPR (',' EXPR)*
|
|
||||||
//! named := IDENT = EXPR (',' named)?
|
|
||||||
//!
|
|
||||||
//! EXPR := a valid Rust expression (examples: `foo()`, `12`, `"hey"`)
|
|
||||||
//! IDENT := a valid Rust identifier (examples: `name`, `age`)
|
|
||||||
//! STRING := an uncooked string literal, as defined by Rust (example: `"hi"`)
|
|
||||||
//! PATH := a path, as defined by Rust (examples: `route`, `my_mod::route`)
|
|
||||||
//! </pre>
|
|
||||||
//!
|
|
||||||
//! #### Semantics
|
|
||||||
//!
|
|
||||||
//! The `uri!` macro returns an [`Origin`](rocket::uri::Origin) structure with
|
|
||||||
//! the URI of the supplied route interpolated with the given values. Note that
|
|
||||||
//! `Origin` implements `Into<Uri>` (and by extension, `TryInto<Uri>`), so it
|
|
||||||
//! can be converted into a [`Uri`](rocket::uri::Uri) using `.into()` as needed.
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! A `uri!` invocation only typechecks if the type of every value in the
|
|
||||||
//! invocation matches the type declared for the parameter in the given route.
|
|
||||||
//! The [`FromUriParam`] trait is used to typecheck and perform a conversion for
|
|
||||||
//! each value. If a `FromUriParam<S>` implementation exists for a type `T`,
|
|
||||||
//! then a value of type `S` can be used in `uri!` macro for a route URI
|
|
||||||
//! parameter declared with a type of `T`. For example, the following
|
|
||||||
//! implementation, provided by Rocket, allows an `&str` to be used in a `uri!`
|
|
||||||
//! invocation for route URI parameters declared as `String`:
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! impl<'a> FromUriParam<&'a str> for String
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Each value passed into `uri!` is rendered in its appropriate place in the
|
|
||||||
//! URI using the [`UriDisplay`] implementation for the value's type. The
|
|
||||||
//! `UriDisplay` implementation ensures that the rendered value is URI-safe.
|
|
||||||
//!
|
|
||||||
//! If a mount-point is provided, the mount-point is prepended to the route's
|
|
||||||
//! URI.
|
|
||||||
//!
|
|
||||||
//! [`Uri`]: /rocket/http/uri/struct.URI.html
|
|
||||||
//! [`FromUriParam`]: /rocket/http/uri/trait.FromUriParam.html
|
|
||||||
//! [`UriDisplay`]: /rocket/http/uri/trait.UriDisplay.html
|
|
||||||
//!
|
|
||||||
//! # Debugging Codegen
|
|
||||||
//!
|
|
||||||
//! When the `ROCKET_CODEGEN_DEBUG` environment variable is set, this crate logs
|
|
||||||
//! the items it has generated to the console at compile-time. For example, you
|
|
||||||
//! might run the following to build a Rocket application with codegen logging
|
|
||||||
//! enabled:
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! ROCKET_CODEGEN_DEBUG=1 cargo build
|
|
||||||
//! ```
|
|
|
@ -1,18 +0,0 @@
|
||||||
use syntax::ast::Expr;
|
|
||||||
use syntax::ast::ExprKind::*;
|
|
||||||
|
|
||||||
pub trait ExprExt {
|
|
||||||
fn is_location(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExprExt for Expr {
|
|
||||||
fn is_location(&self) -> bool {
|
|
||||||
match self.node {
|
|
||||||
Path(..) => true,
|
|
||||||
Cast(ref expr, _) => expr.is_location(),
|
|
||||||
Field(ref expr, _) => expr.is_location(),
|
|
||||||
Index(ref expr, _) => expr.is_location(),
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
use std::fmt::Display;
|
|
||||||
use syntax::ast::Ident;
|
|
||||||
use syntax::symbol::Symbol;
|
|
||||||
|
|
||||||
pub trait IdentExt {
|
|
||||||
fn prepend<T: Display>(&self, other: T) -> Ident;
|
|
||||||
fn append<T: Display>(&self, other: T) -> Ident;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IdentExt for Ident {
|
|
||||||
fn prepend<T: Display>(&self, other: T) -> Ident {
|
|
||||||
let new_ident = format!("{}{}", other, self.name);
|
|
||||||
Ident::new(Symbol::intern(&new_ident), self.span)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append<T: Display>(&self, other: T) -> Ident {
|
|
||||||
let new_ident = format!("{}{}", self.name, other);
|
|
||||||
Ident::new(Symbol::intern(&new_ident), self.span)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
use syntax::source_map;
|
|
||||||
use syntax::parse::{token, SeqSep, PResult};
|
|
||||||
use syntax::parse::parser::{PathStyle, Parser};
|
|
||||||
use syntax::parse::token::Token::{Eof, Comma};
|
|
||||||
use syntax::ast::{self, Path, StrStyle, Ident};
|
|
||||||
use syntax::symbol::Symbol;
|
|
||||||
|
|
||||||
pub trait ParserExt<'a> {
|
|
||||||
// Parse a comma-seperated list of paths: `a::b, b::c`.
|
|
||||||
fn parse_paths(&mut self) -> PResult<'a, Vec<Path>>;
|
|
||||||
|
|
||||||
// Just like `parse_str` but takes into account interpolated expressions.
|
|
||||||
fn parse_str_lit(&mut self) -> PResult<'a, (Symbol, StrStyle)>;
|
|
||||||
|
|
||||||
// Like `parse_ident` but also looks for an `ident` in a `Pat`.
|
|
||||||
fn parse_ident_inc_pat(&mut self) -> PResult<'a, Ident>;
|
|
||||||
|
|
||||||
// Duplicates previously removed method in libsyntax.
|
|
||||||
fn parse_seq<T, F>(&mut self, bra: &token::Token, ket: &token::Token, sep: SeqSep, f: F)
|
|
||||||
-> PResult<'a, source_map::Spanned<Vec<T>>> where F: FnMut(&mut Parser<'a>) -> PResult<'a, T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ParserExt<'a> for Parser<'a> {
|
|
||||||
fn parse_paths(&mut self) -> PResult<'a, Vec<Path>> {
|
|
||||||
self.parse_seq_to_end(&Eof,
|
|
||||||
SeqSep::trailing_allowed(Comma),
|
|
||||||
|p| p.parse_path(PathStyle::Mod))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_str_lit(&mut self) -> PResult<'a, (Symbol, StrStyle)> {
|
|
||||||
self.parse_str()
|
|
||||||
.or_else(|mut e| {
|
|
||||||
let expr = self.parse_expr().map_err(|i| { e.cancel(); i })?;
|
|
||||||
let string_lit = match expr.node {
|
|
||||||
ast::ExprKind::Lit(ref lit) => match lit.node {
|
|
||||||
ast::LitKind::Str(symbol, style) => (symbol, style),
|
|
||||||
_ => return Err(e)
|
|
||||||
}
|
|
||||||
_ => return Err(e)
|
|
||||||
};
|
|
||||||
|
|
||||||
e.cancel();
|
|
||||||
Ok(string_lit)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_ident_inc_pat(&mut self) -> PResult<'a, Ident> {
|
|
||||||
self.parse_ident()
|
|
||||||
.or_else(|mut e| {
|
|
||||||
let pat = self.parse_pat().map_err(|i| { e.cancel(); i })?;
|
|
||||||
let ident = match pat.node {
|
|
||||||
ast::PatKind::Ident(_, ident, _) => ident,
|
|
||||||
_ => return Err(e)
|
|
||||||
};
|
|
||||||
|
|
||||||
e.cancel();
|
|
||||||
Ok(ident)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duplicates previously removed method in libsyntax. NB: Do not use this
|
|
||||||
// function unless you actually plan to place the spanned list in the AST.
|
|
||||||
fn parse_seq<T, F>(
|
|
||||||
&mut self,
|
|
||||||
bra: &token::Token,
|
|
||||||
ket: &token::Token,
|
|
||||||
sep: SeqSep,
|
|
||||||
f: F
|
|
||||||
) -> PResult<'a, source_map::Spanned<Vec<T>>>
|
|
||||||
where F: FnMut(&mut Parser<'a>) -> PResult<'a, T>
|
|
||||||
{
|
|
||||||
let lo = self.span;
|
|
||||||
self.expect(bra)?;
|
|
||||||
let result = self.parse_seq_to_before_end(ket, sep, f)?;
|
|
||||||
let hi = self.span;
|
|
||||||
self.bump();
|
|
||||||
Ok(source_map::respan(lo.to(hi), result))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
use syntax::source_map::{Span, Spanned, BytePos};
|
|
||||||
|
|
||||||
pub trait SpanExt {
|
|
||||||
/// Trim the span on the left and right by `length`.
|
|
||||||
fn trim(self, length: u32) -> Span;
|
|
||||||
|
|
||||||
/// Trim the span on the left by `length`.
|
|
||||||
fn trim_left(self, length: usize) -> Span;
|
|
||||||
|
|
||||||
/// Trim the span on the right by `length`.
|
|
||||||
fn trim_right(self, length: usize) -> Span;
|
|
||||||
|
|
||||||
// Trim from the right so that the span is `length` in size.
|
|
||||||
fn shorten_to(self, to_length: usize) -> Span;
|
|
||||||
|
|
||||||
// Trim from the left so that the span is `length` in size.
|
|
||||||
fn shorten_upto(self, length: usize) -> Span;
|
|
||||||
|
|
||||||
// Wrap `T` into a `Spanned<T>` with `self` as the span.
|
|
||||||
fn wrap<T>(self, node: T) -> Spanned<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpanExt for Span {
|
|
||||||
fn trim_left(self, length: usize) -> Span {
|
|
||||||
self.with_lo(self.lo() + BytePos(length as u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trim_right(self, length: usize) -> Span {
|
|
||||||
self.with_hi(self.hi() - BytePos(length as u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shorten_to(self, to_length: usize) -> Span {
|
|
||||||
self.with_hi(self.lo() + BytePos(to_length as u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shorten_upto(self, length: usize) -> Span {
|
|
||||||
self.with_lo(self.hi() - BytePos(length as u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trim(self, length: u32) -> Span {
|
|
||||||
self.with_lo(self.lo() + BytePos(length))
|
|
||||||
.with_hi(self.hi() - BytePos(length))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrap<T>(self, node: T) -> Spanned<T> {
|
|
||||||
Spanned { node, span: self }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,12 +20,10 @@ proc-macro = true
|
||||||
indexmap = "1.0"
|
indexmap = "1.0"
|
||||||
quote = "0.6.1"
|
quote = "0.6.1"
|
||||||
rocket_http = { version = "0.4.0-dev", path = "../http/" }
|
rocket_http = { version = "0.4.0-dev", path = "../http/" }
|
||||||
indexmap = "1"
|
|
||||||
|
|
||||||
[dependencies.derive_utils]
|
[dependencies.derive_utils]
|
||||||
path = "/Users/sbenitez/Sync/Data/Projects/Snippets/derive-utils/lib"
|
git = "https://github.com/SergioBenitez/derive-utils"
|
||||||
# git = "https://github.com/SergioBenitez/derive-utils"
|
rev = "f14fb4bc855"
|
||||||
# rev = "87ad56ba"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rocket = { version = "0.4.0-dev", path = "../lib" }
|
rocket = { version = "0.4.0-dev", path = "../lib" }
|
||||||
|
|
|
@ -6,15 +6,15 @@ use proc_macro::{Span, Diagnostic};
|
||||||
use http::route::RouteSegment;
|
use http::route::RouteSegment;
|
||||||
use proc_macro_ext::{SpanExt, Diagnostics, PResult, DResult};
|
use proc_macro_ext::{SpanExt, Diagnostics, PResult, DResult};
|
||||||
|
|
||||||
pub use http::route::{Error, Kind, Source};
|
crate use http::route::{Error, Kind, Source};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Segment {
|
crate struct Segment {
|
||||||
pub span: Span,
|
crate span: Span,
|
||||||
pub kind: Kind,
|
crate kind: Kind,
|
||||||
pub source: Source,
|
crate source: Source,
|
||||||
pub name: String,
|
crate name: String,
|
||||||
pub index: Option<usize>,
|
crate index: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Segment {
|
impl Segment {
|
||||||
|
@ -22,14 +22,6 @@ impl Segment {
|
||||||
let (kind, source, index) = (segment.kind, segment.source, segment.index);
|
let (kind, source, index) = (segment.kind, segment.source, segment.index);
|
||||||
Segment { span, kind, source, index, name: segment.name.into_owned() }
|
Segment { span, kind, source, index, name: segment.name.into_owned() }
|
||||||
}
|
}
|
||||||
|
|
||||||
crate fn to_route_segment<'a>(&'a self) -> String {
|
|
||||||
match (self.source, self.kind) {
|
|
||||||
(_, Kind::Single) => format!("<{}>", self.name),
|
|
||||||
(_, Kind::Multi) => format!("<{}..>", self.name),
|
|
||||||
(_, Kind::Static) => self.name.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a syn::Ident> for Segment {
|
impl<'a> From<&'a syn::Ident> for Segment {
|
||||||
|
@ -58,18 +50,18 @@ impl Hash for Segment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subspan(needle: &str, haystack: &str, span: Span) -> Option<Span> {
|
fn subspan(needle: &str, haystack: &str, span: Span) -> Option<Span> {
|
||||||
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
|
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
|
||||||
let remaining = haystack.len() - (index + needle.len());
|
let remaining = haystack.len() - (index + needle.len());
|
||||||
span.trimmed(index, remaining)
|
span.trimmed(index, remaining)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trailspan(needle: &str, haystack: &str, span: Span) -> Option<Span> {
|
fn trailspan(needle: &str, haystack: &str, span: Span) -> Option<Span> {
|
||||||
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
|
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
|
||||||
span.trimmed(index - 1, 0)
|
span.trimmed(index - 1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_diagnostic(
|
fn into_diagnostic(
|
||||||
segment: &str, // The segment that failed.
|
segment: &str, // The segment that failed.
|
||||||
source: &str, // The haystack where `segment` can be found.
|
source: &str, // The haystack where `segment` can be found.
|
||||||
span: Span, // The `Span` of `Source`.
|
span: Span, // The `Span` of `Source`.
|
||||||
|
@ -111,13 +103,13 @@ pub fn into_diagnostic(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_segment(segment: &str, span: Span) -> PResult<Segment> {
|
crate fn parse_segment(segment: &str, span: Span) -> PResult<Segment> {
|
||||||
RouteSegment::parse_one(segment)
|
RouteSegment::parse_one(segment)
|
||||||
.map(|segment| Segment::from(segment, span))
|
.map(|segment| Segment::from(segment, span))
|
||||||
.map_err(|e| into_diagnostic(segment, segment, span, &e))
|
.map_err(|e| into_diagnostic(segment, segment, span, &e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_segments(
|
crate fn parse_segments(
|
||||||
string: &str,
|
string: &str,
|
||||||
sep: char,
|
sep: char,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
|
use std::fmt::Display;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use derive_utils::{syn, Result};
|
use derive_utils::{syn, Result};
|
||||||
use syn_ext::{IdentExt, syn_to_diag};
|
use derive_utils::syn::{Expr, Ident, Type, spanned::Spanned};
|
||||||
|
use http::{uri::Origin, ext::IntoOwned};
|
||||||
|
use http::route::{RouteSegment, Kind, Source};
|
||||||
|
|
||||||
use self::syn::{Expr, Ident, Type};
|
use http_codegen::Optional;
|
||||||
use self::syn::spanned::Spanned as SynSpanned;
|
use syn_ext::{IdentExt, syn_to_diag};
|
||||||
use bang::{prefix_last_segment, uri_parsing::*};
|
use bang::{prefix_last_segment, uri_parsing::*};
|
||||||
|
|
||||||
use rocket_http::{uri::Origin, ext::IntoOwned};
|
use URI_MACRO_PREFIX;
|
||||||
|
|
||||||
const URI_INFO_MACRO_PREFIX: &str = "rocket_uri_for_";
|
|
||||||
|
|
||||||
macro_rules! p {
|
macro_rules! p {
|
||||||
(@go $num:expr, $singular:expr, $plural:expr) => (
|
(@go $num:expr, $singular:expr, $plural:expr) => (
|
||||||
|
@ -26,7 +26,7 @@ macro_rules! p {
|
||||||
crate fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
|
crate fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
|
||||||
let input2: TokenStream2 = input.clone().into();
|
let input2: TokenStream2 = input.clone().into();
|
||||||
let mut params = syn::parse::<UriParams>(input).map_err(syn_to_diag)?;
|
let mut params = syn::parse::<UriParams>(input).map_err(syn_to_diag)?;
|
||||||
prefix_last_segment(&mut params.route_path, URI_INFO_MACRO_PREFIX);
|
prefix_last_segment(&mut params.route_path, URI_MACRO_PREFIX);
|
||||||
|
|
||||||
let path = ¶ms.route_path;
|
let path = ¶ms.route_path;
|
||||||
Ok(quote!(#path!(#input2)).into())
|
Ok(quote!(#path!(#input2)).into())
|
||||||
|
@ -80,21 +80,25 @@ fn extract_exprs(internal: &InternalUriParams) -> Result<Vec<&Expr>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates the mount path and the URI and returns a single Origin URI with
|
// Returns an Origin URI with the mount point and route path concatinated. The
|
||||||
// both paths concatinated. Validation should always succeed since this macro
|
// query string is mangled by replacing single dynamic parameters in query parts
|
||||||
// can only be called if the route attribute succeed, which implies that the
|
// (`<param>`) with `param=<param>`.
|
||||||
// route URI was valid.
|
fn build_origin(internal: &InternalUriParams) -> Origin<'static> {
|
||||||
fn extract_origin(internal: &InternalUriParams) -> Result<Origin<'static>> {
|
let mount_point = internal.uri_params.mount_point.as_ref()
|
||||||
let base_uri = match internal.uri_params.mount_point {
|
.map(|origin| origin.path())
|
||||||
Some(ref base) => Origin::parse(&base.value())
|
.unwrap_or("");
|
||||||
.map_err(|_| base.span().unstable().error("invalid path URI"))?
|
|
||||||
.into_owned(),
|
|
||||||
None => Origin::dummy()
|
|
||||||
};
|
|
||||||
|
|
||||||
Origin::parse_route(&format!("{}/{}", base_uri, internal.uri))
|
let path = format!("{}/{}", mount_point, internal.route_uri.path());
|
||||||
.map(|o| o.to_normalized().into_owned())
|
let query = RouteSegment::parse_query(&internal.route_uri).map(|segments| {
|
||||||
.map_err(|_| internal.uri.span().unstable().error("invalid route URI"))
|
segments.map(|r| r.expect("invalid query segment")).map(|seg| {
|
||||||
|
match (seg.source, seg.kind) {
|
||||||
|
(Source::Query, Kind::Single) => format!("{k}=<{k}>", k = seg.name),
|
||||||
|
_ => seg.string.into_owned()
|
||||||
|
}
|
||||||
|
}).collect::<Vec<_>>().join("&")
|
||||||
|
});
|
||||||
|
|
||||||
|
Origin::new(path, query).to_normalized().into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn explode<'a, I>(route_str: &str, items: I) -> TokenStream2
|
fn explode<'a, I>(route_str: &str, items: I) -> TokenStream2
|
||||||
|
@ -102,9 +106,7 @@ fn explode<'a, I>(route_str: &str, items: I) -> TokenStream2
|
||||||
{
|
{
|
||||||
// Generate the statements to typecheck each parameter.
|
// Generate the statements to typecheck each parameter.
|
||||||
// Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e).
|
// Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e).
|
||||||
let mut let_bindings = vec![];
|
let (mut let_bindings, mut fmt_exprs) = (vec![], vec![]);
|
||||||
let mut fmt_exprs = vec![];
|
|
||||||
|
|
||||||
for (mut ident, ty, expr) in items {
|
for (mut ident, ty, expr) in items {
|
||||||
let (span, expr) = (expr.span(), expr);
|
let (span, expr) = (expr.span(), expr);
|
||||||
let ident_tmp = ident.prepend("tmp_");
|
let ident_tmp = ident.prepend("tmp_");
|
||||||
|
@ -143,26 +145,17 @@ crate fn _uri_internal_macro(input: TokenStream) -> Result<TokenStream> {
|
||||||
// Parse the internal invocation and the user's URI param expressions.
|
// Parse the internal invocation and the user's URI param expressions.
|
||||||
let internal = syn::parse::<InternalUriParams>(input).map_err(syn_to_diag)?;
|
let internal = syn::parse::<InternalUriParams>(input).map_err(syn_to_diag)?;
|
||||||
let exprs = extract_exprs(&internal)?;
|
let exprs = extract_exprs(&internal)?;
|
||||||
let origin = extract_origin(&internal)?;
|
|
||||||
|
|
||||||
// Determine how many parameters there are in the URI path.
|
|
||||||
let path_param_count = origin.path().matches('<').count();
|
|
||||||
|
|
||||||
// Create an iterator over the `ident`, `ty`, and `expr` triple.
|
// Create an iterator over the `ident`, `ty`, and `expr` triple.
|
||||||
let mut arguments = internal.fn_args.iter()
|
let mut arguments = internal.fn_args.iter()
|
||||||
.zip(exprs.iter())
|
.zip(exprs.iter())
|
||||||
.map(|(FnArg { ident, ty }, &expr)| (ident, ty, expr));
|
.map(|(FnArg { ident, ty }, &expr)| (ident, ty, expr));
|
||||||
|
|
||||||
// Generate an expression for both the path and query.
|
// Generate an expression for the path and query.
|
||||||
|
let origin = build_origin(&internal);
|
||||||
|
let path_param_count = origin.path().matches('<').count();
|
||||||
let path = explode(origin.path(), arguments.by_ref().take(path_param_count));
|
let path = explode(origin.path(), arguments.by_ref().take(path_param_count));
|
||||||
|
let query = Optional(origin.query().map(|q| explode(q, arguments)));
|
||||||
// FIXME: Use Optional.
|
|
||||||
// let query = Optional(origin.query().map(|q| explode(q, arguments)));
|
|
||||||
let query = if let Some(expr) = origin.query().map(|q| explode(q, arguments)) {
|
|
||||||
quote!({ Some(#expr) })
|
|
||||||
} else {
|
|
||||||
quote!({ None })
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(quote!({
|
Ok(quote!({
|
||||||
::rocket::http::uri::Origin::new::<
|
::rocket::http::uri::Origin::new::<
|
||||||
|
|
|
@ -9,6 +9,7 @@ use self::syn::{Expr, Ident, LitStr, Path, Token, Type};
|
||||||
use self::syn::parse::{self, Parse, ParseStream};
|
use self::syn::parse::{self, Parse, ParseStream};
|
||||||
use self::syn::punctuated::Punctuated;
|
use self::syn::punctuated::Punctuated;
|
||||||
|
|
||||||
|
use http::{uri::Origin, ext::IntoOwned};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -30,7 +31,7 @@ pub enum Args {
|
||||||
// uri_params.route_path
|
// uri_params.route_path
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UriParams {
|
pub struct UriParams {
|
||||||
pub mount_point: Option<LitStr>,
|
pub mount_point: Option<Origin<'static>>,
|
||||||
pub route_path: Path,
|
pub route_path: Path,
|
||||||
pub arguments: Args,
|
pub arguments: Args,
|
||||||
}
|
}
|
||||||
|
@ -62,11 +63,11 @@ pub enum Validation<'a> {
|
||||||
// `uri` is the full URI used in the origin route's attribute.
|
// `uri` is the full URI used in the origin route's attribute.
|
||||||
//
|
//
|
||||||
// internal_uri!("/<first>/<second>", (first: ty, second: ty), $($tt)*);
|
// internal_uri!("/<first>/<second>", (first: ty, second: ty), $($tt)*);
|
||||||
// ^-----------------| ^-----------|---------| ^-----|
|
// ^--------|--------- ^-----------|---------| ^-----|
|
||||||
// uri fn_args uri_params
|
// route_uri fn_args uri_params
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InternalUriParams {
|
pub struct InternalUriParams {
|
||||||
pub uri: String,
|
pub route_uri: Origin<'static>,
|
||||||
pub fn_args: Vec<FnArg>,
|
pub fn_args: Vec<FnArg>,
|
||||||
pub uri_params: UriParams,
|
pub uri_params: UriParams,
|
||||||
}
|
}
|
||||||
|
@ -100,12 +101,11 @@ impl Parse for UriParams {
|
||||||
// Parse the mount point and suffixing ',', if any.
|
// Parse the mount point and suffixing ',', if any.
|
||||||
let mount_point = if input.peek(LitStr) {
|
let mount_point = if input.peek(LitStr) {
|
||||||
let string = input.parse::<LitStr>()?;
|
let string = input.parse::<LitStr>()?;
|
||||||
let value = string.value();
|
let mount_point = Origin::parse_owned(string.value()).map_err(|_| {
|
||||||
if value.contains('<') || !value.starts_with('/') {
|
// TODO(proc_macro): use error, add example as a help
|
||||||
// TODO(proc_macro): add example as a help, not in error
|
parse::Error::new(string.span(), "invalid mount point; \
|
||||||
return err(string.span().unstable(), "invalid mount point; \
|
mount points must be static, absolute URIs: `/example`")
|
||||||
mount points must be static, absolute URIs: `/example`");
|
})?;
|
||||||
}
|
|
||||||
|
|
||||||
if !input.peek(Token![,]) && input.cursor().eof() {
|
if !input.peek(Token![,]) && input.cursor().eof() {
|
||||||
return err(string.span().unstable(), "unexpected end of input: \
|
return err(string.span().unstable(), "unexpected end of input: \
|
||||||
|
@ -113,7 +113,7 @@ impl Parse for UriParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
Some(string)
|
Some(mount_point)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -171,9 +171,15 @@ impl Parse for FnArg {
|
||||||
|
|
||||||
impl Parse for InternalUriParams {
|
impl Parse for InternalUriParams {
|
||||||
fn parse(input: ParseStream) -> parse::Result<InternalUriParams> {
|
fn parse(input: ParseStream) -> parse::Result<InternalUriParams> {
|
||||||
let uri = input.parse::<LitStr>()?.value();
|
let route_uri_str = input.parse::<LitStr>()?;
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
|
|
||||||
|
// Validation should always succeed since this macro can only be called
|
||||||
|
// if the route attribute succeeded, implying a valid route URI.
|
||||||
|
let route_uri = Origin::parse_route(&route_uri_str.value())
|
||||||
|
.map(|o| o.to_normalized().into_owned())
|
||||||
|
.map_err(|_| input.error("internal error: invalid route URI"))?;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
syn::parenthesized!(content in input);
|
syn::parenthesized!(content in input);
|
||||||
let fn_args: Punctuated<FnArg, Token![,]> = content.parse_terminated(FnArg::parse)?;
|
let fn_args: Punctuated<FnArg, Token![,]> = content.parse_terminated(FnArg::parse)?;
|
||||||
|
@ -181,7 +187,7 @@ impl Parse for InternalUriParams {
|
||||||
|
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
let uri_params = input.parse::<UriParams>()?;
|
let uri_params = input.parse::<UriParams>()?;
|
||||||
Ok(InternalUriParams { uri, fn_args, uri_params })
|
Ok(InternalUriParams { route_uri, fn_args, uri_params })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,31 +7,31 @@ use attribute::segments::{parse_segments, parse_segment, Segment, Kind, Source};
|
||||||
use proc_macro_ext::SpanExt;
|
use proc_macro_ext::SpanExt;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ContentType(pub http::ContentType);
|
crate struct ContentType(crate http::ContentType);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Status(pub http::Status);
|
crate struct Status(crate http::Status);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MediaType(pub http::MediaType);
|
crate struct MediaType(crate http::MediaType);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Method(pub http::Method);
|
crate struct Method(crate http::Method);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Origin(pub http::uri::Origin<'static>);
|
crate struct Origin(crate http::uri::Origin<'static>);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DataSegment(pub Segment);
|
crate struct DataSegment(crate Segment);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Optional<T>(pub Option<T>);
|
crate struct Optional<T>(crate Option<T>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RoutePath {
|
crate struct RoutePath {
|
||||||
pub origin: Origin,
|
crate origin: Origin,
|
||||||
pub path: Vec<Segment>,
|
crate path: Vec<Segment>,
|
||||||
pub query: Option<Vec<Segment>>,
|
crate query: Option<Vec<Segment>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromMeta for Status {
|
impl FromMeta for Status {
|
||||||
|
|
|
@ -4,9 +4,391 @@
|
||||||
#![feature(rustc_private)]
|
#![feature(rustc_private)]
|
||||||
#![recursion_limit="128"]
|
#![recursion_limit="128"]
|
||||||
|
|
||||||
|
//! # Rocket - Code Generation
|
||||||
|
//!
|
||||||
|
//! This crate implements the code generation portions of Rocket. This includes
|
||||||
|
//! custom derives, custom attributes, and procedural macros. The documentation
|
||||||
|
//! here is purely technical. The code generation facilities are documented
|
||||||
|
//! thoroughly in the [Rocket programming guide](https://rocket.rs/guide).
|
||||||
|
//!
|
||||||
|
//! ## **Table of Contents**
|
||||||
|
//!
|
||||||
|
//! 1. [Custom Attributes](#custom-attributes)
|
||||||
|
//! 2. [Custom Derives](#custom-derives)
|
||||||
|
//! * [`FromForm`](#fromform)
|
||||||
|
//! * [`FromFormValue`](#fromformvalue)
|
||||||
|
//! * [`Responder`](#responder)
|
||||||
|
//! 3. [Procedural Macros](#procedural-macros)
|
||||||
|
//! 4. [Debugging Generated Code](#debugging-codegen)
|
||||||
|
//!
|
||||||
|
//! ## Custom Attributes
|
||||||
|
//!
|
||||||
|
//! This crate implements the following custom attributes:
|
||||||
|
//!
|
||||||
|
//! * **route**
|
||||||
|
//! * **get**
|
||||||
|
//! * **put**
|
||||||
|
//! * **post**
|
||||||
|
//! * **delete**
|
||||||
|
//! * **head**
|
||||||
|
//! * **patch**
|
||||||
|
//! * **options**
|
||||||
|
//! * **catch**
|
||||||
|
//!
|
||||||
|
//! The grammar for all _route_ attributes, including **route**, **get**,
|
||||||
|
//! **put**, **post**, **delete**, **head**, **patch**, and **options** is
|
||||||
|
//! defined as:
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! route := METHOD? '(' ('path' '=')? path (',' kv_param)* ')'
|
||||||
|
//!
|
||||||
|
//! path := URI_SEG
|
||||||
|
//! | DYNAMIC_PARAM
|
||||||
|
//! | '?' DYNAMIC_PARAM
|
||||||
|
//! | path '/' path
|
||||||
|
//! (string literal)
|
||||||
|
//!
|
||||||
|
//! kv_param := 'rank' '=' INTEGER
|
||||||
|
//! | 'format' '=' STRING
|
||||||
|
//! | 'data' '=' DYNAMIC_PARAM
|
||||||
|
//!
|
||||||
|
//! INTEGER := isize, as defined by Rust
|
||||||
|
//! STRING := UTF-8 string literal, as defined by Rust
|
||||||
|
//! IDENT := valid identifier, as defined by Rust
|
||||||
|
//!
|
||||||
|
//! URI_SEG := valid HTTP URI Segment
|
||||||
|
//! DYNAMIC_PARAM := '<' IDENT '..'? '>' (string literal)
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! Note that the **route** attribute takes a method as its first argument,
|
||||||
|
//! while the remaining do not. That is, **route** looks like:
|
||||||
|
//!
|
||||||
|
//! #[route(GET, path = "/hello")]
|
||||||
|
//!
|
||||||
|
//! while the equivalent using **get** looks like:
|
||||||
|
//!
|
||||||
|
//! #[get("/hello")]
|
||||||
|
//!
|
||||||
|
//! The syntax for the **catch** attribute is:
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! catch := INTEGER
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! A use of the `catch` attribute looks like:
|
||||||
|
//!
|
||||||
|
//! #[catch(404)]
|
||||||
|
//!
|
||||||
|
//! ## Custom Derives
|
||||||
|
//!
|
||||||
|
//! This crate* implements the following custom derives:
|
||||||
|
//!
|
||||||
|
//! * **FromForm**
|
||||||
|
//! * **FromFormValue**
|
||||||
|
//! * **Responder**
|
||||||
|
//!
|
||||||
|
//! <small>* In reality, all of these custom derives are currently implemented
|
||||||
|
//! by the `rocket_codegen_next` crate. Nonetheless, they are documented
|
||||||
|
//! here.</small>
|
||||||
|
//! ### `FromForm`
|
||||||
|
//!
|
||||||
|
//! The [`FromForm`] derive can be applied to structures with named fields:
|
||||||
|
//!
|
||||||
|
//! #[derive(FromForm)]
|
||||||
|
//! struct MyStruct {
|
||||||
|
//! field: usize,
|
||||||
|
//! other: String
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! Each field's type is required to implement [`FromFormValue`].
|
||||||
|
//!
|
||||||
|
//! The derive accepts one field attribute: `form`, with the following syntax:
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! form := 'field' '=' '"' IDENT '"'
|
||||||
|
//!
|
||||||
|
//! IDENT := valid identifier, as defined by Rust
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! When applied, the attribute looks as follows:
|
||||||
|
//!
|
||||||
|
//! #[derive(FromForm)]
|
||||||
|
//! struct MyStruct {
|
||||||
|
//! field: usize,
|
||||||
|
//! #[form(field = "renamed_field")]
|
||||||
|
//! other: String
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! The derive generates an implementation for the [`FromForm`] trait. The
|
||||||
|
//! implementation parses a form whose field names match the field names of the
|
||||||
|
//! structure on which the derive was applied. Each field's value is parsed with
|
||||||
|
//! the [`FromFormValue`] implementation of the field's type. The `FromForm`
|
||||||
|
//! implementation succeeds only when all of the field parses succeed.
|
||||||
|
//!
|
||||||
|
//! The `form` field attribute can be used to direct that a different incoming
|
||||||
|
//! field name is expected. In this case, the `field` name in the attribute is
|
||||||
|
//! used instead of the structure's actual field name when parsing a form.
|
||||||
|
//!
|
||||||
|
//! [`FromForm`]: /rocket/request/trait.FromForm.html
|
||||||
|
//! [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
|
||||||
|
//!
|
||||||
|
//! ### `FromFormValue`
|
||||||
|
//!
|
||||||
|
//! The [`FromFormValue`] derive can be applied to enums with nullary
|
||||||
|
//! (zero-length) fields:
|
||||||
|
//!
|
||||||
|
//! #[derive(FromFormValue)]
|
||||||
|
//! enum MyValue {
|
||||||
|
//! First,
|
||||||
|
//! Second,
|
||||||
|
//! Third,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! The derive generates an implementation of the [`FromFormValue`] trait for
|
||||||
|
//! the decorated `enum`. The implementation returns successfully when the form
|
||||||
|
//! value matches, case insensitively, the stringified version of a variant's
|
||||||
|
//! name, returning an instance of said variant.
|
||||||
|
//!
|
||||||
|
//! As an example, for the `enum` above, the form values `"first"`, `"FIRST"`,
|
||||||
|
//! `"fiRSt"`, and so on would parse as `MyValue::First`, while `"second"` and
|
||||||
|
//! `"third"` would parse as `MyValue::Second` and `MyValue::Third`,
|
||||||
|
//! respectively.
|
||||||
|
//!
|
||||||
|
//! The `form` field attribute can be used to change the string that is compared
|
||||||
|
//! against for a given variant:
|
||||||
|
//!
|
||||||
|
//! #[derive(FromFormValue)]
|
||||||
|
//! enum MyValue {
|
||||||
|
//! First,
|
||||||
|
//! Second,
|
||||||
|
//! #[form(value = "fourth")]
|
||||||
|
//! Third,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! The attribute's grammar is:
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! form := 'field' '=' STRING_LIT
|
||||||
|
//!
|
||||||
|
//! STRING_LIT := any valid string literal, as defined by Rust
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! The attribute accepts a single string parameter of name `value`
|
||||||
|
//! corresponding to the string to use to match against for the decorated
|
||||||
|
//! variant. In the example above, the the strings `"fourth"`, `"FOUrth"` and so
|
||||||
|
//! on would parse as `MyValue::Third`.
|
||||||
|
//!
|
||||||
|
//! ## `Responder`
|
||||||
|
//!
|
||||||
|
//! The [`Responder`] derive can be applied to enums and named structs. When
|
||||||
|
//! applied to enums, variants must have at least one field. When applied to
|
||||||
|
//! structs, the struct must have at least one field.
|
||||||
|
//!
|
||||||
|
//! #[derive(Responder)]
|
||||||
|
//! enum MyResponder {
|
||||||
|
//! A(String),
|
||||||
|
//! B(OtherResponse, ContentType),
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(Responder)]
|
||||||
|
//! struct MyResponder {
|
||||||
|
//! inner: OtherResponder,
|
||||||
|
//! header: ContentType,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! The derive generates an implementation of the [`Responder`] trait for the
|
||||||
|
//! decorated enum or structure. The derive uses the _first_ field of a variant
|
||||||
|
//! or structure to generate a `Response`. As such, the type of the first field
|
||||||
|
//! must implement [`Responder`]. The remaining fields of a variant or structure
|
||||||
|
//! are set as headers in the produced [`Response`] using
|
||||||
|
//! [`Response::set_header()`]. As such, every other field (unless explicitly
|
||||||
|
//! ignored, explained next) must implement `Into<Header>`.
|
||||||
|
//!
|
||||||
|
//! Except for the first field, fields decorated with `#[response(ignore)]` are
|
||||||
|
//! ignored by the derive:
|
||||||
|
//!
|
||||||
|
//! #[derive(Responder)]
|
||||||
|
//! enum MyResponder {
|
||||||
|
//! A(String),
|
||||||
|
//! B(OtherResponse, ContentType, #[response(ignore)] Other),
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(Responder)]
|
||||||
|
//! struct MyResponder {
|
||||||
|
//! inner: InnerResponder,
|
||||||
|
//! header: ContentType,
|
||||||
|
//! #[response(ignore)]
|
||||||
|
//! other: Other,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! Decorating the first field with `#[response(ignore)]` has no effect.
|
||||||
|
//!
|
||||||
|
//! Additionally, the `response` attribute can be used on named structures and
|
||||||
|
//! enum variants to override the status and/or content-type of the [`Response`]
|
||||||
|
//! produced by the generated implementation. The `response` attribute used in
|
||||||
|
//! these positions has the following grammar:
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! response := parameter (',' parameter)?
|
||||||
|
//!
|
||||||
|
//! parameter := 'status' '=' STATUS
|
||||||
|
//! | 'content_type' '=' CONTENT_TYPE
|
||||||
|
//!
|
||||||
|
//! STATUS := unsigned integer >= 100 and < 600
|
||||||
|
//! CONTENT_TYPE := string literal, as defined by Rust, identifying a valid
|
||||||
|
//! Content-Type, as defined by Rocket
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! It can be used as follows:
|
||||||
|
//!
|
||||||
|
//! #[derive(Responder)]
|
||||||
|
//! enum Error {
|
||||||
|
//! #[response(status = 500, content_type = "json")]
|
||||||
|
//! A(String),
|
||||||
|
//! #[response(status = 404)]
|
||||||
|
//! B(OtherResponse, ContentType),
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(Responder)]
|
||||||
|
//! #[response(status = 400)]
|
||||||
|
//! struct MyResponder {
|
||||||
|
//! inner: InnerResponder,
|
||||||
|
//! header: ContentType,
|
||||||
|
//! #[response(ignore)]
|
||||||
|
//! other: Other,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! The attribute accepts two key/value pairs: `status` and `content_type`. The
|
||||||
|
//! value of `status` must be an unsigned integer representing a valid status
|
||||||
|
//! code. The [`Response`] produced from the generated implementation will have
|
||||||
|
//! its status overriden to this value.
|
||||||
|
//!
|
||||||
|
//! The value of `content_type` must be a valid media-type in `top/sub` form or
|
||||||
|
//! `shorthand` form. Examples include:
|
||||||
|
//!
|
||||||
|
//! * `"text/html"`
|
||||||
|
//! * `"application/x-custom"`
|
||||||
|
//! * `"html"`
|
||||||
|
//! * `"json"`
|
||||||
|
//! * `"plain"`
|
||||||
|
//! * `"binary"`
|
||||||
|
//!
|
||||||
|
//! The [`Response`] produced from the generated implementation will have its
|
||||||
|
//! content-type overriden to this value.
|
||||||
|
//!
|
||||||
|
//! [`Responder`]: /rocket/response/trait.Responder.html
|
||||||
|
//! [`Response`]: /rocket/struct.Response.html
|
||||||
|
//! [`Response::set_header()`]: /rocket/struct.Response.html#method.set_header
|
||||||
|
//!
|
||||||
|
//! ## Procedural Macros
|
||||||
|
//!
|
||||||
|
//! This crate implements the following procedural macros:
|
||||||
|
//!
|
||||||
|
//! * **routes**
|
||||||
|
//! * **catchers**
|
||||||
|
//! * **uri**
|
||||||
|
//!
|
||||||
|
//! The syntax for `routes!` and `catchers!` is defined as:
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! macro := PATH (',' PATH)*
|
||||||
|
//!
|
||||||
|
//! PATH := a path, as defined by Rust
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! ### Typed URIs: `uri!`
|
||||||
|
//!
|
||||||
|
//! The `uri!` macro creates a type-safe URI given a route and values for the
|
||||||
|
//! route's URI parameters. The inputs to the macro are the path to a route, a
|
||||||
|
//! colon, and one argument for each dynamic parameter (parameters in `<>`) in
|
||||||
|
//! the route's path.
|
||||||
|
//!
|
||||||
|
//! For example, for the following route:
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! #[get("/person/<name>/<age>")]
|
||||||
|
//! fn person(name: String, age: u8) -> String {
|
||||||
|
//! format!("Hello {}! You're {} years old.", name, age)
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! A URI can be created as follows:
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! // with unnamed parameters, in route path declaration order
|
||||||
|
//! let mike = uri!(person: "Mike", 28);
|
||||||
|
//!
|
||||||
|
//! // with named parameters, order irrelevant
|
||||||
|
//! let mike = uri!(person: name = "Mike", age = 28);
|
||||||
|
//! let mike = uri!(person: age = 28, name = "Mike");
|
||||||
|
//!
|
||||||
|
//! // with a specific mount-point
|
||||||
|
//! let mike = uri!("/api", person: name = "Mike", age = 28);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! #### Grammar
|
||||||
|
//!
|
||||||
|
//! The grammar for the `uri!` macro is as follows:
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! uri := (mount ',')? PATH (':' params)?
|
||||||
|
//!
|
||||||
|
//! mount = STRING
|
||||||
|
//! params := unnamed | named
|
||||||
|
//! unnamed := EXPR (',' EXPR)*
|
||||||
|
//! named := IDENT = EXPR (',' named)?
|
||||||
|
//!
|
||||||
|
//! EXPR := a valid Rust expression (examples: `foo()`, `12`, `"hey"`)
|
||||||
|
//! IDENT := a valid Rust identifier (examples: `name`, `age`)
|
||||||
|
//! STRING := an uncooked string literal, as defined by Rust (example: `"hi"`)
|
||||||
|
//! PATH := a path, as defined by Rust (examples: `route`, `my_mod::route`)
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! #### Semantics
|
||||||
|
//!
|
||||||
|
//! The `uri!` macro returns an [`Origin`](rocket::uri::Origin) structure with
|
||||||
|
//! the URI of the supplied route interpolated with the given values. Note that
|
||||||
|
//! `Origin` implements `Into<Uri>` (and by extension, `TryInto<Uri>`), so it
|
||||||
|
//! can be converted into a [`Uri`](rocket::uri::Uri) using `.into()` as needed.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! A `uri!` invocation only typechecks if the type of every value in the
|
||||||
|
//! invocation matches the type declared for the parameter in the given route.
|
||||||
|
//! The [`FromUriParam`] trait is used to typecheck and perform a conversion for
|
||||||
|
//! each value. If a `FromUriParam<S>` implementation exists for a type `T`,
|
||||||
|
//! then a value of type `S` can be used in `uri!` macro for a route URI
|
||||||
|
//! parameter declared with a type of `T`. For example, the following
|
||||||
|
//! implementation, provided by Rocket, allows an `&str` to be used in a `uri!`
|
||||||
|
//! invocation for route URI parameters declared as `String`:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! impl<'a> FromUriParam<&'a str> for String
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Each value passed into `uri!` is rendered in its appropriate place in the
|
||||||
|
//! URI using the [`UriDisplay`] implementation for the value's type. The
|
||||||
|
//! `UriDisplay` implementation ensures that the rendered value is URI-safe.
|
||||||
|
//!
|
||||||
|
//! If a mount-point is provided, the mount-point is prepended to the route's
|
||||||
|
//! URI.
|
||||||
|
//!
|
||||||
|
//! [`Uri`]: /rocket/http/uri/struct.URI.html
|
||||||
|
//! [`FromUriParam`]: /rocket/http/uri/trait.FromUriParam.html
|
||||||
|
//! [`UriDisplay`]: /rocket/http/uri/trait.UriDisplay.html
|
||||||
|
//!
|
||||||
|
//! # Debugging Codegen
|
||||||
|
//!
|
||||||
|
//! When the `ROCKET_CODEGEN_DEBUG` environment variable is set, this crate logs
|
||||||
|
//! the items it has generated to the console at compile-time. For example, you
|
||||||
|
//! might run the following to build a Rocket application with codegen logging
|
||||||
|
//! enabled:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! ROCKET_CODEGEN_DEBUG=1 cargo build
|
||||||
|
//! ```
|
||||||
|
|
||||||
#[macro_use] extern crate quote;
|
#[macro_use] extern crate quote;
|
||||||
#[macro_use] extern crate derive_utils;
|
#[macro_use] extern crate derive_utils;
|
||||||
extern crate indexmap;
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
extern crate rocket_http as http;
|
extern crate rocket_http as http;
|
||||||
extern crate indexmap;
|
extern crate indexmap;
|
||||||
|
@ -39,6 +421,7 @@ macro_rules! route_attribute {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
route_attribute!(route => None);
|
route_attribute!(route => None);
|
||||||
route_attribute!(get => Method::Get);
|
route_attribute!(get => Method::Get);
|
||||||
route_attribute!(put => Method::Put);
|
route_attribute!(put => Method::Put);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, proc_macro_non_items, proc_macro_gen, decl_macro)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,11 @@ const CATCH: &str = "Catcher";
|
||||||
//~^ HELP #[catch(404)]
|
//~^ HELP #[catch(404)]
|
||||||
fn e1(_request: &Request) { }
|
fn e1(_request: &Request) { }
|
||||||
|
|
||||||
#[catch(code = "404")] //~ ERROR unexpected parameter
|
#[catch(code = "404")] //~ ERROR unexpected keyed parameter
|
||||||
//~^ HELP #[catch(404)]
|
//~^ HELP #[catch(404)]
|
||||||
fn e2(_request: &Request) { }
|
fn e2(_request: &Request) { }
|
||||||
|
|
||||||
#[catch(code = 404)] //~ ERROR unexpected parameter
|
#[catch(code = 404)] //~ ERROR unexpected keyed parameter
|
||||||
//~^ HELP #[catch(404)]
|
//~^ HELP #[catch(404)]
|
||||||
fn e3(_request: &Request) { }
|
fn e3(_request: &Request) { }
|
||||||
|
|
||||||
|
|
|
@ -22,18 +22,18 @@ error: invalid value: expected unsigned integer literal
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
||||||
|
|
||||||
error: unexpected parameter: expected literal or identifier
|
error: unexpected keyed parameter: expected literal or identifier
|
||||||
--> $DIR/catch.rs:19:9
|
--> $DIR/catch.rs:19:9
|
||||||
|
|
|
|
||||||
19 | #[catch(code = "404")] //~ ERROR unexpected named parameter
|
19 | #[catch(code = "404")] //~ ERROR unexpected keyed parameter
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
||||||
|
|
||||||
error: unexpected parameter: expected literal or identifier
|
error: unexpected keyed parameter: expected literal or identifier
|
||||||
--> $DIR/catch.rs:23:9
|
--> $DIR/catch.rs:23:9
|
||||||
|
|
|
|
||||||
23 | #[catch(code = 404)] //~ ERROR unexpected named parameter
|
23 | #[catch(code = 404)] //~ ERROR unexpected keyed parameter
|
||||||
| ^^^^^^^^^^
|
| ^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
||||||
|
|
|
@ -127,7 +127,7 @@ impl<'a> Origin<'a> {
|
||||||
|
|
||||||
// Used mostly for testing and to construct known good URIs from other parts
|
// Used mostly for testing and to construct known good URIs from other parts
|
||||||
// of Rocket. This should _really_ not be used outside of Rocket because the
|
// of Rocket. This should _really_ not be used outside of Rocket because the
|
||||||
// resulting `Origin's` may not be valid origin URIs!
|
// resulting `Origin's` are not guaranteed to be valid origin URIs!
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn new<P, Q>(path: P, query: Option<Q>) -> Origin<'a>
|
pub fn new<P, Q>(path: P, query: Option<Q>) -> Origin<'a>
|
||||||
where P: Into<Cow<'a, str>>, Q: Into<Cow<'a, str>>
|
where P: Into<Cow<'a, str>>, Q: Into<Cow<'a, str>>
|
||||||
|
|
|
@ -120,4 +120,3 @@ impl<'a> Iterator for Segments<'a> {
|
||||||
// self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1))
|
// self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1))
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue