Fix typed URI generation for query reform.

This commit is contained in:
Sergio Benitez 2018-10-04 03:54:46 -07:00
parent 61f107f550
commit b9bf1ee37d
18 changed files with 471 additions and 658 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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
//! ```

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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))
}
}

View File

@ -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 }
}
}

View File

@ -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" }

View File

@ -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,

View File

@ -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 = &params.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())
}

View File

@ -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 })
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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;

View File

@ -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) { }

View File

@ -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)]

View File

@ -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>>

View File

@ -120,4 +120,3 @@ impl<'a> Iterator for Segments<'a> {
// self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1))
// }
}