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]
|
||||
git = "https://github.com/SergioBenitez/derive-utils"
|
||||
rev = "87ad56ba"
|
||||
rev = "f14fb4bc855"
|
||||
|
||||
[dependencies]
|
||||
quote = "0.6"
|
||||
|
|
|
@ -11,12 +11,7 @@ keywords = ["rocket", "web", "framework", "code", "generation"]
|
|||
license = "MIT/Apache-2.0"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
plugin = true
|
||||
|
||||
[dependencies]
|
||||
rocket_http = { version = "0.4.0-dev", path = "../http" }
|
||||
indexmap = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
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"
|
||||
quote = "0.6.1"
|
||||
rocket_http = { version = "0.4.0-dev", path = "../http/" }
|
||||
indexmap = "1"
|
||||
|
||||
[dependencies.derive_utils]
|
||||
path = "/Users/sbenitez/Sync/Data/Projects/Snippets/derive-utils/lib"
|
||||
# git = "https://github.com/SergioBenitez/derive-utils"
|
||||
# rev = "87ad56ba"
|
||||
git = "https://github.com/SergioBenitez/derive-utils"
|
||||
rev = "f14fb4bc855"
|
||||
|
||||
[dev-dependencies]
|
||||
rocket = { version = "0.4.0-dev", path = "../lib" }
|
||||
|
|
|
@ -6,15 +6,15 @@ use proc_macro::{Span, Diagnostic};
|
|||
use http::route::RouteSegment;
|
||||
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)]
|
||||
pub struct Segment {
|
||||
pub span: Span,
|
||||
pub kind: Kind,
|
||||
pub source: Source,
|
||||
pub name: String,
|
||||
pub index: Option<usize>,
|
||||
crate struct Segment {
|
||||
crate span: Span,
|
||||
crate kind: Kind,
|
||||
crate source: Source,
|
||||
crate name: String,
|
||||
crate index: Option<usize>,
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
|
@ -22,14 +22,6 @@ impl Segment {
|
|||
let (kind, source, index) = (segment.kind, segment.source, segment.index);
|
||||
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 {
|
||||
|
@ -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 remaining = haystack.len() - (index + needle.len());
|
||||
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;
|
||||
span.trimmed(index - 1, 0)
|
||||
}
|
||||
|
||||
pub fn into_diagnostic(
|
||||
fn into_diagnostic(
|
||||
segment: &str, // The segment that failed.
|
||||
source: &str, // The haystack where `segment` can be found.
|
||||
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)
|
||||
.map(|segment| Segment::from(segment, span))
|
||||
.map_err(|e| into_diagnostic(segment, segment, span, &e))
|
||||
}
|
||||
|
||||
pub fn parse_segments(
|
||||
crate fn parse_segments(
|
||||
string: &str,
|
||||
sep: char,
|
||||
source: Source,
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use std::fmt::Display;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use std::fmt::Display;
|
||||
|
||||
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 self::syn::spanned::Spanned as SynSpanned;
|
||||
use http_codegen::Optional;
|
||||
use syn_ext::{IdentExt, syn_to_diag};
|
||||
use bang::{prefix_last_segment, uri_parsing::*};
|
||||
|
||||
use rocket_http::{uri::Origin, ext::IntoOwned};
|
||||
|
||||
const URI_INFO_MACRO_PREFIX: &str = "rocket_uri_for_";
|
||||
use URI_MACRO_PREFIX;
|
||||
|
||||
macro_rules! p {
|
||||
(@go $num:expr, $singular:expr, $plural:expr) => (
|
||||
|
@ -26,7 +26,7 @@ macro_rules! p {
|
|||
crate fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
|
||||
let input2: TokenStream2 = input.clone().into();
|
||||
let mut params = syn::parse::<UriParams>(input).map_err(syn_to_diag)?;
|
||||
prefix_last_segment(&mut params.route_path, URI_INFO_MACRO_PREFIX);
|
||||
prefix_last_segment(&mut params.route_path, URI_MACRO_PREFIX);
|
||||
|
||||
let path = ¶ms.route_path;
|
||||
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
|
||||
// both paths concatinated. Validation should always succeed since this macro
|
||||
// can only be called if the route attribute succeed, which implies that the
|
||||
// route URI was valid.
|
||||
fn extract_origin(internal: &InternalUriParams) -> Result<Origin<'static>> {
|
||||
let base_uri = match internal.uri_params.mount_point {
|
||||
Some(ref base) => Origin::parse(&base.value())
|
||||
.map_err(|_| base.span().unstable().error("invalid path URI"))?
|
||||
.into_owned(),
|
||||
None => Origin::dummy()
|
||||
};
|
||||
// Returns an Origin URI with the mount point and route path concatinated. 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())
|
||||
.unwrap_or("");
|
||||
|
||||
Origin::parse_route(&format!("{}/{}", base_uri, internal.uri))
|
||||
.map(|o| o.to_normalized().into_owned())
|
||||
.map_err(|_| internal.uri.span().unstable().error("invalid route URI"))
|
||||
let path = format!("{}/{}", mount_point, internal.route_uri.path());
|
||||
let query = RouteSegment::parse_query(&internal.route_uri).map(|segments| {
|
||||
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
|
||||
|
@ -102,9 +106,7 @@ fn explode<'a, I>(route_str: &str, items: I) -> TokenStream2
|
|||
{
|
||||
// Generate the statements to typecheck each parameter.
|
||||
// Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e).
|
||||
let mut let_bindings = vec![];
|
||||
let mut fmt_exprs = vec![];
|
||||
|
||||
let (mut let_bindings, mut fmt_exprs) = (vec![], vec![]);
|
||||
for (mut ident, ty, expr) in items {
|
||||
let (span, expr) = (expr.span(), expr);
|
||||
let ident_tmp = ident.prepend("tmp_");
|
||||
|
@ -143,31 +145,22 @@ crate fn _uri_internal_macro(input: TokenStream) -> Result<TokenStream> {
|
|||
// Parse the internal invocation and the user's URI param expressions.
|
||||
let internal = syn::parse::<InternalUriParams>(input).map_err(syn_to_diag)?;
|
||||
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.
|
||||
let mut arguments = internal.fn_args.iter()
|
||||
.zip(exprs.iter())
|
||||
.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));
|
||||
|
||||
// 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 })
|
||||
};
|
||||
let query = Optional(origin.query().map(|q| explode(q, arguments)));
|
||||
|
||||
Ok(quote!({
|
||||
::rocket::http::uri::Origin::new::<
|
||||
::std::borrow::Cow<'static, str>,
|
||||
::std::borrow::Cow<'static, str>,
|
||||
>(#path, #query)
|
||||
::std::borrow::Cow<'static, str>,
|
||||
::std::borrow::Cow<'static, str>,
|
||||
>(#path, #query)
|
||||
}).into())
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use self::syn::{Expr, Ident, LitStr, Path, Token, Type};
|
|||
use self::syn::parse::{self, Parse, ParseStream};
|
||||
use self::syn::punctuated::Punctuated;
|
||||
|
||||
use http::{uri::Origin, ext::IntoOwned};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -30,7 +31,7 @@ pub enum Args {
|
|||
// uri_params.route_path
|
||||
#[derive(Debug)]
|
||||
pub struct UriParams {
|
||||
pub mount_point: Option<LitStr>,
|
||||
pub mount_point: Option<Origin<'static>>,
|
||||
pub route_path: Path,
|
||||
pub arguments: Args,
|
||||
}
|
||||
|
@ -62,11 +63,11 @@ pub enum Validation<'a> {
|
|||
// `uri` is the full URI used in the origin route's attribute.
|
||||
//
|
||||
// internal_uri!("/<first>/<second>", (first: ty, second: ty), $($tt)*);
|
||||
// ^-----------------| ^-----------|---------| ^-----|
|
||||
// uri fn_args uri_params
|
||||
// ^--------|--------- ^-----------|---------| ^-----|
|
||||
// route_uri fn_args uri_params
|
||||
#[derive(Debug)]
|
||||
pub struct InternalUriParams {
|
||||
pub uri: String,
|
||||
pub route_uri: Origin<'static>,
|
||||
pub fn_args: Vec<FnArg>,
|
||||
pub uri_params: UriParams,
|
||||
}
|
||||
|
@ -100,12 +101,11 @@ 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 value = string.value();
|
||||
if value.contains('<') || !value.starts_with('/') {
|
||||
// TODO(proc_macro): add example as a help, not in error
|
||||
return err(string.span().unstable(), "invalid mount point; \
|
||||
mount points must be static, absolute URIs: `/example`");
|
||||
}
|
||||
let mount_point = Origin::parse_owned(string.value()).map_err(|_| {
|
||||
// TODO(proc_macro): use error, add example as a help
|
||||
parse::Error::new(string.span(), "invalid mount point; \
|
||||
mount points must be static, absolute URIs: `/example`")
|
||||
})?;
|
||||
|
||||
if !input.peek(Token![,]) && input.cursor().eof() {
|
||||
return err(string.span().unstable(), "unexpected end of input: \
|
||||
|
@ -113,7 +113,7 @@ impl Parse for UriParams {
|
|||
}
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
Some(string)
|
||||
Some(mount_point)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -171,9 +171,15 @@ impl Parse for FnArg {
|
|||
|
||||
impl Parse for InternalUriParams {
|
||||
fn parse(input: ParseStream) -> parse::Result<InternalUriParams> {
|
||||
let uri = input.parse::<LitStr>()?.value();
|
||||
let route_uri_str = input.parse::<LitStr>()?;
|
||||
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;
|
||||
syn::parenthesized!(content in input);
|
||||
let fn_args: Punctuated<FnArg, Token![,]> = content.parse_terminated(FnArg::parse)?;
|
||||
|
@ -181,7 +187,7 @@ impl Parse for InternalUriParams {
|
|||
|
||||
input.parse::<Token![,]>()?;
|
||||
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;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContentType(pub http::ContentType);
|
||||
crate struct ContentType(crate http::ContentType);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Status(pub http::Status);
|
||||
crate struct Status(crate http::Status);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MediaType(pub http::MediaType);
|
||||
crate struct MediaType(crate http::MediaType);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Method(pub http::Method);
|
||||
crate struct Method(crate http::Method);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Origin(pub http::uri::Origin<'static>);
|
||||
crate struct Origin(crate http::uri::Origin<'static>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DataSegment(pub Segment);
|
||||
crate struct DataSegment(crate Segment);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Optional<T>(pub Option<T>);
|
||||
crate struct Optional<T>(crate Option<T>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RoutePath {
|
||||
pub origin: Origin,
|
||||
pub path: Vec<Segment>,
|
||||
pub query: Option<Vec<Segment>>,
|
||||
crate struct RoutePath {
|
||||
crate origin: Origin,
|
||||
crate path: Vec<Segment>,
|
||||
crate query: Option<Vec<Segment>>,
|
||||
}
|
||||
|
||||
impl FromMeta for Status {
|
||||
|
|
|
@ -4,9 +4,391 @@
|
|||
#![feature(rustc_private)]
|
||||
#![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 derive_utils;
|
||||
extern crate indexmap;
|
||||
extern crate proc_macro;
|
||||
extern crate rocket_http as http;
|
||||
extern crate indexmap;
|
||||
|
@ -39,6 +421,7 @@ macro_rules! route_attribute {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
route_attribute!(route => None);
|
||||
route_attribute!(get => Method::Get);
|
||||
route_attribute!(put => Method::Put);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#![feature(plugin, proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
|
|
|
@ -16,11 +16,11 @@ const CATCH: &str = "Catcher";
|
|||
//~^ HELP #[catch(404)]
|
||||
fn e1(_request: &Request) { }
|
||||
|
||||
#[catch(code = "404")] //~ ERROR unexpected parameter
|
||||
#[catch(code = "404")] //~ ERROR unexpected keyed parameter
|
||||
//~^ HELP #[catch(404)]
|
||||
fn e2(_request: &Request) { }
|
||||
|
||||
#[catch(code = 404)] //~ ERROR unexpected parameter
|
||||
#[catch(code = 404)] //~ ERROR unexpected keyed parameter
|
||||
//~^ HELP #[catch(404)]
|
||||
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)]
|
||||
|
||||
error: unexpected parameter: expected literal or identifier
|
||||
error: unexpected keyed parameter: expected literal or identifier
|
||||
--> $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)]
|
||||
|
||||
error: unexpected parameter: expected literal or identifier
|
||||
error: unexpected keyed parameter: expected literal or identifier
|
||||
--> $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)]
|
||||
|
|
|
@ -127,7 +127,7 @@ impl<'a> Origin<'a> {
|
|||
|
||||
// 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
|
||||
// resulting `Origin's` may not be valid origin URIs!
|
||||
// resulting `Origin's` are not guaranteed to be valid origin URIs!
|
||||
#[doc(hidden)]
|
||||
pub fn new<P, Q>(path: P, query: Option<Q>) -> Origin<'a>
|
||||
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))
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue