Port all codegen tests to codegen_next.

This commit is contained in:
Sergio Benitez 2018-10-04 21:44:42 -07:00
parent b9bf1ee37d
commit 360b0e80b0
88 changed files with 1857 additions and 1221 deletions

View File

@ -4,7 +4,6 @@ codegen-units = 4
[workspace]
members = [
"core/lib/",
"core/codegen/",
"core/codegen_next/",
"core/http/",
"contrib/lib",

View File

@ -21,7 +21,7 @@ proc-macro = true
[dependencies.derive_utils]
git = "https://github.com/SergioBenitez/derive-utils"
rev = "f14fb4bc855"
rev = "62f361f"
[dependencies]
quote = "0.6"

View File

@ -68,9 +68,6 @@ r2d2_redis = { version = "0.8", optional = true }
# Contrib codegen dependencies
rocket_contrib_codegen = { path = "../codegen", optional = true }
[dev-dependencies]
rocket_codegen = { version = "0.4.0-dev", path = "../../core/codegen" }
[target.'cfg(debug_assertions)'.dependencies]
notify = { version = "^4.0" }

View File

@ -74,10 +74,9 @@
//! Whenever a connection to the database is needed:
//!
//! ```rust
//! # #![feature(plugin, decl_macro)]
//! # #![plugin(rocket_codegen)]
//! # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
//! #
//! # extern crate rocket;
//! # #[macro_use] extern crate rocket;
//! # extern crate rocket_contrib;
//! #
//! # use rocket_contrib::databases::{database, diesel};
@ -263,10 +262,9 @@
//! connection to a given database:
//!
//! ```rust
//! # #![feature(plugin, decl_macro)]
//! # #![plugin(rocket_codegen)]
//! # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
//! #
//! # extern crate rocket;
//! # #[macro_use] extern crate rocket;
//! # extern crate rocket_contrib;
//! # use rocket_contrib::databases::{database, diesel};
//! #[database("my_db")]
@ -285,10 +283,9 @@
//! connection type:
//!
//! ```rust
//! # #![feature(plugin, decl_macro)]
//! # #![plugin(rocket_codegen)]
//! # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
//! #
//! # extern crate rocket;
//! # #[macro_use] extern crate rocket;
//! # extern crate rocket_contrib;
//! # use rocket_contrib::databases::{database, diesel};
//! #

View File

@ -27,9 +27,8 @@ pub use self::rmp_serde::decode::Error as MsgPackError;
/// HTTP request body.
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type User = usize;
/// # fn main() { }
@ -55,9 +54,8 @@ pub use self::rmp_serde::decode::Error as MsgPackError;
/// is set to `application/msgpack` automatically.
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type User = usize;
/// # fn main() { }

View File

@ -30,9 +30,8 @@ use super::ContextManager;
/// can be used as a request guard in any request handler.
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib;
/// # fn main() { }
/// #

View File

@ -1,5 +1,4 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
extern crate rocket;
extern crate rocket_contrib;

View File

@ -1,5 +1,4 @@
#![feature(plugin, decl_macro, proc_macro_non_items)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
extern crate rocket_contrib;

View File

@ -1,22 +0,0 @@
[package]
name = "rocket_codegen"
version = "0.4.0-dev"
authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Code generation for the Rocket web framework."
documentation = "https://api.rocket.rs/rocket_codegen/"
homepage = "https://rocket.rs"
repository = "https://github.com/SergioBenitez/Rocket"
readme = "../../README.md"
keywords = ["rocket", "web", "framework", "code", "generation"]
license = "MIT/Apache-2.0"
build = "build.rs"
[dependencies]
[dev-dependencies]
compiletest_rs = "0.3.14"
rocket = { version = "0.4.0-dev", path = "../lib" }
[build-dependencies]
yansi = "0.4"
version_check = "0.1.3"

View File

@ -1,58 +0,0 @@
//! This tiny build script ensures that rocket_codegen is not compiled with an
//! incompatible version of rust.
extern crate yansi;
extern crate version_check;
use yansi::Color::{Red, Yellow, Blue, White};
use version_check::{supports_features, is_min_version, is_min_date};
// Specifies the minimum nightly version needed to compile Rocket's codegen.
const MIN_DATE: &'static str = "2018-08-23";
const MIN_VERSION: &'static str = "1.30.0-nightly";
fn main() {
let ok_channel = supports_features();
let ok_version = is_min_version(MIN_VERSION);
let ok_date = is_min_date(MIN_DATE);
let print_version_err = |version: &str, date: &str| {
eprintln!("{} {}. {} {}.",
White.paint("Installed version is:"),
Yellow.paint(format!("{} ({})", version, date)),
White.paint("Minimum required:"),
Yellow.paint(format!("{} ({})", MIN_VERSION, MIN_DATE)));
};
match (ok_channel, ok_version, ok_date) {
(Some(ok_channel), Some((ok_version, version)), Some((ok_date, date))) => {
if !ok_channel {
eprintln!("{} {}",
Red.paint("Error:").bold(),
White.paint("Rocket requires a nightly or dev version of Rust."));
print_version_err(&*version, &*date);
eprintln!("{}{}{}",
Blue.paint("See the getting started guide ("),
White.paint("https://rocket.rs/guide/getting-started/"),
Blue.paint(") for more information."));
panic!("Aborting compilation due to incompatible compiler.")
}
if !ok_version || !ok_date {
eprintln!("{} {}",
Red.paint("Error:").bold(),
White.paint("Rocket codegen requires a more recent version of rustc."));
eprintln!("{}{}{}",
Blue.paint("Use `"),
White.paint("rustup update"),
Blue.paint("` or your preferred method to update Rust."));
print_version_err(&*version, &*date);
panic!("Aborting compilation due to incompatible compiler.")
}
},
_ => {
println!("cargo:warning={}", "Rocket was unable to check rustc compatibility.");
println!("cargo:warning={}", "Build may fail due to incompatible rustc version.");
}
}
}

View File

@ -1,13 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#[get("a")] //~ ERROR invalid
fn get() -> &'static str { "hi" }
#[get("")] //~ ERROR invalid
fn get1(id: usize) -> &'static str { "hi" }
#[get("a/b/c")] //~ ERROR invalid
fn get2(id: usize) -> &'static str { "hi" }
fn main() { }

View File

@ -1,13 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get] //~ ERROR incorrect use of attribute
//~^ ERROR malformed attribute
fn get() -> &'static str { "hi" }
fn main() {
let _ = routes![get];
}

View File

@ -1,12 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get(path = "/hello", 123)] //~ ERROR expected
fn get() -> &'static str { "hi" }
fn main() {
let _ = routes![get];
}

View File

@ -1,9 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("/")]
fn get(_: usize) -> &'static str { "hi" } //~ ERROR argument
fn main() { }

View File

@ -1,20 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get(1)] //~ ERROR expected `path = string`
fn get0() -> &'static str { "hi" }
#[get(path = 1)] //~ ERROR must be a string
fn get1() -> &'static str { "hi" }
#[get(path = "/", rank = "2")] //~ ERROR must be an int
fn get2() -> &'static str { "hi" }
#[get(path = "/", format = 100)] //~ ERROR must be a "media/type"
fn get3() -> &'static str { "hi" }
fn main() {
}

View File

@ -1,12 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("")] //~ ERROR can only be used on functions
enum B { } //~ ERROR but was applied
fn main() {
let _ = routes![get];
}

View File

@ -1,12 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("")] //~ ERROR can only be used on functions
impl C for A { } //~ ERROR but was applied
fn main() {
let _ = routes![get];
}

View File

@ -1,11 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("")] //~ ERROR can only be used on functions
struct A; //~ ERROR but was applied
fn main() {
let _ = routes![get];
}

View File

@ -1,11 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("")] //~ ERROR can only be used on functions
trait C { } //~ ERROR but was applied
fn main() {
let _ = routes![get];
}

View File

@ -1,28 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#[get("/a/b/c//d")] //~ ERROR paths cannot contain empty segments
fn get() -> &'static str { "hi" }
#[get("//")] //~ ERROR paths cannot contain empty segments
fn get1(name: &str) -> &'static str { "hi" }
#[get("/a/")] //~ ERROR paths cannot contain empty segments
fn get2(name: &str) -> &'static str { "hi" }
#[get("////")] //~ ERROR paths cannot contain empty segments
fn get3() -> &'static str { "hi" }
#[get("/a///")] //~ ERROR paths cannot contain empty segments
fn get4() -> &'static str { "hi" }
#[get("/a/b//")] //~ ERROR paths cannot contain empty segments
fn get5() -> &'static str { "hi" }
#[get("/a/b/c/")] //~ ERROR paths cannot contain empty segments
fn get6() -> &'static str { "hi" }
#[get("/a/b/c/d//e/")] //~ ERROR paths cannot contain empty segments
fn get7() -> &'static str { "hi" }
fn main() { }

View File

@ -1,16 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#[get("/<name>")] //~ ERROR unused dynamic parameter: `name`
fn get(other: usize) -> &'static str { "hi" } //~ NOTE expected
#[get("/a?<r>")] //~ ERROR unused dynamic parameter: `r`
fn get1() -> &'static str { "hi" } //~ NOTE expected
#[post("/a", data = "<test>")] //~ ERROR unused dynamic parameter: `test`
fn post() -> &'static str { "hi" } //~ NOTE expected
#[get("/<_r>")] //~ ERROR unused dynamic parameter: `_r`
fn get2(r: usize) -> &'static str { "hi" } //~ NOTE expected
fn main() { }

View File

@ -1,38 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("/", format = "applicationx-custom")] //~ ERROR malformed
//~^ ERROR `format` must be a "media/type"
fn one() -> &'static str { "hi" }
#[get("/", format = "")] //~ ERROR malformed
//~^ ERROR `format` must be a "media/type"
fn two() -> &'static str { "hi" }
#[get("/", format = "//")] //~ ERROR malformed
//~^ ERROR `format` must be a "media/type"
fn three() -> &'static str { "hi" }
#[get("/", format = "/")] //~ ERROR malformed
//~^ ERROR `format` must be a "media/type"
fn four() -> &'static str { "hi" }
#[get("/", format = "a/")] //~ ERROR malformed
//~^ ERROR `format` must be a "media/type"
fn five() -> &'static str { "hi" }
#[get("/", format = "/a")] //~ ERROR malformed
//~^ ERROR `format` must be a "media/type"
fn six() -> &'static str { "hi" }
#[get("/", format = "/a/")] //~ ERROR malformed
//~^ ERROR `format` must be a "media/type"
fn seven() -> &'static str { "hi" }
#[get("/", format = "a/b/")] //~ ERROR malformed
//~^ ERROR `format` must be a "media/type"
fn eight() -> &'static str { "hi" }
fn main() { }

View File

@ -1,17 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#[get("/<param>")] //~ ERROR unused dynamic parameter: `param`
fn get() { } //~ NOTE expected
#[get("/<a>")] //~ ERROR unused dynamic parameter: `a`
fn get2() { } //~ NOTE expected
#[get("/a/b/c/<a>/<b>")]
//~^ ERROR unused dynamic parameter: `a`
//~^^ ERROR unused dynamic parameter: `b`
fn get32() { }
//~^ NOTE expected
//~^^ NOTE expected
fn main() { }

View File

@ -1,12 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get(path = "/hello", unknown = 123)] //~ ERROR 'unknown' is not a known param
fn get() -> &'static str { "hi" }
fn main() {
let _ = routes![get];
}

View File

@ -1,17 +0,0 @@
// must-compile-successfully
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("/", format = "application/x-custom")] //~ WARNING not a known media type
fn one() -> &'static str { "hi" }
#[get("/", format = "x-custom/plain")] //~ WARNING not a known media type
fn two() -> &'static str { "hi" }
#[get("/", format = "x-custom/x-custom")] //~ WARNING not a known media type
fn three() -> &'static str { "hi" }
fn main() { }

View File

@ -1,104 +0,0 @@
extern crate compiletest_rs as compiletest;
use std::path::{Path, PathBuf};
use std::{io, fs::Metadata, time::SystemTime};
#[derive(Copy, Clone)]
enum Kind {
Dynamic, Static
}
impl Kind {
fn extension(self) -> &'static str {
match self {
#[cfg(windows)] Kind::Dynamic => ".dll",
#[cfg(all(unix, target_os = "macos"))] Kind::Dynamic => ".dylib",
#[cfg(all(unix, not(target_os = "macos")))] Kind::Dynamic => ".so",
Kind::Static => ".rlib"
}
}
fn prefix(self) -> &'static str {
#[cfg(windows)] { "" }
#[cfg(not(windows))] { "lib" }
}
}
fn target_path() -> PathBuf {
#[cfg(debug_assertions)] const ENVIRONMENT: &str = "debug";
#[cfg(not(debug_assertions))] const ENVIRONMENT: &str = "release";
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent().unwrap().parent().unwrap()
.join("target")
.join(ENVIRONMENT)
}
fn link_flag(flag: &str, lib: &str, rel_path: &[&str]) -> String {
let mut path = target_path();
for component in rel_path {
path = path.join(component);
}
format!("{} {}={}", flag, lib, path.display())
}
fn best_time_for(metadata: &Metadata) -> SystemTime {
metadata.created()
.or_else(|_| metadata.modified())
.or_else(|_| metadata.accessed())
.unwrap_or_else(|_| SystemTime::now())
}
fn extern_dep(name: &str, kind: Kind) -> io::Result<String> {
let deps_root = target_path().join("deps");
let dep_name = format!("{}{}", kind.prefix(), name);
let mut dep_path: Option<PathBuf> = None;
for entry in deps_root.read_dir().expect("read_dir call failed") {
let entry = match entry {
Ok(entry) => entry,
Err(_) => continue
};
let filename = entry.file_name();
let filename = filename.to_string_lossy();
let lib_name = filename.split('.').next().unwrap().split('-').next().unwrap();
if lib_name == dep_name && filename.ends_with(kind.extension()) {
if let Some(ref mut existing) = dep_path {
if best_time_for(&entry.metadata()?) > best_time_for(&existing.metadata()?) {
*existing = entry.path().into();
}
} else {
dep_path = Some(entry.path().into());
}
}
}
let dep = dep_path.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?;
let filename = dep.file_name().ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
Ok(link_flag("--extern", name, &["deps", &filename.to_string_lossy()]))
}
fn run_mode(mode: &'static str) {
let mut config = compiletest::Config::default();
config.mode = mode.parse().expect("Invalid mode");
config.src_base = format!("tests/{}", mode).into();
config.clean_rmeta();
config.target_rustcflags = Some([
link_flag("-L", "crate", &[]),
link_flag("-L", "dependency", &["deps"]),
extern_dep("rocket_codegen", Kind::Dynamic).expect("find codegen dep"),
extern_dep("rocket", Kind::Static).expect("find core dep")
].join(" "));
compiletest::run_tests(&config);
}
#[test]
fn compile_test() {
run_mode("compile-fail");
run_mode("ui");
}

View File

@ -1,18 +0,0 @@
error: error catchers can have at most 2 arguments
--> $DIR/bad-error-fn.rs:9:1
|
9 | fn err_a(_a: Error, _b: Request, _c: Error) -> &'static str { "hi" }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: error catchers can take either a `rocket::Error`, `rocket::Request` type, or both.
error: unknown error catcher argument
--> $DIR/bad-error-fn.rs:12:14
|
12 | fn err_b(_a: (isize, usize)) -> &'static str { "hi" }
| ^^^^^^^^^^^^^^
|
= help: error catchers can take either a `rocket::Error`, `rocket::Request` type, or both.
error: aborting due to 2 previous errors

View File

@ -1,9 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("/", data = "<something>")]
fn get(something: rocket::Data) -> &'static str { "hi" }
fn main() { }

View File

@ -1,9 +0,0 @@
warning: `data` route parameter used with non-payload-supporting method
--> $DIR/data-without-post.rs:6:12
|
6 | #[get("/", data = "<something>")]
| ^^^^^^^^^^^^^^^^^^^^
|
= note: 'GET' does not typically support payloads
= note: the 'format' attribute parameter will match against the 'Accept' header

View File

@ -1,29 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#[get("/><")]
fn get() -> &'static str { "hi" }
#[get("/<id><")]
fn get1(id: usize) -> &'static str { "hi" }
#[get("/<<<<id><")]
fn get2(id: usize) -> &'static str { "hi" }
#[get("/<!>")]
fn get3() -> &'static str { "hi" }
#[get("/<_>")]
fn get4() -> &'static str { "hi" }
#[get("/<1>")]
fn get5() -> &'static str { "hi" }
#[get("/<>name><")]
fn get6() -> &'static str { "hi" }
#[get("/<name>:<id>")]
fn get7() -> &'static str { "hi" }
#[get("/<>")]
fn get8() -> &'static str { "hi" }

View File

@ -1,72 +0,0 @@
error: malformed parameter
--> $DIR/malformed-param-list.rs:4:9
|
4 | #[get("/><")]
| ^^
|
= help: parameters must be of the form '<param>'
error: malformed parameter
--> $DIR/malformed-param-list.rs:7:9
|
7 | #[get("/<id><")]
| ^^^^^
|
= help: parameters must be of the form '<param>'
error: malformed parameter
--> $DIR/malformed-param-list.rs:10:9
|
10 | #[get("/<<<<id><")]
| ^^^^^^^^
|
= help: parameters must be of the form '<param>'
error: parameter names must be valid identifiers
--> $DIR/malformed-param-list.rs:13:9
|
13 | #[get("/<!>")]
| ^^^
|
= note: "!" is not a valid identifier
error: parameters must be named
--> $DIR/malformed-param-list.rs:16:9
|
16 | #[get("/<_>")]
| ^^^
|
= help: use a name such as `_guard` or `_param`
error: parameter names must be valid identifiers
--> $DIR/malformed-param-list.rs:19:9
|
19 | #[get("/<1>")]
| ^^^
|
= note: "1" is not a valid identifier
error: malformed parameter
--> $DIR/malformed-param-list.rs:22:9
|
22 | #[get("/<>name><")]
| ^^^^^^^^
|
= help: parameters must be of the form '<param>'
error: parameter names must be valid identifiers
--> $DIR/malformed-param-list.rs:25:9
|
25 | #[get("/<name>:<id>")]
| ^^^^^^^^^^^
|
= note: "name>:<id" is not a valid identifier
error: parameters cannot be empty
--> $DIR/malformed-param-list.rs:28:9
|
28 | #[get("/<>")]
| ^^
error: aborting due to 9 previous errors

View File

@ -1,19 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[route(FIX, "/hello")]
fn get1() -> &'static str { "hi" }
#[route("hi", "/hello")]
fn get2() -> &'static str { "hi" }
#[route("GET", "/hello")]
fn get3() -> &'static str { "hi" }
#[route(120, "/hello")]
fn get4() -> &'static str { "hi" }
#[route(CONNECT, "/hello")]
fn get5() -> &'static str { "hi" }

View File

@ -1,42 +0,0 @@
error: 'FIX' is not a valid method
--> $DIR/route-bad-method.rs:6:9
|
6 | #[route(FIX, "/hello")]
| ^^^
|
= help: valid methods are: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: expected a valid HTTP method identifier
--> $DIR/route-bad-method.rs:9:9
|
9 | #[route("hi", "/hello")]
| ^^^^
|
= help: valid methods are: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: expected a valid HTTP method identifier
--> $DIR/route-bad-method.rs:12:9
|
12 | #[route("GET", "/hello")]
| ^^^^^
|
= help: valid methods are: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: expected a valid HTTP method identifier
--> $DIR/route-bad-method.rs:15:9
|
15 | #[route(120, "/hello")]
| ^^^
|
= help: valid methods are: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: 'CONNECT' is not a valid method
--> $DIR/route-bad-method.rs:18:9
|
18 | #[route(CONNECT, "/hello")]
| ^^^^^^^
|
= help: valid methods are: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: aborting due to 5 previous errors

View File

@ -1,31 +0,0 @@
#![feature(plugin, decl_macro, proc_macro_non_items)]
#![plugin(rocket_codegen)]
#![allow(dead_code, unused_variables)]
#[macro_use] extern crate rocket;
use rocket::http::RawStr;
use rocket::request::FromParam;
struct S;
impl<'a> FromParam<'a> for S {
type Error = ();
fn from_param(param: &'a RawStr) -> Result<Self, Self::Error> { Ok(S) }
}
#[post("/<id>")]
fn simple(id: i32) -> &'static str { "" }
#[post("/<id>/<name>")]
fn not_uri_display(id: i32, name: S) -> &'static str { "" }
#[post("/<id>/<name>")]
fn not_uri_display_but_unused(id: i32, name: S) -> &'static str { "" }
fn main() {
uri!(simple: id = "hi");
uri!(simple: "hello");
uri!(simple: id = 239239i64);
uri!(not_uri_display: 10, S);
}

View File

@ -1,30 +0,0 @@
#![feature(plugin, decl_macro, proc_macro_non_items)]
#![plugin(rocket_codegen)]
#![allow(dead_code, unused_variables)]
#[macro_use] extern crate rocket;
use std::fmt;
use rocket::http::Cookies;
#[post("/<id>")]
fn simple(id: i32) -> &'static str { "" }
#[post("/<id>")]
fn guard_1(cookies: Cookies, id: i32) -> &'static str { "" }
fn main() {
uri!(simple);
uri!(simple: 1, 23);
uri!(simple: "Hello", 23, );
uri!(guard_1: "hi", 100);
uri!(simple: id = 100, name = "hi");
uri!(simple: id = 100, id = 100);
uri!(simple: name = 100, id = 100);
uri!(simple: id = 100, id = 100, );
uri!(simple: name = "hi");
uri!(guard_1: cookies = "hi", id = 100);
uri!(guard_1: id = 100, cookies = "hi");
}

View File

@ -1,126 +0,0 @@
error: `simple` route uri expects 1 parameter but 0 were supplied
--> $DIR/typed-uris-bad-params.rs:18:10
|
18 | uri!(simple);
| ^^^^^^
|
= note: expected parameter: id: i32
error: `simple` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:19:18
|
19 | uri!(simple: 1, 23);
| ^^^^^
|
= note: expected parameter: id: i32
error: `simple` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:20:18
|
20 | uri!(simple: "Hello", 23, );
| ^^^^^^^^^^^^
|
= note: expected parameter: id: i32
error: `guard_1` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:21:19
|
21 | uri!(guard_1: "hi", 100);
| ^^^^^^^^^
|
= note: expected parameter: id: i32
error: invalid parameters for `simple` route uri
--> $DIR/typed-uris-bad-params.rs:23:18
|
23 | uri!(simple: id = 100, name = "hi");
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:23:28
|
23 | uri!(simple: id = 100, name = "hi");
| ^^^^
error: invalid parameters for `simple` route uri
--> $DIR/typed-uris-bad-params.rs:24:18
|
24 | uri!(simple: id = 100, id = 100);
| ^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:24:28
|
24 | uri!(simple: id = 100, id = 100);
| ^^
error: invalid parameters for `simple` route uri
--> $DIR/typed-uris-bad-params.rs:25:18
|
25 | uri!(simple: name = 100, id = 100);
| ^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:25:18
|
25 | uri!(simple: name = 100, id = 100);
| ^^^^
error: invalid parameters for `simple` route uri
--> $DIR/typed-uris-bad-params.rs:26:18
|
26 | uri!(simple: id = 100, id = 100, );
| ^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:26:28
|
26 | uri!(simple: id = 100, id = 100, );
| ^^
error: invalid parameters for `simple` route uri
--> $DIR/typed-uris-bad-params.rs:27:18
|
27 | uri!(simple: name = "hi");
| ^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:27:18
|
27 | uri!(simple: name = "hi");
| ^^^^
= help: missing parameter: `id`
error: invalid parameters for `guard_1` route uri
--> $DIR/typed-uris-bad-params.rs:28:19
|
28 | uri!(guard_1: cookies = "hi", id = 100);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:28:19
|
28 | uri!(guard_1: cookies = "hi", id = 100);
| ^^^^^^^
error: invalid parameters for `guard_1` route uri
--> $DIR/typed-uris-bad-params.rs:29:19
|
29 | uri!(guard_1: id = 100, cookies = "hi");
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:29:29
|
29 | uri!(guard_1: id = 100, cookies = "hi");
| ^^^^^^^
error: aborting due to 11 previous errors

View File

@ -1,21 +0,0 @@
#![feature(plugin, decl_macro, proc_macro_non_items)]
#![plugin(rocket_codegen)]
#![allow(dead_code, unused_variables)]
#[macro_use] extern crate rocket;
#[post("/<id>/<name>")]
fn simple(id: i32, name: String) -> &'static str { "" }
fn main() {
uri!(simple: id = 100, "Hello");
uri!(simple: "Hello", id = 100);
uri!(simple,);
uri!(simple:);
uri!("/mount");
uri!("/mount",);
uri!("mount", simple);
uri!("/mount/<id>", simple);
uri!();
uri!(simple: id = );
}

View File

@ -1,62 +0,0 @@
error: named and unnamed parameters cannot be mixed
--> $DIR/typed-uris-invalid-syntax.rs:11:18
|
11 | uri!(simple: id = 100, "Hello");
| ^^^^^^^^^^^^^^^^^
error: named and unnamed parameters cannot be mixed
--> $DIR/typed-uris-invalid-syntax.rs:12:18
|
12 | uri!(simple: "Hello", id = 100);
| ^^^^^^^^^^^^^^^^^
error: expected `:`
--> $DIR/typed-uris-invalid-syntax.rs:13:16
|
13 | uri!(simple,);
| ^
error: expected argument list after `:`
--> $DIR/typed-uris-invalid-syntax.rs:14:16
|
14 | uri!(simple:);
| ^
error: unexpected end of input: expected ',' followed by route path
--> $DIR/typed-uris-invalid-syntax.rs:15:10
|
15 | uri!("/mount");
| ^^^^^^^^
error: unexpected end of input, expected identifier
--> $DIR/typed-uris-invalid-syntax.rs:16:5
|
16 | uri!("/mount",);
| ^^^^^^^^^^^^^^^^
error: invalid mount point; mount points must be static, absolute URIs: `/example`
--> $DIR/typed-uris-invalid-syntax.rs:17:10
|
17 | uri!("mount", simple);
| ^^^^^^^
error: invalid mount point; mount points must be static, absolute URIs: `/example`
--> $DIR/typed-uris-invalid-syntax.rs:18:10
|
18 | uri!("/mount/<id>", simple);
| ^^^^^^^^^^^^^
error: unexpected end of input, call to `uri!` cannot be empty
--> $DIR/typed-uris-invalid-syntax.rs:19:5
|
19 | uri!();
| ^^^^^^^
error: unexpected end of input, expected expression
--> $DIR/typed-uris-invalid-syntax.rs:20:5
|
20 | uri!(simple: id = );
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to 10 previous errors

View File

@ -23,9 +23,8 @@ rocket_http = { version = "0.4.0-dev", path = "../http/" }
[dependencies.derive_utils]
git = "https://github.com/SergioBenitez/derive-utils"
rev = "f14fb4bc855"
rev = "62f361f"
[dev-dependencies]
rocket = { version = "0.4.0-dev", path = "../lib" }
rocket_codegen = { version = "0.4.0-dev", path = "../codegen" }
compiletest_rs = "0.3.14"

View File

@ -1,9 +1,9 @@
use proc_macro::{TokenStream, Span};
use proc_macro2::TokenStream as TokenStream2;
use derive_utils::{syn, Spanned, Result, FromMeta, ext::TypeExt};
use derive_utils::{syn, Spanned, SpanWrapped, Result, FromMeta, ext::TypeExt};
use indexmap::IndexSet;
use proc_macro_ext::Diagnostics;
use proc_macro_ext::{Diagnostics, SpanExt};
use syn_ext::{syn_to_diag, IdentExt};
use self::syn::{Attribute, parse::Parser};
@ -15,9 +15,9 @@ use {ROUTE_FN_PREFIX, ROUTE_STRUCT_PREFIX, URI_MACRO_PREFIX, ROCKET_PARAM_PREFIX
#[derive(Debug, FromMeta)]
struct RouteAttribute {
#[meta(naked)]
method: Method,
method: SpanWrapped<Method>,
path: RoutePath,
data: Option<DataSegment>,
data: Option<SpanWrapped<DataSegment>>,
format: Option<MediaType>,
rank: Option<isize>,
}
@ -27,7 +27,7 @@ struct RouteAttribute {
struct MethodRouteAttribute {
#[meta(naked)]
path: RoutePath,
data: Option<DataSegment>,
data: Option<SpanWrapped<DataSegment>>,
format: Option<MediaType>,
rank: Option<isize>,
}
@ -51,6 +51,16 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
// Gather diagnostics as we proceed.
let mut diags = Diagnostics::new();
// Emit a warning if a `data` param was supplied for non-payload methods.
if let Some(ref data) = attr.data {
if !attr.method.0.supports_payload() {
let msg = format!("'{}' does not typically support payloads", attr.method.0);
data.full_span.warning("`data` used with non-payload-supporting method")
.span_note(attr.method.span, msg)
.emit()
}
}
// Collect all of the dynamic segments in an `IndexSet`, checking for dups.
let mut segments: IndexSet<Segment> = IndexSet::new();
fn dup_check<I>(set: &mut IndexSet<Segment>, iter: I, diags: &mut Diagnostics)
@ -67,7 +77,7 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
dup_check(&mut segments, attr.path.path.iter().cloned(), &mut diags);
attr.path.query.as_ref().map(|q| dup_check(&mut segments, q.iter().cloned(), &mut diags));
dup_check(&mut segments, attr.data.clone().map(|s| s.0).into_iter(), &mut diags);
dup_check(&mut segments, attr.data.clone().map(|s| s.value.0).into_iter(), &mut diags);
// Check the validity of function arguments.
let mut inputs = vec![];
@ -100,7 +110,11 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
}
// Check that all of the declared parameters are function inputs.
let span = function.decl.inputs.span();
let span = match function.decl.inputs.is_empty() {
false => function.decl.inputs.span(),
true => function.span()
};
for missing in segments.difference(&fn_segments) {
diags.push(missing.span.error("unused dynamic parameter")
.span_note(span, format!("expected argument named `{}` here", missing.name)))
@ -210,13 +224,16 @@ fn query_exprs(route: &Route) -> Option<TokenStream2> {
let matcher = match segment.kind {
Kind::Single => quote_spanned! { span =>
(_, #name, __v) => {
#ident = Some(match <#ty as FromFormValue>::from_form_value(__v) {
#[allow(unreachable_patterns, unreachable_code)]
let __v = match <#ty as FromFormValue>::from_form_value(__v) {
Ok(__v) => __v,
Err(__e) => {
log_warn_(&format!("Failed to parse '{}': {:?}", #name, __e));
return Outcome::Forward(__data);
}
});
};
#ident = Some(__v);
}
},
Kind::Static => quote! {
@ -408,7 +425,9 @@ fn incomplete_route(
input: TokenStream
) -> Result<TokenStream> {
let method_str = method.to_string().to_lowercase();
let method_ident = syn::Ident::new(&method_str, args.span().into());
// FIXME(proc_macro): there should be a way to get this `Span`.
let method_span = Span::call_site().subspan(2..2 + method_str.len()).unwrap();
let method_ident = syn::Ident::new(&method_str, method_span.into());
let function: syn::ItemFn = syn::parse(input).map_err(syn_to_diag)
.map_err(|d| d.help(format!("#[{}] can only be used on functions", method_str)))?;
@ -421,7 +440,9 @@ fn incomplete_route(
};
let attribute = RouteAttribute {
method: Method(method),
method: SpanWrapped {
full_span: method_span, span: method_span, value: Method(method)
},
path: method_attribute.path,
data: method_attribute.data,
format: method_attribute.format,

View File

@ -52,13 +52,16 @@ impl Hash for Segment {
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)
span.subspan(index..index + needle.len())
}
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)
if needle.as_ptr() as usize > haystack.as_ptr() as usize {
span.subspan((index - 1)..)
} else {
span.subspan(index..)
}
}
fn into_diagnostic(
@ -67,7 +70,7 @@ fn into_diagnostic(
span: Span, // The `Span` of `Source`.
error: &Error, // The error.
) -> Diagnostic {
let seg_span = subspan(segment, source, span).unwrap();
let seg_span = subspan(segment, source, span).expect("seg_span");
match error {
Error::Empty => {
seg_span.error("parameter names cannot be empty")
@ -94,8 +97,8 @@ fn into_diagnostic(
.note("components cannot contain '%' and '+' characters")
}
Error::Trailing(multi) => {
let multi_span = subspan(multi, source, span).unwrap();
trailspan(segment, source, span).unwrap()
let multi_span = subspan(multi, source, span).expect("mutli_span");
trailspan(segment, source, span).expect("trailspan")
.error("unexpected trailing text after a '..' param")
.help("a multi-segment param must be the final component")
.span_note(multi_span, "multi-segment param is here")
@ -125,7 +128,7 @@ crate fn parse_segments(
break;
}
} else if let Ok(segment) = result {
let seg_span = subspan(&segment.string, string, span).unwrap();
let seg_span = subspan(&segment.string, string, span).expect("seg");
segments.push(Segment::from(segment, seg_span));
}
}

View File

@ -54,10 +54,16 @@ fn extract_exprs(internal: &InternalUriParams) -> Result<Vec<&Expr>> {
.note(format!("uri parameters are: {}", internal.fn_args_str()));
fn join<S: Display, T: Iterator<Item = S>>(iter: T) -> (&'static str, String) {
let items: Vec<_> = iter.map(|i| format!("`{}`", i)).collect();
let mut items: Vec<_> = iter.map(|i| format!("`{}`", i)).collect();
items.dedup();
(p!("parameter", items.len()), items.join(", "))
}
if !missing.is_empty() {
let (ps, msg) = join(missing.iter());
diag = diag.help(format!("missing {}: {}", ps, msg));
}
if !extra.is_empty() {
let (ps, msg) = join(extra.iter());
let spans: Vec<_> = extra.iter().map(|ident| ident.span().unstable()).collect();
@ -70,11 +76,6 @@ fn extract_exprs(internal: &InternalUriParams) -> Result<Vec<&Expr>> {
diag = diag.span_help(spans, format!("duplicate {}: {}", ps, msg));
}
if !missing.is_empty() {
let (ps, msg) = join(missing.iter());
diag = diag.help(format!("missing {}: {}", ps, msg));
}
Err(diag)
}
}

View File

@ -70,9 +70,16 @@ impl ToTokens for ContentType {
impl FromMeta for MediaType {
fn from_meta(meta: MetaItem) -> Result<Self> {
http::MediaType::parse_flexible(&String::from_meta(meta)?)
.map(MediaType)
.ok_or(meta.value_span().error("invalid or unknown media type"))
let mt = http::MediaType::parse_flexible(&String::from_meta(meta)?)
.ok_or(meta.value_span().error("invalid or unknown media type"))?;
if !mt.is_known() {
meta.value_span()
.warning(format!("'{}' is not a known media type", mt))
.emit();
}
Ok(MediaType(mt))
}
}
@ -158,7 +165,7 @@ impl FromMeta for Origin {
let uri = http::uri::Origin::parse_route(&string)
.map_err(|e| {
let span = e.index()
.map(|i| span.trimmed(i + 1, 0).unwrap())
.map(|i| span.subspan(i + 1..).expect("origin"))
.unwrap_or(span);
span.error(format!("invalid path URI: {}", e))
@ -178,7 +185,9 @@ impl FromMeta for Origin {
impl FromMeta for Segment {
fn from_meta(meta: MetaItem) -> Result<Self> {
let string = String::from_meta(meta)?;
let span = meta.value_span().trimmed(1, 1).unwrap();
let span = meta.value_span()
.subspan(1..(string.len() + 1))
.expect("segment");
let segment = parse_segment(&string, span)?;
if segment.kind != Kind::Single {
@ -201,20 +210,18 @@ impl FromMeta for DataSegment {
impl FromMeta for RoutePath {
fn from_meta(meta: MetaItem) -> Result<Self> {
let origin = Origin::from_meta(meta)?;
let span = meta.value_span().trimmed(1, 1).unwrap();
let query_len = origin.0.query().map(|q| q.len() + 1).unwrap_or(0);
let path_span = span.trimmed(0, query_len).unwrap();
let (origin, span) = (Origin::from_meta(meta)?, meta.value_span());
let path_span = span.subspan(1..origin.0.path().len() + 1).expect("path");
let path = parse_segments(origin.0.path(), '/', Source::Path, path_span);
let query = origin.0.query()
.map(|q| {
let len_to_q = origin.0.path().len() + 1;
let query_span = span.trimmed(len_to_q, 0).unwrap();
let len_to_q = 1 + origin.0.path().len() + 1;
let end_of_q = len_to_q + q.len();
let query_span = span.subspan(len_to_q..end_of_q).expect("query");
if q.starts_with('&') || q.contains("&&") || q.ends_with('&') {
// TODO: Show a help message with what's expected.
Err(query_span.error("query cannot contain empty components").into())
Err(query_span.error("query cannot contain empty segments").into())
} else {
parse_segments(q, '&', Source::Query, query_span)
}

View File

@ -14,11 +14,15 @@
//! ## **Table of Contents**
//!
//! 1. [Custom Attributes](#custom-attributes)
//! * [`route`, `get`, `put`, ...](#route-attributes)
//! * [`catch`](#catch-attribute)
//! 2. [Custom Derives](#custom-derives)
//! * [`FromForm`](#fromform)
//! * [`FromFormValue`](#fromformvalue)
//! * [`Responder`](#responder)
//! 3. [Procedural Macros](#procedural-macros)
//! * [`routes`, `catchers`](#routes-and-catchers)
//! * [`uri`](#typed-uris-uri)
//! 4. [Debugging Generated Code](#debugging-codegen)
//!
//! ## Custom Attributes
@ -35,11 +39,13 @@
//! * **options**
//! * **catch**
//!
//! ### Route Attributes
//!
//! The grammar for all _route_ attributes, including **route**, **get**,
//! **put**, **post**, **delete**, **head**, **patch**, and **options** is
//! defined as:
//!
//! <pre>
//! ```text
//! route := METHOD? '(' ('path' '=')? path (',' kv_param)* ')'
//!
//! path := URI_SEG
@ -58,26 +64,34 @@
//!
//! 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")]
//! ```rust,ignore
//! #[route(GET, path = "/hello")]
//! ```
//!
//! while the equivalent using **get** looks like:
//!
//! #[get("/hello")]
//! ```rust,ignore
//! #[get("/hello")]
//! ```
//!
//! ### Catch Attribute
//!
//! The syntax for the **catch** attribute is:
//!
//! <pre>
//! ```text
//! catch := INTEGER
//! </pre>
//! ```
//!
//! A use of the `catch` attribute looks like:
//!
//! #[catch(404)]
//! ```rust,ignore
//! #[catch(404)]
//! ```
//!
//! ## Custom Derives
//!
@ -87,37 +101,43 @@
//! * **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>
//! <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
//! }
//! ```rust,ignore
//! #[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>
//! ```text
//! 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
//! }
//! ```rust,ignore
//! #[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
@ -137,12 +157,14 @@
//! The [`FromFormValue`] derive can be applied to enums with nullary
//! (zero-length) fields:
//!
//! #[derive(FromFormValue)]
//! enum MyValue {
//! First,
//! Second,
//! Third,
//! }
//! ```rust,ignore
//! #[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
@ -157,21 +179,23 @@
//! 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,
//! }
//! ```rust,ignore
//! #[derive(FromFormValue)]
//! enum MyValue {
//! First,
//! Second,
//! #[form(value = "fourth")]
//! Third,
//! }
//! ```
//!
//! The attribute's grammar is:
//!
//! <pre>
//! ```text
//! 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
@ -184,17 +208,19 @@
//! 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),
//! }
//! ```rust,ignore
//! #[derive(Responder)]
//! enum MyResponder {
//! A(String),
//! B(OtherResponse, ContentType),
//! }
//!
//! #[derive(Responder)]
//! struct MyResponder {
//! inner: OtherResponder,
//! header: 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
@ -207,19 +233,21 @@
//! 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),
//! }
//! ```rust,ignore
//! #[derive(Responder)]
//! enum MyResponder {
//! A(String),
//! B(OtherResponse, ContentType, #[response(ignore)] Other),
//! }
//!
//! #[derive(Responder)]
//! struct MyResponder {
//! inner: InnerResponder,
//! header: ContentType,
//! #[response(ignore)]
//! other: Other,
//! }
//! #[derive(Responder)]
//! struct MyResponder {
//! inner: InnerResponder,
//! header: ContentType,
//! #[response(ignore)]
//! other: Other,
//! }
//! ```
//!
//! Decorating the first field with `#[response(ignore)]` has no effect.
//!
@ -228,7 +256,7 @@
//! produced by the generated implementation. The `response` attribute used in
//! these positions has the following grammar:
//!
//! <pre>
//! ```text
//! response := parameter (',' parameter)?
//!
//! parameter := 'status' '=' STATUS
@ -237,26 +265,28 @@
//! 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),
//! }
//! ```rust,ignore
//! #[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,
//! }
//! #[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
@ -288,13 +318,15 @@
//! * **catchers**
//! * **uri**
//!
//! ## Routes and Catchers
//!
//! The syntax for `routes!` and `catchers!` is defined as:
//!
//! <pre>
//! ```text
//! macro := PATH (',' PATH)*
//!
//! PATH := a path, as defined by Rust
//! </pre>
//! ```
//!
//! ### Typed URIs: `uri!`
//!
@ -330,7 +362,7 @@
//!
//! The grammar for the `uri!` macro is as follows:
//!
//! <pre>
//! ```text
//! uri := (mount ',')? PATH (':' params)?
//!
//! mount = STRING
@ -342,7 +374,7 @@
//! 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
//!
@ -361,7 +393,7 @@
//! implementation, provided by Rocket, allows an `&str` to be used in a `uri!`
//! invocation for route URI parameters declared as `String`:
//!
//! ```
//! ```rust,ignore
//! impl<'a> FromUriParam<&'a str> for String
//! ```
//!
@ -383,7 +415,7 @@
//! might run the following to build a Rocket application with codegen logging
//! enabled:
//!
//! ```
//! ```sh
//! ROCKET_CODEGEN_DEBUG=1 cargo build
//! ```
@ -413,11 +445,25 @@ crate static ROUTE_FN_PREFIX: &str = "rocket_route_fn_";
crate static URI_MACRO_PREFIX: &str = "rocket_uri_macro_";
crate static ROCKET_PARAM_PREFIX: &str = "__rocket_param_";
macro_rules! emit {
($tokens:expr) => ({
let tokens = $tokens;
if ::std::env::var_os("ROCKET_CODEGEN_DEBUG").is_some() {
::proc_macro::Span::call_site()
.note("emitting Rocket code generation debug output")
.note(tokens.to_string())
.emit()
}
tokens
})
}
macro_rules! route_attribute {
($name:ident => $method:expr) => (
#[proc_macro_attribute]
pub fn $name(args: TokenStream, input: TokenStream) -> TokenStream {
attribute::route::route_attribute($method, args, input)
emit!(attribute::route::route_attribute($method, args, input))
}
)
}
@ -433,40 +479,41 @@ route_attribute!(options => Method::Options);
#[proc_macro_derive(FromFormValue, attributes(form))]
pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
derive::from_form_value::derive_from_form_value(input)
emit!(derive::from_form_value::derive_from_form_value(input))
}
#[proc_macro_derive(FromForm, attributes(form))]
pub fn derive_from_form(input: TokenStream) -> TokenStream {
derive::from_form::derive_from_form(input)
emit!(derive::from_form::derive_from_form(input))
}
#[proc_macro_derive(Responder, attributes(response))]
pub fn derive_responder(input: TokenStream) -> TokenStream {
derive::responder::derive_responder(input)
emit!(derive::responder::derive_responder(input))
}
#[proc_macro_attribute]
pub fn catch(args: TokenStream, input: TokenStream) -> TokenStream {
attribute::catch::catch_attribute(args, input)
emit!(attribute::catch::catch_attribute(args, input))
}
#[proc_macro]
pub fn routes(input: TokenStream) -> TokenStream {
bang::routes_macro(input)
emit!(bang::routes_macro(input))
}
#[proc_macro]
pub fn catchers(input: TokenStream) -> TokenStream {
bang::catchers_macro(input)
emit!(bang::catchers_macro(input))
}
#[proc_macro]
pub fn uri(input: TokenStream) -> TokenStream {
bang::uri_macro(input)
emit!(bang::uri_macro(input))
}
#[doc(hidden)]
#[proc_macro]
pub fn rocket_internal_uri(input: TokenStream) -> TokenStream {
bang::uri_internal_macro(input)
emit!(bang::uri_internal_macro(input))
}

View File

@ -1,3 +1,5 @@
use std::ops::{Bound, RangeBounds};
use proc_macro::{Span, Diagnostic, /* MultiSpan */};
use syntax_pos::{Span as InnerSpan, Pos, BytePos};
@ -61,37 +63,40 @@ impl From<Vec<Diagnostic>> for Diagnostics {
}
pub trait SpanExt {
fn trimmed(&self, left: usize, right: usize) -> Option<Span>;
fn subspan<R: RangeBounds<usize>>(self, range: R) -> Option<Span>;
}
impl SpanExt for Span {
/// Trim the span on the left by `left` characters and on the right by
/// `right` characters.
fn trimmed(&self, left: usize, right: usize) -> Option<Span> {
let inner: InnerSpan = unsafe { ::std::mem::transmute(*self) };
if left > u32::max_value() as usize || right > u32::max_value() as usize {
return None;
}
// Ensure that the addition won't overflow.
let (left, right) = (left as u32, right as u32);
if u32::max_value() - left < inner.lo().to_u32() {
return None;
}
// Ensure that the subtraction won't underflow.
if right > inner.hi().to_u32() {
return None;
}
let new_lo = inner.lo() + BytePos(left);
let new_hi = inner.hi() - BytePos(right);
// Ensure we're still inside the old `Span` and didn't cross paths.
if new_lo >= new_hi {
/// Create a `subspan` from `start` to `end`.
fn subspan<R: RangeBounds<usize>>(self, range: R) -> Option<Span> {
let inner: InnerSpan = unsafe { ::std::mem::transmute(self) };
let length = inner.hi().to_usize() - inner.lo().to_usize();
let start = match range.start_bound() {
Bound::Included(&lo) => lo,
Bound::Excluded(&lo) => lo + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&hi) => hi + 1,
Bound::Excluded(&hi) => hi,
Bound::Unbounded => length,
};
// Bounds check the values, preventing addition overflow and OOB spans.
if start > u32::max_value() as usize
|| end > u32::max_value() as usize
|| (u32::max_value() - start as u32) < inner.lo().to_u32()
|| (u32::max_value() - end as u32) < inner.lo().to_u32()
|| start >= end
|| end > length
{
return None;
}
let new_lo = inner.lo() + BytePos(start as u32);
let new_hi = inner.lo() + BytePos(end as u32);
let new_inner = inner.with_lo(new_lo).with_hi(new_hi);
Some(unsafe { ::std::mem::transmute(new_inner) })
}

View File

@ -90,7 +90,6 @@ fn run_mode(mode: &'static str, path: &'static str) {
config.target_rustcflags = Some([
link_flag("-L", "crate", &[]),
link_flag("-L", "dependency", &["deps"]),
extern_dep("rocket_codegen_next", Kind::Dynamic).expect("find codegen dep"),
extern_dep("rocket_http", Kind::Static).expect("find http dep"),
extern_dep("rocket", Kind::Static).expect("find core dep"),
].join(" "));

View File

@ -1,3 +1,5 @@
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
use rocket::local::Client;

View File

@ -27,10 +27,10 @@ fn msgpack() -> &'static str { "msgpack" }
#[get("/", format = "plain")]
fn plain() -> &'static str { "plain" }
#[get("/", format = "binary")]
#[get("/", format = "binary", rank = 2)]
fn binary() -> &'static str { "binary" }
#[get("/", rank = 2)]
#[get("/", rank = 3)]
fn other() -> &'static str { "other" }
#[test]
@ -57,10 +57,13 @@ fn test_formats() {
assert_eq!(response.body_string().unwrap(), "binary");
let mut response = client.get("/").header(ContentType::JSON).dispatch();
assert_eq!(response.body_string().unwrap(), "other");
assert_eq!(response.body_string().unwrap(), "plain");
let mut response = client.get("/").dispatch();
assert_eq!(response.body_string().unwrap(), "other");
assert_eq!(response.body_string().unwrap(), "plain");
let response = client.put("/").header(ContentType::HTML).dispatch();
assert_eq!(response.status(), Status::NotFound);
}
// Test custom formats.
@ -71,7 +74,7 @@ fn get_foo() -> &'static str { "get_foo" }
#[post("/", format = "application/foo")]
fn post_foo() -> &'static str { "post_foo" }
#[get("/", format = "bar/baz")]
#[get("/", format = "bar/baz", rank = 2)]
fn get_bar_baz() -> &'static str { "get_bar_baz" }
#[put("/", format = "bar/baz")]
@ -101,6 +104,12 @@ fn test_custom_formats() {
let mut response = client.put("/").header(bar_baz_ct).dispatch();
assert_eq!(response.body_string().unwrap(), "put_bar_baz");
let response = client.get("/").dispatch();
let mut response = client.get("/").dispatch();
assert_eq!(response.body_string().unwrap(), "get_foo");
let response = client.put("/").header(ContentType::HTML).dispatch();
assert_eq!(response.status(), Status::NotFound);
let response = client.post("/").header(ContentType::HTML).dispatch();
assert_eq!(response.status(), Status::NotFound);
}

View File

@ -119,3 +119,33 @@ fn test_full_route() {
assert_eq!(response.body_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
sky, name, "A A", "inside", path, simple, expected_uri));
}
// Check that we propogate span information correctly to allow re-expansion.
#[get("/easy/<id>")]
fn easy(id: i32) -> String {
format!("easy id: {}", id)
}
macro_rules! make_handler {
() => {
#[get("/hard/<id>")]
fn hard(id: i32) -> String {
format!("hard id: {}", id)
}
}
}
make_handler!();
#[test]
fn test_reexpansion() {
let rocket = rocket::ignite().mount("/", routes![easy, hard]);
let client = Client::new(rocket).unwrap();
let mut response = client.get("/easy/327").dispatch();
assert_eq!(response.body_string().unwrap(), "easy id: 327");
let mut response = client.get("/hard/72").dispatch();
assert_eq!(response.body_string().unwrap(), "hard id: 72");
}

View File

@ -1,5 +1,4 @@
#![feature(plugin, proc_macro_non_items, proc_macro_gen, decl_macro)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#![allow(dead_code, unused_variables)]
#[macro_use] extern crate rocket;
@ -42,47 +41,56 @@ struct Second {
}
#[post("/<id>")]
fn simple(id: i32) -> &'static str { "" }
fn simple(id: i32) { }
#[post("/<id>/<name>")]
fn simple2(id: i32, name: String) -> &'static str { "" }
fn simple2(id: i32, name: String) { }
#[post("/<id>/<name>")]
fn simple2_flipped(name: String, id: i32) -> &'static str { "" }
fn simple2_flipped(name: String, id: i32) { }
#[post("/?<id>")]
fn simple3(id: i32) { }
#[post("/?<id>&<name>")]
fn simple4(id: i32, name: String) { }
#[post("/?<id>&<name>")]
fn simple4_flipped(name: String, id: i32) { }
#[post("/<used>/<_unused>")]
fn unused_param(used: i32, _unused: i32) -> &'static str { "" }
fn unused_param(used: i32, _unused: i32) { }
#[post("/<id>")]
fn guard_1(cookies: Cookies, id: i32) -> &'static str { "" }
fn guard_1(cookies: Cookies, id: i32) { }
#[post("/<id>/<name>")]
fn guard_2(name: String, cookies: Cookies, id: i32) -> &'static str { "" }
fn guard_2(name: String, cookies: Cookies, id: i32) { }
#[post("/a/<id>/hi/<name>/hey")]
fn guard_3(id: i32, name: String, cookies: Cookies) -> &'static str { "" }
fn guard_3(id: i32, name: String, cookies: Cookies) { }
#[post("/<id>", data = "<form>")]
fn no_uri_display_okay(id: i32, form: Form<Second>) -> &'static str {
"Typed URI testing."
}
fn no_uri_display_okay(id: i32, form: Form<Second>) { }
#[post("/<name>?<query..>", data = "<user>", rank = 2)]
#[post("/name/<name>?<foo>&bar=10&<bar>&<query..>", data = "<user>", rank = 2)]
fn complex<'r>(
foo: usize,
name: &RawStr,
query: Form<User<'r>>,
user: Form<User<'r>>,
bar: &RawStr,
cookies: Cookies
) -> &'static str { "" }
) { }
#[post("/a/<path..>")]
fn segments(path: PathBuf) -> &'static str { "" }
fn segments(path: PathBuf) { }
#[post("/a/<id>/then/<path..>")]
fn param_and_segments(path: PathBuf, id: usize) -> &'static str { "" }
fn param_and_segments(path: PathBuf, id: usize) { }
#[post("/a/<id>/then/<path..>")]
fn guarded_segments(cookies: Cookies, path: PathBuf, id: usize) -> &'static str { "" }
fn guarded_segments(cookies: Cookies, path: PathBuf, id: usize) { }
macro assert_uri_eq($($uri:expr => $expected:expr,)+) {
$(assert_eq!($uri, Origin::parse($expected).expect("valid origin URI"));)+
@ -115,6 +123,16 @@ fn check_simple_unnamed() {
uri!(simple2: 100, "hello there") => "/100/hello%20there",
uri!(simple2_flipped: 100, "hello there") => "/100/hello%20there",
}
// Ensure that query parameters are handled properly.
assert_uri_eq! {
uri!(simple3: 100) => "/?id=100",
uri!(simple3: 1349) => "/?id=1349",
uri!(simple4: 100, "bob") => "/?id=100&name=bob",
uri!(simple4: 1349, "Bob Anderson") => "/?id=1349&name=Bob%20Anderson",
uri!(simple4_flipped: 100, "bob") => "/?id=100&name=bob",
uri!(simple4_flipped: 1349, "Bob Anderson") => "/?id=1349&name=Bob%20Anderson",
}
}
#[test]
@ -149,6 +167,17 @@ fn check_simple_named() {
uri!(simple2_flipped: id = 100, name = "hello there") => "/100/hello%20there",
uri!(simple2_flipped: name = "hello there", id = 100) => "/100/hello%20there",
}
// Ensure that query parameters are handled properly.
assert_uri_eq! {
uri!(simple3: id = 100) => "/?id=100",
uri!(simple3: id = 1349) => "/?id=1349",
uri!(simple4: id = 100, name = "bob") => "/?id=100&name=bob",
uri!(simple4: id = 1349, name = "Bob A") => "/?id=1349&name=Bob%20A",
uri!(simple4: name = "Bob A", id = 1349) => "/?id=1349&name=Bob%20A",
uri!(simple4_flipped: id = 1349, name = "Bob A") => "/?id=1349&name=Bob%20A",
uri!(simple4_flipped: name = "Bob A", id = 1349) => "/?id=1349&name=Bob%20A",
}
}
#[test]
@ -214,32 +243,43 @@ fn check_with_segments() {
#[test]
fn check_complex() {
assert_uri_eq! {
uri!(complex: "no idea", ("A B C", "a c")) => "/no%20idea?name=A+B+C&nickname=a+c",
uri!(complex: "Bob", User { name: "Robert".into(), nickname: "Bob".into() })
=> "/Bob?name=Robert&nickname=Bob",
uri!(complex: "Bob", &User { name: "Robert".into(), nickname: "Bob".into() })
=> "/Bob?name=Robert&nickname=Bob",
uri!(complex: "no idea", User { name: "Robert Mike".into(), nickname: "Bob".into() })
=> "/no%20idea?name=Robert+Mike&nickname=Bob",
uri!("/some/path", complex: "no idea", ("A B C", "a c"))
=> "/some/path/no%20idea?name=A+B+C&nickname=a+c",
uri!(complex: name = "Bob", query = &User { name: "Robert".into(), nickname: "Bob".into() })
=> "/Bob?name=Robert&nickname=Bob",
uri!(complex: query = User { name: "Robert".into(), nickname: "Bob".into() }, name = "Bob")
=> "/Bob?name=Robert&nickname=Bob",
uri!(complex: name = "no idea", query = ("A B C", "a c"))
=> "/no%20idea?name=A+B+C&nickname=a+c",
uri!(complex: query = ("A B C", "a c"), name = "no idea")
=> "/no%20idea?name=A+B+C&nickname=a+c",
uri!("/hey", complex: name = "no idea", query = ("A B C", "a c"))
=> "/hey/no%20idea?name=A+B+C&nickname=a+c",
uri!(complex: "no idea", 10, "high", ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
uri!(complex: "Bob", 248, "?", User { name: "Robert".into(), nickname: "Bob".into() }) =>
"/name/Bob?foo=248&bar=10&bar=%3F&name=Robert&nickname=Bob",
uri!(complex: "Bob", 248, "a a", &User { name: "Robert".into(), nickname: "B".into() }) =>
"/name/Bob?foo=248&bar=10&bar=a%20a&name=Robert&nickname=B",
uri!(complex: "no idea", 248, "", &User { name: "A B".into(), nickname: "A".into() }) =>
"/name/no%20idea?foo=248&bar=10&bar=&name=A+B&nickname=A",
uri!(complex: "hi", 3, "b", &User { name: "A B C".into(), nickname: "a b".into() }) =>
"/name/hi?foo=3&bar=10&bar=b&name=A+B+C&nickname=a+b",
uri!(complex: name = "no idea", foo = 10, bar = "high", query = ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
uri!(complex: foo = 10, name = "no idea", bar = "high", query = ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", bar = "high", ) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", bar = "high") =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
uri!(complex: query = *&("A B C", "a c"), foo = 10, name = "no idea", bar = "high") =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
uri!(complex: foo = 3, name = "hi", bar = "b",
query = &User { name: "A B C".into(), nickname: "a b".into() }) =>
"/name/hi?foo=3&bar=10&bar=b&name=A+B+C&nickname=a+b",
uri!(complex: query = &User { name: "A B C".into(), nickname: "a b".into() },
foo = 3, name = "hi", bar = "b") =>
"/name/hi?foo=3&bar=10&bar=b&name=A+B+C&nickname=a+b",
}
// Ensure variables are correctly processed.
let user = User { name: "Robert".into(), nickname: "Bob".into() };
assert_uri_eq! {
uri!(complex: "complex", &user) => "/complex?name=Robert&nickname=Bob",
uri!(complex: "complex", user) => "/complex?name=Robert&nickname=Bob",
uri!(complex: "complex", 0, "high", &user) =>
"/name/complex?foo=0&bar=10&bar=high&name=Robert&nickname=Bob",
uri!(complex: "complex", 0, "high", &user) =>
"/name/complex?foo=0&bar=10&bar=high&name=Robert&nickname=Bob",
uri!(complex: "complex", 0, "high", user) =>
"/name/complex?foo=0&bar=10&bar=high&name=Robert&nickname=Bob",
}
}
@ -293,7 +333,7 @@ mod typed_uris {
use super::assert_uri_eq;
#[post("/typed_uris/<id>")]
fn simple(id: i32) -> &'static str { "" }
fn simple(id: i32) { }
#[test]
fn check_simple_scoped() {
@ -309,7 +349,7 @@ mod typed_uris {
use super::assert_uri_eq;
#[post("/typed_uris/deeper/<id>")]
fn simple(id: i32) -> &'static str { "" }
fn simple(id: i32) { }
#[test]
fn check_deep_scoped() {

View File

@ -0,0 +1,130 @@
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
// Check a path is supplied, at least.
#[get()] //~ ERROR missing expected parameter
fn a0() {}
// Check that it only works on functions.
#[get("/")]
struct S;
//~^ ERROR expected `fn`
//~^^ HELP on functions
#[get("/")]
enum A { }
//~^ ERROR expected `fn`
//~^^ HELP on functions
#[get("/")]
trait Foo { }
//~^ ERROR expected `fn`
//~^^ HELP on functions
#[get("/")]
impl S { }
//~^ ERROR expected `fn`
//~^^ HELP on functions
// Check that additional parameter weirdness is caught.
#[get("/", 123)] //~ ERROR expected
fn b0() {}
#[get("/", "/")] //~ ERROR expected
fn b1() {}
#[get(data = "<foo>", "/")] //~ ERROR unexpected keyed parameter
fn b2(foo: usize) {}
#[get("/", unknown = "foo")] //~ ERROR unexpected
fn b3() {}
#[get("/", ...)] //~ ERROR malformed
//~^ HELP expected syntax
fn b4() {}
// Check that all identifiers are named
#[get("/")]
fn c1(_: usize) {} //~ ERROR cannot be ignored
//~^ HELP must be of the form
// Check that the path is a string, rank is an integer.
#[get(100)] //~ ERROR expected string
fn d0() {}
#[get('/')] //~ ERROR expected string
fn d1() {}
#[get("/", rank = "1")] //~ ERROR expected integer
fn d2() {}
#[get("/", rank = '1')] //~ ERROR expected integer
fn d3() {}
// Check that formats are valid media-type strings.
#[get("/", format = "applicationx-custom")] //~ ERROR invalid or unknown media type
fn e0() {}
#[get("/", format = "")] //~ ERROR invalid or unknown media type
fn e1() {}
#[get("/", format = "//")] //~ ERROR invalid or unknown media type
fn e2() {}
#[get("/", format = "/")] //~ ERROR invalid or unknown media type
fn e3() {}
#[get("/", format = "a/")] //~ ERROR invalid or unknown media type
fn e4() {}
#[get("/", format = "/a")] //~ ERROR invalid or unknown media type
fn e5() {}
#[get("/", format = "/a/")] //~ ERROR invalid or unknown media type
fn e6() {}
#[get("/", format = "a/b/")] //~ ERROR invalid or unknown media type
fn e7() {}
#[get("/", format = "unknown")] //~ ERROR unknown media type
fn e8() {}
#[get("/", format = 12)] //~ ERROR expected string
fn e9() {}
#[get("/", format = 'j')] //~ ERROR expected string
fn e10() {}
#[get("/", format = "text//foo")] //~ ERROR invalid or unknown media type
fn e12() {}
// Check that route methods are validated properly.
#[route(CONNECT, "/")] //~ ERROR invalid HTTP method for route
//~^ HELP method must be one of
fn f0() {}
#[route(FIX, "/")] //~ ERROR invalid HTTP method
//~^ HELP method must be one of
fn f1() {}
#[route("hi", "/")] //~ ERROR expected identifier
//~^ HELP method must be one of
fn f2() {}
#[route("GET", "/")] //~ ERROR expected identifier
//~^ HELP method must be one of
fn f3() {}
#[route(120, "/")] //~ ERROR expected identifier
//~^ HELP method must be one of
fn f4() {}
fn main() {}

View File

@ -0,0 +1,216 @@
error: missing expected parameter: `path`
--> $DIR/route-attribute-general-syntax.rs:7:1
|
7 | #[get()] //~ ERROR missing expected parameter
| ^^^^^^^^
error: expected `fn`
--> $DIR/route-attribute-general-syntax.rs:13:1
|
13 | struct S;
| ^^^^^^
|
= help: #[get] can only be used on functions
error: expected `fn`
--> $DIR/route-attribute-general-syntax.rs:18:1
|
18 | enum A { }
| ^^^^
|
= help: #[get] can only be used on functions
error: expected `fn`
--> $DIR/route-attribute-general-syntax.rs:23:1
|
23 | trait Foo { }
| ^^^^^
|
= help: #[get] can only be used on functions
error: expected `fn`
--> $DIR/route-attribute-general-syntax.rs:28:1
|
28 | impl S { }
| ^^^^
|
= help: #[get] can only be used on functions
error: expected key/value pair
--> $DIR/route-attribute-general-syntax.rs:34:12
|
34 | #[get("/", 123)] //~ ERROR expected
| ^^^
error: expected key/value pair
--> $DIR/route-attribute-general-syntax.rs:37:12
|
37 | #[get("/", "/")] //~ ERROR expected
| ^^^
error: unexpected keyed parameter: expected literal or identifier
--> $DIR/route-attribute-general-syntax.rs:40:7
|
40 | #[get(data = "<foo>", "/")] //~ ERROR unexpected keyed parameter
| ^^^^^^^^^^^^^^
error: unexpected attribute parameter: `unknown`
--> $DIR/route-attribute-general-syntax.rs:43:12
|
43 | #[get("/", unknown = "foo")] //~ ERROR unexpected
| ^^^^^^^^^^^^^^^
error: malformed attribute
--> $DIR/route-attribute-general-syntax.rs:46:1
|
46 | #[get("/", ...)] //~ ERROR malformed
| ^^^^^^^^^^^^^^^^
|
= help: expected syntax: #[get(key = value, ..)]
error: handler arguments cannot be ignored
--> $DIR/route-attribute-general-syntax.rs:53:7
|
53 | fn c1(_: usize) {} //~ ERROR cannot be ignored
| ^^^^^^^^
|
= help: all handler arguments must be of the form: `ident: Type`
error: invalid value: expected string literal
--> $DIR/route-attribute-general-syntax.rs:58:7
|
58 | #[get(100)] //~ ERROR expected string
| ^^^
error: invalid value: expected string literal
--> $DIR/route-attribute-general-syntax.rs:61:7
|
61 | #[get('/')] //~ ERROR expected string
| ^^^
error: invalid value: expected integer literal
--> $DIR/route-attribute-general-syntax.rs:64:19
|
64 | #[get("/", rank = "1")] //~ ERROR expected integer
| ^^^
error: invalid value: expected integer literal
--> $DIR/route-attribute-general-syntax.rs:67:19
|
67 | #[get("/", rank = '1')] //~ ERROR expected integer
| ^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:72:21
|
72 | #[get("/", format = "applicationx-custom")] //~ ERROR invalid or unknown media type
| ^^^^^^^^^^^^^^^^^^^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:75:21
|
75 | #[get("/", format = "")] //~ ERROR invalid or unknown media type
| ^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:78:21
|
78 | #[get("/", format = "//")] //~ ERROR invalid or unknown media type
| ^^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:81:21
|
81 | #[get("/", format = "/")] //~ ERROR invalid or unknown media type
| ^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:84:21
|
84 | #[get("/", format = "a/")] //~ ERROR invalid or unknown media type
| ^^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:87:21
|
87 | #[get("/", format = "/a")] //~ ERROR invalid or unknown media type
| ^^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:90:21
|
90 | #[get("/", format = "/a/")] //~ ERROR invalid or unknown media type
| ^^^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:93:21
|
93 | #[get("/", format = "a/b/")] //~ ERROR invalid or unknown media type
| ^^^^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:96:21
|
96 | #[get("/", format = "unknown")] //~ ERROR unknown media type
| ^^^^^^^^^
error: invalid value: expected string literal
--> $DIR/route-attribute-general-syntax.rs:99:21
|
99 | #[get("/", format = 12)] //~ ERROR expected string
| ^^
error: invalid value: expected string literal
--> $DIR/route-attribute-general-syntax.rs:102:21
|
102 | #[get("/", format = 'j')] //~ ERROR expected string
| ^^^
error: invalid or unknown media type
--> $DIR/route-attribute-general-syntax.rs:105:21
|
105 | #[get("/", format = "text//foo")] //~ ERROR invalid or unknown media type
| ^^^^^^^^^^^
error: invalid HTTP method for route handlers
--> $DIR/route-attribute-general-syntax.rs:110:9
|
110 | #[route(CONNECT, "/")] //~ ERROR invalid HTTP method for route
| ^^^^^^^
|
= help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: invalid HTTP method
--> $DIR/route-attribute-general-syntax.rs:114:9
|
114 | #[route(FIX, "/")] //~ ERROR invalid HTTP method
| ^^^
|
= help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: expected identifier, found string literal
--> $DIR/route-attribute-general-syntax.rs:118:9
|
118 | #[route("hi", "/")] //~ ERROR expected identifier
| ^^^^
|
= help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: expected identifier, found string literal
--> $DIR/route-attribute-general-syntax.rs:122:9
|
122 | #[route("GET", "/")] //~ ERROR expected identifier
| ^^^^^
|
= help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: expected identifier, found integer literal
--> $DIR/route-attribute-general-syntax.rs:126:9
|
126 | #[route(120, "/")] //~ ERROR expected identifier
| ^^^
|
= help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`
error: aborting due to 32 previous errors

View File

@ -0,0 +1,135 @@
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
// Check that route paths are absolute and normalized.
#[get("a")] //~ ERROR invalid path URI
//~^ HELP expected
fn f0() {}
#[get("")] //~ ERROR invalid path URI
//~^ HELP expected
fn f1() {}
#[get("a/b/c")] //~ ERROR invalid path URI
//~^ HELP expected
fn f2() {}
#[get("/a///b")] //~ ERROR empty segments
//~^ NOTE expected
fn f3() {}
#[get("/?bat&&")] //~ ERROR empty segments
fn f4() {}
#[get("/?bat&&")] //~ ERROR empty segments
fn f5() {}
#[get("/a/b//")] //~ ERROR empty segments
//~^ NOTE expected
fn f6() {}
// Check that paths contain only valid URI characters
#[get("/?????")] //~ ERROR invalid URI characters
//~^ NOTE cannot contain
fn g0() {}
#[get("/!@#$%^&*()")] //~ ERROR invalid path URI
//~^ HELP origin form
fn g1() {}
#[get("/a%20b")] //~ ERROR invalid URI characters
//~^ NOTE cannot contain
fn g2() {}
#[get("/a?a%20b")] //~ ERROR invalid URI characters
//~^ NOTE cannot contain
fn g3() {}
#[get("/a?a+b")] //~ ERROR invalid URI characters
//~^ NOTE cannot contain
fn g4() {}
// Check that all declared parameters are accounted for
#[get("/<name>")] //~ ERROR unused dynamic parameter
fn h0(_name: usize) {} //~ NOTE expected argument named `name` here
#[get("/a?<r>")] //~ ERROR unused dynamic parameter
fn h1() {} //~ NOTE expected argument named `r` here
#[post("/a", data = "<test>")] //~ ERROR unused dynamic parameter
fn h2() {} //~ NOTE expected argument named `test` here
#[get("/<_r>")] //~ ERROR unused dynamic parameter
fn h3() {} //~ NOTE expected argument named `_r` here
#[get("/<_r>/<b>")] //~ ERROR unused dynamic parameter
//~^ ERROR unused dynamic parameter
fn h4() {} //~ NOTE expected argument named `_r` here
//~^ NOTE expected argument named `b` here
// Check dynamic parameters are valid idents
#[get("/<foo_.>")] //~ ERROR `foo_.` is not a valid identifier
//~^ HELP must be valid
fn i0() {}
#[get("/<foo*>")] //~ ERROR `foo*` is not a valid identifier
//~^ HELP must be valid
fn i1() {}
#[get("/<!>")] //~ ERROR `!` is not a valid identifier
//~^ HELP must be valid
fn i2() {}
#[get("/<name>:<id>")] //~ ERROR `name>:<id` is not a valid identifier
//~^ HELP must be valid
fn i3() {}
// Check that a data parameter is exactly `<param>`
#[get("/", data = "foo")] //~ ERROR malformed parameter
//~^ HELP must be of the form
fn j0() {}
#[get("/", data = "<foo..>")] //~ ERROR malformed parameter
//~^ HELP must be of the form
fn j1() {}
#[get("/", data = "<foo")] //~ ERROR missing a closing bracket
//~^ HELP did you mean
fn j2() {}
#[get("/", data = "<test >")] //~ ERROR `test ` is not a valid identifier
//~^ HELP must be valid
fn j3() {}
// Check that all identifiers are named
#[get("/<_>")] //~ ERROR must be named
fn k0(_: usize) {} //~^ HELP use a name such as
// Check that strange dynamic syntax is caught.
#[get("/<>")] //~ ERROR cannot be empty
fn m0() {}
#[get("/<id><")] //~ ERROR malformed parameter
//~^ HELP must be of the form
//~^^ HELP identifiers cannot contain
fn m1() {}
#[get("/<<<<id><")] //~ ERROR malformed parameter
//~^ HELP must be of the form
//~^^ HELP identifiers cannot contain
fn m2() {}
#[get("/<>name><")] //~ ERROR malformed parameter
//~^ HELP must be of the form
//~^^ HELP identifiers cannot contain
fn m3() {}
fn main() { }

View File

@ -0,0 +1,271 @@
error: invalid path URI: expected token '/' but found 'a' at index 0
--> $DIR/route-path-bad-syntax.rs:7:8
|
7 | #[get("a")] //~ ERROR invalid path URI
| ^^
|
= help: expected path in origin form: "/path/<param>"
error: invalid path URI: expected token '/' but none was found at index 0
--> $DIR/route-path-bad-syntax.rs:11:8
|
11 | #[get("")] //~ ERROR invalid path URI
| ^
|
= help: expected path in origin form: "/path/<param>"
error: invalid path URI: expected token '/' but found 'a' at index 0
--> $DIR/route-path-bad-syntax.rs:15:8
|
15 | #[get("a/b/c")] //~ ERROR invalid path URI
| ^^^^^^
|
= help: expected path in origin form: "/path/<param>"
error: paths cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:19:7
|
19 | #[get("/a///b")] //~ ERROR empty segments
| ^^^^^^^^
|
= note: expected '/a/b', found '/a///b'
error: query cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:23:10
|
23 | #[get("/?bat&&")] //~ ERROR empty segments
| ^^^^^
error: query cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:26:10
|
26 | #[get("/?bat&&")] //~ ERROR empty segments
| ^^^^^
error: paths cannot contain empty segments
--> $DIR/route-path-bad-syntax.rs:29:7
|
29 | #[get("/a/b//")] //~ ERROR empty segments
| ^^^^^^^^
|
= note: expected '/a/b', found '/a/b//'
error: component contains invalid URI characters
--> $DIR/route-path-bad-syntax.rs:35:10
|
35 | #[get("/?????")] //~ ERROR invalid URI characters
| ^^^^
|
= note: components cannot contain '%' and '+' characters
error: invalid path URI: expected EOF but found '#' at index 3
--> $DIR/route-path-bad-syntax.rs:39:11
|
39 | #[get("/!@#$%^&*()")] //~ ERROR invalid path URI
| ^^^^^^^^^
|
= help: expected path in origin form: "/path/<param>"
error: component contains invalid URI characters
--> $DIR/route-path-bad-syntax.rs:43:9
|
43 | #[get("/a%20b")] //~ ERROR invalid URI characters
| ^^^^^
|
= note: components cannot contain '%' and '+' characters
error: component contains invalid URI characters
--> $DIR/route-path-bad-syntax.rs:47:11
|
47 | #[get("/a?a%20b")] //~ ERROR invalid URI characters
| ^^^^^
|
= note: components cannot contain '%' and '+' characters
error: component contains invalid URI characters
--> $DIR/route-path-bad-syntax.rs:51:11
|
51 | #[get("/a?a+b")] //~ ERROR invalid URI characters
| ^^^
|
= note: components cannot contain '%' and '+' characters
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:57:9
|
57 | #[get("/<name>")] //~ ERROR unused dynamic parameter
| ^^^^^^
|
note: expected argument named `name` here
--> $DIR/route-path-bad-syntax.rs:58:7
|
58 | fn h0(_name: usize) {} //~ NOTE expected argument named `name` here
| ^^^^^^^^^^^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:60:11
|
60 | #[get("/a?<r>")] //~ ERROR unused dynamic parameter
| ^^^
|
note: expected argument named `r` here
--> $DIR/route-path-bad-syntax.rs:61:1
|
61 | fn h1() {} //~ NOTE expected argument named `r` here
| ^^^^^^^^^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:63:22
|
63 | #[post("/a", data = "<test>")] //~ ERROR unused dynamic parameter
| ^^^^^^
|
note: expected argument named `test` here
--> $DIR/route-path-bad-syntax.rs:64:1
|
64 | fn h2() {} //~ NOTE expected argument named `test` here
| ^^^^^^^^^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:66:9
|
66 | #[get("/<_r>")] //~ ERROR unused dynamic parameter
| ^^^^
|
note: expected argument named `_r` here
--> $DIR/route-path-bad-syntax.rs:67:1
|
67 | fn h3() {} //~ NOTE expected argument named `_r` here
| ^^^^^^^^^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:69:9
|
69 | #[get("/<_r>/<b>")] //~ ERROR unused dynamic parameter
| ^^^^
|
note: expected argument named `_r` here
--> $DIR/route-path-bad-syntax.rs:71:1
|
71 | fn h4() {} //~ NOTE expected argument named `_r` here
| ^^^^^^^^^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:69:14
|
69 | #[get("/<_r>/<b>")] //~ ERROR unused dynamic parameter
| ^^^
|
note: expected argument named `b` here
--> $DIR/route-path-bad-syntax.rs:71:1
|
71 | fn h4() {} //~ NOTE expected argument named `_r` here
| ^^^^^^^^^^
error: `foo_.` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:76:9
|
76 | #[get("/<foo_.>")] //~ ERROR `foo_.` is not a valid identifier
| ^^^^^^^
|
= help: parameter names must be valid identifiers
error: `foo*` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:80:9
|
80 | #[get("/<foo*>")] //~ ERROR `foo*` is not a valid identifier
| ^^^^^^
|
= help: parameter names must be valid identifiers
error: `!` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:84:9
|
84 | #[get("/<!>")] //~ ERROR `!` is not a valid identifier
| ^^^
|
= help: parameter names must be valid identifiers
error: `name>:<id` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:88:9
|
88 | #[get("/<name>:<id>")] //~ ERROR `name>:<id` is not a valid identifier
| ^^^^^^^^^^^
|
= help: parameter names must be valid identifiers
error: malformed parameter
--> $DIR/route-path-bad-syntax.rs:94:20
|
94 | #[get("/", data = "foo")] //~ ERROR malformed parameter
| ^^^
|
= help: parameter must be of the form '<param>'
error: malformed parameter
--> $DIR/route-path-bad-syntax.rs:98:20
|
98 | #[get("/", data = "<foo..>")] //~ ERROR malformed parameter
| ^^^^^^^
|
= help: parameter must be of the form '<param>'
error: parameter is missing a closing bracket
--> $DIR/route-path-bad-syntax.rs:102:20
|
102 | #[get("/", data = "<foo")] //~ ERROR missing a closing bracket
| ^^^^
|
= help: did you mean '<foo>'?
error: `test ` is not a valid identifier
--> $DIR/route-path-bad-syntax.rs:106:20
|
106 | #[get("/", data = "<test >")] //~ ERROR `test ` is not a valid identifier
| ^^^^^^^
|
= help: parameter names must be valid identifiers
error: parameters must be named
--> $DIR/route-path-bad-syntax.rs:112:9
|
112 | #[get("/<_>")] //~ ERROR must be named
| ^^^
|
= help: use a name such as `_guard` or `_param`
error: parameter names cannot be empty
--> $DIR/route-path-bad-syntax.rs:117:9
|
117 | #[get("/<>")] //~ ERROR cannot be empty
| ^^
error: malformed parameter or identifier
--> $DIR/route-path-bad-syntax.rs:120:9
|
120 | #[get("/<id><")] //~ ERROR malformed parameter
| ^^^^^
|
= help: parameters must be of the form '<param>'
= help: identifiers cannot contain '<' or '>'
error: malformed parameter or identifier
--> $DIR/route-path-bad-syntax.rs:125:9
|
125 | #[get("/<<<<id><")] //~ ERROR malformed parameter
| ^^^^^^^^
|
= help: parameters must be of the form '<param>'
= help: identifiers cannot contain '<' or '>'
error: malformed parameter or identifier
--> $DIR/route-path-bad-syntax.rs:130:9
|
130 | #[get("/<>name><")] //~ ERROR malformed parameter
| ^^^^^^^^
|
= help: parameters must be of the form '<param>'
= help: identifiers cannot contain '<' or '>'
error: aborting due to 31 previous errors

View File

@ -0,0 +1,33 @@
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
struct Q;
#[get("/<foo>")]
fn f0(foo: Q) {} //~ ERROR FromParam
#[get("/<foo..>")]
fn f1(foo: Q) {} //~ ERROR FromSegments
#[get("/?<foo>")]
fn f2(foo: Q) {} //~ ERROR FromFormValue
#[get("/?<foo..>")]
fn f3(foo: Q) {} //~ ERROR FromQuery
#[post("/", data = "<foo>")]
fn f4(foo: Q) {} //~ ERROR FromData
#[get("/<foo>")]
fn f5(a: Q, foo: Q) {}
//~^ ERROR FromParam
//~^^ ERROR FromRequest
#[get("/<foo>/other/<bar>/<good>/okay")]
fn f6(a: Q, foo: Q, good: usize, bar: Q) {}
//~^ ERROR FromParam
//~^^ ERROR FromParam
//~^^^ ERROR FromRequest
fn main() { }

View File

@ -0,0 +1,65 @@
error[E0277]: the trait bound `Q: rocket::request::FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:8:7
|
8 | fn f0(foo: Q) {} //~ ERROR FromParam
| ^^^^^^ the trait `rocket::request::FromParam<'_>` is not implemented for `Q`
error[E0277]: the trait bound `Q: rocket::request::FromSegments<'_>` is not satisfied
--> $DIR/route-type-errors.rs:11:7
|
11 | fn f1(foo: Q) {} //~ ERROR FromSegments
| ^^^^^^ the trait `rocket::request::FromSegments<'_>` is not implemented for `Q`
error[E0277]: the trait bound `Q: rocket::request::FromFormValue<'_>` is not satisfied
--> $DIR/route-type-errors.rs:14:7
|
14 | fn f2(foo: Q) {} //~ ERROR FromFormValue
| ^^^^^^ the trait `rocket::request::FromFormValue<'_>` is not implemented for `Q`
error[E0277]: the trait bound `Q: rocket::request::FromQuery<'_>` is not satisfied
--> $DIR/route-type-errors.rs:17:7
|
17 | fn f3(foo: Q) {} //~ ERROR FromQuery
| ^^^^^^ the trait `rocket::request::FromQuery<'_>` is not implemented for `Q`
error[E0277]: the trait bound `Q: rocket::data::FromDataSimple` is not satisfied
--> $DIR/route-type-errors.rs:20:7
|
20 | fn f4(foo: Q) {} //~ ERROR FromData
| ^^^^^^ the trait `rocket::data::FromDataSimple` is not implemented for `Q`
|
= note: required because of the requirements on the impl of `rocket::data::FromData<'_>` for `Q`
error[E0277]: the trait bound `Q: rocket::request::FromRequest<'_, '_>` is not satisfied
--> $DIR/route-type-errors.rs:23:7
|
23 | fn f5(a: Q, foo: Q) {}
| ^^^^ the trait `rocket::request::FromRequest<'_, '_>` is not implemented for `Q`
error[E0277]: the trait bound `Q: rocket::request::FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:23:13
|
23 | fn f5(a: Q, foo: Q) {}
| ^^^^^^ the trait `rocket::request::FromParam<'_>` is not implemented for `Q`
error[E0277]: the trait bound `Q: rocket::request::FromRequest<'_, '_>` is not satisfied
--> $DIR/route-type-errors.rs:28:7
|
28 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {}
| ^^^^ the trait `rocket::request::FromRequest<'_, '_>` is not implemented for `Q`
error[E0277]: the trait bound `Q: rocket::request::FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:28:13
|
28 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {}
| ^^^^^^ the trait `rocket::request::FromParam<'_>` is not implemented for `Q`
error[E0277]: the trait bound `Q: rocket::request::FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:28:34
|
28 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {}
| ^^^^^^ the trait `rocket::request::FromParam<'_>` is not implemented for `Q`
error: aborting due to 10 previous errors
For more information about this error, try `rustc --explain E0277`.

View File

@ -0,0 +1,26 @@
// must-compile-successfully
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
// Check for unknown media types.
#[get("/", format = "application/x-custom")] //~ WARNING not a known media type
fn f0() {}
#[get("/", format = "x-custom/plain")] //~ WARNING not a known media type
fn f1() {}
#[get("/", format = "x-custom/x-custom")] //~ WARNING not a known media type
fn f2() {}
// Check if a data argument is used with a usually non-payload bearing method.
#[get("/", data = "<_foo>")] //~ WARNING used with non-payload-supporting method
fn g0(_foo: rocket::Data) {}
#[head("/", data = "<_foo>")] //~ WARNING used with non-payload-supporting method
fn g1(_foo: rocket::Data) {}
fn main() { }

View File

@ -0,0 +1,42 @@
warning: 'application/x-custom' is not a known media type
--> $DIR/route-warnings.rs:9:21
|
9 | #[get("/", format = "application/x-custom")] //~ WARNING not a known media type
| ^^^^^^^^^^^^^^^^^^^^^^
warning: 'x-custom/plain' is not a known media type
--> $DIR/route-warnings.rs:12:21
|
12 | #[get("/", format = "x-custom/plain")] //~ WARNING not a known media type
| ^^^^^^^^^^^^^^^^
warning: 'x-custom/x-custom' is not a known media type
--> $DIR/route-warnings.rs:15:21
|
15 | #[get("/", format = "x-custom/x-custom")] //~ WARNING not a known media type
| ^^^^^^^^^^^^^^^^^^^
warning: `data` used with non-payload-supporting method
--> $DIR/route-warnings.rs:20:12
|
20 | #[get("/", data = "<_foo>")] //~ WARNING used with non-payload-supporting method
| ^^^^^^^^^^^^^^^
|
note: 'GET' does not typically support payloads
--> $DIR/route-warnings.rs:20:3
|
20 | #[get("/", data = "<_foo>")] //~ WARNING used with non-payload-supporting method
| ^^^
warning: `data` used with non-payload-supporting method
--> $DIR/route-warnings.rs:23:13
|
23 | #[head("/", data = "<_foo>")] //~ WARNING used with non-payload-supporting method
| ^^^^^^^^^^^^^^^
|
note: 'HEAD' does not typically support payloads
--> $DIR/route-warnings.rs:23:3
|
23 | #[head("/", data = "<_foo>")] //~ WARNING used with non-payload-supporting method
| ^^^^

View File

@ -0,0 +1,29 @@
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
use rocket::http::RawStr;
use rocket::request::FromParam;
struct S;
impl<'a> FromParam<'a> for S {
type Error = ();
fn from_param(param: &'a RawStr) -> Result<Self, Self::Error> { Ok(S) }
}
#[post("/<id>")]
fn simple(id: i32) { }
#[post("/<id>/<name>")]
fn not_uri_display(id: i32, name: S) { }
#[post("/<id>/<name>")]
fn not_uri_display_but_unused(id: i32, name: S) { }
fn main() {
uri!(simple: id = "hi"); //~ ERROR i32: rocket::http::uri::FromUriParam<&str>
uri!(simple: "hello"); //~ ERROR i32: rocket::http::uri::FromUriParam<&str>
uri!(simple: id = 239239i64); //~ ERROR i32: rocket::http::uri::FromUriParam<i64>
uri!(not_uri_display: 10, S); //~ ERROR S: rocket::http::uri::FromUriParam<_>
}

View File

@ -1,31 +1,31 @@
error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<&str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:27:23
--> $DIR/typed-uri-bad-type.rs:25:23
|
27 | uri!(simple: id = "hi");
25 | uri!(simple: id = "hi"); //~ ERROR i32: rocket::http::uri::FromUriParam<&str>
| ^^^^ the trait `rocket::http::uri::FromUriParam<&str>` is not implemented for `i32`
|
= note: required by `rocket::http::uri::FromUriParam::from_uri_param`
error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<&str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:28:18
--> $DIR/typed-uri-bad-type.rs:26:18
|
28 | uri!(simple: "hello");
26 | uri!(simple: "hello"); //~ ERROR i32: rocket::http::uri::FromUriParam<&str>
| ^^^^^^^ the trait `rocket::http::uri::FromUriParam<&str>` is not implemented for `i32`
|
= note: required by `rocket::http::uri::FromUriParam::from_uri_param`
error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<i64>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:29:23
--> $DIR/typed-uri-bad-type.rs:27:23
|
29 | uri!(simple: id = 239239i64);
27 | uri!(simple: id = 239239i64); //~ ERROR i32: rocket::http::uri::FromUriParam<i64>
| ^^^^^^^^^ the trait `rocket::http::uri::FromUriParam<i64>` is not implemented for `i32`
|
= note: required by `rocket::http::uri::FromUriParam::from_uri_param`
error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam<_>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:30:31
--> $DIR/typed-uri-bad-type.rs:28:31
|
30 | uri!(not_uri_display: 10, S);
28 | uri!(not_uri_display: 10, S); //~ ERROR S: rocket::http::uri::FromUriParam<_>
| ^ the trait `rocket::http::uri::FromUriParam<_>` is not implemented for `S`
error: aborting due to 4 previous errors

View File

@ -0,0 +1,72 @@
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
use std::fmt;
use rocket::http::Cookies;
#[post("/<id>")]
fn has_one(id: i32) { }
#[post("/<id>")]
fn has_one_guarded(cookies: Cookies, id: i32) { }
#[post("/<id>/<name>")]
fn has_two(cookies: Cookies, id: i32, name: String) { }
fn main() {
uri!(has_one); //~ ERROR expects 1 parameter but 0
uri!(has_one: 1, 23); //~ ERROR expects 1 parameter but 2
uri!(has_one: "Hello", 23, ); //~ ERROR expects 1 parameter but 2
uri!(has_one_guarded: "hi", 100); //~ ERROR expects 1 parameter but 2
uri!(has_two: 10, "hi", "there"); //~ ERROR expects 2 parameters but 3
uri!(has_two: 10); //~ ERROR expects 2 parameters but 1
uri!(has_one: id = 100, name = "hi"); //~ ERROR invalid parameters
//~^ HELP unknown parameter: `name`
uri!(has_one: name = 100, id = 100); //~ ERROR invalid parameters
//~^ HELP unknown parameter: `name`
uri!(has_one: name = 100, age = 50, id = 100); //~ ERROR invalid parameters
//~^ HELP unknown parameters: `name`, `age`
uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters
//~^ HELP unknown parameters: `name`, `age`
//~^^ HELP duplicate parameter: `id`
uri!(has_one: id = 100, id = 100); //~ ERROR invalid parameters
//~^ HELP duplicate parameter: `id`
uri!(has_one: id = 100, id = 100, ); //~ ERROR invalid parameters
//~^ HELP duplicate parameter: `id`
uri!(has_one: name = "hi"); //~ ERROR invalid parameters
//~^ HELP unknown parameter: `name`
//~^^ HELP missing parameter: `id`
uri!(has_one_guarded: cookies = "hi", id = 100); //~ ERROR invalid parameters
//~^ HELP unknown parameter: `cookies`
uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters
//~^ HELP unknown parameter: `cookies`
uri!(has_two: id = 100, id = 100, ); //~ ERROR invalid parameters
//~^ HELP duplicate parameter: `id`
//~^^ HELP missing parameter: `name`
uri!(has_two: name = "hi"); //~ ERROR invalid parameters
//~^ HELP missing parameter: `id`
uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters
//~^ HELP duplicate parameter: `id`
//~^^ HELP missing parameter: `name`
//~^^^ HELP unknown parameter: `cookies`
uri!(has_two: id = 100, cookies = "hi"); //~ ERROR invalid parameters
//~^ HELP missing parameter: `name`
//~^^ HELP unknown parameter: `cookies`
}

View File

@ -0,0 +1,229 @@
error: `has_one` route uri expects 1 parameter but 0 were supplied
--> $DIR/typed-uris-bad-params.rs:19:10
|
19 | uri!(has_one); //~ ERROR expects 1 parameter but 0
| ^^^^^^^
|
= note: expected parameter: id: i32
error: `has_one` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:21:19
|
21 | uri!(has_one: 1, 23); //~ ERROR expects 1 parameter but 2
| ^^^^^
|
= note: expected parameter: id: i32
error: `has_one` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:22:19
|
22 | uri!(has_one: "Hello", 23, ); //~ ERROR expects 1 parameter but 2
| ^^^^^^^^^^^^
|
= note: expected parameter: id: i32
error: `has_one_guarded` route uri expects 1 parameter but 2 were supplied
--> $DIR/typed-uris-bad-params.rs:23:27
|
23 | uri!(has_one_guarded: "hi", 100); //~ ERROR expects 1 parameter but 2
| ^^^^^^^^^
|
= note: expected parameter: id: i32
error: `has_two` route uri expects 2 parameters but 3 were supplied
--> $DIR/typed-uris-bad-params.rs:25:19
|
25 | uri!(has_two: 10, "hi", "there"); //~ ERROR expects 2 parameters but 3
| ^^^^^^^^^^^^^^^^^
|
= note: expected parameters: id: i32, name: String
error: `has_two` route uri expects 2 parameters but 1 was supplied
--> $DIR/typed-uris-bad-params.rs:26:19
|
26 | uri!(has_two: 10); //~ ERROR expects 2 parameters but 1
| ^^
|
= note: expected parameters: id: i32, name: String
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:28:19
|
28 | uri!(has_one: id = 100, name = "hi"); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:28:29
|
28 | uri!(has_one: id = 100, name = "hi"); //~ ERROR invalid parameters
| ^^^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:31:19
|
31 | uri!(has_one: name = 100, id = 100); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:31:19
|
31 | uri!(has_one: name = 100, id = 100); //~ ERROR invalid parameters
| ^^^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:34:19
|
34 | uri!(has_one: name = 100, age = 50, id = 100); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameters: `name`, `age`
--> $DIR/typed-uris-bad-params.rs:34:19
|
34 | uri!(has_one: name = 100, age = 50, id = 100); //~ ERROR invalid parameters
| ^^^^ ^^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:37:19
|
37 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameters: `name`, `age`
--> $DIR/typed-uris-bad-params.rs:37:19
|
37 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters
| ^^^^ ^^^
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:37:51
|
37 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters
| ^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:41:19
|
41 | uri!(has_one: id = 100, id = 100); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:41:29
|
41 | uri!(has_one: id = 100, id = 100); //~ ERROR invalid parameters
| ^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:44:19
|
44 | uri!(has_one: id = 100, id = 100, ); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:44:29
|
44 | uri!(has_one: id = 100, id = 100, ); //~ ERROR invalid parameters
| ^^
error: invalid parameters for `has_one` route uri
--> $DIR/typed-uris-bad-params.rs:47:19
|
47 | uri!(has_one: name = "hi"); //~ ERROR invalid parameters
| ^^^^^^^^^^^
|
= note: uri parameters are: id: i32
= help: missing parameter: `id`
help: unknown parameter: `name`
--> $DIR/typed-uris-bad-params.rs:47:19
|
47 | uri!(has_one: name = "hi"); //~ ERROR invalid parameters
| ^^^^
error: invalid parameters for `has_one_guarded` route uri
--> $DIR/typed-uris-bad-params.rs:51:27
|
51 | uri!(has_one_guarded: cookies = "hi", id = 100); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:51:27
|
51 | uri!(has_one_guarded: cookies = "hi", id = 100); //~ ERROR invalid parameters
| ^^^^^^^
error: invalid parameters for `has_one_guarded` route uri
--> $DIR/typed-uris-bad-params.rs:54:27
|
54 | uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:54:37
|
54 | uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters
| ^^^^^^^
error: invalid parameters for `has_two` route uri
--> $DIR/typed-uris-bad-params.rs:57:19
|
57 | uri!(has_two: id = 100, id = 100, ); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32, name: String
= help: missing parameter: `name`
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:57:29
|
57 | uri!(has_two: id = 100, id = 100, ); //~ ERROR invalid parameters
| ^^
error: invalid parameters for `has_two` route uri
--> $DIR/typed-uris-bad-params.rs:61:19
|
61 | uri!(has_two: name = "hi"); //~ ERROR invalid parameters
| ^^^^^^^^^^^
|
= note: uri parameters are: id: i32, name: String
= help: missing parameter: `id`
error: invalid parameters for `has_two` route uri
--> $DIR/typed-uris-bad-params.rs:64:19
|
64 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32, name: String
= help: missing parameter: `name`
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:64:19
|
64 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters
| ^^^^^^^
help: duplicate parameter: `id`
--> $DIR/typed-uris-bad-params.rs:64:45
|
64 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters
| ^^ ^^
error: invalid parameters for `has_two` route uri
--> $DIR/typed-uris-bad-params.rs:69:19
|
69 | uri!(has_two: id = 100, cookies = "hi"); //~ ERROR invalid parameters
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: uri parameters are: id: i32, name: String
= help: missing parameter: `name`
help: unknown parameter: `cookies`
--> $DIR/typed-uris-bad-params.rs:69:29
|
69 | uri!(has_two: id = 100, cookies = "hi"); //~ ERROR invalid parameters
| ^^^^^^^
error: aborting due to 19 previous errors

View File

@ -0,0 +1,19 @@
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;
#[post("/<id>/<name>")]
fn simple(id: i32, name: String) -> &'static str { "" }
fn main() {
uri!(simple: id = 100, "Hello"); //~ ERROR named and unnamed
uri!(simple: "Hello", id = 100); //~ ERROR named and unnamed
uri!(simple,); //~ ERROR expected `:`
uri!(simple:); //~ ERROR argument list
uri!("/mount"); //~ ERROR route path
uri!("/mount",); //~ ERROR expected identifier
uri!("mount", simple); //~ invalid mount point
uri!("/mount/<id>", simple); //~ invalid mount point
uri!(); //~ unexpected end of input
uri!(simple: id = ); //~ expected expression
}

View File

@ -0,0 +1,62 @@
error: named and unnamed parameters cannot be mixed
--> $DIR/typed-uris-invalid-syntax.rs:9:18
|
9 | uri!(simple: id = 100, "Hello"); //~ ERROR named and unnamed
| ^^^^^^^^^^^^^^^^^
error: named and unnamed parameters cannot be mixed
--> $DIR/typed-uris-invalid-syntax.rs:10:18
|
10 | uri!(simple: "Hello", id = 100); //~ ERROR named and unnamed
| ^^^^^^^^^^^^^^^^^
error: expected `:`
--> $DIR/typed-uris-invalid-syntax.rs:11:16
|
11 | uri!(simple,); //~ ERROR expected `:`
| ^
error: expected argument list after `:`
--> $DIR/typed-uris-invalid-syntax.rs:12:16
|
12 | uri!(simple:); //~ ERROR argument list
| ^
error: unexpected end of input: expected ',' followed by route path
--> $DIR/typed-uris-invalid-syntax.rs:13:10
|
13 | uri!("/mount"); //~ ERROR route path
| ^^^^^^^^
error: unexpected end of input, expected identifier
--> $DIR/typed-uris-invalid-syntax.rs:14:5
|
14 | uri!("/mount",); //~ ERROR expected identifier
| ^^^^^^^^^^^^^^^^
error: invalid mount point; mount points must be static, absolute URIs: `/example`
--> $DIR/typed-uris-invalid-syntax.rs:15:10
|
15 | uri!("mount", simple); //~ invalid mount point
| ^^^^^^^
error: invalid mount point; mount points must be static, absolute URIs: `/example`
--> $DIR/typed-uris-invalid-syntax.rs:16:10
|
16 | uri!("/mount/<id>", simple); //~ invalid mount point
| ^^^^^^^^^^^^^
error: unexpected end of input, call to `uri!` cannot be empty
--> $DIR/typed-uris-invalid-syntax.rs:17:5
|
17 | uri!(); //~ unexpected end of input
| ^^^^^^^
error: unexpected end of input, expected expression
--> $DIR/typed-uris-invalid-syntax.rs:18:5
|
18 | uri!(simple: id = ); //~ expected expression
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to 10 previous errors

View File

@ -34,7 +34,7 @@ shift
shopt -s nullglob
while [[ "$1" != "" ]]; do
for EXT in "stderr" "stdout" "fixed"; do
for EXT in "stderr" "fixed"; do
for OUT_NAME in $BUILD_DIR/${1%.rs}.*$EXT; do
OUT_DIR=`dirname "$1"`
OUT_BASE=`basename "$OUT_NAME"`

View File

@ -35,4 +35,3 @@ optional = true
[dev-dependencies]
rocket = { version = "0.4.0-dev", path = "../lib" }
rocket_codegen = { version = "0.4.0-dev", path = "../codegen" }

View File

@ -40,7 +40,15 @@ impl Method {
}
/// Returns `true` if an HTTP request with the method represented by `self`
/// supports a payload.
/// always supports a payload.
///
/// The following methods always support payloads:
///
/// * `PUT`, `POST`, `DELETE`, `PATCH`
///
/// The following methods _do not_ always support payloads:
///
/// * `GET`, `HEAD`, `CONNECT`, `TRACE`, `OPTIONS`
///
/// # Example
///

View File

@ -108,7 +108,8 @@ impl<'a> RouteSegment<'a> {
return Ok(RouteSegment { string, source, name, kind, index });
} else if segment.is_empty() {
return Err(Empty);
} else if segment.starts_with('<') && segment.len() > 1 {
} else if segment.starts_with('<') && segment.len() > 1
&& !segment[1..].contains('<') && !segment[1..].contains('>') {
return Err(MissingClose);
} else if segment.contains('>') || segment.contains('<') {
return Err(Malformed);

View File

@ -105,8 +105,7 @@ use self::priv_encode_set::PATH_ENCODE_SET;
/// dynamic parameter type.
///
/// ```rust
/// # #![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;
/// # fn main() { }
/// use rocket::http::RawStr;
@ -229,7 +228,9 @@ macro_rules! impl_with_display {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
impl_with_display! {
i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, bool,
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
f32, f64, bool,
IpAddr, Ipv4Addr, Ipv6Addr
}

View File

@ -34,7 +34,6 @@ isatty = "0.1"
[dev-dependencies]
# TODO: Find a way to not depend on this.
lazy_static = "1.0"
rocket_codegen = { version = "0.4.0-dev", path = "../codegen" }
[build-dependencies]
yansi = "0.4"

View File

@ -1,5 +1,5 @@
#![feature(specialization)]
#![feature(plugin, decl_macro)]
#![feature(decl_macro)]
#![feature(try_trait)]
#![feature(fnbox)]
#![feature(never_type)]
@ -30,12 +30,10 @@
//!
//! ## Libraries
//!
//! Rocket's functionality is split into three crates:
//! Rocket's functionality is split into two crates:
//!
//! 1. [Core](/rocket) - The core library. Needed by every Rocket application.
//! 2. [Codegen](/rocket_codegen) - Core code generation plugin. Should always
//! be used alongside `rocket`, though it's not necessary.
//! 3. [Contrib](/rocket_contrib) - Provides useful functionality for many
//! 2. [Contrib](/rocket_contrib) - Provides useful functionality for many
//! Rocket applications. Completely optional.
//!
//! ## Usage
@ -48,7 +46,6 @@
//! ```rust,ignore
//! [dependencies]
//! rocket = "*"
//! rocket_codegen = "*"
//! ```
//!
//! If you'll be deploying your project to [crates.io](https://crates.io),

View File

@ -5,13 +5,11 @@ use request::FormItems;
///
/// # Deriving
///
/// This trait can be automatically derived via the
/// [rocket_codegen](/rocket_codegen) plugin. When deriving `FromForm`, every
/// field in the structure must implement
/// [FromFormValue](trait.FromFormValue.html). Rocket validates each field in
/// the structure by calling its `FromFormValue` implementation. You may wish to
/// implement `FromFormValue` for your own types for custom, automatic
/// validation.
/// This trait can be automatically derived. When deriving `FromForm`, every
/// field in the structure must implement [`FromFormValue`]. Rocket validates
/// each field in the structure by calling its `FromFormValue` implementation.
/// You may wish to implement `FromFormValue` for your own types for custom,
/// automatic validation.
///
/// ```rust
/// #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]

View File

@ -428,11 +428,12 @@ impl<'r> Request<'r> {
}).as_ref()
}
/// Returns the media type "format" of the request if it is present.
/// Returns the media type "format" of the request.
///
/// The "format" of a request is either the Content-Type, if the request
/// methods indicates support for a payload, or the preferred media type in
/// the Accept header otherwise.
/// the Accept header otherwise. If the method indicates no payload and no
/// Accept header is specified, a media type of `Any` is returned.
///
/// The media type returned from this method is used to match against the
/// `format` route attribute.
@ -455,13 +456,16 @@ impl<'r> Request<'r> {
/// # });
/// ```
pub fn format(&self) -> Option<&MediaType> {
static ANY: MediaType = MediaType::Any;
if self.method().supports_payload() {
self.content_type().map(|ct| ct.media_type())
} else {
// FIXME: Should we be using `accept_first` or `preferred`? Or
// should we be checking neither and instead pass things through
// where the client accepts the thing at all?
self.accept().map(|accept| accept.preferred().media_type())
self.accept()
.map(|accept| accept.preferred().media_type())
.or(Some(&ANY))
}
}

View File

@ -21,10 +21,7 @@ impl Route {
self.method == other.method
&& self.rank == other.rank
&& paths_collide(self, other)
&& match (self.format.as_ref(), other.format.as_ref()) {
(Some(a), Some(b)) => media_types_collide(a, b),
_ => true
}
&& formats_collide(self, other)
}
/// Determines if this route matches against the given request. This means
@ -43,13 +40,7 @@ impl Route {
self.method == req.method()
&& paths_match(self, req)
&& queries_match(self, req)
&& match self.format {
Some(ref a) => match req.format() {
Some(ref b) => media_types_collide(a, b),
None => false
},
None => true
}
&& formats_match(self, req)
}
}
@ -116,6 +107,40 @@ fn queries_match(route: &Route, request: &Request) -> bool {
true
}
fn formats_collide(route: &Route, other: &Route) -> bool {
// When matching against the `Accept` header, the client can always provide
// a media type that will cause a collision through non-specificity.
if !route.method.supports_payload() {
return true;
}
// When matching against the `Content-Type` header, we'll only consider
// requests as having a `Content-Type` if they're fully specified. If a
// route doesn't have a `format`, it accepts all `Content-Type`s. If a
// request doesn't have a format, it only matches routes without a format.
match (route.format.as_ref(), other.format.as_ref()) {
(Some(a), Some(b)) => media_types_collide(a, b),
_ => true
}
}
fn formats_match(route: &Route, request: &Request) -> bool {
if !route.method.supports_payload() {
route.format.as_ref()
.and_then(|a| request.format().map(|b| (a, b)))
.map(|(a, b)| media_types_collide(a, b))
.unwrap_or(true)
} else {
match route.format.as_ref() {
Some(a) => match request.format() {
Some(b) if b.specificity() == 2 => media_types_collide(a, b),
_ => false
}
None => true
}
}
}
fn media_types_collide(first: &MediaType, other: &MediaType) -> bool {
let collide = |a, b| a == "*" || b == "*" || a == b;
collide(first.top(), other.top()) && collide(first.sub(), other.sub())
@ -313,15 +338,15 @@ mod tests {
assert!(!mt_mt_collide("something/*", "random/else"));
}
fn r_mt_mt_collide<S1, S2>(m1: Method, mt1: S1, m2: Method, mt2: S2) -> bool
fn r_mt_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
{
let mut route_a = Route::new(m1, "/", dummy_handler);
let mut route_a = Route::new(m, "/", dummy_handler);
if let Some(mt_str) = mt1.into() {
route_a.format = Some(mt_str.parse::<MediaType>().unwrap());
}
let mut route_b = Route::new(m2, "/", dummy_handler);
let mut route_b = Route::new(m, "/", dummy_handler);
if let Some(mt_str) = mt2.into() {
route_b.format = Some(mt_str.parse::<MediaType>().unwrap());
}
@ -331,23 +356,44 @@ mod tests {
#[test]
fn test_route_content_type_colliions() {
assert!(r_mt_mt_collide(Get, "application/json", Get, "application/json"));
assert!(r_mt_mt_collide(Get, "*/json", Get, "application/json"));
assert!(r_mt_mt_collide(Get, "*/json", Get, "application/*"));
assert!(r_mt_mt_collide(Get, "text/html", Get, "text/*"));
assert!(r_mt_mt_collide(Get, "any/thing", Get, "*/*"));
// non-payload bearing routes always collide
assert!(r_mt_mt_collide(Get, "application/json", "application/json"));
assert!(r_mt_mt_collide(Get, "*/json", "application/json"));
assert!(r_mt_mt_collide(Get, "*/json", "application/*"));
assert!(r_mt_mt_collide(Get, "text/html", "text/*"));
assert!(r_mt_mt_collide(Get, "any/thing", "*/*"));
assert!(r_mt_mt_collide(Get, None, Get, "text/*"));
assert!(r_mt_mt_collide(Get, None, Get, "text/html"));
assert!(r_mt_mt_collide(Get, None, Get, "*/*"));
assert!(r_mt_mt_collide(Get, "text/html", Get, None));
assert!(r_mt_mt_collide(Get, "*/*", Get, None));
assert!(r_mt_mt_collide(Get, "application/json", Get, None));
assert!(r_mt_mt_collide(Get, None, "text/*"));
assert!(r_mt_mt_collide(Get, None, "text/html"));
assert!(r_mt_mt_collide(Get, None, "*/*"));
assert!(r_mt_mt_collide(Get, "text/html", None));
assert!(r_mt_mt_collide(Get, "*/*", None));
assert!(r_mt_mt_collide(Get, "application/json", None));
assert!(!r_mt_mt_collide(Get, "text/html", Get, "application/*"));
assert!(!r_mt_mt_collide(Get, "application/html", Get, "text/*"));
assert!(!r_mt_mt_collide(Get, "*/json", Get, "text/html"));
assert!(!r_mt_mt_collide(Get, "text/html", Get, "text/css"));
assert!(r_mt_mt_collide(Get, "application/*", "text/*"));
assert!(r_mt_mt_collide(Get, "application/json", "text/*"));
assert!(r_mt_mt_collide(Get, "application/json", "text/html"));
assert!(r_mt_mt_collide(Get, "text/html", "text/html"));
// payload bearing routes collide if the media types collide
assert!(r_mt_mt_collide(Post, "application/json", "application/json"));
assert!(r_mt_mt_collide(Post, "*/json", "application/json"));
assert!(r_mt_mt_collide(Post, "*/json", "application/*"));
assert!(r_mt_mt_collide(Post, "text/html", "text/*"));
assert!(r_mt_mt_collide(Post, "any/thing", "*/*"));
assert!(r_mt_mt_collide(Post, None, "text/*"));
assert!(r_mt_mt_collide(Post, None, "text/html"));
assert!(r_mt_mt_collide(Post, None, "*/*"));
assert!(r_mt_mt_collide(Post, "text/html", None));
assert!(r_mt_mt_collide(Post, "*/*", None));
assert!(r_mt_mt_collide(Post, "application/json", None));
assert!(!r_mt_mt_collide(Post, "text/html", "application/*"));
assert!(!r_mt_mt_collide(Post, "application/html", "text/*"));
assert!(!r_mt_mt_collide(Post, "*/json", "text/html"));
assert!(!r_mt_mt_collide(Post, "text/html", "text/css"));
assert!(!r_mt_mt_collide(Post, "other/html", "text/html"));
}
fn req_route_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
@ -381,8 +427,9 @@ mod tests {
assert!(req_route_mt_collide(Get, "application/json", "application/json"));
assert!(req_route_mt_collide(Get, "text/html", "text/html"));
assert!(req_route_mt_collide(Get, "text/html", "*/*"));
assert!(req_route_mt_collide(Get, None, "text/html"));
assert!(req_route_mt_collide(Get, None, "*/*"));
assert!(req_route_mt_collide(Get, None, "text/*"));
assert!(req_route_mt_collide(Get, None, "text/html"));
assert!(req_route_mt_collide(Get, None, "application/json"));
assert!(req_route_mt_collide(Post, "text/html", None));
@ -394,10 +441,19 @@ mod tests {
assert!(req_route_mt_collide(Get, "application/json", None));
assert!(req_route_mt_collide(Get, "x-custom/anything", None));
assert!(req_route_mt_collide(Get, None, None));
assert!(req_route_mt_collide(Get, None, "text/html"));
assert!(req_route_mt_collide(Get, None, "application/json"));
assert!(req_route_mt_collide(Get, "text/html, text/plain", "text/html"));
assert!(req_route_mt_collide(Get, "text/html; q=0.5, text/xml", "text/xml"));
assert!(!req_route_mt_collide(Post, None, "text/html"));
assert!(!req_route_mt_collide(Post, None, "text/*"));
assert!(!req_route_mt_collide(Post, None, "*/text"));
assert!(!req_route_mt_collide(Post, None, "*/*"));
assert!(!req_route_mt_collide(Post, None, "text/html"));
assert!(!req_route_mt_collide(Post, None, "application/json"));
assert!(!req_route_mt_collide(Post, "application/json", "text/html"));
assert!(!req_route_mt_collide(Post, "application/json", "text/*"));
assert!(!req_route_mt_collide(Post, "application/json", "*/xml"));
@ -406,7 +462,6 @@ mod tests {
assert!(!req_route_mt_collide(Get, "application/json", "*/xml"));
assert!(!req_route_mt_collide(Post, None, "text/html"));
assert!(!req_route_mt_collide(Post, None, "*/*"));
assert!(!req_route_mt_collide(Post, None, "application/json"));
}

View File

@ -1,5 +1,4 @@
#![feature(plugin, decl_macro, proc_macro_non_items)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;

View File

@ -1,5 +1,4 @@
#![feature(plugin, decl_macro, proc_macro_non_items)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;

View File

@ -1,5 +1,4 @@
#![feature(plugin, decl_macro, proc_macro_non_items)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
#[macro_use] extern crate rocket;

View File

@ -6,4 +6,3 @@ publish = false
[dependencies]
rocket = { path = "../../core/lib" }
rocket_codegen = { path = "../../core/codegen" }

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

@ -6,7 +6,6 @@ publish = false
[dependencies]
rocket = { path = "../../core/lib" }
rocket_codegen = { path = "../../core/codegen" }
[dependencies.rocket_contrib]
path = "../../contrib/lib"

View File

@ -1,5 +1,4 @@
#![feature(plugin, proc_macro_non_items, proc_macro_gen, decl_macro, never_type)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro, never_type)]
#[macro_use] extern crate rocket;
extern crate rocket_contrib;

View File

@ -6,7 +6,6 @@ publish = false
[dependencies]
rocket = { path = "../../core/lib" }
rocket_codegen = { path = "../../core/codegen" }
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"

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;
#[macro_use] extern crate serde_derive;

View File

@ -30,7 +30,7 @@ CONTRIB_ROOT=$(relative "contrib") || exit $?
# Root of project-like directories.
CORE_LIB_ROOT=$(relative "core/lib") || exit $?
CORE_CODEGEN_ROOT=$(relative "core/codegen") || exit $?
# CORE_CODEGEN_ROOT=$(relative "core/codegen") || exit $?
CORE_CODEGEN_NEXT_ROOT=$(relative "core/codegen_next") || exit $?
CORE_HTTP_ROOT=$(relative "core/http") || exit $?
CONTRIB_LIB_ROOT=$(relative "contrib/lib") || exit $?
@ -41,7 +41,7 @@ DOC_DIR=$(relative "target/doc") || exit $?
ALL_PROJECT_DIRS=(
"${CORE_LIB_ROOT}"
"${CORE_CODEGEN_ROOT}"
# "${CORE_CODEGEN_ROOT}"
"${CORE_CODEGEN_NEXT_ROOT}"
"${CORE_HTTP_ROOT}"
"${CONTRIB_LIB_ROOT}"
@ -53,7 +53,7 @@ if [ "${1}" = "-p" ]; then
echo "CORE_ROOT: ${CORE_ROOT}"
echo "CONTRIB_ROOT: ${CONTRIB_ROOT}"
echo "CORE_LIB_ROOT: ${CORE_LIB_ROOT}"
echo "CORE_CODEGEN_ROOT: ${CORE_CODEGEN_ROOT}"
# echo "CORE_CODEGEN_ROOT: ${CORE_CODEGEN_ROOT}"
echo "CORE_CODEGEN_NEXT_ROOT: ${CORE_CODEGEN_NEXT_ROOT}"
echo "CORE_HTTP_ROOT: ${CORE_HTTP_ROOT}"
echo "CONTRIB_LIB_ROOT: ${CONTRIB_LIB_ROOT}"

View File

@ -61,10 +61,9 @@ Modify `src/main.rs` so that it contains the code for the Rocket `Hello, world!`
program, reproduced below:
```rust
#![feature(plugin)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
extern crate rocket;
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {

View File

@ -138,10 +138,9 @@ We typically call `launch` from the `main` function. Our complete _Hello,
world!_ application thus looks like:
```rust
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
extern crate rocket;
#[macro_use] extern crate rocket;
#[get("/world")]
fn world() -> &'static str {
@ -153,11 +152,11 @@ fn main() {
}
```
Note that we've added the `#![feature(plugin, decl_macro)]` and
`#![plugin(rocket_codegen)]` lines to tell Rust that we'll be using Rocket's
code generation plugin. We've also imported the `rocket` crate into our
namespace via `extern crate rocket`. Finally, we call the `launch` method in the
`main` function.
Note the `#![feature]` line: this tells Rust that we're opting in to compiler
features vailable in the nightly release channel. This line must be in the crate
root, typically `main.rs`. We've also imported the `rocket` crate and all of its
macros into our namespace via `#[macro_use] extern crate rocket`. Finally, we
call the `launch` method in the `main` function.
Running the application, the console shows:

View File

@ -51,10 +51,9 @@ And finally, create a skeleton Rocket application to work off of in
`src/main.rs`:
```rust
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
extern crate rocket;
#[macro_use] extern crate rocket;
fn main() {
rocket::ignite().launch();

View File

@ -44,10 +44,9 @@ margin = 9
[[sections]]
title = "Hello, Rocket!"
code = '''
#![feature(plugin)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
extern crate rocket;
#[macro_use] extern crate rocket;
#[get("/hello/<name>/<age>")]
fn hello(name: String, age: u8) -> String {