UTF-8 routes. Forms revamp. Temp files. Capped.

So. Many. Changes.

This is an insane commit: simultaneously one of the best (because of all
the wonderful improvements!) and one of the worst (because it is just
massive) in the project's history.

Routing:
  * All UTF-8 characters are accepted everywhere in route paths. (#998)
  * `path` is now `uri` in `route` attribute: `#[route(GET, path = "..")]`
    becomes `#[route(GET, uri = "..")]`.

Forms Revamp
  * All form related types now reside in a new `form` module.
  * Multipart forms are supported. (resolves #106)
  * Collections are supported in forms and queries. (resolves #205)
  * Nested structures in forms and queries are supported. (resolves #313)
  * Form fields can be ad-hoc validated with `#[field(validate = expr)]`.
  * `FromFormValue` is now `FromFormField`, blanket implements `FromForm`.
  * Form field values are always percent-decoded apriori.

Temporary Files
  * A new `TempFile` data and form guard allows streaming data directly to a
    file which can then be persisted.
  * A new `temp_dir` config parameter specifies where to store `TempFile`.
  * The limits `file` and `file/$ext`, where `$ext` is the file extension,
    determines the data limit for a `TempFile`.

Capped
  * A new `Capped` type is used to indicate when data has been truncated due to
    incoming data limits. It allows checking whether data is complete or
    truncated.
  * `DataStream` methods return `Capped` types.
  * `DataStream` API has been revamped to account for `Capped` types.
  * Several `Capped<T>` types implement `FromData`, `FromForm`.
  * HTTP 413 (Payload Too Large) errors are now returned when data limits are
    exceeded. (resolves #972)

Hierarchical Limits
  * Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c`
    falls back to `a/b` then `a`.

Core
  * `&RawStr` no longer implements `FromParam`.
  * `&str` implements `FromParam`, `FromData`, `FromForm`.
  * `FromTransformedData` was removed.
  * `FromData` gained a lifetime for use with request-local data.
  * The default error HTML is more compact.
  * `&Config` is a request guard.
  * The `DataStream` interface was entirely revamped.
  * `State` is only exported via `rocket::State`.
  * A `request::local_cache!()` macro was added for storing values in
    request-local cache without consideration for type uniqueness by using a
    locally generated anonymous type.
  * `Request::get_param()` is now `Request::param()`.
  * `Request::get_segments()` is now `Request::segments()`, takes a range.
  * `Request::get_query_value()` is now `Request::query_value()`, can parse any
    `FromForm` including sequences.
  * `std::io::Error` implements `Responder` like `Debug<std::io::Error>`.
  * `(Status, R)` where `R: Responder` implements `Responder` by overriding the
    `Status` of `R`.
  * The name of a route is printed first during route matching.
  * `FlashMessage` now only has one lifetime generic.

HTTP
  * `RawStr` implements `serde::{Serialize, Deserialize}`.
  * `RawStr` implements _many_ more methods, in particular, those related to the
    `Pattern` API.
  * `RawStr::from_str()` is now `RawStr::new()`.
  * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as
    necessary, return `Cow`.
  * `Status` implements `Default` with `Status::Ok`.
  * `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`.
  * Authority and origin part of `Absolute` can be modified with new
    `Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods.
  * `Origin::segments()` was removed in favor of methods split into query and
    path parts and into raw and decoded versions.
  * The `Segments` iterator is smarter, returns decoded `&str` items.
  * `Segments::into_path_buf()` is now `Segments::to_path_buf()`.
  * A new `QuerySegments` is the analogous query segment iterator.
  * Once set, `expires` on private cookies is not overwritten. (resolves #1506)
  * `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`.

Codegen
  * Preserve more spans in `uri!` macro.
  * Preserve spans `FromForm` field types.
  * All dynamic parameters in a query string must typecheck as `FromForm`.
  * `FromFormValue` derive removed; `FromFormField` added.
  * The `form` `FromForm` and `FromFormField` field attribute is now named
    `field`. `#[form(field = ..)]` is now `#[field(name = ..)]`.

Contrib
  * `Json` implements `FromForm`.
  * `MsgPack` implements `FromForm`.
  * The `json!` macro is exported as `rocket_contrib::json::json!`.
  * Added clarifying docs to `StaticFiles`.

Examples
  * `form_validation` and `form_kitchen_sink` removed in favor of `forms`.
  * The `hello_world` example uses unicode in paths.
  * The `json` example only allocates as necessary.

Internal
  * Codegen uses new `exports` module with the following conventions:
    - Locals starts with `__` and are lowercased.
    - Rocket modules start with `_` and are lowercased.
    - `std` types start with `_` and are titlecased.
    - Rocket types are titlecased.
  * A `header` module was added to `http`, contains header types.
  * `SAFETY` is used as doc-string keyword for `unsafe` related comments.
  * The `Uri` parser no longer recognizes Rocket route URIs.
This commit is contained in:
Sergio Benitez 2020-10-29 20:50:06 -07:00
parent 93e62c86ed
commit 63a14525d8
191 changed files with 11524 additions and 5695 deletions

View File

@ -1,6 +1,3 @@
[profile.dev]
codegen-units = 4
[workspace]
members = [
"core/lib/",
@ -11,7 +8,7 @@ members = [
"site/tests",
"examples/cookies",
"examples/errors",
"examples/form_validation",
"examples/forms",
"examples/hello_person",
"examples/query_params",
"examples/hello_world",
@ -30,7 +27,6 @@ members = [
"examples/msgpack",
"examples/handlebars_templates",
"examples/tera_templates",
"examples/form_kitchen_sink",
"examples/config",
"examples/raw_upload",
"examples/pastebin",

View File

@ -19,7 +19,7 @@ proc-macro = true
[dependencies]
quote = "1.0"
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "3648468" }
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "bd221a4" }
[dev-dependencies]
rocket = { version = "0.5.0-dev", path = "../../core/lib" }

View File

@ -14,32 +14,33 @@
//! features = ["json"]
//! ```
use std::ops::{Deref, DerefMut};
use std::io;
use std::ops::{Deref, DerefMut};
use std::iter::FromIterator;
use rocket::request::Request;
use rocket::outcome::Outcome::*;
use rocket::data::{Data, ByteUnit, Transform::*, Transformed};
use rocket::data::{FromTransformedData, TransformFuture, FromDataFuture};
use rocket::http::Status;
use rocket::request::{Request, local_cache};
use rocket::data::{ByteUnit, Data, FromData, Outcome};
use rocket::response::{self, Responder, content};
use rocket::http::Status;
use rocket::form::prelude as form;
use serde::{Serialize, Serializer};
use serde::de::{Deserialize, Deserializer};
use serde::de::{Deserialize, DeserializeOwned, Deserializer};
#[doc(hidden)]
pub use serde_json::{json_internal, json_internal_vec};
/// The JSON type: implements [`FromTransformedData`] and [`Responder`], allowing you to
/// easily consume and respond with JSON.
/// The JSON data guard: easily consume and respond with JSON.
///
/// ## Receiving JSON
///
/// If you're receiving JSON data, simply add a `data` parameter to your route
/// arguments and ensure the type of the parameter is a `Json<T>`, where `T` is
/// some type you'd like to parse from JSON. `T` must implement [`Deserialize`]
/// from [`serde`]. The data is parsed from the HTTP request body.
/// `Json` is both a data guard and a form guard.
///
/// ### Data Guard
///
/// To parse request body data as JSON , add a `data` route argument with a
/// target type of `Json<T>`, where `T` is some type you'd like to parse from
/// JSON. `T` must implement [`serde::Deserialize`].
///
/// ```rust
/// # #[macro_use] extern crate rocket;
@ -47,7 +48,7 @@ pub use serde_json::{json_internal, json_internal_vec};
/// # type User = usize;
/// use rocket_contrib::json::Json;
///
/// #[post("/users", format = "json", data = "<user>")]
/// #[post("/user", format = "json", data = "<user>")]
/// fn new_user(user: Json<User>) {
/// /* ... */
/// }
@ -58,6 +59,30 @@ pub use serde_json::{json_internal, json_internal_vec};
/// "application/json" as its `Content-Type` header value will not be routed to
/// the handler.
///
/// ### Form Guard
///
/// `Json<T>`, as a form guard, accepts value and data fields and parses the
/// data as a `T`. Simple use `Json<T>`:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type Metadata = usize;
/// use rocket::form::{Form, FromForm};
/// use rocket_contrib::json::Json;
///
/// #[derive(FromForm)]
/// struct User<'r> {
/// name: &'r str,
/// metadata: Json<Metadata>
/// }
///
/// #[post("/user", data = "<form>")]
/// fn new_user(form: Form<User<'_>>) {
/// /* ... */
/// }
/// ```
///
/// ## Sending JSON
///
/// If you're responding with JSON data, return a `Json<T>` type, where `T`
@ -94,22 +119,6 @@ pub use serde_json::{json_internal, json_internal_vec};
#[derive(Debug)]
pub struct Json<T>(pub T);
impl<T> Json<T> {
/// Consumes the JSON wrapper and returns the wrapped item.
///
/// # Example
/// ```rust
/// # use rocket_contrib::json::Json;
/// let string = "Hello".to_string();
/// let my_json = Json(string);
/// assert_eq!(my_json.into_inner(), "Hello".to_string());
/// ```
#[inline(always)]
pub fn into_inner(self) -> T {
self.0
}
}
/// An error returned by the [`Json`] data guard when incoming data fails to
/// serialize as JSON.
#[derive(Debug)]
@ -126,36 +135,54 @@ pub enum JsonError<'a> {
const DEFAULT_LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
impl<'a, T: Deserialize<'a>> FromTransformedData<'a> for Json<T> {
type Error = JsonError<'a>;
type Owned = String;
type Borrowed = str;
impl<T> Json<T> {
/// Consumes the JSON wrapper and returns the wrapped item.
///
/// # Example
/// ```rust
/// # use rocket_contrib::json::Json;
/// let string = "Hello".to_string();
/// let my_json = Json(string);
/// assert_eq!(my_json.into_inner(), "Hello".to_string());
/// ```
#[inline(always)]
pub fn into_inner(self) -> T {
self.0
}
}
fn transform<'r>(r: &'r Request<'_>, d: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
Box::pin(async move {
let size_limit = r.limits().get("json").unwrap_or(DEFAULT_LIMIT);
match d.open(size_limit).stream_to_string().await {
Ok(s) => Borrowed(Success(s)),
Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(e))))
}
})
impl<'r, T: Deserialize<'r>> Json<T> {
fn from_str(s: &'r str) -> Result<Self, JsonError<'r>> {
serde_json::from_str(s).map(Json).map_err(|e| JsonError::Parse(s, e))
}
fn from_data(_: &'a Request<'_>, o: Transformed<'a, Self>) -> FromDataFuture<'a, Self, Self::Error> {
Box::pin(async move {
let string = try_outcome!(o.borrowed());
match serde_json::from_str(&string) {
Ok(v) => Success(Json(v)),
Err(e) => {
error_!("Couldn't parse JSON body: {:?}", e);
if e.is_data() {
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
} else {
Failure((Status::BadRequest, JsonError::Parse(string, e)))
}
}
}
})
async fn from_data(req: &'r Request<'_>, data: Data) -> Result<Self, JsonError<'r>> {
let size_limit = req.limits().get("json").unwrap_or(DEFAULT_LIMIT);
let string = match data.open(size_limit).into_string().await {
Ok(s) if s.is_complete() => s.into_inner(),
Ok(_) => {
let eof = io::ErrorKind::UnexpectedEof;
return Err(JsonError::Io(io::Error::new(eof, "data limit exceeded")));
},
Err(e) => return Err(JsonError::Io(e)),
};
Self::from_str(local_cache!(req, string))
}
}
#[rocket::async_trait]
impl<'r, T: Deserialize<'r>> FromData<'r> for Json<T> {
type Error = JsonError<'r>;
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
match Self::from_data(req, data).await {
Ok(value) => Outcome::Success(value),
Err(JsonError::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
Outcome::Failure((Status::PayloadTooLarge, JsonError::Io(e)))
},
Err(e) => Outcome::Failure((Status::BadRequest, e)),
}
}
}
@ -190,6 +217,26 @@ impl<T> DerefMut for Json<T> {
}
}
impl From<JsonError<'_>> for form::Error<'_> {
fn from(e: JsonError<'_>) -> Self {
match e {
JsonError::Io(e) => e.into(),
JsonError::Parse(_, e) => form::Error::custom(e)
}
}
}
#[rocket::async_trait]
impl<'v, T: DeserializeOwned + Send> form::FromFormField<'v> for Json<T> {
fn from_value(field: form::ValueField<'v>) -> Result<Self, form::Errors<'v>> {
Ok(Self::from_str(field.value)?)
}
async fn from_data(f: form::DataField<'v, '_>) -> Result<Self, form::Errors<'v>> {
Ok(Self::from_data(f.request, f.data).await?)
}
}
/// An arbitrary JSON value.
///
/// This structure wraps `serde`'s [`Value`] type. Importantly, unlike `Value`,
@ -397,3 +444,5 @@ macro_rules! json {
$crate::json::JsonValue($crate::json::json_internal!($($json)+))
};
}
pub use json;

View File

@ -1,4 +1,5 @@
//! Automatic MessagePack (de)serialization support.
//!
//! See the [`MsgPack`](crate::msgpack::MsgPack) type for further details.
//!
@ -14,32 +15,32 @@
//! features = ["msgpack"]
//! ```
use std::io;
use std::ops::{Deref, DerefMut};
use tokio::io::AsyncReadExt;
use rocket::request::Request;
use rocket::outcome::Outcome::*;
use rocket::data::{Data, ByteUnit, Transform::*, TransformFuture, Transformed};
use rocket::data::{FromTransformedData, FromDataFuture};
use rocket::response::{self, content, Responder};
use rocket::request::{Request, local_cache};
use rocket::data::{ByteUnit, Data, FromData, Outcome};
use rocket::response::{self, Responder, content};
use rocket::http::Status;
use rocket::form::prelude as form;
use serde::Serialize;
use serde::de::Deserialize;
use serde::de::{Deserialize, DeserializeOwned};
pub use rmp_serde::decode::Error;
/// The `MsgPack` type: implements [`FromTransformedData`] and [`Responder`], allowing you
/// to easily consume and respond with MessagePack data.
/// The `MsgPack` data guard and responder: easily consume and respond with
/// MessagePack.
///
/// ## Receiving MessagePack
///
/// If you're receiving MessagePack data, simply add a `data` parameter to your
/// route arguments and ensure the type of the parameter is a `MsgPack<T>`,
/// where `T` is some type you'd like to parse from MessagePack. `T` must
/// implement [`Deserialize`] from [`serde`]. The data is parsed from the HTTP
/// request body.
/// `MsgPack` is both a data guard and a form guard.
///
/// ### Data Guard
///
/// To parse request body data as MessagePack , add a `data` route argument with
/// a target type of `MsgPack<T>`, where `T` is some type you'd like to parse
/// from JSON. `T` must implement [`serde::Deserialize`].
///
/// ```rust
/// # #[macro_use] extern crate rocket;
@ -58,6 +59,30 @@ pub use rmp_serde::decode::Error;
/// "application/msgpack" as its first `Content-Type:` header parameter will not
/// be routed to this handler.
///
/// ### Form Guard
///
/// `MsgPack<T>`, as a form guard, accepts value and data fields and parses the
/// data as a `T`. Simple use `MsgPack<T>`:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type Metadata = usize;
/// use rocket::form::{Form, FromForm};
/// use rocket_contrib::msgpack::MsgPack;
///
/// #[derive(FromForm)]
/// struct User<'r> {
/// name: &'r str,
/// metadata: MsgPack<Metadata>
/// }
///
/// #[post("/users", data = "<form>")]
/// fn new_user(form: Form<User<'_>>) {
/// /* ... */
/// }
/// ```
///
/// ## Sending MessagePack
///
/// If you're responding with MessagePack data, return a `MsgPack<T>` type,
@ -113,41 +138,37 @@ impl<T> MsgPack<T> {
const DEFAULT_LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
impl<'a, T: Deserialize<'a>> FromTransformedData<'a> for MsgPack<T> {
type Error = Error;
type Owned = Vec<u8>;
type Borrowed = [u8];
fn transform<'r>(r: &'r Request<'_>, d: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
Box::pin(async move {
let size_limit = r.limits().get("msgpack").unwrap_or(DEFAULT_LIMIT);
let mut buf = Vec::new();
let mut reader = d.open(size_limit);
match reader.read_to_end(&mut buf).await {
Ok(_) => Borrowed(Success(buf)),
Err(e) => Borrowed(Failure((Status::BadRequest, Error::InvalidDataRead(e)))),
}
})
impl<'r, T: Deserialize<'r>> MsgPack<T> {
fn from_bytes(buf: &'r [u8]) -> Result<Self, Error> {
rmp_serde::from_slice(buf).map(MsgPack)
}
fn from_data(_: &'a Request<'_>, o: Transformed<'a, Self>) -> FromDataFuture<'a, Self, Self::Error> {
use self::Error::*;
async fn from_data(req: &'r Request<'_>, data: Data) -> Result<Self, Error> {
let size_limit = req.limits().get("msgpack").unwrap_or(DEFAULT_LIMIT);
let bytes = match data.open(size_limit).into_bytes().await {
Ok(buf) if buf.is_complete() => buf.into_inner(),
Ok(_) => {
let eof = io::ErrorKind::UnexpectedEof;
return Err(Error::InvalidDataRead(io::Error::new(eof, "data limit exceeded")));
},
Err(e) => return Err(Error::InvalidDataRead(e)),
};
Box::pin(async move {
let buf = try_outcome!(o.borrowed());
match rmp_serde::from_slice(&buf) {
Ok(val) => Success(MsgPack(val)),
Err(e) => {
error_!("Couldn't parse MessagePack body: {:?}", e);
match e {
TypeMismatch(_) | OutOfRange | LengthMismatch(_) => {
Failure((Status::UnprocessableEntity, e))
}
_ => Failure((Status::BadRequest, e)),
}
}
}
})
Self::from_bytes(local_cache!(req, bytes))
}
}
#[rocket::async_trait]
impl<'r, T: Deserialize<'r>> FromData<'r> for MsgPack<T> {
type Error = Error;
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
match Self::from_data(req, data).await {
Ok(value) => Outcome::Success(value),
Err(Error::InvalidDataRead(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
Outcome::Failure((Status::PayloadTooLarge, Error::InvalidDataRead(e)))
},
Err(e) => Outcome::Failure((Status::BadRequest, e)),
}
}
}
@ -166,6 +187,19 @@ impl<'r, T: Serialize> Responder<'r, 'static> for MsgPack<T> {
}
}
#[rocket::async_trait]
impl<'v, T: DeserializeOwned + Send> form::FromFormField<'v> for MsgPack<T> {
async fn from_data(f: form::DataField<'v, '_>) -> Result<Self, form::Errors<'v>> {
Self::from_data(f.request, f.data).await.map_err(|e| {
match e {
Error::InvalidMarkerRead(e) | Error::InvalidDataRead(e) => e.into(),
Error::Utf8Error(e) => e.into(),
_ => form::Error::custom(e).into(),
}
})
}
}
impl<T> Deref for MsgPack<T> {
type Target = T;

View File

@ -77,21 +77,22 @@ pub struct Options(u8);
#[allow(non_upper_case_globals, non_snake_case)]
impl Options {
/// `Options` representing the empty set. No dotfiles or index pages are
/// rendered. This is different than [`Options::default()`](#impl-Default),
/// which enables `Index`.
/// `Options` representing the empty set: no options are enabled. This is
/// different than [`Options::default()`](#impl-Default), which enables
/// `Index`.
pub const None: Options = Options(0b0000);
/// `Options` enabling responding to requests for a directory with the
/// `index.html` file in that directory, if it exists. When this is enabled,
/// the [`StaticFiles`] handler will respond to requests for a directory
/// `/foo` with the file `${root}/foo/index.html` if it exists. This is
/// enabled by default.
/// `/foo` of `/foo/` with the file `${root}/foo/index.html` if it exists.
/// This is enabled by default.
pub const Index: Options = Options(0b0001);
/// `Options` enabling returning dot files. When this is enabled, the
/// [`StaticFiles`] handler will respond to requests for files or
/// directories beginning with `.`. This is _not_ enabled by default.
/// directories beginning with `.`. When disabled, any dotfiles will be
/// treated as missing. This is _not_ enabled by default.
pub const DotFiles: Options = Options(0b0010);
/// `Options` that normalizes directory requests by redirecting requests to
@ -100,8 +101,28 @@ impl Options {
/// When enabled, the [`StaticFiles`] handler will respond to requests for a
/// directory without a trailing `/` with a permanent redirect (308) to the
/// same path with a trailing `/`. This ensures relative URLs within any
/// document served for that directory will be interpreted relative to that
/// directory, rather than its parent. This is _not_ enabled by default.
/// document served from that directory will be interpreted relative to that
/// directory rather than its parent. This is _not_ enabled by default.
///
/// # Example
///
/// Given the following directory structure...
///
/// ```text
/// static/
/// └── foo/
/// ├── cat.jpeg
/// └── index.html
/// ```
///
/// ...with `StaticFiles::from("static")`, both requests to `/foo` and
/// `/foo/` will serve `static/foo/index.html`. If `index.html` references
/// `cat.jpeg` as a relative URL, the browser will request `/cat.jpeg`
/// (`static/cat.jpeg`) when the request for `/foo` was handled and
/// `/foo/cat.jpeg` (`static/foo/cat.jpeg`) if `/foo/` was handled. As a
/// result, the request in the former case will fail. To avoid this,
/// `NormalizeDirs` will redirect requests to `/foo` to `/foo/` if the file
/// that would be served is a directory.
pub const NormalizeDirs: Options = Options(0b0100);
/// Returns `true` if `self` is a superset of `other`. In other words,
@ -335,7 +356,7 @@ async fn handle_dir<'r, P>(opt: Options, r: &'r Request<'_>, d: Data, p: P) -> O
where P: AsRef<Path>
{
if opt.contains(Options::NormalizeDirs) && !r.uri().path().ends_with('/') {
let new_path = r.uri().map_path(|p| p.to_owned() + "/")
let new_path = r.uri().map_path(|p| format!("{}/", p))
.expect("adding a trailing slash to a known good path results in a valid path")
.into_owned();
@ -364,9 +385,9 @@ impl Handler for StaticFiles {
// Otherwise, we're handling segments. Get the segments as a `PathBuf`,
// only allowing dotfiles if the user allowed it.
let allow_dotfiles = self.options.contains(Options::DotFiles);
let path = req.get_segments::<Segments<'_>>(0)
let path = req.segments::<Segments<'_>>(0..)
.and_then(|res| res.ok())
.and_then(|segments| segments.into_path_buf(allow_dotfiles).ok())
.and_then(|segments| segments.to_path_buf(allow_dotfiles).ok())
.map(|path| self.root.join(path));
match path {

View File

@ -20,8 +20,8 @@ use std::fmt;
use std::str::FromStr;
use std::ops::Deref;
use rocket::request::{FromParam, FromFormValue};
use rocket::http::RawStr;
use rocket::request::FromParam;
use rocket::form::{self, FromFormField, ValueField};
type ParseError = <self::uuid_crate::Uuid as FromStr>::Err;
@ -104,19 +104,14 @@ impl<'a> FromParam<'a> for Uuid {
/// A value is successfully parsed if `param` is a properly formatted Uuid.
/// Otherwise, a `ParseError` is returned.
#[inline(always)]
fn from_param(param: &'a RawStr) -> Result<Uuid, Self::Error> {
fn from_param(param: &'a str) -> Result<Uuid, Self::Error> {
param.parse()
}
}
impl<'v> FromFormValue<'v> for Uuid {
type Error = &'v RawStr;
/// A value is successfully parsed if `form_value` is a properly formatted
/// Uuid. Otherwise, the raw form value is returned.
#[inline(always)]
fn from_form_value(form_value: &'v RawStr) -> Result<Uuid, &'v RawStr> {
form_value.parse().map_err(|_| form_value)
impl<'v> FromFormField<'v> for Uuid {
fn from_value(field: ValueField<'v>) -> form::Result<'v, Self> {
Ok(field.value.parse().map_err(form::error::Error::custom)?)
}
}

View File

@ -119,14 +119,13 @@ mod static_tests {
#[test]
fn test_forwarding() {
use rocket::http::RawStr;
use rocket::{get, routes};
#[get("/<value>", rank = 20)]
fn catch_one(value: String) -> String { value }
#[get("/<a>/<b>", rank = 20)]
fn catch_two(a: &RawStr, b: &RawStr) -> String { format!("{}/{}", a, b) }
fn catch_two(a: &str, b: &str) -> String { format!("{}/{}", a, b) }
let rocket = rocket().mount("/default", routes![catch_one, catch_two]);
let client = Client::tracked(rocket).expect("valid rocket");

View File

@ -5,12 +5,12 @@
mod templates_tests {
use std::path::{Path, PathBuf};
use rocket::{Rocket, http::RawStr};
use rocket::Rocket;
use rocket::config::Config;
use rocket_contrib::templates::{Template, Metadata};
#[get("/<engine>/<name>")]
fn template_check(md: Metadata<'_>, engine: &RawStr, name: &RawStr) -> Option<()> {
fn template_check(md: Metadata<'_>, engine: &str, name: &str) -> Option<()> {
match md.contains_template(&format!("{}/{}", engine, name)) {
true => Some(()),
false => None

View File

@ -18,7 +18,7 @@ proc-macro = true
indexmap = "1.0"
quote = "1.0"
rocket_http = { version = "0.5.0-dev", path = "../http/" }
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "3648468" }
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "bd221a4" }
glob = "0.3"
[dev-dependencies]

View File

@ -3,8 +3,7 @@ use devise::{syn, MetaItem, Spanned, Result, FromMeta, Diagnostic};
use crate::http_codegen::{self, Optional};
use crate::proc_macro2::{TokenStream, Span};
use crate::syn_ext::{ReturnTypeExt, TokenStreamExt};
use self::syn::{Attribute, parse::Parser};
use crate::syn_ext::ReturnTypeExt;
/// The raw, parsed `#[catch(code)]` attribute.
#[derive(Debug, FromMeta)]
@ -18,19 +17,19 @@ struct CatchAttribute {
struct CatcherCode(Option<http_codegen::Status>);
impl FromMeta for CatcherCode {
fn from_meta(m: MetaItem<'_>) -> Result<Self> {
if usize::from_meta(m).is_ok() {
let status = http_codegen::Status::from_meta(m)?;
fn from_meta(meta: &MetaItem) -> Result<Self> {
if usize::from_meta(meta).is_ok() {
let status = http_codegen::Status::from_meta(meta)?;
Ok(CatcherCode(Some(status)))
} else if let MetaItem::Path(path) = m {
} else if let MetaItem::Path(path) = meta {
if path.is_ident("default") {
Ok(CatcherCode(None))
} else {
Err(m.span().error(format!("expected `default`")))
Err(meta.span().error("expected `default`"))
}
} else {
let msg = format!("expected integer or identifier, found {}", m.description());
Err(m.span().error(msg))
let msg = format!("expected integer or identifier, found {}", meta.description());
Err(meta.span().error(msg))
}
}
}
@ -51,15 +50,9 @@ fn parse_params(
.map_err(Diagnostic::from)
.map_err(|diag| diag.help("`#[catch]` can only be used on functions"))?;
let full_attr = quote!(#[catch(#args)]);
let attrs = Attribute::parse_outer.parse2(full_attr)?;
let attribute = match CatchAttribute::from_attrs("catch", &attrs) {
Some(result) => result.map_err(|diag| {
diag.help("`#[catch]` expects a status code int or `default`: \
`#[catch(404)]` or `#[catch(default)]`")
})?,
None => return Err(Span::call_site().error("internal error: bad attribute"))
};
let attribute = CatchAttribute::from_meta(&syn::parse2(quote!(catch(#args)))?)
.map_err(|diag| diag.help("`#[catch]` expects a status code int or `default`: \
`#[catch(404)]` or `#[catch(default)]`"))?;
Ok(CatchParams { status: attribute.status.0, function })
}
@ -78,8 +71,8 @@ pub fn _catch(
let status_code = Optional(catcher_status.as_ref().map(|s| s.0.code));
// Variables names we'll use and reuse.
define_vars_and_mods!(catch.function.span().into() =>
req, status, _Box, Request, Response, StaticCatcherInfo, Catcher,
define_spanned_export!(catch.function.span().into() =>
__req, __status, _Box, Request, Response, StaticCatcherInfo, Catcher,
ErrorHandlerFuture, Status);
// Determine the number of parameters that will be passed in.
@ -96,7 +89,7 @@ pub fn _catch(
// Set the `req` and `status` spans to that of their respective function
// arguments for a more correct `wrong type` error span. `rev` to be cute.
let codegen_args = &[&req, &status];
let codegen_args = &[__req, __status];
let inputs = catch.function.sig.inputs.iter().rev()
.zip(codegen_args.into_iter())
.map(|(fn_arg, codegen_arg)| match fn_arg {
@ -110,7 +103,7 @@ pub fn _catch(
let catcher_response = quote_spanned!(return_type_span => {
let ___responder = #user_catcher_fn_name(#(#inputs),*) #dot_await;
::rocket::response::Responder::respond_to(___responder, #req)?
::rocket::response::Responder::respond_to(___responder, #__req)?
});
// Generate the catcher, keeping the user's input around.
@ -126,13 +119,13 @@ pub fn _catch(
impl From<#user_catcher_fn_name> for #StaticCatcherInfo {
fn from(_: #user_catcher_fn_name) -> #StaticCatcherInfo {
fn monomorphized_function<'_b>(
#status: #Status,
#req: &'_b #Request<'_>
#__status: #Status,
#__req: &'_b #Request<'_>
) -> #ErrorHandlerFuture<'_b> {
#_Box::pin(async move {
let __response = #catcher_response;
#Response::build()
.status(#status)
.status(#__status)
.merge(__response)
.ok()
})

View File

@ -11,7 +11,6 @@ use crate::syn_ext::{IdentExt, NameSource};
use crate::proc_macro2::{TokenStream, Span};
use crate::http_codegen::{Method, MediaType, RoutePath, DataSegment, Optional};
use crate::attribute::segments::{Source, Kind, Segment};
use crate::syn::{Attribute, parse::Parser};
use crate::{URI_MACRO_PREFIX, ROCKET_PARAM_PREFIX};
@ -51,6 +50,14 @@ struct Route {
inputs: Vec<(NameSource, syn::Ident, syn::Type)>,
}
impl Route {
fn find_input<T>(&self, name: &T) -> Option<&(NameSource, syn::Ident, syn::Type)>
where T: PartialEq<NameSource>
{
self.inputs.iter().find(|(n, ..)| name == n)
}
}
fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
// Gather diagnostics as we proceed.
let mut diags = Diagnostics::new();
@ -125,75 +132,60 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
}
fn param_expr(seg: &Segment, ident: &syn::Ident, ty: &syn::Type) -> TokenStream {
define_vars_and_mods!(req, data, error, log, request, _None, _Some, _Ok, _Err, Outcome);
let i = seg.index.expect("dynamic parameters must be indexed");
let span = ident.span().join(ty.span()).unwrap_or_else(|| ty.span());
let name = ident.to_string();
define_spanned_export!(span =>
__req, __data, _log, _request, _None, _Some, _Ok, _Err, Outcome
);
// All dynamic parameter should be found if this function is being called;
// that's the point of statically checking the URI parameters.
let internal_error = quote!({
#log::error("Internal invariant error: expected dynamic parameter not found.");
#log::error("Please report this error to the Rocket issue tracker.");
#Outcome::Forward(#data)
#_log::error("Internal invariant error: expected dynamic parameter not found.");
#_log::error("Please report this error to the Rocket issue tracker.");
#Outcome::Forward(#__data)
});
// Returned when a dynamic parameter fails to parse.
let parse_error = quote!({
#log::warn_(&format!("Failed to parse '{}': {:?}", #name, #error));
#Outcome::Forward(#data)
#_log::warn_(&format!("Failed to parse '{}': {:?}", #name, __error));
#Outcome::Forward(#__data)
});
let expr = match seg.kind {
Kind::Single => quote_spanned! { span =>
match #req.raw_segment_str(#i) {
#_Some(__s) => match <#ty as #request::FromParam>::from_param(__s) {
match #__req.routed_segment(#i) {
#_Some(__s) => match <#ty as #_request::FromParam>::from_param(__s) {
#_Ok(__v) => __v,
#_Err(#error) => return #parse_error,
#_Err(__error) => return #parse_error,
},
#_None => return #internal_error
}
},
Kind::Multi => quote_spanned! { span =>
match #req.raw_segments(#i) {
#_Some(__s) => match <#ty as #request::FromSegments>::from_segments(__s) {
#_Ok(__v) => __v,
#_Err(#error) => return #parse_error,
},
#_None => return #internal_error
match <#ty as #_request::FromSegments>::from_segments(#__req.routed_segments(#i..)) {
#_Ok(__v) => __v,
#_Err(__error) => return #parse_error,
}
},
Kind::Static => return quote!()
};
quote! {
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
let #ident: #ty = #expr;
}
}
fn data_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream {
define_vars_and_mods!(req, data, FromTransformedData, Outcome, Transform);
let span = ident.span().join(ty.span()).unwrap_or_else(|| ty.span());
define_spanned_export!(span => __req, __data, FromData, Outcome);
quote_spanned! { span =>
let __transform = <#ty as #FromTransformedData>::transform(#req, #data).await;
let __outcome = <#ty as #FromData>::from_data(#__req, #__data).await;
#[allow(unreachable_patterns, unreachable_code)]
let __outcome = match __transform {
#Transform::Owned(#Outcome::Success(__v)) => {
#Transform::Owned(#Outcome::Success(__v))
},
#Transform::Borrowed(#Outcome::Success(ref __v)) => {
#Transform::Borrowed(#Outcome::Success(::std::borrow::Borrow::borrow(__v)))
},
#Transform::Borrowed(__o) => #Transform::Borrowed(__o.map(|_| {
unreachable!("Borrowed(Success(..)) case handled in previous block")
})),
#Transform::Owned(__o) => #Transform::Owned(__o),
};
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
let #ident: #ty = match <#ty as #FromTransformedData>::from_data(#req, __outcome).await {
let #ident: #ty = match __outcome {
#Outcome::Success(__d) => __d,
#Outcome::Forward(__d) => return #Outcome::Forward(__d),
#Outcome::Failure((__c, _)) => return #Outcome::Failure(__c),
@ -202,118 +194,95 @@ fn data_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream {
}
fn query_exprs(route: &Route) -> Option<TokenStream> {
define_vars_and_mods!(_None, _Some, _Ok, _Err, _Option);
define_vars_and_mods!(data, trail, log, request, req, Outcome, SmallVec, Query);
use devise::ext::{Split2, Split6};
define_spanned_export!(Span::call_site() =>
__req, __data, _log, _form, Outcome, _Ok, _Err, _Some, _None
);
let query_segments = route.attribute.path.query.as_ref()?;
let (mut decls, mut matchers, mut builders) = (vec![], vec![], vec![]);
for segment in query_segments {
let (ident, ty, span) = if segment.kind != Kind::Static {
let (ident, ty) = route.inputs.iter()
.find(|(name, _, _)| name == &segment.name)
.map(|(_, rocket_ident, ty)| (rocket_ident, ty))
.unwrap();
let span = ident.span().join(ty.span()).unwrap_or_else(|| ty.span());
(Some(ident), Some(ty), span.into())
} else {
(None, None, segment.span.into())
};
let decl = match segment.kind {
Kind::Single => quote_spanned! { span =>
#[allow(non_snake_case)]
let mut #ident: #_Option<#ty> = #_None;
},
Kind::Multi => quote_spanned! { span =>
#[allow(non_snake_case)]
let mut #trail = #SmallVec::<[#request::FormItem; 8]>::new();
},
Kind::Static => quote!()
};
let name = segment.name.name();
let matcher = match segment.kind {
Kind::Single => quote_spanned! { span =>
(_, #name, __v) => {
#[allow(unreachable_patterns, unreachable_code)]
let __v = match <#ty as #request::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! {
(#name, _, _) => continue,
},
Kind::Multi => quote! {
_ => #trail.push(__i),
// Record all of the static parameters for later filtering.
let (raw_name, raw_value) = query_segments.iter()
.filter(|s| !s.is_dynamic())
.map(|s| {
let name = s.name.name();
match name.find('=') {
Some(i) => (&name[..i], &name[i + 1..]),
None => (name, "")
}
};
})
.split2();
let builder = match segment.kind {
Kind::Single => quote_spanned! { span =>
#[allow(non_snake_case)]
let #ident = match #ident.or_else(<#ty as #request::FromFormValue>::default) {
#_Some(__v) => __v,
#_None => {
#log::warn_(&format!("Missing required query parameter '{}'.", #name));
return #Outcome::Forward(#data);
}
};
},
Kind::Multi => quote_spanned! { span =>
#[allow(non_snake_case)]
let #ident = match <#ty as #request::FromQuery>::from_query(#Query(&#trail)) {
#_Ok(__v) => __v,
#_Err(__e) => {
#log::warn_(&format!("Failed to parse '{}': {:?}", #name, __e));
return #Outcome::Forward(#data);
}
};
},
Kind::Static => quote!()
};
// Now record all of the dynamic parameters.
let (name, matcher, ident, init_expr, push_expr, finalize_expr) = query_segments.iter()
.filter(|s| s.is_dynamic())
.map(|s| (s, s.name.name(), route.find_input(&s.name).expect("dynamic has input")))
.map(|(seg, name, (_, ident, ty))| {
let matcher = match seg.kind {
Kind::Multi => quote_spanned!(seg.span => _),
_ => quote_spanned!(seg.span => #name)
};
decls.push(decl);
matchers.push(matcher);
builders.push(builder);
}
let span = ty.span();
define_spanned_export!(span => FromForm, _form);
matchers.push(quote!(_ => continue));
let ty = quote_spanned!(span => <#ty as #FromForm>);
let i = ident.clone().with_span(span);
let init = quote_spanned!(span => #ty::init(#_form::Options::Lenient));
let finalize = quote_spanned!(span => #ty::finalize(#i));
let push = match seg.kind {
Kind::Multi => quote_spanned!(span => #ty::push_value(&mut #i, _f)),
_ => quote_spanned!(span => #ty::push_value(&mut #i, _f.shift())),
};
(name, matcher, ident, init, push, finalize)
})
.split6();
#[allow(non_snake_case)]
Some(quote! {
#(#decls)*
let mut _e = #_form::Errors::new();
#(let mut #ident = #init_expr;)*
if let #_Some(__items) = #req.raw_query_items() {
for __i in __items {
match (__i.raw.as_str(), __i.key.as_str(), __i.value) {
#(
#[allow(unreachable_patterns, unreachable_code)]
#matchers
)*
}
for _f in #__req.query_fields() {
let _raw = (_f.name.source().as_str(), _f.value);
let _key = _f.name.key_lossy().as_str();
match (_raw, _key) {
// Skip static parameters so <param..> doesn't see them.
#(((#raw_name, #raw_value), _) => { /* skip */ },)*
#((_, #matcher) => #push_expr,)*
_ => { /* in case we have no trailing, ignore all else */ },
}
}
#(
#[allow(unreachable_patterns, unreachable_code)]
#builders
let #ident = match #finalize_expr {
#_Ok(_v) => #_Some(_v),
#_Err(_err) => {
_e.extend(_err.with_name(#_form::NameView::new(#name)));
#_None
},
};
)*
if !_e.is_empty() {
#_log::warn_("query string failed to match declared route");
for _err in _e { #_log::warn_(_err); }
return #Outcome::Forward(#__data);
}
#(let #ident = #ident.unwrap();)*
})
}
fn request_guard_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream {
define_vars_and_mods!(req, data, request, Outcome);
let span = ident.span().join(ty.span()).unwrap_or_else(|| ty.span());
define_spanned_export!(span => __req, __data, _request, Outcome);
quote_spanned! { span =>
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
let #ident: #ty = match <#ty as #request::FromRequest>::from_request(#req).await {
let #ident: #ty = match <#ty as #_request::FromRequest>::from_request(#__req).await {
#Outcome::Success(__v) => __v,
#Outcome::Forward(_) => return #Outcome::Forward(#data),
#Outcome::Forward(_) => return #Outcome::Forward(#__data),
#Outcome::Failure((__c, _)) => return #Outcome::Failure(__c),
};
}
@ -327,7 +296,7 @@ fn generate_internal_uri_macro(route: &Route) -> TokenStream {
.filter(|seg| seg.source == Source::Path || seg.source == Source::Query)
.filter(|seg| seg.kind != Kind::Static)
.map(|seg| &seg.name)
.map(|seg_name| route.inputs.iter().find(|(in_name, ..)| in_name == seg_name).unwrap())
.map(|seg_name| route.find_input(seg_name).unwrap())
.map(|(name, _, ty)| (name.ident(), ty))
.map(|(ident, ty)| quote!(#ident: #ty));
@ -364,20 +333,21 @@ fn generate_respond_expr(route: &Route) -> TokenStream {
syn::ReturnType::Type(_, ref ty) => ty.span().into()
};
define_vars_and_mods!(req);
define_vars_and_mods!(ret_span => handler);
define_spanned_export!(ret_span => __req, _handler);
let user_handler_fn_name = &route.function.sig.ident;
let parameter_names = route.inputs.iter()
.map(|(_, rocket_ident, _)| rocket_ident);
let _await = route.function.sig.asyncness.map(|a| quote_spanned!(a.span().into() => .await));
let _await = route.function.sig.asyncness
.map(|a| quote_spanned!(a.span().into() => .await));
let responder_stmt = quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*) #_await;
};
quote_spanned! { ret_span =>
#responder_stmt
#handler::Outcome::from(#req, ___responder)
#_handler::Outcome::from(#__req, ___responder)
}
}
@ -409,7 +379,10 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
}
// Gather everything we need.
define_vars_and_mods!(req, data, _Box, Request, Data, Route, StaticRouteInfo, HandlerFuture);
use crate::exports::{
__req, __data, _Box, Request, Data, Route, StaticRouteInfo, HandlerFuture
};
let (vis, user_handler_fn) = (&route.function.vis, &route.function);
let user_handler_fn_name = &user_handler_fn.sig.ident;
let generated_internal_uri_macro = generate_internal_uri_macro(&route);
@ -430,10 +403,11 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
/// Rocket code generated proxy static conversion implementation.
impl From<#user_handler_fn_name> for #StaticRouteInfo {
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
fn from(_: #user_handler_fn_name) -> #StaticRouteInfo {
fn monomorphized_function<'_b>(
#req: &'_b #Request<'_>,
#data: #Data
#__req: &'_b #Request<'_>,
#__data: #Data
) -> #HandlerFuture<'_b> {
#_Box::pin(async move {
#(#req_guard_definitions)*
@ -473,13 +447,8 @@ fn complete_route(args: TokenStream, input: TokenStream) -> Result<TokenStream>
.map_err(|e| Diagnostic::from(e))
.map_err(|diag| diag.help("`#[route]` can only be used on functions"))?;
let full_attr = quote!(#[route(#args)]);
let attrs = Attribute::parse_outer.parse2(full_attr)?;
let attribute = match RouteAttribute::from_attrs("route", &attrs) {
Some(result) => result?,
None => return Err(Span::call_site().error("internal error: bad attribute"))
};
let attr_tokens = quote!(route(#args));
let attribute = RouteAttribute::from_meta(&syn::parse2(attr_tokens)?)?;
codegen_route(parse_route(attribute, function)?)
}
@ -499,12 +468,8 @@ fn incomplete_route(
.map_err(|e| Diagnostic::from(e))
.map_err(|d| d.help(format!("#[{}] can only be used on functions", method_str)))?;
let full_attr = quote!(#[#method_ident(#args)]);
let attrs = Attribute::parse_outer.parse2(full_attr)?;
let method_attribute = match MethodRouteAttribute::from_attrs(&method_str, &attrs) {
Some(result) => result?,
None => return Err(Span::call_site().error("internal error: bad attribute"))
};
let full_attr = quote!(#method_ident(#args));
let method_attribute = MethodRouteAttribute::from_meta(&syn::parse2(full_attr)?)?;
let attribute = RouteAttribute {
method: SpanWrapped {

View File

@ -3,6 +3,7 @@ use std::hash::{Hash, Hasher};
use devise::{syn, Diagnostic, ext::SpanDiagnosticExt};
use crate::proc_macro2::Span;
use crate::http::RawStr;
use crate::http::uri::{self, UriPart};
use crate::http::route::RouteSegment;
use crate::proc_macro_ext::{Diagnostics, StringLit, PResult, DResult};
@ -145,7 +146,7 @@ pub fn parse_data_segment(segment: &str, span: Span) -> PResult<Segment> {
}
pub fn parse_segments<P: UriPart>(
string: &str,
string: &RawStr,
span: Span
) -> DResult<Vec<Segment>> {
let mut segments = vec![];
@ -154,11 +155,11 @@ pub fn parse_segments<P: UriPart>(
for result in <RouteSegment<'_, P>>::parse_many(string) {
match result {
Ok(segment) => {
let seg_span = subspan(&segment.string, string, span);
let seg_span = subspan(&segment.string, string.as_str(), span);
segments.push(Segment::from(segment, seg_span));
},
Err((segment_string, error)) => {
diags.push(into_diagnostic(segment_string, string, span, &error));
diags.push(into_diagnostic(segment_string, string.as_str(), span, &error));
if let Error::Trailing(..) = error {
break;
}

View File

@ -12,7 +12,7 @@ fn struct_maker_vec(
input: proc_macro::TokenStream,
ty: TokenStream,
) -> Result<TokenStream> {
define_vars_and_mods!(_Vec);
use crate::exports::_Vec;
// Parse a comma-separated list of paths.
let paths = <Punctuated<Path, Token![,]>>::parse_terminated.parse(input)?;

View File

@ -28,7 +28,12 @@ fn entry_to_tests(root_glob: &LitStr) -> Result<Vec<TokenStream>, Box<dyn Error>
let ident = Ident::new(&name, root_glob.span());
let full_path = Path::new(&manifest_dir).join(&path).display().to_string();
tests.push(quote_spanned!(root_glob.span() => doc_comment::doctest!(#full_path, #ident);))
tests.push(quote_spanned!(root_glob.span() =>
mod #ident {
macro_rules! doc_comment { ($x:expr) => (#[doc = $x] extern {}); }
doc_comment!(include_str!(#full_path));
}
));
}
Ok(tests)

View File

@ -1,6 +1,7 @@
use std::fmt::Display;
use devise::{syn, Result, ext::SpanDiagnosticExt};
use devise::{syn, Result};
use devise::ext::{SpanDiagnosticExt, quote_respanned};
use crate::http::{uri::{Origin, Path, Query}, ext::IntoOwned};
use crate::http::route::{RouteSegment, Kind};
@ -46,7 +47,7 @@ fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
let route_name = &internal.uri_params.route_path;
match internal.validate() {
Validation::Ok(exprs) => {
let path_param_count = internal.route_uri.path().matches('<').count();
let path_param_count = internal.route_uri.path().as_str().matches('<').count();
for expr in exprs.iter().take(path_param_count) {
if !expr.as_expr().is_some() {
return Err(expr.span().error("path parameters cannot be ignored"));
@ -112,18 +113,19 @@ fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
}
fn add_binding(to: &mut Vec<TokenStream>, ident: &Ident, ty: &Type, expr: &Expr, source: Source) {
let uri_mod = quote!(rocket::http::uri);
let (span, ident_tmp) = (expr.span(), ident.prepend("__tmp_"));
let from_uri_param = if source == Source::Query {
quote_spanned!(span => #uri_mod::FromUriParam<#uri_mod::Query, _>)
} else {
quote_spanned!(span => #uri_mod::FromUriParam<#uri_mod::Path, _>)
let span = expr.span();
define_spanned_export!(span => _uri);
let part = match source {
Source::Query => quote_spanned!(span => #_uri::Query),
_ => quote_spanned!(span => #_uri::Path),
};
let tmp_ident = ident.clone().with_span(expr.span());
let let_stmt = quote_spanned!(span => let #tmp_ident = #expr);
to.push(quote_spanned!(span =>
#[allow(non_snake_case)]
let #ident_tmp = #expr;
let #ident = <#ty as #from_uri_param>::from_uri_param(#ident_tmp);
#[allow(non_snake_case)] #let_stmt;
let #ident = <#ty as #_uri::FromUriParam<#part, _>>::from_uri_param(#tmp_ident);
));
}
@ -132,7 +134,7 @@ fn explode_path<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>>(
bindings: &mut Vec<TokenStream>,
mut items: I
) -> TokenStream {
let (uri_mod, path) = (quote!(rocket::http::uri), uri.path());
let (uri_mod, path) = (quote!(rocket::http::uri), uri.path().as_str());
if !path.contains('<') {
return quote!(#uri_mod::UriArgumentsKind::Static(#path));
}
@ -161,7 +163,7 @@ fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a ArgExpr)>>(
bindings: &mut Vec<TokenStream>,
mut items: I
) -> Option<TokenStream> {
let (uri_mod, query) = (quote!(rocket::http::uri), uri.query()?);
let (uri_mod, query) = (quote!(rocket::http::uri), uri.query()?.as_str());
if !query.contains('<') {
return Some(quote!(#uri_mod::UriArgumentsKind::Static(#query)));
}
@ -181,7 +183,7 @@ fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a ArgExpr)>>(
None => {
// Force a typecheck for the `Ignoreable` trait. Note that write
// out the path to `is_ignorable` to get the right span.
bindings.push(quote_spanned! { arg_expr.span() =>
bindings.push(quote_respanned! { arg_expr.span() =>
rocket::http::uri::assert_ignorable::<#uri_mod::Query, #ty>();
});
@ -210,11 +212,11 @@ fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a ArgExpr)>>(
// (`<param>`) with `param=<param>`.
fn build_origin(internal: &InternalUriParams) -> Origin<'static> {
let mount_point = internal.uri_params.mount_point.as_ref()
.map(|origin| origin.path())
.map(|origin| origin.path().as_str())
.unwrap_or("");
let path = format!("{}/{}", mount_point, internal.route_uri.path());
let query = internal.route_uri.query();
let query = internal.route_uri.query().map(|q| q.as_str());
Origin::new(path, query).into_normalized().into_owned()
}

View File

@ -0,0 +1,147 @@
use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
use crate::exports::*;
use crate::proc_macro2::Span;
use crate::syn_ext::NameSource;
pub struct FormField {
pub span: Span,
pub name: NameSource,
}
#[derive(FromMeta)]
pub struct FieldAttr {
pub name: Option<FormField>,
pub validate: Option<syn::Expr>,
}
impl FieldAttr {
const NAME: &'static str = "field";
}
pub(crate) trait FieldExt {
fn ident(&self) -> &syn::Ident;
fn field_name(&self) -> Result<String>;
fn stripped_ty(&self) -> syn::Type;
fn name_view(&self) -> Result<syn::Expr>;
}
impl FromMeta for FormField {
fn from_meta(meta: &MetaItem) -> Result<Self> {
// These are used during parsing.
const CONTROL_CHARS: &[char] = &['&', '=', '?', '.', '[', ']'];
fn is_valid_field_name(s: &str) -> bool {
// The HTML5 spec (4.10.18.1) says 'isindex' is not allowed.
if s == "isindex" || s.is_empty() {
return false
}
// We allow all visible ASCII characters except `CONTROL_CHARS`.
s.chars().all(|c| c.is_ascii_graphic() && !CONTROL_CHARS.contains(&c))
}
let name = NameSource::from_meta(meta)?;
if !is_valid_field_name(name.name()) {
let chars = CONTROL_CHARS.iter()
.map(|c| format!("{:?}", c))
.collect::<Vec<_>>()
.join(", ");
return Err(meta.value_span()
.error("invalid form field name")
.help(format!("field name cannot be `isindex` or contain {}", chars)));
}
Ok(FormField { span: meta.value_span(), name })
}
}
impl FieldExt for Field<'_> {
fn ident(&self) -> &syn::Ident {
self.ident.as_ref().expect("named")
}
fn field_name(&self) -> Result<String> {
let mut fields = FieldAttr::from_attrs(FieldAttr::NAME, &self.attrs)?
.into_iter()
.filter_map(|attr| attr.name);
let name = fields.next()
.map(|f| f.name)
.unwrap_or_else(|| NameSource::from(self.ident().clone()));
if let Some(field) = fields.next() {
return Err(field.span
.error("duplicate form field renaming")
.help("a field can only be renamed once"));
}
Ok(name.to_string())
}
fn stripped_ty(&self) -> syn::Type {
self.ty.with_stripped_lifetimes()
}
fn name_view(&self) -> Result<syn::Expr> {
let field_name = self.field_name()?;
Ok(syn::parse_quote!(#_form::NameBuf::from((__c.__parent, #field_name))))
}
}
pub fn validators<'v>(
field: Field<'v>,
out: &'v syn::Ident,
local: bool,
) -> Result<impl Iterator<Item = syn::Expr> + 'v> {
use syn::visit_mut::VisitMut;
struct ValidationMutator<'a> {
field: syn::Expr,
self_expr: syn::Expr,
form: &'a syn::Ident,
visited: bool,
rec: bool,
}
impl<'a> ValidationMutator<'a> {
fn new(field: &'a syn::Ident, form: &'a syn::Ident) -> Self {
let self_expr = syn::parse_quote!(&#form.#field);
let field = syn::parse_quote!(&#field);
ValidationMutator { field, self_expr, form, visited: false, rec: false }
}
}
impl VisitMut for ValidationMutator<'_> {
fn visit_expr_call_mut(&mut self, call: &mut syn::ExprCall) {
syn::visit_mut::visit_expr_call_mut(self, call);
let ident = if self.rec { &self.self_expr } else { &self.field };
if !self.visited {
call.args.insert(0, ident.clone());
self.visited = true;
}
}
fn visit_ident_mut(&mut self, i: &mut syn::Ident) {
if i == "self" {
*i = self.form.clone();
self.rec = true;
}
syn::visit_mut::visit_ident_mut(self, i);
}
}
Ok(FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?
.into_iter()
.filter_map(|a| a.validate)
.map(move |mut expr| {
let mut mutator = ValidationMutator::new(field.ident(), out);
mutator.visit_expr_mut(&mut expr);
(expr, mutator.rec)
})
.filter(move |(_, global)| local != *global)
.map(|(e, _)| e))
}

View File

@ -1,133 +1,268 @@
use devise::{*, ext::{TypeExt, Split3, SpanDiagnosticExt}};
use devise::{*, ext::{TypeExt, SpanDiagnosticExt, GenericsExt, Split2, quote_respanned}};
use crate::proc_macro2::{Span, TokenStream};
use crate::syn_ext::NameSource;
use crate::exports::*;
use crate::proc_macro2::TokenStream;
use crate::derive::form_field::*;
#[derive(FromMeta)]
pub struct Form {
pub field: FormField,
}
// F: fn(field_ty: Ty, field_context: Expr)
fn fields_map<F>(fields: Fields<'_>, map_f: F) -> Result<TokenStream>
where F: Fn(&syn::Type, &syn::Expr) -> TokenStream
{
let matchers = fields.iter()
.map(|f| {
let (ident, field_name, ty) = (f.ident(), f.field_name()?, f.stripped_ty());
let field_context = quote_spanned!(ty.span() => {
let _o = __c.__opts;
__c.#ident.get_or_insert_with(|| <#ty as #_form::FromForm<'__f>>::init(_o))
});
pub struct FormField {
pub span: Span,
pub name: NameSource,
}
let field_context = syn::parse2(field_context).expect("valid expr");
let expr = map_f(&ty, &field_context);
Ok(quote!(#field_name => { #expr }))
})
.collect::<Result<Vec<TokenStream>>>()?;
fn is_valid_field_name(s: &str) -> bool {
// The HTML5 spec (4.10.18.1) says 'isindex' is not allowed.
if s == "isindex" || s.is_empty() {
return false
}
Ok(quote! {
__c.__parent = __f.name.parent();
// We allow all visible ASCII characters except '&', '=', and '?' since we
// use those as control characters for parsing.
s.chars().all(|c| (c >= ' ' && c <= '~') && c != '&' && c != '=' && c != '?')
}
impl FromMeta for FormField {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
let name = NameSource::from_meta(meta)?;
if !is_valid_field_name(name.name()) {
return Err(meta.value_span().error("invalid form field name"));
match __f.name.key_lossy().as_str() {
#(#matchers,)*
_k if _k == "_method" || !__c.__opts.strict => { /* ok */ },
_ => __c.__errors.push(__f.unexpected()),
}
Ok(FormField { span: meta.value_span(), name })
}
})
}
fn validate_struct(_: &DeriveGenerator, data: Struct<'_>) -> Result<()> {
if data.fields().is_empty() {
return Err(data.fields.span().error("at least one field is required"));
fn context_type(input: Input<'_>) -> (TokenStream, Option<syn::WhereClause>) {
let mut gen = input.generics().clone();
let lifetime = syn::parse_quote!('__f);
if !gen.replace_lifetime(0, &lifetime) {
gen.insert_lifetime(syn::LifetimeDef::new(lifetime.clone()));
}
let mut names = ::std::collections::HashMap::new();
for field in data.fields().iter() {
let id = field.ident.as_ref().expect("named field");
let field = match Form::from_attrs("form", &field.attrs) {
Some(result) => result?.field,
None => FormField { span: Spanned::span(&id), name: id.clone().into() }
};
if let Some(span) = names.get(&field.name) {
return Err(field.span.error("duplicate field name")
.span_note(*span, "previous definition here"));
}
names.insert(field.name, field.span);
}
Ok(())
let span = input.ident().span();
gen.add_type_bound(&syn::parse_quote!(#_form::FromForm<#lifetime>));
gen.add_type_bound(&syn::TypeParamBound::from(lifetime));
let (_, ty_gen, where_clause) = gen.split_for_impl();
(quote_spanned!(span => FromFormGeneratedContext #ty_gen), where_clause.cloned())
}
pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
let form_error = quote!(::rocket::request::FormParseError);
DeriveGenerator::build_for(input, quote!(impl<'__f> ::rocket::request::FromForm<'__f>))
.generic_support(GenericSupport::Lifetime | GenericSupport::Type)
DeriveGenerator::build_for(input, quote!(impl<'__f> #_form::FromForm<'__f>))
.support(Support::NamedStruct | Support::Lifetime | Support::Type)
.replace_generic(0, 0)
.data_support(DataSupport::NamedStruct)
.map_type_generic(|_, ident, _| quote! {
#ident : ::rocket::request::FromFormValue<'__f>
})
.validate_generics(|_, generics| match generics.lifetimes().enumerate().last() {
Some((i, lt)) if i >= 1 => Err(lt.span().error("only one lifetime is supported")),
_ => Ok(())
})
.validate_struct(validate_struct)
.function(|_, inner| quote! {
type Error = ::rocket::request::FormParseError<'__f>;
fn from_form(
__items: &mut ::rocket::request::FormItems<'__f>,
__strict: bool,
) -> ::std::result::Result<Self, Self::Error> {
#inner
}
})
.try_map_fields(move |_, fields| {
define_vars_and_mods!(_None, _Some, _Ok, _Err);
let (constructors, matchers, builders) = fields.iter().map(|field| {
let (ident, span) = (&field.ident, field.span());
let default_name = NameSource::from(ident.clone().expect("named"));
let name = Form::from_attrs("form", &field.attrs)
.map(|result| result.map(|form| form.field.name))
.unwrap_or_else(|| Ok(default_name))?;
let ty = field.ty.with_stripped_lifetimes();
let ty = quote_spanned! {
span => <#ty as ::rocket::request::FromFormValue>
};
let constructor = quote_spanned!(span => let mut #ident = #_None;);
let name = name.name();
let matcher = quote_spanned! { span =>
#name => { #ident = #_Some(#ty::from_form_value(__v)
.map_err(|_| #form_error::BadValue(__k, __v))?); },
};
let builder = quote_spanned! { span =>
#ident: #ident.or_else(#ty::default)
.ok_or_else(|| #form_error::Missing(#name.into()))?,
};
Ok((constructor, matcher, builder))
}).collect::<Result<Vec<_>>>()?.into_iter().split3();
Ok(quote! {
#(#constructors)*
for (__k, __v) in __items.map(|item| item.key_value()) {
match __k.as_str() {
#(#matchers)*
_ if __strict && __k != "_method" => {
return #_Err(#form_error::Unknown(__k, __v));
}
_ => { /* lenient or "method"; let it pass */ }
}
.type_bound(quote!(#_form::FromForm<'__f> + '__f))
.validator(ValidatorBuild::new()
.input_validate(|_, i| match i.generics().lifetimes().enumerate().last() {
Some((i, lt)) if i >= 1 => Err(lt.span().error("only one lifetime is supported")),
_ => Ok(())
})
.fields_validate(|_, fields| {
if fields.is_empty() {
return Err(fields.span().error("at least one field is required"));
}
#_Ok(Self { #(#builders)* })
let mut names = ::std::collections::HashMap::new();
for field in fields.iter() {
let name = field.field_name()?;
if let Some(span) = names.get(&name) {
return Err(field.span().error("duplicate form field")
.span_note(*span, "previously defined here"));
}
names.insert(name, field.span());
}
Ok(())
})
})
.to_tokens2()
)
.outer_mapper(MapperBuild::new()
.try_input_map(|mapper, input| {
let (ctxt_ty, where_clause) = context_type(input);
let output = mapper::input_default(mapper, input)?;
Ok(quote! {
/// Rocket generated FormForm context.
#[doc(hidden)]
pub struct #ctxt_ty #where_clause {
__opts: #_form::Options,
__errors: #_form::Errors<'__f>,
__parent: #_Option<&'__f #_form::Name>,
#output
}
})
})
.try_fields_map(|m, f| mapper::fields_null(m, f))
.field_map(|_, field| {
let (ident, mut ty) = (field.ident(), field.stripped_ty());
ty.replace_lifetimes(syn::parse_quote!('__f));
let field_ty = quote_respanned!(ty.span() =>
#_Option<<#ty as #_form::FromForm<'__f>>::Context>
);
quote_spanned!(ty.span() => #ident: #field_ty,)
})
)
.outer_mapper(quote!(#[rocket::async_trait]))
.inner_mapper(MapperBuild::new()
.try_input_map(|mapper, input| {
let (ctxt_ty, _) = context_type(input);
let output = mapper::input_default(mapper, input)?;
Ok(quote! {
type Context = #ctxt_ty;
fn init(__opts: #_form::Options) -> Self::Context {
Self::Context {
__opts,
__errors: #_form::Errors::new(),
__parent: #_None,
#output
}
}
})
})
.try_fields_map(|m, f| mapper::fields_null(m, f))
.field_map(|_, field| {
let ident = field.ident.as_ref().expect("named");
let ty = field.ty.with_stripped_lifetimes();
quote_spanned!(ty.span() =>
#ident: #_None,
// #ident: <#ty as #_form::FromForm<'__f>>::init(__opts),
)
})
)
.inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! {
fn push_value(__c: &mut Self::Context, __f: #_form::ValueField<'__f>) {
#output
}
})
.try_fields_map(|_, f| fields_map(f, |ty, ctxt| quote_spanned!(ty.span() => {
<#ty as #_form::FromForm<'__f>>::push_value(#ctxt, __f.shift());
})))
)
.inner_mapper(MapperBuild::new()
.try_input_map(|mapper, input| {
let (ctxt_ty, _) = context_type(input);
let output = mapper::input_default(mapper, input)?;
Ok(quote! {
async fn push_data(
__c: &mut #ctxt_ty,
__f: #_form::DataField<'__f, '_>
) {
#output
}
})
})
// Without the `let _fut`, we get a wild lifetime error. It don't
// make no sense, Rust async/await, it don't make no sense.
.try_fields_map(|_, f| fields_map(f, |ty, ctxt| quote_spanned!(ty.span() => {
let _fut = <#ty as #_form::FromForm<'__f>>::push_data(#ctxt, __f.shift());
_fut.await;
})))
)
.inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! {
fn finalize(mut __c: Self::Context) -> #_Result<Self, #_form::Errors<'__f>> {
#[allow(unused_imports)]
use #_form::validate::*;
#output
}
})
.try_fields_map(|mapper, fields| {
let finalize_field = fields.iter()
.map(|f| mapper.map_field(f))
.collect::<Result<Vec<TokenStream>>>()?;
let ident: Vec<_> = fields.iter()
.map(|f| f.ident().clone())
.collect();
let o = syn::Ident::new("__o", fields.span());
let (_ok, _some, _err, _none) = (_Ok, _Some, _Err, _None);
let (name_view, validate) = fields.iter()
.map(|f| (f.name_view().unwrap(), validators(f, &o, false).unwrap()))
.map(|(nv, vs)| vs.map(move |v| (nv.clone(), v)))
.flatten()
.split2();
Ok(quote_spanned! { fields.span() =>
// if __c.__parent.is_none() {
// let _e = #_form::Error::from(#_form::ErrorKind::Missing)
// .with_entity(#_form::Entity::Form);
//
// return #_Err(_e.into());
// }
#(let #ident = match #finalize_field {
#_ok(#ident) => #_some(#ident),
#_err(_e) => { __c.__errors.extend(_e); #_none }
};)*
if !__c.__errors.is_empty() {
return #_Err(__c.__errors);
}
let #o = Self { #(#ident: #ident.unwrap()),* };
#(
if let #_err(_e) = #validate {
__c.__errors.extend(_e.with_name(#name_view));
}
)*
if !__c.__errors.is_empty() {
return #_Err(__c.__errors);
}
Ok(#o)
})
})
.try_field_map(|_, f| {
let (ident, ty, name_view) = (f.ident(), f.stripped_ty(), f.name_view()?);
let validator = validators(f, &ident, true)?;
let _err = _Err;
Ok(quote_spanned! { ty.span() => {
let _name = #name_view;
__c.#ident
.map(<#ty as #_form::FromForm<'__f>>::finalize)
.unwrap_or_else(|| <#ty as #_form::FromForm<'__f>>::default()
.ok_or_else(|| #_form::ErrorKind::Missing.into())
)
// <#ty as #_form::FromForm<'__f>>::finalize(__c.#ident)
.and_then(|#ident| {
let mut _es = #_form::Errors::new();
#(if let #_err(_e) = #validator { _es.extend(_e); })*
match _es.is_empty() {
true => #_Ok(#ident),
false => #_Err(_es)
}
})
.map_err(|_e| _e.with_name(_name))
.map_err(|_e| match _e.is_empty() {
true => #_form::ErrorKind::Unknown.into(),
false => _e,
})
}})
})
)
// .inner_mapper(MapperBuild::new()
// .with_output(|_, output| quote! {
// fn default() -> #_Option<Self> {
// Some(Self { #output })
// }
// })
// .try_fields_map(|m, f| mapper::fields_null(m, f))
// .field_map(|_, field| {
// let ident = field.ident.as_ref().expect("named");
// let ty = field.ty.with_stripped_lifetimes();
// quote_spanned!(ty.span() =>
// #ident: <#ty as #_form::FromForm<'__f>>::default()?,
// )
// })
// )
.to_tokens()
}

View File

@ -0,0 +1,75 @@
use devise::{*, ext::SpanDiagnosticExt};
use crate::exports::*;
use crate::proc_macro2::TokenStream;
use crate::syn_ext::NameSource;
#[derive(FromMeta)]
pub struct FieldAttr {
value: NameSource,
}
pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'__v> #_form::FromFormField<'__v>))
.support(Support::Enum)
.validator(ValidatorBuild::new()
// We only accepts C-like enums with at least one variant.
.fields_validate(|_, fields| {
if !fields.is_empty() {
return Err(fields.span().error("variants cannot have fields"));
}
Ok(())
})
.enum_validate(|_, data| {
if data.variants.is_empty() {
return Err(data.span().error("enum must have at least one variant"));
}
Ok(())
})
)
// TODO: Devise should have a try_variant_map.
.inner_mapper(MapperBuild::new()
.try_enum_map(|_, data| {
let variant_name_sources = data.variants()
.map(|v| FieldAttr::one_from_attrs("field", &v.attrs).map(|o| {
o.map(|f| f.value).unwrap_or_else(|| v.ident.clone().into())
}))
.collect::<Result<Vec<NameSource>>>()?;
let variant_name = variant_name_sources.iter()
.map(|n| n.name())
.collect::<Vec<_>>();
let builder = data.variants()
.map(|v| v.builder(|_| unreachable!("fieldless")));
let (_ok, _cow) = (std::iter::repeat(_Ok), std::iter::repeat(_Cow));
Ok(quote! {
fn from_value(
__f: #_form::ValueField<'__v>
) -> Result<Self, #_form::Errors<'__v>> {
#[allow(unused_imports)]
use #_http::uncased::AsUncased;
#(
if __f.value.as_uncased() == #variant_name {
return #_ok(#builder);
}
)*
const OPTS: &'static [#_Cow<'static, str>] =
&[#(#_cow::Borrowed(#variant_name)),*];
let _error = #_form::Error::from(OPTS)
.with_name(__f.name)
.with_value(__f.value);
#_Err(_error)?
}
})
})
)
.to_tokens()
}

View File

@ -1,59 +0,0 @@
use devise::{*, ext::SpanDiagnosticExt};
use crate::proc_macro2::TokenStream;
use crate::syn_ext::NameSource;
#[derive(FromMeta)]
struct Form {
value: NameSource,
}
pub fn derive_from_form_value(input: proc_macro::TokenStream) -> TokenStream {
define_vars_and_mods!(_Ok, _Err, _Result);
DeriveGenerator::build_for(input, quote!(impl<'__v> ::rocket::request::FromFormValue<'__v>))
.generic_support(GenericSupport::None)
.data_support(DataSupport::Enum)
.validate_enum(|_, data| {
// This derive only works for variants that are nullary.
for variant in data.variants() {
if !variant.fields().is_empty() {
return Err(variant.fields().span().error("variants cannot have fields"));
}
}
// Emit a warning if the enum is empty.
if data.variants.is_empty() {
return Err(data.brace_token.span.error("enum must have at least one field"));
}
Ok(())
})
.function(move |_, inner| quote! {
type Error = &'__v ::rocket::http::RawStr;
fn from_form_value(
value: &'__v ::rocket::http::RawStr
) -> #_Result<Self, Self::Error> {
let uncased = value.as_uncased_str();
#inner
#_Err(value)
}
})
.try_map_enum(null_enum_mapper)
.try_map_variant(|_, variant| {
define_vars_and_mods!(_Ok);
let variant_name_source = Form::from_attrs("form", &variant.attrs)
.unwrap_or_else(|| Ok(Form { value: variant.ident.clone().into() }))?
.value;
let variant_str = variant_name_source.name();
let builder = variant.builder(|_| unreachable!("no fields"));
Ok(quote! {
if uncased == #variant_str {
return #_Ok(#builder);
}
})
})
.to_tokens2()
}

View File

@ -1,4 +1,5 @@
mod form_field;
pub mod from_form;
pub mod from_form_value;
pub mod from_form_field;
pub mod responder;
pub mod uri_display;

View File

@ -1,11 +1,11 @@
use quote::ToTokens;
use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
use crate::exports::*;
use crate::proc_macro2::TokenStream;
use crate::http_codegen::{ContentType, Status};
#[derive(Default, FromMeta)]
#[derive(Debug, Default, FromMeta)]
struct ItemAttr {
content_type: Option<SpanWrapped<ContentType>>,
status: Option<SpanWrapped<Status>>,
@ -18,65 +18,64 @@ struct FieldAttr {
pub fn derive_responder(input: proc_macro::TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'__r, '__o: '__r> ::rocket::response::Responder<'__r, '__o>))
.generic_support(GenericSupport::Lifetime)
.data_support(DataSupport::Struct | DataSupport::Enum)
.support(Support::Struct | Support::Enum | Support::Lifetime)
.replace_generic(1, 0)
.validate_generics(|_, generics| match generics.lifetimes().count() > 1 {
true => Err(generics.span().error("only one lifetime is supported")),
false => Ok(())
})
.validate_fields(|_, fields| match fields.is_empty() {
true => return Err(fields.span().error("need at least one field")),
false => Ok(())
})
.function(|_, inner| quote! {
fn respond_to(
self,
__req: &'__r ::rocket::request::Request
) -> ::rocket::response::Result<'__o> {
#inner
}
})
.try_map_fields(|_, fields| {
define_vars_and_mods!(_Ok);
fn set_header_tokens<T: ToTokens + Spanned>(item: T) -> TokenStream {
quote_spanned!(item.span().into() => __res.set_header(#item);)
}
let attr = ItemAttr::from_attrs("response", fields.parent.attrs())
.unwrap_or_else(|| Ok(Default::default()))?;
let responder = fields.iter().next().map(|f| {
let (accessor, ty) = (f.accessor(), f.ty.with_stripped_lifetimes());
quote_spanned! { f.span().into() =>
let mut __res = <#ty as ::rocket::response::Responder>::respond_to(
#accessor, __req
)?;
}
}).expect("have at least one field");
let mut headers = vec![];
for field in fields.iter().skip(1) {
let attr = FieldAttr::from_attrs("response", &field.attrs)
.unwrap_or_else(|| Ok(Default::default()))?;
if !attr.ignore {
headers.push(set_header_tokens(field.accessor()));
}
}
let content_type = attr.content_type.map(set_header_tokens);
let status = attr.status.map(|status| {
quote_spanned!(status.span().into() => __res.set_status(#status);)
});
Ok(quote! {
#responder
#(#headers)*
#content_type
#status
#_Ok(__res)
.validator(ValidatorBuild::new()
.input_validate(|_, i| match i.generics().lifetimes().count() > 1 {
true => Err(i.generics().span().error("only one lifetime is supported")),
false => Ok(())
})
})
.to_tokens2()
.fields_validate(|_, fields| match fields.is_empty() {
true => return Err(fields.span().error("need at least one field")),
false => Ok(())
})
)
.inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! {
fn respond_to(self, __req: &'__r #Request) -> #_response::Result<'__o> {
#output
}
})
.try_fields_map(|_, fields| {
fn set_header_tokens<T: ToTokens + Spanned>(item: T) -> TokenStream {
quote_spanned!(item.span().into() => __res.set_header(#item);)
}
let attr = ItemAttr::one_from_attrs("response", fields.parent.attrs())?
.unwrap_or_default();
let responder = fields.iter().next().map(|f| {
let (accessor, ty) = (f.accessor(), f.ty.with_stripped_lifetimes());
quote_spanned! { f.span().into() =>
let mut __res = <#ty as ::rocket::response::Responder>::respond_to(
#accessor, __req
)?;
}
}).expect("have at least one field");
let mut headers = vec![];
for field in fields.iter().skip(1) {
let attr = FieldAttr::one_from_attrs("response", &field.attrs)?
.unwrap_or_default();
if !attr.ignore {
headers.push(set_header_tokens(field.accessor()));
}
}
let content_type = attr.content_type.map(set_header_tokens);
let status = attr.status.map(|status| {
quote_spanned!(status.span().into() => __res.set_status(#status);)
});
Ok(quote! {
#responder
#(#headers)*
#content_type
#status
#_Ok(__res)
})
})
)
.to_tokens()
}

View File

@ -1,7 +1,9 @@
use devise::{*, ext::SpanDiagnosticExt};
use rocket_http::uri::UriPart;
use crate::derive::from_form::Form;
use crate::exports::*;
use crate::derive::form_field::FieldExt;
use crate::proc_macro2::TokenStream;
const NO_EMPTY_FIELDS: &str = "fieldless structs or variants are not supported";
@ -10,113 +12,69 @@ const NO_EMPTY_ENUMS: &str = "empty enums are not supported";
const ONLY_ONE_UNNAMED: &str = "tuple structs or variants must have exactly one field";
const EXACTLY_ONE_FIELD: &str = "struct must have exactly one field";
fn validate_fields(ident: &syn::Ident, fields: Fields<'_>) -> Result<()> {
fn validate_fields(fields: Fields<'_>) -> Result<()> {
if fields.count() == 0 {
return Err(ident.span().error(NO_EMPTY_FIELDS))
return Err(fields.parent.span().error(NO_EMPTY_FIELDS))
} else if fields.are_unnamed() && fields.count() > 1 {
return Err(fields.span.error(ONLY_ONE_UNNAMED));
return Err(fields.span().error(ONLY_ONE_UNNAMED));
} else if fields.are_unit() {
return Err(fields.span.error(NO_NULLARY));
return Err(fields.span().error(NO_NULLARY));
}
Ok(())
}
fn validate_enum(_: &DeriveGenerator, data: Enum<'_>) -> Result<()> {
fn validate_enum(data: Enum<'_>) -> Result<()> {
if data.variants().count() == 0 {
return Err(data.brace_token.span.error(NO_EMPTY_ENUMS));
}
for variant in data.variants() {
validate_fields(&variant.ident, variant.fields())?;
}
Ok(())
}
#[allow(non_snake_case)]
pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
let Query = quote!(::rocket::http::uri::Query);
let UriDisplay = quote!(::rocket::http::uri::UriDisplay<#Query>);
let Formatter = quote!(::rocket::http::uri::Formatter<#Query>);
let FromUriParam = quote!(::rocket::http::uri::FromUriParam);
use crate::http::uri::Query;
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #UriDisplay))
.data_support(DataSupport::Struct | DataSupport::Enum)
.generic_support(GenericSupport::Type | GenericSupport::Lifetime)
.validate_enum(validate_enum)
.validate_struct(|gen, data| validate_fields(&gen.input.ident, data.fields()))
.map_type_generic(move |_, ident, _| quote!(#ident : #UriDisplay))
.function(move |_, inner| quote! {
fn fmt(&self, f: &mut #Formatter) -> ::std::fmt::Result {
#inner
Ok(())
}
})
.try_map_field(|_, field| {
let span = field.span().into();
let accessor = field.accessor();
let tokens = if let Some(ref ident) = field.ident {
let name_source = Form::from_attrs("form", &field.attrs)
.map(|result| result.map(|form| form.field.name))
.unwrap_or_else(|| Ok(ident.clone().into()))?;
const URI_DISPLAY: StaticTokens = quote_static!(#_uri::UriDisplay<#_uri::Query>);
const FORMATTER: StaticTokens = quote_static!(#_uri::Formatter<#_uri::Query>);
let name = name_source.name();
quote_spanned!(span => f.write_named_value(#name, &#accessor)?;)
} else {
quote_spanned!(span => f.write_value(&#accessor)?;)
};
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
.support(Support::Struct | Support::Enum | Support::Type | Support::Lifetime)
.validator(ValidatorBuild::new()
.enum_validate(|_, v| validate_enum(v))
.fields_validate(|_, v| validate_fields(v))
)
.type_bound(URI_DISPLAY)
.inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! {
fn fmt(&self, f: &mut #FORMATTER) -> ::std::fmt::Result {
#output
Ok(())
}
})
.try_field_map(|_, field| {
let span = field.span().into();
let accessor = field.accessor();
let tokens = if field.ident.is_some() {
let name = field.field_name()?;
quote_spanned!(span => f.write_named_value(#name, &#accessor)?;)
} else {
quote_spanned!(span => f.write_value(&#accessor)?;)
};
Ok(tokens)
})
.try_to_tokens();
Ok(tokens)
})
)
.try_to_tokens::<TokenStream>();
let uri_display = match uri_display {
Ok(tokens) => tokens,
Err(diag) => return diag.emit_as_item_tokens()
};
let i = input.clone();
let gen_trait = quote!(impl #FromUriParam<#Query, Self>);
let UriDisplay = quote!(::rocket::http::uri::UriDisplay<#Query>);
let from_self = DeriveGenerator::build_for(i, gen_trait)
.data_support(DataSupport::Struct | DataSupport::Enum)
.generic_support(GenericSupport::Type | GenericSupport::Lifetime)
.map_type_generic(move |_, ident, _| quote!(#ident : #UriDisplay))
.function(|_, _| quote! {
type Target = Self;
#[inline(always)]
fn from_uri_param(param: Self) -> Self { param }
})
.to_tokens();
let i = input.clone();
let gen_trait = quote!(impl<'__r> #FromUriParam<#Query, &'__r Self>);
let UriDisplay = quote!(::rocket::http::uri::UriDisplay<#Query>);
let from_ref = DeriveGenerator::build_for(i, gen_trait)
.data_support(DataSupport::Struct | DataSupport::Enum)
.generic_support(GenericSupport::Type | GenericSupport::Lifetime)
.map_type_generic(move |_, ident, _| quote!(#ident : #UriDisplay))
.function(|_, _| quote! {
type Target = &'__r Self;
#[inline(always)]
fn from_uri_param(param: &'__r Self) -> &'__r Self { param }
})
.to_tokens();
let i = input.clone();
let gen_trait = quote!(impl<'__r> #FromUriParam<#Query, &'__r mut Self>);
let UriDisplay = quote!(::rocket::http::uri::UriDisplay<#Query>);
let from_mut = DeriveGenerator::build_for(i, gen_trait)
.data_support(DataSupport::Struct | DataSupport::Enum)
.generic_support(GenericSupport::Type | GenericSupport::Lifetime)
.map_type_generic(move |_, ident, _| quote!(#ident : #UriDisplay))
.function(|_, _| quote! {
type Target = &'__r mut Self;
#[inline(always)]
fn from_uri_param(param: &'__r mut Self) -> &'__r mut Self { param }
})
.to_tokens();
let from_self = from_uri_param::<Query>(input.clone(), quote!(Self));
let from_ref = from_uri_param::<Query>(input.clone(), quote!(&'__r Self));
let from_mut = from_uri_param::<Query>(input.clone(), quote!(&'__r mut Self));
let mut ts = TokenStream::from(uri_display);
ts.extend(TokenStream::from(from_self));
@ -127,67 +85,72 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
#[allow(non_snake_case)]
pub fn derive_uri_display_path(input: proc_macro::TokenStream) -> TokenStream {
let Path = quote!(::rocket::http::uri::Path);
let UriDisplay = quote!(::rocket::http::uri::UriDisplay<#Path>);
let Formatter = quote!(::rocket::http::uri::Formatter<#Path>);
let FromUriParam = quote!(::rocket::http::uri::FromUriParam);
use crate::http::uri::Path;
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #UriDisplay))
.data_support(DataSupport::TupleStruct)
.generic_support(GenericSupport::Type | GenericSupport::Lifetime)
.map_type_generic(move |_, ident, _| quote!(#ident : #UriDisplay))
.validate_fields(|_, fields| match fields.count() {
1 => Ok(()),
_ => Err(fields.span.error(EXACTLY_ONE_FIELD))
})
.function(move |_, inner| quote! {
fn fmt(&self, f: &mut #Formatter) -> ::std::fmt::Result {
#inner
Ok(())
}
})
.map_field(|_, field| {
let span = field.span().into();
let accessor = field.accessor();
quote_spanned!(span => f.write_value(&#accessor)?;)
})
.try_to_tokens();
const URI_DISPLAY: StaticTokens = quote_static!(#_uri::UriDisplay<#_uri::Path>);
const FORMATTER: StaticTokens = quote_static!(#_uri::Formatter<#_uri::Path>);
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
.support(Support::TupleStruct | Support::Type | Support::Lifetime)
.type_bound(URI_DISPLAY)
.validator(ValidatorBuild::new()
.fields_validate(|_, fields| match fields.count() {
1 => Ok(()),
_ => Err(fields.span().error(EXACTLY_ONE_FIELD))
})
)
.inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! {
fn fmt(&self, f: &mut #FORMATTER) -> ::std::fmt::Result {
#output
Ok(())
}
})
.field_map(|_, field| {
let accessor = field.accessor();
quote_spanned!(field.span() => f.write_value(&#accessor)?;)
})
)
.try_to_tokens::<TokenStream>();
let uri_display = match uri_display {
Ok(tokens) => tokens,
Err(diag) => return diag.emit_as_item_tokens()
};
let i = input.clone();
let gen_trait = quote!(impl #FromUriParam<#Path, Self>);
let UriDisplay = quote!(::rocket::http::uri::UriDisplay<#Path>);
let from_self = DeriveGenerator::build_for(i, gen_trait)
.data_support(DataSupport::All)
.generic_support(GenericSupport::Type | GenericSupport::Lifetime)
.map_type_generic(move |_, ident, _| quote!(#ident : #UriDisplay))
.function(|_, _| quote! {
type Target = Self;
#[inline(always)]
fn from_uri_param(param: Self) -> Self { param }
})
.to_tokens();
let i = input.clone();
let gen_trait = quote!(impl<'__r> #FromUriParam<#Path, &'__r Self>);
let UriDisplay = quote!(::rocket::http::uri::UriDisplay<#Path>);
let from_ref = DeriveGenerator::build_for(i, gen_trait)
.data_support(DataSupport::All)
.generic_support(GenericSupport::Type | GenericSupport::Lifetime)
.map_type_generic(move |_, ident, _| quote!(#ident : #UriDisplay))
.function(|_, _| quote! {
type Target = &'__r Self;
#[inline(always)]
fn from_uri_param(param: &'__r Self) -> &'__r Self { param }
})
.to_tokens();
let from_self = from_uri_param::<Path>(input.clone(), quote!(Self));
let from_ref = from_uri_param::<Path>(input.clone(), quote!(&'__r Self));
let from_mut = from_uri_param::<Path>(input.clone(), quote!(&'__r mut Self));
let mut ts = TokenStream::from(uri_display);
ts.extend(TokenStream::from(from_self));
ts.extend(TokenStream::from(from_ref));
ts.extend(TokenStream::from(from_mut));
ts.into()
}
fn from_uri_param<P: UriPart>(input: proc_macro::TokenStream, ty: TokenStream) -> TokenStream {
let part = match P::DELIMITER {
'/' => quote!(#_uri::Path),
'&' => quote!(#_uri::Query),
_ => unreachable!("sealed trait with path/query")
};
let ty: syn::Type = syn::parse2(ty).expect("valid type");
let gen = match ty {
syn::Type::Reference(ref r) => r.lifetime.as_ref().map(|l| quote!(<#l>)),
_ => None
};
let param_trait = quote!(impl #gen #_uri::FromUriParam<#part, #ty>);
DeriveGenerator::build_for(input, param_trait)
.support(Support::All)
.type_bound(quote!(#_uri::UriDisplay<#part>))
.inner_mapper(MapperBuild::new()
.with_output(move |_, _| quote! {
type Target = #ty;
#[inline(always)] fn from_uri_param(_p: #ty) -> #ty { _p }
})
)
.to_tokens()
}

105
core/codegen/src/exports.rs Normal file
View File

@ -0,0 +1,105 @@
use crate::syn;
use crate::proc_macro2::{Span, TokenStream};
use crate::quote::{ToTokens, TokenStreamExt};
#[derive(Debug, Copy, Clone)]
pub struct StaticPath(pub Option<Span>, pub &'static str);
#[derive(Debug, Copy, Clone)]
pub struct StaticTokens(pub fn() -> TokenStream);
macro_rules! quote_static {
($($token:tt)*) => {
$crate::exports::StaticTokens(|| quote!($($token)*))
}
}
impl ToTokens for StaticTokens {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all((self.0)());
}
}
impl StaticPath {
pub fn respanned(mut self, span: Span) -> Self {
self.0 = Some(span);
self
}
}
impl ToTokens for StaticPath {
fn to_tokens(&self, tokens: &mut TokenStream) {
let path: syn::Path = syn::parse_str(self.1).unwrap();
if let Some(span) = self.0 {
let new_tokens = path.into_token_stream()
.into_iter()
.map(|mut t| { t.set_span(span); t });
tokens.append_all(new_tokens);
} else {
path.to_tokens(tokens)
}
}
}
macro_rules! define_exported_paths {
($($name:ident => $path:path),* $(,)?) => {
$(
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
pub const $name: StaticPath = $crate::exports::StaticPath(None, stringify!($path));
)*
macro_rules! define {
// Note: the `i` is to capture the input's span.
$(($span:expr => $i:ident $name) => {
#[allow(non_snake_case)]
let $i = $crate::exports::StaticPath(Some($span), stringify!($path));
};)*
}
};
}
define_exported_paths! {
__req => __req,
__status => __status,
__catcher => __catcher,
__data => __data,
__error => __error,
__trail => __trail,
_request => rocket::request,
_response => rocket::response,
_handler => rocket::handler,
_log => rocket::logger,
_form => rocket::form::prelude,
_http => rocket::http,
_uri => rocket::http::uri,
_Option => ::std::option::Option,
_Result => ::std::result::Result,
_Some => ::std::option::Option::Some,
_None => ::std::option::Option::None,
_Ok => ::std::result::Result::Ok,
_Err => ::std::result::Result::Err,
_Box => ::std::boxed::Box,
_Vec => ::std::vec::Vec,
_Cow => ::std::borrow::Cow,
BorrowMut => ::std::borrow::BorrowMut,
Outcome => rocket::outcome::Outcome,
FromForm => rocket::form::FromForm,
FromData => rocket::data::FromData,
Request => rocket::request::Request,
Response => rocket::response::Response,
Data => rocket::data::Data,
StaticRouteInfo => rocket::StaticRouteInfo,
StaticCatcherInfo => rocket::StaticCatcherInfo,
Route => rocket::Route,
Catcher => rocket::Catcher,
SmallVec => rocket::http::private::SmallVec,
Status => rocket::http::Status,
HandlerFuture => rocket::handler::HandlerFuture,
ErrorHandlerFuture => rocket::catcher::ErrorHandlerFuture,
}
macro_rules! define_spanned_export {
($span:expr => $($name:ident),*) => ($(define!($span => $name $name);)*)
}

View File

@ -30,7 +30,7 @@ pub struct DataSegment(pub Segment);
pub struct Optional<T>(pub Option<T>);
impl FromMeta for StringLit {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
fn from_meta(meta: &MetaItem) -> Result<Self> {
Ok(StringLit::new(String::from_meta(meta)?, meta.value_span()))
}
}
@ -43,7 +43,7 @@ pub struct RoutePath {
}
impl FromMeta for Status {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let num = usize::from_meta(meta)?;
if num < 100 || num >= 600 {
return Err(meta.value_span().error("status must be in range [100, 599]"));
@ -61,7 +61,7 @@ impl ToTokens for Status {
}
impl FromMeta for ContentType {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
fn from_meta(meta: &MetaItem) -> Result<Self> {
http::ContentType::parse_flexible(&String::from_meta(meta)?)
.map(ContentType)
.ok_or(meta.value_span().error("invalid or unknown content type"))
@ -77,7 +77,7 @@ impl ToTokens for ContentType {
}
impl FromMeta for MediaType {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let mt = http::MediaType::parse_flexible(&String::from_meta(meta)?)
.ok_or(meta.value_span().error("invalid or unknown media type"))?;
@ -95,7 +95,7 @@ impl FromMeta for MediaType {
impl ToTokens for MediaType {
fn to_tokens(&self, tokens: &mut TokenStream) {
let (top, sub) = (self.0.top().as_str(), self.0.sub().as_str());
let (keys, values) = self.0.params().split2();
let (keys, values) = self.0.params().map(|(k, v)| (k.as_str(), v)).split2();
let http = quote!(::rocket::http);
tokens.extend(quote! {
@ -114,7 +114,7 @@ const VALID_METHODS: &[http::Method] = &[
];
impl FromMeta for Method {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let span = meta.value_span();
let help_text = format!("method must be one of: {}", VALID_METHODS_STR);
@ -156,13 +156,13 @@ impl ToTokens for Method {
}
impl FromMeta for Origin {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let string = StringLit::from_meta(meta)?;
let uri = http::uri::Origin::parse_route(&string)
.map_err(|e| {
let span = string.subspan(e.index() + 1..);
span.error(format!("invalid path URI: {}", e))
let span = string.subspan(e.index() + 1..(e.index() + 2));
span.error(format!("invalid route URI: {}", e))
.help("expected path in origin form: \"/path/<param>\"")
})?;
@ -177,7 +177,7 @@ impl FromMeta for Origin {
}
impl FromMeta for DataSegment {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let string = StringLit::from_meta(meta)?;
let span = string.subspan(1..(string.len() + 1));
@ -192,7 +192,7 @@ impl FromMeta for DataSegment {
}
impl FromMeta for RoutePath {
fn from_meta(meta: MetaItem<'_>) -> Result<Self> {
fn from_meta(meta: &MetaItem) -> Result<Self> {
let (origin, string) = (Origin::from_meta(meta)?, StringLit::from_meta(meta)?);
let path_span = string.subspan(1..origin.0.path().len() + 1);
let path = parse_segments::<Path>(origin.0.path(), path_span);
@ -220,9 +220,11 @@ impl FromMeta for RoutePath {
impl<T: ToTokens> ToTokens for Optional<T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
define_vars_and_mods!(_Some, _None);
use crate::exports::{_Some, _None};
use devise::Spanned;
let opt_tokens = match self.0 {
Some(ref val) => quote!(#_Some(#val)),
Some(ref val) => quote_spanned!(val.span() => #_Some(#val)),
None => quote!(#_None)
};

View File

@ -58,63 +58,8 @@
use rocket_http as http;
macro_rules! vars_and_mods {
($($name:ident => $path:path,)*) => {
macro_rules! define {
// Note: the `o` is to capture the input's span
$(($i:ident $name) => {
#[allow(non_snake_case)] let $i = quote!($path);
};)*
$(($span:expr => $i:ident $name) => {
#[allow(non_snake_case)] let $i = quote_spanned!($span => $path);
};)*
}
}
}
vars_and_mods! {
req => __req,
status => __status,
catcher => __catcher,
data => __data,
error => __error,
trail => __trail,
request => rocket::request,
response => rocket::response,
handler => rocket::handler,
log => rocket::logger,
Outcome => rocket::outcome::Outcome,
FromTransformedData => rocket::data::FromTransformedData,
Transform => rocket::data::Transform,
Query => rocket::request::Query,
FromFormValue => rocket::request::FromFormValue,
Request => rocket::request::Request,
Response => rocket::response::Response,
Data => rocket::data::Data,
StaticRouteInfo => rocket::StaticRouteInfo,
StaticCatcherInfo => rocket::StaticCatcherInfo,
Route => rocket::Route,
Catcher => rocket::Catcher,
SmallVec => rocket::http::private::SmallVec,
Status => rocket::http::Status,
HandlerFuture => rocket::handler::HandlerFuture,
ErrorHandlerFuture => rocket::catcher::ErrorHandlerFuture,
_Option => ::std::option::Option,
_Result => ::std::result::Result,
_Some => ::std::option::Option::Some,
_None => ::std::option::Option::None,
_Ok => ::std::result::Result::Ok,
_Err => ::std::result::Result::Err,
_Box => ::std::boxed::Box,
_Vec => ::std::vec::Vec,
}
macro_rules! define_vars_and_mods {
($($name:ident),*) => ($(define!($name $name);)*);
($span:expr => $($name:ident),*) => ($(define!($span => $name $name);)*)
}
#[macro_use]
mod exports;
mod proc_macro_ext;
mod derive;
mod attribute;
@ -238,11 +183,11 @@ macro_rules! route_attribute {
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # use rocket::request::Form;
/// # use rocket::form::Form;
/// # use std::path::PathBuf;
/// # #[derive(FromForm)] struct F { a: usize }
/// #[get("/<foo>/bar/<baz..>?<msg>&closed&<rest..>", data = "<form>")]
/// # fn f(foo: usize, baz: PathBuf, msg: String, rest: Form<F>, form: Form<F>) { }
/// # fn f(foo: usize, baz: PathBuf, msg: String, rest: F, form: Form<F>) { }
/// ```
///
/// The type of each function argument corresponding to a dynamic
@ -256,9 +201,9 @@ macro_rules! route_attribute {
/// |----------|-------------|-------------------|
/// | path | `<ident>` | [`FromParam`] |
/// | path | `<ident..>` | [`FromSegments`] |
/// | query | `<ident>` | [`FromFormValue`] |
/// | query | `<ident..>` | [`FromQuery`] |
/// | data | `<ident>` | [`FromTransformedData`] |
/// | query | `<ident>` | [`FromFormField`] |
/// | query | `<ident..>` | [`FromFrom`] |
/// | data | `<ident>` | [`FromData`] |
///
/// The type of each function argument that _does not_ have a
/// corresponding dynamic parameter is required to implement the
@ -269,9 +214,9 @@ macro_rules! route_attribute {
///
/// [`FromParam`]: ../rocket/request/trait.FromParam.html
/// [`FromSegments`]: ../rocket/request/trait.FromSegments.html
/// [`FromFormValue`]: ../rocket/request/trait.FromFormValue.html
/// [`FromQuery`]: ../rocket/request/trait.FromQuery.html
/// [`FromTransformedData`]: ../rocket/data/trait.FromTransformedData.html
/// [`FromFormField`]: ../rocket/request/trait.FromFormField.html
/// [`FromForm`]: ../rocket/form/trait.FromForm.html
/// [`FromData`]: ../rocket/data/trait.FromData.html
/// [`FromRequest`]: ../rocket/request/trait.FromRequest.html
/// [`Route`]: ../rocket/struct.Route.html
/// [`Responder`]: ../rocket/response/trait.Responder.html
@ -303,7 +248,7 @@ macro_rules! route_attribute {
///
/// If a data guard fails, the request is forwarded if the
/// [`Outcome`] is `Forward` or failed if the [`Outcome`] is
/// `Failure`. See [`FromTransformedData` Outcomes] for further detail.
/// `Failure`. See [`FromData` Outcomes] for further detail.
///
/// If all validation succeeds, the decorated function is called.
/// The returned value is used to generate a [`Response`] via the
@ -326,7 +271,7 @@ macro_rules! route_attribute {
/// [`Outcome`]: ../rocket/outcome/enum.Outcome.html
/// [`Response`]: ../rocket/struct.Response.html
/// [`FromRequest` Outcomes]: ../rocket/request/trait.FromRequest.html#outcomes
/// [`FromTransformedData` Outcomes]: ../rocket/data/trait.FromTransformedData.html#outcomes
/// [`FromData` Outcomes]: ../rocket/data/trait.FromData.html#outcomes
#[proc_macro_attribute]
pub fn $name(args: TokenStream, input: TokenStream) -> TokenStream {
emit!(attribute::route::route_attribute($method, args, input))
@ -412,33 +357,30 @@ pub fn catch(args: TokenStream, input: TokenStream) -> TokenStream {
emit!(attribute::catch::catch_attribute(args, input))
}
/// FIXME: Document.
#[proc_macro_attribute]
pub fn async_test(args: TokenStream, input: TokenStream) -> TokenStream {
emit!(attribute::async_entry::async_test_attribute(args, input))
}
/// FIXME: Document.
#[proc_macro_attribute]
pub fn main(args: TokenStream, input: TokenStream) -> TokenStream {
emit!(attribute::async_entry::main_attribute(args, input))
}
/// FIXME: Document.
#[proc_macro_attribute]
pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream {
emit!(attribute::async_entry::launch_attribute(args, input))
}
/// Derive for the [`FromFormValue`] trait.
/// Derive for the [`FromFormField`] trait.
///
/// The [`FromFormValue`] derive can be applied to enums with nullary
/// The [`FromFormField`] derive can be applied to enums with nullary
/// (zero-length) fields:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// #
/// #[derive(FromFormValue)]
/// #[derive(FromFormField)]
/// enum MyValue {
/// First,
/// Second,
@ -446,37 +388,36 @@ pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// The derive generates an implementation of the [`FromFormValue`] trait for
/// The derive generates an implementation of the [`FromFormField`] trait for
/// the decorated `enum`. The implementation returns successfully when the form
/// value matches, case insensitively, the stringified version of a variant's
/// name, returning an instance of said variant. If there is no match, an error
/// ([`FromFormValue::Error`]) of type [`&RawStr`] is returned, the value of
/// which is the raw form field value that failed to match.
/// recording all of the available options is returned.
///
/// As an example, for the `enum` above, the form values `"first"`, `"FIRST"`,
/// `"fiRSt"`, and so on would parse as `MyValue::First`, while `"second"` and
/// `"third"` would parse as `MyValue::Second` and `MyValue::Third`,
/// respectively.
///
/// The `form` field attribute can be used to change the string that is compared
/// against for a given variant:
/// The `field` field attribute can be used to change the string value that is
/// compared against for a given variant:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// #
/// #[derive(FromFormValue)]
/// #[derive(FromFormField)]
/// enum MyValue {
/// First,
/// Second,
/// #[form(value = "fourth")]
/// #[field(value = "fourth")]
/// Third,
/// }
/// ```
///
/// The `#[form]` attribute's grammar is:
/// The `#[field]` attribute's grammar is:
///
/// ```text
/// form := 'field' '=' STRING_LIT
/// field := 'value' '=' STRING_LIT
///
/// STRING_LIT := any valid string literal, as defined by Rust
/// ```
@ -486,13 +427,12 @@ pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream {
/// variant. In the example above, the the strings `"fourth"`, `"FOUrth"` and so
/// on would parse as `MyValue::Third`.
///
/// [`FromFormValue`]: ../rocket/request/trait.FromFormValue.html
/// [`FromFormValue::Error`]: ../rocket/request/trait.FromFormValue.html#associatedtype.Error
/// [`&RawStr`]: ../rocket/http/struct.RawStr.html
/// [`FromFormField`]: ../rocket/request/trait.FromFormField.html
/// [`FromFormField::Error`]: ../rocket/request/trait.FromFormField.html#associatedtype.Error
// FIXME(rustdoc): We should be able to refer to items in `rocket`.
#[proc_macro_derive(FromFormValue, attributes(form))]
pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
emit!(derive::from_form_value::derive_from_form_value(input))
#[proc_macro_derive(FromFormField, attributes(field))]
pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
emit!(derive::from_form_field::derive_from_form_field(input))
}
/// Derive for the [`FromForm`] trait.
@ -509,22 +449,26 @@ pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// Each field's type is required to implement [`FromFormValue`].
/// Each field's type is required to implement [`FromFormField`].
///
/// The derive generates an implementation of the [`FromForm`] trait. The
/// implementation parses a form whose field names match the field names of the
/// structure on which the derive was applied. Each field's value is parsed with
/// the [`FromFormValue`] implementation of the field's type. The `FromForm`
/// the [`FromFormField`] implementation of the field's type. The `FromForm`
/// implementation succeeds only when all of the field parses succeed. If
/// parsing fails, an error ([`FromForm::Error`]) of type [`FormParseError`] is
/// returned.
///
/// The derive accepts one field attribute: `form`, with the following syntax:
/// The derive accepts one field attribute: `field`, with the following syntax:
///
/// ```text
/// form := 'field' '=' '"' IDENT '"'
/// field := name? validate*
///
/// name := 'name' '=' '"' IDENT '"'
/// validate := 'validate' '=' EXPR
///
/// IDENT := valid identifier, as defined by Rust
/// EXPR := valid expression, as defined by Rust
/// ```
///
/// When applied, the attribute looks as follows:
@ -535,22 +479,22 @@ pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
/// #[derive(FromForm)]
/// struct MyStruct {
/// field: usize,
/// #[form(field = "renamed_field")]
/// #[field(name = "renamed_field")]
/// other: String
/// }
/// ```
///
/// The field attribute directs that a different incoming field name is
/// expected, and the value of the `field` attribute is used instead of the
/// structure's actual field name when parsing a form. In the example above, the
/// value of the `MyStruct::other` struct field will be parsed from the incoming
/// form's `renamed_field` field.
/// expected, the value of `name`, which is used instead of the structure's
/// actual field name when parsing a form. In the example above, the value of
/// the `MyStruct::other` struct field will be parsed from the incoming form's
/// `renamed_field` field.
///
/// [`FromForm`]: ../rocket/request/trait.FromForm.html
/// [`FromFormValue`]: ../rocket/request/trait.FromFormValue.html
/// [`FromFormField`]: ../rocket/request/trait.FromFormField.html
/// [`FormParseError`]: ../rocket/request/enum.FormParseError.html
/// [`FromForm::Error`]: ../rocket/request/trait.FromForm.html#associatedtype.Error
#[proc_macro_derive(FromForm, attributes(form))]
#[proc_macro_derive(FromForm, attributes(field))]
pub fn derive_from_form(input: TokenStream) -> TokenStream {
emit!(derive::from_form::derive_from_form(input))
}
@ -716,10 +660,10 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
/// `name` parameter, and [`Formatter::write_value()`] for every unnamed field
/// in the order the fields are declared.
///
/// The derive accepts one field attribute: `form`, with the following syntax:
/// The derive accepts one field attribute: `field`, with the following syntax:
///
/// ```text
/// form := 'field' '=' '"' IDENT '"'
/// field := 'name' '=' '"' IDENT '"'
///
/// IDENT := valid identifier, as defined by Rust
/// ```
@ -734,21 +678,21 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
/// struct MyStruct {
/// name: String,
/// id: usize,
/// #[form(field = "type")]
/// #[field(name = "type")]
/// kind: Kind,
/// }
/// ```
///
/// The field attribute directs that a different field name be used when calling
/// [`Formatter::write_named_value()`] for the given field. The value of the
/// `field` attribute is used instead of the structure's actual field name. In
/// `name` attribute is used instead of the structure's actual field name. In
/// the example above, the field `MyStruct::kind` is rendered with a name of
/// `type`.
///
/// [`UriDisplay<Query>`]: ../rocket/http/uri/trait.UriDisplay.html
/// [`Formatter::write_named_value()`]: ../rocket/http/uri/struct.Formatter.html#method.write_named_value
/// [`Formatter::write_value()`]: ../rocket/http/uri/struct.Formatter.html#method.write_value
#[proc_macro_derive(UriDisplayQuery, attributes(form))]
#[proc_macro_derive(UriDisplayQuery, attributes(field))]
pub fn derive_uri_display_query(input: TokenStream) -> TokenStream {
emit!(derive::uri_display::derive_uri_display_query(input))
}
@ -1030,6 +974,6 @@ pub fn rocket_internal_uri(input: TokenStream) -> TokenStream {
#[doc(hidden)]
#[proc_macro]
pub fn rocket_internal_guide_tests(input: TokenStream) -> TokenStream {
pub fn internal_guide_tests(input: TokenStream) -> TokenStream {
emit!(bang::guide_tests_internal(input))
}

View File

@ -1,11 +1,14 @@
//! Extensions to `syn` types.
use devise::ext::SpanDiagnosticExt;
use devise::syn::{self, Ident, ext::IdentExt as _};
use crate::syn::{self, Ident, ext::IdentExt as _};
use crate::proc_macro2::Span;
pub trait IdentExt {
fn prepend(&self, string: &str) -> syn::Ident;
fn append(&self, string: &str) -> syn::Ident;
fn with_span(self, span: Span) -> syn::Ident;
}
impl IdentExt for syn::Ident {
@ -16,6 +19,11 @@ impl IdentExt for syn::Ident {
fn append(&self, string: &str) -> syn::Ident {
syn::Ident::new(&format!("{}{}", self, string), self.span())
}
fn with_span(mut self, span: Span) -> syn::Ident {
self.set_span(span);
self
}
}
pub trait ReturnTypeExt {
@ -83,7 +91,7 @@ impl NameSource {
}
impl devise::FromMeta for NameSource {
fn from_meta(meta: devise::MetaItem<'_>) -> devise::Result<Self> {
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
if let syn::Lit::Str(s) = meta.lit()? {
return Ok(NameSource::new(s.value(), s.span()));
}
@ -104,9 +112,9 @@ impl std::hash::Hash for NameSource {
}
}
impl PartialEq for NameSource {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
impl AsRef<str> for NameSource {
fn as_ref(&self) -> &str {
self.name()
}
}

View File

@ -1,30 +1,19 @@
#[macro_use] extern crate rocket;
#[macro_use]extern crate rocket;
use rocket::request::{FromForm, FormItems, FormParseError};
use rocket::http::RawStr;
use rocket::form::{Form, Strict, FromForm, Errors};
fn parse<'f, T>(string: &'f str, strict: bool) -> Result<T, FormParseError<'f>>
where T: FromForm<'f, Error = FormParseError<'f>>
{
let mut items = FormItems::from(string);
let result = T::from_form(items.by_ref(), strict);
if !items.exhaust() {
panic!("Invalid form input.");
}
result
fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
Form::<Strict<T>>::parse(string).map(|s| s.into_inner())
}
fn strict<'f, T>(string: &'f str) -> Result<T, FormParseError<'f>>
where T: FromForm<'f, Error = FormParseError<'f>>
{
parse(string, true)
fn lenient<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
Form::<T>::parse(string)
}
fn lenient<'f, T>(string: &'f str) -> Result<T, FormParseError<'f>>
where T: FromForm<'f, Error = FormParseError<'f>>
fn strict_encoded<T: 'static>(string: &'static str) -> Result<T, Errors<'static>>
where for<'a> T: FromForm<'a>
{
parse(string, false)
Form::<Strict<T>>::parse_encoded(string.into()).map(|s| s.into_inner())
}
#[derive(Debug, PartialEq, FromForm)]
@ -46,6 +35,12 @@ fn simple() {
let task: Option<TodoTask> = strict("other=a&description=Hello&completed=on").ok();
assert!(task.is_none());
let task: Option<TodoTask> = lenient("other=a&description=Hello&completed=on").ok();
assert_eq!(task, Some(TodoTask {
description: "Hello".to_string(),
completed: true
}));
// Ensure _method isn't required.
let task: Option<TodoTask> = strict("_method=patch&description=Hello&completed=off").ok();
assert_eq!(task, Some(TodoTask {
@ -54,7 +49,7 @@ fn simple() {
}));
}
#[derive(Debug, PartialEq, FromFormValue)]
#[derive(Debug, PartialEq, FromFormField)]
enum FormOption {
A, B, C
}
@ -64,19 +59,19 @@ struct FormInput<'r> {
checkbox: bool,
number: usize,
radio: FormOption,
password: &'r RawStr,
password: &'r str,
textarea: String,
select: FormOption,
}
#[derive(Debug, PartialEq, FromForm)]
struct DefaultInput<'r> {
arg: Option<&'r RawStr>,
arg: Option<&'r str>,
}
#[derive(Debug, PartialEq, FromForm)]
struct ManualMethod<'r> {
_method: Option<&'r RawStr>,
_method: Option<&'r str>,
done: bool
}
@ -88,23 +83,23 @@ struct UnpresentCheckbox {
#[derive(Debug, PartialEq, FromForm)]
struct UnpresentCheckboxTwo<'r> {
checkbox: bool,
something: &'r RawStr
something: &'r str
}
#[derive(Debug, PartialEq, FromForm)]
struct FieldNamedV<'r> {
v: &'r RawStr,
v: &'r str,
}
#[test]
fn base_conditions() {
let form_string = &[
"password=testing", "checkbox=off", "checkbox=on", "number=10",
"checkbox=off", "textarea=", "select=a", "radio=c",
"password=testing", "checkbox=off", "number=10", "textarea=",
"select=a", "radio=c",
].join("&");
let input: Option<FormInput<'_>> = strict(&form_string).ok();
assert_eq!(input, Some(FormInput {
let input: Result<FormInput<'_>, _> = strict(&form_string);
assert_eq!(input, Ok(FormInput {
checkbox: false,
number: 10,
radio: FormOption::C,
@ -193,28 +188,26 @@ fn lenient_parsing() {
assert!(manual.is_none());
}
#[derive(Debug, PartialEq, FromForm)]
struct RenamedForm {
single: usize,
#[form(field = "camelCase")]
camel_case: String,
#[form(field = "TitleCase")]
title_case: String,
#[form(field = "type")]
field_type: isize,
#[form(field = "DOUBLE")]
double: String,
#[form(field = "a.b")]
dot: isize,
#[form(field = "some space")]
some_space: String,
}
#[test]
fn field_renaming() {
#[derive(Debug, PartialEq, FromForm)]
struct RenamedForm {
single: usize,
#[field(name = "camelCase")]
camel_case: String,
#[field(name = "TitleCase")]
title_case: String,
#[field(name = "type")]
field_type: isize,
#[field(name = "DOUBLE")]
double: String,
#[field(name = "a:b")]
colon: isize,
}
let form_string = &[
"single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2",
"DOUBLE=bing_bong", "a.b=123", "some space=okay"
"DOUBLE=bing_bong", "a:b=123"
].join("&");
let form: Option<RenamedForm> = strict(&form_string).ok();
@ -224,35 +217,34 @@ fn field_renaming() {
title_case: "HiHi".into(),
field_type: -2,
double: "bing_bong".into(),
dot: 123,
some_space: "okay".into(),
colon: 123,
}));
let form_string = &[
"single=100", "camel_case=helloThere", "TitleCase=HiHi", "type=-2",
"DOUBLE=bing_bong", "dot=123", "some_space=okay"
"DOUBLE=bing_bong", "colon=123"
].join("&");
let form: Option<RenamedForm> = strict(&form_string).ok();
assert!(form.is_none());
}
#[derive(FromForm, Debug, PartialEq)]
struct YetOneMore<'f, T> {
string: &'f RawStr,
other: T,
}
#[derive(FromForm, Debug, PartialEq)]
struct Oops<A, B, C> {
base: String,
a: A,
b: B,
c: C,
}
#[test]
fn generics() {
#[derive(FromForm, Debug, PartialEq)]
struct Oops<A, B, C> {
base: String,
a: A,
b: B,
c: C,
}
#[derive(FromForm, Debug, PartialEq)]
struct YetOneMore<'f, T> {
string: &'f str,
other: T,
}
let form_string = &[
"string=hello", "other=00128"
].join("&");
@ -272,59 +264,304 @@ fn generics() {
let form: Option<YetOneMore<'_, i8>> = strict(&form_string).ok();
assert!(form.is_none());
let form_string = &[
"base=just%20a%20test", "a=hey%20there", "b=a", "c=811",
].join("&");
let form: Option<Oops<&RawStr, FormOption, usize>> = strict(&form_string).ok();
let form_string = "base=just%20a%20test&a=hey%20there&b=a&c=811";
let form: Option<Oops<String, FormOption, usize>> = strict_encoded(&form_string).ok();
assert_eq!(form, Some(Oops {
base: "just a test".into(),
a: "hey%20there".into(),
a: "hey there".into(),
b: FormOption::A,
c: 811,
}));
}
#[derive(Debug, PartialEq, FromForm)]
struct WhoopsForm {
complete: bool,
other: usize,
}
#[test]
fn form_errors() {
use rocket::form::error::{ErrorKind, Entity};
#[derive(Debug, PartialEq, FromForm)]
struct WhoopsForm {
complete: bool,
other: usize,
}
let form: Result<WhoopsForm, _> = strict("complete=true&other=781");
assert_eq!(form, Ok(WhoopsForm { complete: true, other: 781 }));
let form: Result<WhoopsForm, _> = strict("complete=true&other=unknown");
assert_eq!(form, Err(FormParseError::BadValue("other".into(), "unknown".into())));
let errors = strict::<WhoopsForm>("complete=true&other=unknown").unwrap_err();
assert!(errors.iter().any(|e| {
"other" == e.name.as_ref().unwrap()
&& Some("unknown") == e.value.as_deref()
&& match e.kind {
ErrorKind::Int(..) => true,
_ => false
}
}));
let form: Result<WhoopsForm, _> = strict("complete=unknown&other=unknown");
assert_eq!(form, Err(FormParseError::BadValue("complete".into(), "unknown".into())));
let errors = strict::<WhoopsForm>("complete=unknown&other=unknown").unwrap_err();
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "complete"
&& Some("unknown") == e.value.as_deref()
&& match e.kind {
ErrorKind::Bool(..) => true,
_ => false
}
}));
let form: Result<WhoopsForm, _> = strict("complete=true&other=1&extra=foo");
assert_eq!(form, Err(FormParseError::Unknown("extra".into(), "foo".into())));
let errors = strict::<WhoopsForm>("complete=true&other=1&extra=foo").unwrap_err();
dbg!(&errors);
assert!(errors.iter().any(|e| {
"extra" == e.name.as_ref().unwrap()
&& Some("foo") == e.value.as_deref()
&& match e.kind {
ErrorKind::Unexpected => true,
_ => false
}
}));
// Bad values take highest precedence.
let form: Result<WhoopsForm, _> = strict("complete=unknown&unknown=foo");
assert_eq!(form, Err(FormParseError::BadValue("complete".into(), "unknown".into())));
let errors = strict::<WhoopsForm>("complete=unknown&unknown=!").unwrap_err();
assert!(errors.iter().any(|e| {
"complete" == e.name.as_ref().unwrap()
&& Some("unknown") == e.value.as_deref()
&& match e.kind {
ErrorKind::Bool(..) => true,
_ => false
}
}));
// Then unknown key/values for strict parses.
let form: Result<WhoopsForm, _> = strict("complete=true&unknown=foo");
assert_eq!(form, Err(FormParseError::Unknown("unknown".into(), "foo".into())));
assert!(errors.iter().any(|e| {
"unknown" == e.name.as_ref().unwrap()
&& Some("!") == e.value.as_deref()
&& match e.kind {
ErrorKind::Unexpected => true,
_ => false
}
}));
// Finally, missing.
let form: Result<WhoopsForm, _> = strict("complete=true");
assert_eq!(form, Err(FormParseError::Missing("other".into())));
}
#[derive(Debug, PartialEq, FromForm)]
struct RawIdentForm {
r#type: String,
let errors = strict::<WhoopsForm>("complete=true").unwrap_err();
assert!(errors.iter().any(|e| {
"other" == e.name.as_ref().unwrap()
&& e.value.is_none()
&& e.entity == Entity::Field
&& match e.kind {
ErrorKind::Missing => true,
_ => false
}
}));
}
#[test]
fn raw_ident_form() {
#[derive(Debug, PartialEq, FromForm)]
struct RawIdentForm {
r#type: String,
}
let form: Result<RawIdentForm, _> = strict("type=a");
assert_eq!(form, Ok(RawIdentForm { r#type: "a".into() }));
}
#[test]
fn test_multi() {
use std::collections::HashMap;
#[derive(Debug, PartialEq, FromForm)]
struct Multi<'r> {
checks: Vec<bool>,
names: Vec<&'r str>,
news: Vec<String>,
dogs: HashMap<String, Dog>,
#[field(name = "more:dogs")]
more_dogs: HashMap<&'r str, Dog>,
}
let multi: Multi = strict("checks=true&checks=false&checks=false\
&names=Sam&names[]=Smith&names[]=Bob\
&news[]=Here&news[]=also here\
&dogs[fido].barks=true&dogs[George].barks=false\
&dogs[fido].trained=on&dogs[George].trained=yes\
&dogs[bob boo].trained=no&dogs[bob boo].barks=off\
&more:dogs[k:0]=My Dog&more:dogs[v:0].barks=true&more:dogs[v:0].trained=yes\
").unwrap();
assert_eq!(multi, Multi {
checks: vec![true, false, false],
names: vec!["Sam".into(), "Smith".into(), "Bob".into()],
news: vec!["Here".into(), "also here".into()],
dogs: {
let mut map = HashMap::new();
map.insert("fido".into(), Dog { barks: true, trained: true });
map.insert("George".into(), Dog { barks: false, trained: true });
map.insert("bob boo".into(), Dog { barks: false, trained: false });
map
},
more_dogs: {
let mut map = HashMap::new();
map.insert("My Dog".into(), Dog { barks: true, trained: true });
map
}
});
#[derive(Debug, PartialEq, FromForm)]
struct MultiOwned {
names: Vec<String>,
}
let raw = "names=Sam&names%5B%5D=Smith&names%5B%5D=Bob%20Smith%3F";
let multi: MultiOwned = strict_encoded(raw).unwrap();
assert_eq!(multi, MultiOwned {
names: vec!["Sam".into(), "Smith".into(), "Bob Smith?".into()],
});
}
#[derive(Debug, FromForm, PartialEq)]
struct Dog {
barks: bool,
trained: bool,
}
#[derive(Debug, FromForm, PartialEq)]
struct Cat<'r> {
nip: &'r str,
meows: bool
}
#[derive(Debug, FromForm, PartialEq)]
struct Pet<'r, T> {
pet: T,
name: &'r str,
age: u8
}
#[derive(Debug, PartialEq, FromForm)]
struct Person<'r> {
dogs: Vec<Pet<'r, Dog>>,
cats: Vec<Pet<'r, Cat<'r>>>,
sitting: Dog,
}
#[test]
fn test_nested_multi() {
let person: Person = strict("sitting.barks=true&sitting.trained=true").unwrap();
assert_eq!(person, Person {
sitting: Dog { barks: true, trained: true },
cats: vec![],
dogs: vec![],
});
let person: Person = strict("sitting.barks=true&sitting.trained=true\
&dogs[0].name=fido&dogs[0].pet.trained=yes&dogs[0].age=7&dogs[0].pet.barks=no\
").unwrap();
assert_eq!(person, Person {
sitting: Dog { barks: true, trained: true },
cats: vec![],
dogs: vec![Pet {
pet: Dog { barks: false, trained: true },
name: "fido".into(),
age: 7
}]
});
let person: Person = strict("sitting.trained=no&sitting.barks=true\
&dogs[0].name=fido&dogs[0].pet.trained=yes&dogs[0].age=7&dogs[0].pet.barks=no\
&dogs[1].pet.barks=true&dogs[1].name=Bob&dogs[1].pet.trained=no&dogs[1].age=1\
").unwrap();
assert_eq!(person, Person {
sitting: Dog { barks: true, trained: false },
cats: vec![],
dogs: vec![
Pet {
pet: Dog { barks: false, trained: true },
name: "fido".into(),
age: 7
},
Pet {
pet: Dog { barks: true, trained: false },
name: "Bob".into(),
age: 1
},
]
});
let person: Person = strict("sitting.barks=true&sitting.trained=no\
&dogs[0].name=fido&dogs[0].pet.trained=yes&dogs[0].age=7&dogs[0].pet.barks=no\
&dogs[1].pet.barks=true&dogs[1].name=Bob&dogs[1].pet.trained=no&dogs[1].age=1\
&cats[george].pet.nip=paws&cats[george].name=George&cats[george].age=2\
&cats[george].pet.meows=yes\
").unwrap();
assert_eq!(person, Person {
sitting: Dog { barks: true, trained: false },
cats: vec![
Pet {
pet: Cat { nip: "paws".into(), meows: true },
name: "George".into(),
age: 2
}
],
dogs: vec![
Pet {
pet: Dog { barks: false, trained: true },
name: "fido".into(),
age: 7
},
Pet {
pet: Dog { barks: true, trained: false },
name: "Bob".into(),
age: 1
},
]
});
}
// fn test_multipart() {
// use std::{io, path::Path};
//
// use crate::*;
// use crate::http::ContentType;
// use crate::local::blocking::Client;
// use crate::{data::TempFile, form::Errors};
//
// #[derive(FromForm)]
// struct MyForm {
// names: Vec<String>,
// file: String,
// }
//
// #[post("/", data = "<form>")]
// async fn form(mut form: Form<MyForm>) -> io::Result<&'static str> {
// let path = Path::new("/tmp").join(form.file_name().unwrap_or("upload"));
// form.persist(path).await?;
// println!("result: {:?}", form);
// Ok("hi")
// }
//
// let client = Client::untracked(crate::ignite().mount("/", routes![form])).unwrap();
// let ct = "multipart/form-data; boundary=X-BOUNDARY"
// .parse::<ContentType>()
// .unwrap();
//
// let body = &[
// // "--X-BOUNDARY",
// // r#"Content-Disposition: form-data; name="names[]""#,
// // "",
// // "abcd",
// // "--X-BOUNDARY",
// // r#"Content-Disposition: form-data; name="names[]""#,
// // "",
// // "123",
// "--X-BOUNDARY",
// r#"Content-Disposition: form-data; name="file"; filename="foo.txt""#,
// "Content-Type: text/plain",
// "",
// "hi there",
// "--X-BOUNDARY--",
// "",
// ].join("\r\n");
//
// let response = client.post("/")
// .header(ct)
// .body(body)
// .dispatch();
//
// let string = response.into_string().unwrap();
// println!("String: {}", string);
// panic!(string);
// }

View File

@ -1,12 +1,18 @@
use rocket::request::FromFormValue;
use rocket::form::{FromFormField, ValueField, FromForm, Options, Errors};
fn parse<'v, T: FromForm<'v>>(value: &'v str) -> Result<T, Errors<'v>> {
let mut context = T::init(Options::Lenient);
T::push_value(&mut context, ValueField::from_value(value));
T::finalize(context)
}
macro_rules! assert_parse {
($($string:expr),* => $item:ident :: $variant:ident) => ($(
match $item::from_form_value($string.into()) {
match parse::<$item>($string) {
Ok($item::$variant) => { /* okay */ },
Ok(item) => panic!("Failed to parse {} as {:?}. Got {:?} instead.",
$string, $item::$variant, item),
Err(e) => panic!("Failed to parse {} as {}: {:?}",
Err(e) => panic!("Failed to parse {} as {}: {}",
$string, stringify!($item), e),
}
@ -15,7 +21,7 @@ macro_rules! assert_parse {
macro_rules! assert_no_parse {
($($string:expr),* => $item:ident) => ($(
match $item::from_form_value($string.into()) {
match parse::<$item>($string) {
Err(_) => { /* okay */ },
Ok(item) => panic!("Unexpectedly parsed {} as {:?}", $string, item)
}
@ -24,7 +30,7 @@ macro_rules! assert_no_parse {
#[test]
fn from_form_value_simple() {
#[derive(Debug, FromFormValue)]
#[derive(Debug, FromFormField)]
enum Foo { A, B, C, }
assert_parse!("a", "A" => Foo::A);
@ -35,7 +41,7 @@ fn from_form_value_simple() {
#[test]
fn from_form_value_weirder() {
#[allow(non_camel_case_types)]
#[derive(Debug, FromFormValue)]
#[derive(Debug, FromFormField)]
enum Foo { Ab_Cd, OtherA }
assert_parse!("ab_cd", "ab_CD", "Ab_CD" => Foo::Ab_Cd);
@ -44,7 +50,7 @@ fn from_form_value_weirder() {
#[test]
fn from_form_value_no_parse() {
#[derive(Debug, FromFormValue)]
#[derive(Debug, FromFormField)]
enum Foo { A, B, C, }
assert_no_parse!("abc", "ab", "bc", "ca" => Foo);
@ -53,11 +59,11 @@ fn from_form_value_no_parse() {
#[test]
fn from_form_value_renames() {
#[derive(Debug, FromFormValue)]
#[derive(Debug, FromFormField)]
enum Foo {
#[form(value = "foo")]
#[field(value = "foo")]
Bar,
#[form(value = ":book")]
#[field(value = ":book")]
Book
}
@ -69,7 +75,7 @@ fn from_form_value_renames() {
#[test]
fn from_form_value_raw() {
#[allow(non_camel_case_types)]
#[derive(Debug, FromFormValue)]
#[derive(Debug, FromFormField)]
enum Keyword {
r#type,
this,
@ -79,3 +85,21 @@ fn from_form_value_raw() {
assert_parse!("this" => Keyword::this);
assert_no_parse!("r#type" => Keyword);
}
#[test]
fn form_value_errors() {
use rocket::form::error::{ErrorKind, Entity};
#[derive(Debug, FromFormField)]
enum Foo { Bar, Bob }
let errors = parse::<Foo>("blob").unwrap_err();
assert!(errors.iter().any(|e| {
&& "blob" == &e.value.as_ref().unwrap()
&& e.entity == Entity::Value
&& match &e.kind {
ErrorKind::InvalidChoice { choices } => &choices[..] == &["Bar", "Bob"],
_ => false
}
}));
}

View File

@ -1,37 +1,34 @@
#[macro_use] extern crate rocket;
#[macro_use]extern crate rocket;
use rocket::{Request, Data};
use rocket::local::blocking::Client;
use rocket::request::Form;
use rocket::data::{self, FromData, ToByteUnit};
use rocket::http::{RawStr, ContentType, Status};
use rocket::data::{self, FromData};
use rocket::http::ContentType;
use rocket::form::Form;
// Test that the data parameters works as expected.
#[derive(FromForm)]
struct Inner<'r> {
field: &'r RawStr
field: &'r str
}
struct Simple(String);
struct Simple<'r>(&'r str);
#[async_trait]
impl FromData for Simple {
type Error = ();
impl<'r> FromData<'r> for Simple<'r> {
type Error = std::io::Error;
async fn from_data(_: &Request<'_>, data: Data) -> data::Outcome<Self, ()> {
match data.open(64.bytes()).stream_to_string().await {
Ok(string) => data::Outcome::Success(Simple(string)),
Err(_) => data::Outcome::Failure((Status::InternalServerError, ())),
}
async fn from_data(req: &'r Request<'_>, data: Data) -> data::Outcome<Self, Self::Error> {
<&'r str>::from_data(req, data).await.map(Simple)
}
}
#[post("/f", data = "<form>")]
fn form(form: Form<Inner<'_>>) -> String { form.field.url_decode_lossy() }
fn form<'r>(form: Form<Inner<'r>>) -> &'r str { form.into_inner().field }
#[post("/s", data = "<simple>")]
fn simple(simple: Simple) -> String { simple.0 }
fn simple<'r>(simple: Simple<'r>) -> &'r str { simple.0 }
#[test]
fn test_data() {

View File

@ -66,15 +66,19 @@ fn test_formats() {
// Test custom formats.
// TODO: #[rocket(allow(unknown_format))]
#[get("/", format = "application/foo")]
fn get_foo() -> &'static str { "get_foo" }
// TODO: #[rocket(allow(unknown_format))]
#[post("/", format = "application/foo")]
fn post_foo() -> &'static str { "post_foo" }
// TODO: #[rocket(allow(unknown_format))]
#[get("/", format = "bar/baz", rank = 2)]
fn get_bar_baz() -> &'static str { "get_bar_baz" }
// TODO: #[rocket(allow(unknown_format))]
#[put("/", format = "bar/baz")]
fn put_bar_baz() -> &'static str { "put_bar_baz" }

View File

@ -7,61 +7,71 @@
use std::path::PathBuf;
use rocket::request::Request;
use rocket::http::ext::Normalize;
use rocket::local::blocking::Client;
use rocket::data::{self, Data, FromData, ToByteUnit};
use rocket::request::{Request, Form};
use rocket::data::{self, Data, FromData};
use rocket::http::{Status, RawStr, ContentType};
// Use all of the code generation available at once.
#[derive(FromForm, UriDisplayQuery)]
struct Inner<'r> {
field: &'r RawStr
field: &'r str
}
struct Simple(String);
#[async_trait]
impl FromData for Simple {
type Error = ();
impl<'r> FromData<'r> for Simple {
type Error = std::io::Error;
async fn from_data(_: &Request<'_>, data: Data) -> data::Outcome<Self, ()> {
let string = data.open(64.bytes()).stream_to_string().await.unwrap();
data::Outcome::Success(Simple(string))
async fn from_data(req: &'r Request<'_>, data: Data) -> data::Outcome<Self, Self::Error> {
String::from_data(req, data).await.map(Simple)
}
}
#[post("/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>", format = "json", data = "<simple>", rank = 138)]
#[post(
"/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>",
format = "json",
data = "<simple>",
rank = 138
)]
fn post1(
sky: usize,
name: &RawStr,
name: &str,
a: String,
query: Form<Inner<'_>>,
query: Inner<'_>,
path: PathBuf,
simple: Simple,
) -> String {
let string = format!("{}, {}, {}, {}, {}, {}",
sky, name, a, query.field, path.normalized_str(), simple.0);
let uri = uri!(post2: a, name.url_decode_lossy(), path, sky, query.into_inner());
let uri = uri!(post1: a, name, path, sky, query);
format!("({}) ({})", string, uri.to_string())
}
#[route(POST, path = "/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>", format = "json", data = "<simple>", rank = 138)]
#[route(
POST,
path = "/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>",
format = "json",
data = "<simple>",
rank = 138
)]
fn post2(
sky: usize,
name: &RawStr,
name: &str,
a: String,
query: Form<Inner<'_>>,
query: Inner<'_>,
path: PathBuf,
simple: Simple,
) -> String {
let string = format!("{}, {}, {}, {}, {}, {}",
sky, name, a, query.field, path.normalized_str(), simple.0);
let uri = uri!(post2: a, name.url_decode_lossy(), path, sky, query.into_inner());
let uri = uri!(post2: a, name, path, sky, query);
format!("({}) ({})", string, uri.to_string())
}
@ -79,8 +89,8 @@ fn test_full_route() {
let client = Client::tracked(rocket).unwrap();
let a = "A%20A";
let name = "Bob%20McDonald";
let a = RawStr::new("A%20A");
let name = RawStr::new("Bob%20McDonald");
let path = "this/path/here";
let sky = 777;
let query = "field=inside";
@ -104,7 +114,7 @@ fn test_full_route() {
.dispatch();
assert_eq!(response.into_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
sky, name, "A A", "inside", path, simple, expected_uri));
sky, name.percent_decode().unwrap(), "A A", "inside", path, simple, expected_uri));
let response = client.post(format!("/2{}", uri)).body(simple).dispatch();
assert_eq!(response.status(), Status::NotFound);
@ -116,7 +126,7 @@ fn test_full_route() {
.dispatch();
assert_eq!(response.into_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
sky, name, "A A", "inside", path, simple, expected_uri));
sky, name.percent_decode().unwrap(), "A A", "inside", path, simple, expected_uri));
}
mod scopes {
@ -138,3 +148,151 @@ mod scopes {
rocket::ignite().mount("/", rocket::routes![hello, world])
}
}
use rocket::form::Contextual;
#[derive(Default, Debug, PartialEq, FromForm)]
struct Filtered<'r> {
bird: Option<&'r str>,
color: Option<&'r str>,
cat: Option<&'r str>,
rest: Option<&'r str>,
}
#[get("/?bird=1&color=blue&<bird>&<color>&cat=bob&<rest..>")]
fn filtered_raw_query(bird: usize, color: &str, rest: Contextual<'_, Filtered<'_>>) -> String {
assert_ne!(bird, 1);
assert_ne!(color, "blue");
assert_eq!(rest.value.unwrap(), Filtered::default());
format!("{} - {}", bird, color)
}
#[test]
fn test_filtered_raw_query() {
let rocket = rocket::ignite().mount("/", routes![filtered_raw_query]);
let client = Client::untracked(rocket).unwrap();
#[track_caller]
fn run(client: &Client, birds: &[&str], colors: &[&str], cats: &[&str]) -> (Status, String) {
let join = |slice: &[&str], name: &str| slice.iter()
.map(|v| format!("{}={}", name, v))
.collect::<Vec<_>>()
.join("&");
let q = format!("{}&{}&{}",
join(birds, "bird"),
join(colors, "color"),
join(cats, "cat"));
let response = client.get(format!("/?{}", q)).dispatch();
let status = response.status();
let body = response.into_string().unwrap();
(status, body)
}
let birds = &["2", "3"];
let colors = &["red", "blue", "green"];
let cats = &["bob", "bob"];
assert_eq!(run(&client, birds, colors, cats).0, Status::NotFound);
let birds = &["2", "1", "3"];
let colors = &["red", "green"];
let cats = &["bob", "bob"];
assert_eq!(run(&client, birds, colors, cats).0, Status::NotFound);
let birds = &["2", "1", "3"];
let colors = &["red", "blue", "green"];
let cats = &[];
assert_eq!(run(&client, birds, colors, cats).0, Status::NotFound);
let birds = &["2", "1", "3"];
let colors = &["red", "blue", "green"];
let cats = &["bob", "bob"];
assert_eq!(run(&client, birds, colors, cats).1, "2 - red");
let birds = &["1", "2", "1", "3"];
let colors = &["blue", "red", "blue", "green"];
let cats = &["bob"];
assert_eq!(run(&client, birds, colors, cats).1, "2 - red");
let birds = &["5", "1"];
let colors = &["blue", "orange", "red", "blue", "green"];
let cats = &["bob"];
assert_eq!(run(&client, birds, colors, cats).1, "5 - orange");
}
#[derive(Debug, PartialEq, FromForm)]
struct Dog<'r> {
name: &'r str,
age: usize
}
#[derive(Debug, PartialEq, FromForm)]
struct Q<'r> {
dog: Dog<'r>
}
#[get("/?<color>&color=red&<q..>")]
fn query_collection(color: Vec<&str>, q: Q<'_>) -> String {
format!("{} - {} - {}", color.join("&"), q.dog.name, q.dog.age)
}
#[get("/?<color>&color=red&<dog>")]
fn query_collection_2(color: Vec<&str>, dog: Dog<'_>) -> String {
format!("{} - {} - {}", color.join("&"), dog.name, dog.age)
}
#[test]
fn test_query_collection() {
#[track_caller]
fn run(client: &Client, colors: &[&str], dog: &[&str]) -> (Status, String) {
let join = |slice: &[&str], prefix: &str| slice.iter()
.map(|v| format!("{}{}", prefix, v))
.collect::<Vec<_>>()
.join("&");
let q = format!("{}&{}", join(colors, "color="), join(dog, "dog."));
let response = client.get(format!("/?{}", q)).dispatch();
(response.status(), response.into_string().unwrap())
}
fn run_tests(rocket: rocket::Rocket) {
let client = Client::untracked(rocket).unwrap();
let colors = &["blue", "green"];
let dog = &["name=Fido", "age=10"];
assert_eq!(run(&client, colors, dog).0, Status::NotFound);
let colors = &["red"];
let dog = &["name=Fido"];
assert_eq!(run(&client, colors, dog).0, Status::NotFound);
let colors = &["red"];
let dog = &["name=Fido", "age=2"];
assert_eq!(run(&client, colors, dog).1, " - Fido - 2");
let colors = &["red", "blue", "green"];
let dog = &["name=Fido", "age=10"];
assert_eq!(run(&client, colors, dog).1, "blue&green - Fido - 10");
let colors = &["red", "blue", "green"];
let dog = &["name=Fido", "age=10", "toy=yes"];
assert_eq!(run(&client, colors, dog).1, "blue&green - Fido - 10");
let colors = &["blue", "red", "blue"];
let dog = &["name=Fido", "age=10"];
assert_eq!(run(&client, colors, dog).1, "blue&blue - Fido - 10");
let colors = &["blue", "green", "red", "blue"];
let dog = &["name=Max+Fido", "age=10"];
assert_eq!(run(&client, colors, dog).1, "blue&green&blue - Max Fido - 10");
}
let rocket = rocket::ignite().mount("/", routes![query_collection]);
run_tests(rocket);
let rocket = rocket::ignite().mount("/", routes![query_collection_2]);
run_tests(rocket);
}

View File

@ -4,13 +4,13 @@
use std::path::PathBuf;
use rocket::http::{RawStr, CookieJar};
use rocket::http::CookieJar;
use rocket::http::uri::{FromUriParam, Query};
use rocket::request::Form;
use rocket::form::{Form, error::{Errors, ErrorKind}};
#[derive(FromForm, UriDisplayQuery)]
struct User<'a> {
name: &'a RawStr,
name: &'a str,
nickname: String,
}
@ -65,10 +65,10 @@ fn no_uri_display_okay(id: i32, form: Form<Second>) { }
#[post("/name/<name>?<foo>&bar=10&<bar>&<query..>", data = "<user>", rank = 2)]
fn complex<'r>(
foo: usize,
name: &RawStr,
query: Form<User<'r>>,
name: &str,
query: User<'r>,
user: Form<User<'r>>,
bar: &RawStr,
bar: &str,
cookies: &CookieJar<'_>
) { }
@ -354,15 +354,15 @@ mod typed_uris {
#[derive(FromForm, UriDisplayQuery)]
struct Third<'r> {
one: String,
two: &'r RawStr,
two: &'r str,
}
#[post("/<foo>/<bar>?<q1>&<rest..>")]
fn optionals(
foo: Option<usize>,
bar: Result<String, &RawStr>,
q1: Result<usize, &RawStr>,
rest: Option<Form<Third<'_>>>
bar: Result<String, &'_ str>,
q1: Result<usize, Errors<'_>>,
rest: Option<Third<'_>>
) { }
#[test]
@ -408,7 +408,7 @@ fn test_optional_uri_parameters() {
uri!(optionals:
foo = 10,
bar = &"hi there",
q1 = Err("foo".into()) as Result<usize, &RawStr>,
q1 = Err(ErrorKind::Missing.into()) as Result<usize, _>,
rest = _
) => "/10/hi%20there",

View File

@ -1,327 +1,338 @@
error: enums are not supported
--> $DIR/from_form.rs:6:1
--> $DIR/from_form.rs:4:1
|
6 | enum Thing { }
4 | enum Thing { }
| ^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:5:10
--> $DIR/from_form.rs:3:10
|
5 | #[derive(FromForm)]
3 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs are not supported
--> $DIR/from_form.rs:9:1
--> $DIR/from_form.rs:7:1
|
9 | struct Foo1;
7 | struct Foo1;
| ^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:8:10
--> $DIR/from_form.rs:6:10
|
8 | #[derive(FromForm)]
6 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: at least one field is required
--> $DIR/from_form.rs:12:13
--> $DIR/from_form.rs:10:13
|
12 | struct Foo2 { }
10 | struct Foo2 { }
| ^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:11:10
--> $DIR/from_form.rs:9:10
|
11 | #[derive(FromForm)]
9 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs are not supported
--> $DIR/from_form.rs:15:1
--> $DIR/from_form.rs:13:1
|
15 | struct Foo3(usize);
13 | struct Foo3(usize);
| ^^^^^^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:14:10
--> $DIR/from_form.rs:12:10
|
14 | #[derive(FromForm)]
12 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: only one lifetime is supported
--> $DIR/from_form.rs:18:25
--> $DIR/from_form.rs:16:25
|
18 | struct NextTodoTask<'f, 'a> {
16 | struct NextTodoTask<'f, 'a> {
| ^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:17:10
--> $DIR/from_form.rs:15:10
|
17 | #[derive(FromForm)]
15 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:27:20
--> $DIR/from_form.rs:25:20
|
27 | #[form(field = "isindex")]
25 | #[field(name = "isindex")]
| ^^^^^^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:25:10
--> $DIR/from_form.rs:23:10
|
25 | #[derive(FromForm)]
23 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate field name
--> $DIR/from_form.rs:35:5
error: duplicate form field
--> $DIR/from_form.rs:33:5
|
35 | foo: usize,
| ^^^
33 | foo: usize,
| ^^^^^^^^^^
|
note: previous definition here
--> $DIR/from_form.rs:33:20
note: previously defined here
--> $DIR/from_form.rs:31:5
|
33 | #[form(field = "foo")]
| ^^^^^
31 | / #[field(name = "foo")]
32 | | field: String,
| |_________________^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:31:10
--> $DIR/from_form.rs:29:10
|
31 | #[derive(FromForm)]
29 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate field name
--> $DIR/from_form.rs:42:20
error: duplicate form field
--> $DIR/from_form.rs:40:5
|
42 | #[form(field = "hello")]
| ^^^^^^^
40 | / #[field(name = "hello")]
41 | | other: String,
| |_________________^
|
note: previous definition here
--> $DIR/from_form.rs:40:20
note: previously defined here
--> $DIR/from_form.rs:38:5
|
40 | #[form(field = "hello")]
| ^^^^^^^
38 | / #[field(name = "hello")]
39 | | first: String,
| |_________________^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:38:10
--> $DIR/from_form.rs:36:10
|
38 | #[derive(FromForm)]
36 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate field name
--> $DIR/from_form.rs:49:20
error: duplicate form field
--> $DIR/from_form.rs:47:5
|
49 | #[form(field = "first")]
| ^^^^^^^
47 | / #[field(name = "first")]
48 | | other: String,
| |_________________^
|
note: previous definition here
--> $DIR/from_form.rs:48:5
note: previously defined here
--> $DIR/from_form.rs:46:5
|
48 | first: String,
| ^^^^^
46 | first: String,
| ^^^^^^^^^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:46:10
--> $DIR/from_form.rs:44:10
|
46 | #[derive(FromForm)]
44 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate attribute parameter: field
--> $DIR/from_form.rs:55:28
error: unexpected attribute parameter: `field`
--> $DIR/from_form.rs:53:28
|
55 | #[form(field = "blah", field = "bloo")]
53 | #[field(name = "blah", field = "bloo")]
| ^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:53:10
--> $DIR/from_form.rs:51:10
|
53 | #[derive(FromForm)]
51 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: malformed attribute: expected list
--> $DIR/from_form.rs:61:7
error: expected list `#[field(..)]`, found bare path "field"
--> $DIR/from_form.rs:59:7
|
61 | #[form]
| ^^^^
59 | #[field]
| ^^^^^
|
= help: expected syntax: #[form(key = value, ..)]
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:59:10
--> $DIR/from_form.rs:57:10
|
59 | #[derive(FromForm)]
57 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected key/value pair
--> $DIR/from_form.rs:67:12
error: expected key/value `key = value`
--> $DIR/from_form.rs:65:13
|
67 | #[form("blah")]
| ^^^^^^
65 | #[field("blah")]
| ^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:65:10
--> $DIR/from_form.rs:63:10
|
65 | #[derive(FromForm)]
63 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected key/value pair
--> $DIR/from_form.rs:73:12
error: expected key/value `key = value`
--> $DIR/from_form.rs:71:13
|
73 | #[form(123)]
| ^^^
71 | #[field(123)]
| ^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:71:10
--> $DIR/from_form.rs:69:10
|
71 | #[derive(FromForm)]
69 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected attribute parameter: `beep`
--> $DIR/from_form.rs:79:12
--> $DIR/from_form.rs:77:13
|
79 | #[form(beep = "bop")]
| ^^^^^^^^^^^^
77 | #[field(beep = "bop")]
| ^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:77:10
--> $DIR/from_form.rs:75:10
|
77 | #[derive(FromForm)]
75 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate invocation of `form` attribute
--> $DIR/from_form.rs:86:7
error: duplicate form field renaming
--> $DIR/from_form.rs:84:20
|
86 | #[form(field = "bleh")]
| ^^^^
84 | #[field(name = "blah")]
| ^^^^^^
|
= help: a field can only be renamed once
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:83:10
--> $DIR/from_form.rs:81:10
|
83 | #[derive(FromForm)]
81 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
--> $DIR/from_form.rs:92:20
--> $DIR/from_form.rs:90:20
|
92 | #[form(field = true)]
90 | #[field(name = true)]
| ^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:90:10
--> $DIR/from_form.rs:88:10
|
90 | #[derive(FromForm)]
88 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected literal or key/value pair
--> $DIR/from_form.rs:98:12
error: expected literal, found bare path "name"
--> $DIR/from_form.rs:96:13
|
98 | #[form(field)]
| ^^^^^
96 | #[field(name)]
| ^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:96:10
--> $DIR/from_form.rs:94:10
|
96 | #[derive(FromForm)]
94 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
--> $DIR/from_form.rs:104:20
--> $DIR/from_form.rs:102:20
|
104 | #[form(field = 123)]
102 | #[field(name = 123)]
| ^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:102:10
--> $DIR/from_form.rs:100:10
|
102 | #[derive(FromForm)]
100 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:110:20
--> $DIR/from_form.rs:108:20
|
110 | #[form(field = "hello&world")]
108 | #[field(name = "hello&world")]
| ^^^^^^^^^^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:108:10
--> $DIR/from_form.rs:106:10
|
108 | #[derive(FromForm)]
106 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:116:20
--> $DIR/from_form.rs:114:20
|
116 | #[form(field = "!@#$%^&*()_")]
114 | #[field(name = "!@#$%^&*()_")]
| ^^^^^^^^^^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:114:10
--> $DIR/from_form.rs:112:10
|
114 | #[derive(FromForm)]
112 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:122:20
--> $DIR/from_form.rs:120:20
|
122 | #[form(field = "?")]
120 | #[field(name = "?")]
| ^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:120:10
--> $DIR/from_form.rs:118:10
|
120 | #[derive(FromForm)]
118 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:128:20
--> $DIR/from_form.rs:126:20
|
128 | #[form(field = "")]
126 | #[field(name = "")]
| ^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:126:10
--> $DIR/from_form.rs:124:10
|
126 | #[derive(FromForm)]
124 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:134:20
--> $DIR/from_form.rs:132:20
|
134 | #[form(field = "a&b")]
132 | #[field(name = "a&b")]
| ^^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:132:10
--> $DIR/from_form.rs:130:10
|
132 | #[derive(FromForm)]
130 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:140:20
--> $DIR/from_form.rs:138:20
|
140 | #[form(field = "a=")]
138 | #[field(name = "a=")]
| ^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:138:10
--> $DIR/from_form.rs:136:10
|
138 | #[derive(FromForm)]
136 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -0,0 +1 @@
../ui-fail/from_form_field.rs

View File

@ -1,105 +1,105 @@
error: tuple structs are not supported
--> $DIR/from_form_value.rs:4:1
--> $DIR/from_form_field.rs:4:1
|
4 | struct Foo1;
| ^^^^^^^^^^^^
|
note: error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:3:10
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:3:10
|
3 | #[derive(FromFormValue)]
3 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs are not supported
--> $DIR/from_form_value.rs:7:1
--> $DIR/from_form_field.rs:7:1
|
7 | struct Foo2(usize);
| ^^^^^^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:6:10
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:6:10
|
6 | #[derive(FromFormValue)]
6 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: named structs are not supported
--> $DIR/from_form_value.rs:10:1
--> $DIR/from_form_field.rs:10:1
|
10 | / struct Foo3 {
11 | | foo: usize,
12 | | }
| |_^
|
note: error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:9:10
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:9:10
|
9 | #[derive(FromFormValue)]
9 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: variants cannot have fields
--> $DIR/from_form_value.rs:16:7
--> $DIR/from_form_field.rs:16:6
|
16 | A(usize),
| ^^^^^
| ^^^^^^^
|
note: error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:14:10
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:14:10
|
14 | #[derive(FromFormValue)]
14 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: enum must have at least one field
--> $DIR/from_form_value.rs:20:11
error: enum must have at least one variant
--> $DIR/from_form_field.rs:20:1
|
20 | enum Foo5 { }
| ^^^
| ^^^^^^^^^^^^^
|
note: error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:19:10
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:19:10
|
19 | #[derive(FromFormValue)]
19 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: type generics are not supported
--> $DIR/from_form_value.rs:23:11
--> $DIR/from_form_field.rs:23:11
|
23 | enum Foo6<T> {
| ^
|
note: error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:22:10
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:22:10
|
22 | #[derive(FromFormValue)]
22 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
--> $DIR/from_form_value.rs:29:20
--> $DIR/from_form_field.rs:29:21
|
29 | #[form(value = 123)]
| ^^^
29 | #[field(value = 123)]
| ^^^
|
note: error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:27:10
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:27:10
|
27 | #[derive(FromFormValue)]
27 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected literal or key/value pair
--> $DIR/from_form_value.rs:35:12
error: expected literal, found bare path "value"
--> $DIR/from_form_field.rs:35:13
|
35 | #[form(value)]
| ^^^^^
35 | #[field(value)]
| ^^^^^
|
note: error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:33:10
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:33:10
|
33 | #[derive(FromFormValue)]
33 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -1,15 +1,15 @@
error[E0277]: the trait bound `Unknown: FromFormValue<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:7:5
error[E0277]: the trait bound `Unknown: FromFormField<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:7:12
|
7 | field: Unknown,
| ^^^^^^^^^^^^^^ the trait `FromFormValue<'_>` is not implemented for `Unknown`
| ^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Unknown`
|
= note: required by `from_form_value`
= note: required because of the requirements on the impl of `FromForm<'__f>` for `Unknown`
error[E0277]: the trait bound `Foo<usize>: FromFormValue<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:14:5
error[E0277]: the trait bound `Foo<usize>: FromFormField<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:14:12
|
14 | field: Foo<usize>,
| ^^^^^^^^^^^^^^^^^ the trait `FromFormValue<'_>` is not implemented for `Foo<usize>`
| ^^^^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Foo<usize>`
|
= note: required by `from_form_value`
= note: required because of the requirements on the impl of `FromForm<'__f>` for `Foo<usize>`

View File

@ -1 +0,0 @@
../ui-fail/from_form_value.rs

View File

@ -38,13 +38,13 @@ error: expected `fn`
|
= help: #[get] can only be used on functions
error: expected key/value pair
error: expected key/value `key = value`
--> $DIR/route-attribute-general-syntax.rs:21:12
|
21 | #[get("/", 123)]
| ^^^
error: expected key/value pair
error: expected key/value `key = value`
--> $DIR/route-attribute-general-syntax.rs:24:12
|
24 | #[get("/", "/")]
@ -62,14 +62,11 @@ error: unexpected attribute parameter: `unknown`
30 | #[get("/", unknown = "foo")]
| ^^^^^^^^^^^^^^^
error: malformed attribute
--> $DIR/route-attribute-general-syntax.rs:33:1
error: expected key/value `key = value`
--> $DIR/route-attribute-general-syntax.rs:33:12
|
33 | #[get("/", ...)]
| ^^^^^^^^^^^^^^^^
|
= help: expected syntax: #[get(key = value, ..)]
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
| ^^^
error: handler arguments cannot be ignored
--> $DIR/route-attribute-general-syntax.rs:39:7

View File

@ -1,12 +1,12 @@
error: invalid path URI: expected token '/' but found 'a' at index 0
error: invalid route URI: expected token '/' but found 'a' at index 0
--> $DIR/route-path-bad-syntax.rs:5:8
|
5 | #[get("a")]
| ^^
| ^
|
= help: expected path in origin form: "/path/<param>"
error: invalid path URI: unexpected EOF: expected token '/' at index 0
error: invalid route URI: unexpected EOF: expected token '/' at index 0
--> $DIR/route-path-bad-syntax.rs:8:8
|
8 | #[get("")]
@ -14,11 +14,11 @@ error: invalid path URI: unexpected EOF: expected token '/' at index 0
|
= help: expected path in origin form: "/path/<param>"
error: invalid path URI: expected token '/' but found 'a' at index 0
error: invalid route URI: expected token '/' but found 'a' at index 0
--> $DIR/route-path-bad-syntax.rs:11:8
|
11 | #[get("a/b/c")]
| ^^^^^^
| ^
|
= help: expected path in origin form: "/path/<param>"
@ -50,41 +50,6 @@ error: paths cannot contain empty segments
|
= note: expected '/a/b', found '/a/b//'
error: invalid path URI: expected EOF but found '#' at index 3
--> $DIR/route-path-bad-syntax.rs:28:11
|
28 | #[get("/!@#$%^&*()")]
| ^^^^^^^^^
|
= help: expected path in origin form: "/path/<param>"
error: segment contains invalid URI characters
--> $DIR/route-path-bad-syntax.rs:31:9
|
31 | #[get("/a%20b")]
| ^^^^^
|
= note: components cannot contain reserved characters
= help: reserved characters include: '%', '+', '&', etc.
error: segment contains invalid URI characters
--> $DIR/route-path-bad-syntax.rs:34:11
|
34 | #[get("/a?a%20b")]
| ^^^^^
|
= note: components cannot contain reserved characters
= help: reserved characters include: '%', '+', '&', etc.
error: segment contains invalid URI characters
--> $DIR/route-path-bad-syntax.rs:37:11
|
37 | #[get("/a?a+b")]
| ^^^
|
= note: components cannot contain reserved characters
= help: reserved characters include: '%', '+', '&', etc.
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:42:9
|

View File

@ -14,29 +14,32 @@ error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied
|
= note: required by `from_segments`
error[E0277]: the trait bound `Q: FromFormValue<'_>` is not satisfied
--> $DIR/route-type-errors.rs:12:7
error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied
--> $DIR/route-type-errors.rs:12:12
|
12 | fn f2(foo: Q) {}
| ^^^^^^ the trait `FromFormValue<'_>` is not implemented for `Q`
| ^ the trait `FromFormField<'_>` is not implemented for `Q`
|
= note: required by `from_form_value`
= note: required because of the requirements on the impl of `FromForm<'_>` for `Q`
error[E0277]: the trait bound `Q: FromQuery<'_>` is not satisfied
--> $DIR/route-type-errors.rs:15:7
error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied
--> $DIR/route-type-errors.rs:15:12
|
15 | fn f3(foo: Q) {}
| ^^^^^^ the trait `FromQuery<'_>` is not implemented for `Q`
| ^ the trait `FromFormField<'_>` is not implemented for `Q`
|
= note: required by `from_query`
= note: required because of the requirements on the impl of `FromForm<'_>` for `Q`
error[E0277]: the trait bound `Q: FromData` is not satisfied
--> $DIR/route-type-errors.rs:18:7
|
18 | fn f4(foo: Q) {}
| ^^^^^^ the trait `FromData` is not implemented for `Q`
|
= note: required because of the requirements on the impl of `FromTransformedData<'_>` for `Q`
error[E0277]: the trait bound `Q: FromData<'_>` is not satisfied
--> $DIR/route-type-errors.rs:18:7
|
18 | fn f4(foo: Q) {}
| ^^^^^^ the trait `FromData<'_>` is not implemented for `Q`
|
::: $WORKSPACE/core/lib/src/data/from_data.rs
|
| async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error>;
| -- required by this bound in `rocket::data::FromData::from_data`
error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
--> $DIR/route-type-errors.rs:21:7
@ -46,8 +49,8 @@ error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
|
::: $WORKSPACE/core/lib/src/request/from_request.rs
|
| #[crate::async_trait]
| --------------------- required by this bound in `from_request`
| pub trait FromRequest<'a, 'r>: Sized {
| -- required by this bound in `from_request`
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:21:13
@ -65,8 +68,8 @@ error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
|
::: $WORKSPACE/core/lib/src/request/from_request.rs
|
| #[crate::async_trait]
| --------------------- required by this bound in `from_request`
| pub trait FromRequest<'a, 'r>: Sized {
| -- required by this bound in `from_request`
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:24:13

View File

@ -1,7 +1,7 @@
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:42:23
--> $DIR/typed-uri-bad-type.rs:45:23
|
42 | uri!(simple: id = "hi");
45 | uri!(simple: id = "hi");
| ^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
= help: the following implementations were found:
@ -11,9 +11,9 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str
= note: required by `from_uri_param`
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:44:18
--> $DIR/typed-uri-bad-type.rs:47:18
|
44 | uri!(simple: "hello");
47 | uri!(simple: "hello");
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
= help: the following implementations were found:
@ -23,9 +23,9 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str
= note: required by `from_uri_param`
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:46:23
--> $DIR/typed-uri-bad-type.rs:49:23
|
46 | uri!(simple: id = 239239i64);
49 | uri!(simple: id = 239239i64);
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, i64>` is not implemented for `usize`
|
= help: the following implementations were found:
@ -35,17 +35,17 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Path, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:48:31
--> $DIR/typed-uri-bad-type.rs:51:31
|
48 | uri!(not_uri_display: 10, S);
51 | uri!(not_uri_display: 10, S);
| ^ the trait `FromUriParam<rocket::http::uri::Path, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:53:26
--> $DIR/typed-uri-bad-type.rs:56:26
|
53 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
| ^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
= help: the following implementations were found:
@ -56,9 +56,9 @@ error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::o
= note: required by `from_uri_param`
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::Path, Result<_, _>>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:53:43
--> $DIR/typed-uri-bad-type.rs:56:43
|
53 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
| ^^^^^^^^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, Result<_, _>>` is not implemented for `std::string::String`
|
= help: the following implementations were found:
@ -67,14 +67,14 @@ error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::u
<std::string::String as FromUriParam<P, &'x mut &'a str>>
<std::string::String as FromUriParam<P, &'x mut std::string::String>>
and 2 others
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, Result<_, _>>` for `Result<std::string::String, &RawStr>`
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, Result<_, _>>` for `Result<std::string::String, &str>`
= note: required by `from_uri_param`
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:55:20
error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:58:20
|
55 | uri!(simple_q: "hi");
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
58 | uri!(simple_q: "hi");
| ^^^^ the trait `FromUriParam<Query, &str>` is not implemented for `isize`
|
= help: the following implementations were found:
<isize as FromUriParam<P, &'x isize>>
@ -82,11 +82,11 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &st
<isize as FromUriParam<P, isize>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:57:25
error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:60:25
|
57 | uri!(simple_q: id = "hi");
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
60 | uri!(simple_q: id = "hi");
| ^^^^ the trait `FromUriParam<Query, &str>` is not implemented for `isize`
|
= help: the following implementations were found:
<isize as FromUriParam<P, &'x isize>>
@ -94,82 +94,48 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &st
<isize as FromUriParam<P, isize>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:59:24
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:62:24
|
59 | uri!(other_q: 100, S);
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
62 | uri!(other_q: 100, S);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:61:26
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:64:26
|
61 | uri!(other_q: rest = S, id = 100);
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
64 | uri!(other_q: rest = S, id = 100);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:36:29
error[E0277]: the trait bound `S: Ignorable<Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:66:26
|
36 | fn other_q(id: usize, rest: S) { }
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `S`
...
63 | uri!(other_q: rest = _, id = 100);
| ---------------------------------- in this macro invocation
66 | uri!(other_q: rest = _, id = 100);
| ^ the trait `Ignorable<Query>` is not implemented for `S`
|
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
| ------------ required by this bound in `assert_ignorable`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:36:16
error[E0277]: the trait bound `usize: Ignorable<Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:68:34
|
36 | fn other_q(id: usize, rest: S) { }
| ^^^^^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `usize`
...
65 | uri!(other_q: rest = S, id = _);
| -------------------------------- in this macro invocation
68 | uri!(other_q: rest = S, id = _);
| ^ the trait `Ignorable<Query>` is not implemented for `usize`
|
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
| ------------ required by this bound in `assert_ignorable`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:65:26
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:68:26
|
65 | uri!(other_q: rest = S, id = _);
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
68 | uri!(other_q: rest = S, id = _);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `std::option::Option<i32>: FromUriParam<rocket::http::uri::Query, {integer}>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:69:28
|
69 | uri!(optionals_q: id = 10, name = "Bob".to_string());
| ^^ the trait `FromUriParam<rocket::http::uri::Query, {integer}>` is not implemented for `std::option::Option<i32>`
|
= help: the following implementations were found:
<std::option::Option<T> as FromUriParam<rocket::http::uri::Path, A>>
<std::option::Option<T> as FromUriParam<rocket::http::uri::Query, Result<A, E>>>
<std::option::Option<T> as FromUriParam<rocket::http::uri::Query, std::option::Option<A>>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `Result<std::string::String, &RawStr>: FromUriParam<rocket::http::uri::Query, std::string::String>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:69:39
|
69 | uri!(optionals_q: id = 10, name = "Bob".to_string());
| ^^^^^^^^^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Query, std::string::String>` is not implemented for `Result<std::string::String, &RawStr>`
|
= help: the following implementations were found:
<Result<T, E> as FromUriParam<rocket::http::uri::Path, A>>
<Result<T, E> as FromUriParam<rocket::http::uri::Query, Result<A, E>>>
<Result<T, E> as FromUriParam<rocket::http::uri::Query, std::option::Option<A>>>
= note: required by `from_uri_param`

View File

@ -1,8 +1,8 @@
error: fieldless structs or variants are not supported
--> $DIR/uri_display.rs:4:8
--> $DIR/uri_display.rs:4:1
|
4 | struct Foo1;
| ^^^^
| ^^^^^^^^^^^^
|
note: error occurred while deriving `UriDisplay`
--> $DIR/uri_display.rs:3:10
@ -12,10 +12,10 @@ note: error occurred while deriving `UriDisplay`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: fieldless structs or variants are not supported
--> $DIR/uri_display.rs:7:8
--> $DIR/uri_display.rs:7:1
|
7 | struct Foo2();
| ^^^^
| ^^^^^^^^^^^^^^
|
note: error occurred while deriving `UriDisplay`
--> $DIR/uri_display.rs:6:10
@ -66,7 +66,7 @@ note: error occurred while deriving `UriDisplay`
error: invalid value: expected string literal
--> $DIR/uri_display.rs:22:20
|
22 | #[form(field = 123)]
22 | #[field(name = 123)]
| ^^^
|
note: error occurred while deriving `UriDisplay`
@ -90,10 +90,10 @@ note: error occurred while deriving `UriDisplay`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: struct must have exactly one field
--> $DIR/uri_display.rs:30:8
--> $DIR/uri_display.rs:30:1
|
30 | struct Foo8;
| ^^^^
| ^^^^^^^^^^^^
|
note: error occurred while deriving `UriDisplay`
--> $DIR/uri_display.rs:29:10

View File

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

View File

@ -1,354 +1,361 @@
error: enums are not supported
--> $DIR/from_form.rs:6:1
--> $DIR/from_form.rs:4:1
|
6 | enum Thing { }
4 | enum Thing { }
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:5:10
--> $DIR/from_form.rs:3:10
|
5 | #[derive(FromForm)]
3 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs are not supported
--> $DIR/from_form.rs:9:1
--> $DIR/from_form.rs:7:1
|
9 | struct Foo1;
7 | struct Foo1;
| ^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:8:10
--> $DIR/from_form.rs:6:10
|
8 | #[derive(FromForm)]
6 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: at least one field is required
--> $DIR/from_form.rs:12:13
--> $DIR/from_form.rs:10:13
|
12 | struct Foo2 { }
10 | struct Foo2 { }
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:11:10
|
11 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
--> $DIR/from_form.rs:9:10
|
9 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs are not supported
--> $DIR/from_form.rs:15:1
--> $DIR/from_form.rs:13:1
|
15 | struct Foo3(usize);
13 | struct Foo3(usize);
| ^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:14:10
--> $DIR/from_form.rs:12:10
|
14 | #[derive(FromForm)]
12 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: only one lifetime is supported
--> $DIR/from_form.rs:18:25
--> $DIR/from_form.rs:16:25
|
18 | struct NextTodoTask<'f, 'a> {
16 | struct NextTodoTask<'f, 'a> {
| ^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:17:10
--> $DIR/from_form.rs:15:10
|
17 | #[derive(FromForm)]
15 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:27:20
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:25:20
|
27 | #[form(field = "isindex")]
25 | #[field(name = "isindex")]
| ^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:25:10
--> $DIR/from_form.rs:23:10
|
25 | #[derive(FromForm)]
23 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate field name
--> $DIR/from_form.rs:35:5
error: duplicate form field
--> $DIR/from_form.rs:33:5
|
35 | foo: usize,
33 | foo: usize,
| ^^^
error: [note] previous definition here
--> $DIR/from_form.rs:33:20
error: [note] previously defined here
--> $DIR/from_form.rs:31:5
|
33 | #[form(field = "foo")]
| ^^^^^
31 | #[field(name = "foo")]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:31:10
--> $DIR/from_form.rs:29:10
|
31 | #[derive(FromForm)]
29 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate field name
--> $DIR/from_form.rs:42:20
error: duplicate form field
--> $DIR/from_form.rs:40:5
|
42 | #[form(field = "hello")]
| ^^^^^^^
40 | #[field(name = "hello")]
| ^
error: [note] previous definition here
--> $DIR/from_form.rs:40:20
error: [note] previously defined here
--> $DIR/from_form.rs:38:5
|
40 | #[form(field = "hello")]
| ^^^^^^^
38 | #[field(name = "hello")]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:38:10
--> $DIR/from_form.rs:36:10
|
38 | #[derive(FromForm)]
36 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate field name
--> $DIR/from_form.rs:49:20
error: duplicate form field
--> $DIR/from_form.rs:47:5
|
49 | #[form(field = "first")]
| ^^^^^^^
47 | #[field(name = "first")]
| ^
error: [note] previous definition here
--> $DIR/from_form.rs:48:5
error: [note] previously defined here
--> $DIR/from_form.rs:46:5
|
48 | first: String,
46 | first: String,
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:46:10
--> $DIR/from_form.rs:44:10
|
46 | #[derive(FromForm)]
44 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate attribute parameter: field
--> $DIR/from_form.rs:55:28
error: unexpected attribute parameter: `field`
--> $DIR/from_form.rs:53:28
|
55 | #[form(field = "blah", field = "bloo")]
53 | #[field(name = "blah", field = "bloo")]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:53:10
--> $DIR/from_form.rs:51:10
|
53 | #[derive(FromForm)]
51 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: malformed attribute: expected list
--- help: expected syntax: #[form(key = value, ..)]
--> $DIR/from_form.rs:61:7
error: expected list `#[field(..)]`, found bare path "field"
--> $DIR/from_form.rs:59:7
|
61 | #[form]
| ^^^^
59 | #[field]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:59:10
--> $DIR/from_form.rs:57:10
|
59 | #[derive(FromForm)]
57 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected key/value pair
--> $DIR/from_form.rs:67:12
error: expected key/value `key = value`
--> $DIR/from_form.rs:65:13
|
67 | #[form("blah")]
| ^^^^^^
65 | #[field("blah")]
| ^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:65:10
--> $DIR/from_form.rs:63:10
|
65 | #[derive(FromForm)]
63 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected key/value pair
--> $DIR/from_form.rs:73:12
error: expected key/value `key = value`
--> $DIR/from_form.rs:71:13
|
73 | #[form(123)]
| ^^^
71 | #[field(123)]
| ^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:71:10
--> $DIR/from_form.rs:69:10
|
71 | #[derive(FromForm)]
69 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected attribute parameter: `beep`
--> $DIR/from_form.rs:79:12
--> $DIR/from_form.rs:77:13
|
79 | #[form(beep = "bop")]
| ^^^^
77 | #[field(beep = "bop")]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:77:10
--> $DIR/from_form.rs:75:10
|
77 | #[derive(FromForm)]
75 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate invocation of `form` attribute
--> $DIR/from_form.rs:86:7
error: duplicate form field renaming
--- help: a field can only be renamed once
--> $DIR/from_form.rs:84:20
|
86 | #[form(field = "bleh")]
| ^^^^
84 | #[field(name = "blah")]
| ^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:83:10
--> $DIR/from_form.rs:81:10
|
83 | #[derive(FromForm)]
81 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
--> $DIR/from_form.rs:92:20
--> $DIR/from_form.rs:90:20
|
92 | #[form(field = true)]
90 | #[field(name = true)]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:90:10
--> $DIR/from_form.rs:88:10
|
90 | #[derive(FromForm)]
88 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected literal or key/value pair
--> $DIR/from_form.rs:98:12
error: expected literal, found bare path "name"
--> $DIR/from_form.rs:96:13
|
98 | #[form(field)]
| ^^^^^
96 | #[field(name)]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:96:10
--> $DIR/from_form.rs:94:10
|
96 | #[derive(FromForm)]
94 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
--> $DIR/from_form.rs:104:20
--> $DIR/from_form.rs:102:20
|
104 | #[form(field = 123)]
102 | #[field(name = 123)]
| ^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:102:10
--> $DIR/from_form.rs:100:10
|
102 | #[derive(FromForm)]
100 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:110:20
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:108:20
|
110 | #[form(field = "hello&world")]
108 | #[field(name = "hello&world")]
| ^^^^^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:108:10
--> $DIR/from_form.rs:106:10
|
108 | #[derive(FromForm)]
106 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:116:20
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:114:20
|
116 | #[form(field = "!@#$%^&*()_")]
114 | #[field(name = "!@#$%^&*()_")]
| ^^^^^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:114:10
--> $DIR/from_form.rs:112:10
|
114 | #[derive(FromForm)]
112 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:122:20
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:120:20
|
122 | #[form(field = "?")]
120 | #[field(name = "?")]
| ^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:120:10
--> $DIR/from_form.rs:118:10
|
120 | #[derive(FromForm)]
118 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:128:20
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:126:20
|
128 | #[form(field = "")]
126 | #[field(name = "")]
| ^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:126:10
--> $DIR/from_form.rs:124:10
|
126 | #[derive(FromForm)]
124 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:134:20
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:132:20
|
134 | #[form(field = "a&b")]
132 | #[field(name = "a&b")]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:132:10
--> $DIR/from_form.rs:130:10
|
132 | #[derive(FromForm)]
130 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:140:20
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:138:20
|
140 | #[form(field = "a=")]
138 | #[field(name = "a=")]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:138:10
--> $DIR/from_form.rs:136:10
|
138 | #[derive(FromForm)]
136 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -0,0 +1 @@
../ui-fail/from_form_field.rs

View File

@ -1,111 +1,111 @@
error: tuple structs are not supported
--> $DIR/from_form_value.rs:4:1
--> $DIR/from_form_field.rs:4:1
|
4 | struct Foo1;
| ^^^^^^
error: [note] error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:3:10
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:3:10
|
3 | #[derive(FromFormValue)]
3 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs are not supported
--> $DIR/from_form_value.rs:7:1
--> $DIR/from_form_field.rs:7:1
|
7 | struct Foo2(usize);
| ^^^^^^
error: [note] error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:6:10
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:6:10
|
6 | #[derive(FromFormValue)]
6 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: named structs are not supported
--> $DIR/from_form_value.rs:10:1
--> $DIR/from_form_field.rs:10:1
|
10 | struct Foo3 {
| ^^^^^^
error: [note] error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:9:10
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:9:10
|
9 | #[derive(FromFormValue)]
9 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: variants cannot have fields
--> $DIR/from_form_value.rs:16:7
--> $DIR/from_form_field.rs:16:6
|
16 | A(usize),
| ^^^^^
| ^^^^^^^
error: [note] error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:14:10
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:14:10
|
14 | #[derive(FromFormValue)]
14 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: enum must have at least one field
--> $DIR/from_form_value.rs:20:11
error: enum must have at least one variant
--> $DIR/from_form_field.rs:20:1
|
20 | enum Foo5 { }
| ^^^
| ^^^^
error: [note] error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:19:10
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:19:10
|
19 | #[derive(FromFormValue)]
19 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: type generics are not supported
--> $DIR/from_form_value.rs:23:11
--> $DIR/from_form_field.rs:23:11
|
23 | enum Foo6<T> {
| ^
error: [note] error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:22:10
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:22:10
|
22 | #[derive(FromFormValue)]
22 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
--> $DIR/from_form_value.rs:29:20
--> $DIR/from_form_field.rs:29:21
|
29 | #[form(value = 123)]
| ^^^
29 | #[field(value = 123)]
| ^^^
error: [note] error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:27:10
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:27:10
|
27 | #[derive(FromFormValue)]
27 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected literal or key/value pair
--> $DIR/from_form_value.rs:35:12
error: expected literal, found bare path "value"
--> $DIR/from_form_field.rs:35:13
|
35 | #[form(value)]
| ^^^^^
35 | #[field(value)]
| ^^^^^
error: [note] error occurred while deriving `FromFormValue`
--> $DIR/from_form_value.rs:33:10
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:33:10
|
33 | #[derive(FromFormValue)]
33 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -1,15 +1,15 @@
error[E0277]: the trait bound `Unknown: FromFormValue<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:7:5
error[E0277]: the trait bound `Unknown: FromFormField<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:7:12
|
7 | field: Unknown,
| ^^^^^ the trait `FromFormValue<'_>` is not implemented for `Unknown`
| ^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Unknown`
|
= note: required by `from_form_value`
= note: required because of the requirements on the impl of `FromForm<'__f>` for `Unknown`
error[E0277]: the trait bound `Foo<usize>: FromFormValue<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:14:5
error[E0277]: the trait bound `Foo<usize>: FromFormField<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:14:12
|
14 | field: Foo<usize>,
| ^^^^^ the trait `FromFormValue<'_>` is not implemented for `Foo<usize>`
| ^^^ the trait `FromFormField<'_>` is not implemented for `Foo<usize>`
|
= note: required by `from_form_value`
= note: required because of the requirements on the impl of `FromForm<'__f>` for `Foo<usize>`

View File

@ -1 +0,0 @@
../ui-fail/from_form_value.rs

View File

@ -34,13 +34,13 @@ error: expected `fn`
18 | impl S { }
| ^^^^
error: expected key/value pair
error: expected key/value `key = value`
--> $DIR/route-attribute-general-syntax.rs:21:12
|
21 | #[get("/", 123)]
| ^^^
error: expected key/value pair
error: expected key/value `key = value`
--> $DIR/route-attribute-general-syntax.rs:24:12
|
24 | #[get("/", "/")]
@ -58,14 +58,11 @@ error: unexpected attribute parameter: `unknown`
30 | #[get("/", unknown = "foo")]
| ^^^^^^^
error: malformed attribute
--- help: expected syntax: #[get(key = value, ..)]
--> $DIR/route-attribute-general-syntax.rs:33:1
error: expected key/value `key = value`
--> $DIR/route-attribute-general-syntax.rs:33:12
|
33 | #[get("/", ...)]
| ^^^^^^^^^^^^^^^^
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
| ^^^
error: handler arguments cannot be ignored
--- help: all handler arguments must be of the form: `ident: Type`

View File

@ -1,18 +1,18 @@
error: invalid path URI: expected token '/' but found 'a' at index 0
error: invalid route URI: expected token '/' but found 'a' at index 0
--- help: expected path in origin form: "/path/<param>"
--> $DIR/route-path-bad-syntax.rs:5:7
|
5 | #[get("a")]
| ^^^
error: invalid path URI: unexpected EOF: expected token '/' at index 0
error: invalid route URI: unexpected EOF: expected token '/' at index 0
--- help: expected path in origin form: "/path/<param>"
--> $DIR/route-path-bad-syntax.rs:8:7
|
8 | #[get("")]
| ^^
error: invalid path URI: expected token '/' but found 'a' at index 0
error: invalid route URI: expected token '/' but found 'a' at index 0
--- help: expected path in origin form: "/path/<param>"
--> $DIR/route-path-bad-syntax.rs:11:7
|
@ -45,37 +45,6 @@ error: paths cannot contain empty segments
23 | #[get("/a/b//")]
| ^^^^^^^^
error: invalid path URI: expected EOF but found '#' at index 3
--- help: expected path in origin form: "/path/<param>"
--> $DIR/route-path-bad-syntax.rs:28:7
|
28 | #[get("/!@#$%^&*()")]
| ^^^^^^^^^^^^^
error: segment contains invalid URI characters
--- note: components cannot contain reserved characters
--- help: reserved characters include: '%', '+', '&', etc.
--> $DIR/route-path-bad-syntax.rs:31:7
|
31 | #[get("/a%20b")]
| ^^^^^^^^
error: segment contains invalid URI characters
--- note: components cannot contain reserved characters
--- help: reserved characters include: '%', '+', '&', etc.
--> $DIR/route-path-bad-syntax.rs:34:7
|
34 | #[get("/a?a%20b")]
| ^^^^^^^^^^
error: segment contains invalid URI characters
--- note: components cannot contain reserved characters
--- help: reserved characters include: '%', '+', '&', etc.
--> $DIR/route-path-bad-syntax.rs:37:7
|
37 | #[get("/a?a+b")]
| ^^^^^^^^
error: unused dynamic parameter
--> $DIR/route-path-bad-syntax.rs:42:7
|

View File

@ -14,40 +14,32 @@ error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied
|
= note: required by `from_segments`
error[E0277]: the trait bound `Q: FromFormValue<'_>` is not satisfied
error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied
--> $DIR/route-type-errors.rs:12:12
|
12 | fn f2(foo: Q) {}
| ^ the trait `FromFormValue<'_>` is not implemented for `Q`
| ^ the trait `FromFormField<'_>` is not implemented for `Q`
|
= note: required by `from_form_value`
= note: required because of the requirements on the impl of `FromForm<'_>` for `Q`
error[E0277]: the trait bound `Q: FromFormValue<'_>` is not satisfied
--> $DIR/route-type-errors.rs:12:7
|
12 | fn f2(foo: Q) {}
| ^^^^^^ the trait `FromFormValue<'_>` is not implemented for `Q`
|
::: $WORKSPACE/core/lib/src/request/form/from_form_value.rs
|
| pub trait FromFormValue<'v>: Sized {
| ---------------------------------- required by this bound in `FromFormValue`
error[E0277]: the trait bound `Q: FromQuery<'_>` is not satisfied
error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied
--> $DIR/route-type-errors.rs:15:12
|
15 | fn f3(foo: Q) {}
| ^ the trait `FromQuery<'_>` is not implemented for `Q`
| ^ the trait `FromFormField<'_>` is not implemented for `Q`
|
= note: required by `from_query`
= note: required because of the requirements on the impl of `FromForm<'_>` for `Q`
error[E0277]: the trait bound `Q: FromData` is not satisfied
--> $DIR/route-type-errors.rs:18:12
|
18 | fn f4(foo: Q) {}
| ^ the trait `FromData` is not implemented for `Q`
|
= note: required because of the requirements on the impl of `FromTransformedData<'_>` for `Q`
error[E0277]: the trait bound `Q: FromData<'_>` is not satisfied
--> $DIR/route-type-errors.rs:18:12
|
18 | fn f4(foo: Q) {}
| ^ the trait `FromData<'_>` is not implemented for `Q`
|
::: $WORKSPACE/core/lib/src/data/from_data.rs
|
| async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error>;
| -- required by this bound in `rocket::data::FromData::from_data`
error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
--> $DIR/route-type-errors.rs:21:10
@ -57,8 +49,8 @@ error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
|
::: $WORKSPACE/core/lib/src/request/from_request.rs
|
| #[crate::async_trait]
| --------------------- required by this bound in `from_request`
| pub trait FromRequest<'a, 'r>: Sized {
| -- required by this bound in `from_request`
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:21:18
@ -76,8 +68,8 @@ error[E0277]: the trait bound `Q: FromRequest<'_, '_>` is not satisfied
|
::: $WORKSPACE/core/lib/src/request/from_request.rs
|
| #[crate::async_trait]
| --------------------- required by this bound in `from_request`
| pub trait FromRequest<'a, 'r>: Sized {
| -- required by this bound in `from_request`
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
--> $DIR/route-type-errors.rs:24:18

View File

@ -1,7 +1,7 @@
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:42:23
--> $DIR/typed-uri-bad-type.rs:45:23
|
42 | uri!(simple: id = "hi");
45 | uri!(simple: id = "hi");
| ^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
= help: the following implementations were found:
@ -11,9 +11,9 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str
= note: required by `from_uri_param`
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:44:18
--> $DIR/typed-uri-bad-type.rs:47:18
|
44 | uri!(simple: "hello");
47 | uri!(simple: "hello");
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
= help: the following implementations were found:
@ -23,9 +23,9 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str
= note: required by `from_uri_param`
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:46:23
--> $DIR/typed-uri-bad-type.rs:49:23
|
46 | uri!(simple: id = 239239i64);
49 | uri!(simple: id = 239239i64);
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, i64>` is not implemented for `usize`
|
= help: the following implementations were found:
@ -35,17 +35,17 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Path, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:48:31
--> $DIR/typed-uri-bad-type.rs:51:31
|
48 | uri!(not_uri_display: 10, S);
51 | uri!(not_uri_display: 10, S);
| ^ the trait `FromUriParam<rocket::http::uri::Path, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:53:26
--> $DIR/typed-uri-bad-type.rs:56:26
|
53 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
| ^^^^ the trait `FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
= help: the following implementations were found:
@ -56,9 +56,9 @@ error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::o
= note: required by `from_uri_param`
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:53:43
--> $DIR/typed-uri-bad-type.rs:56:43
|
53 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
| ^^ the trait `FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` is not implemented for `std::string::String`
|
= help: the following implementations were found:
@ -67,14 +67,14 @@ error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::u
<std::string::String as FromUriParam<P, &'x mut &'a str>>
<std::string::String as FromUriParam<P, &'x mut std::string::String>>
and 2 others
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` for `std::result::Result<std::string::String, &RawStr>`
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` for `std::result::Result<std::string::String, &str>`
= note: required by `from_uri_param`
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:55:20
error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:58:20
|
55 | uri!(simple_q: "hi");
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
58 | uri!(simple_q: "hi");
| ^^^^ the trait `FromUriParam<Query, &str>` is not implemented for `isize`
|
= help: the following implementations were found:
<isize as FromUriParam<P, &'x isize>>
@ -82,11 +82,11 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &st
<isize as FromUriParam<P, isize>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:57:25
error[E0277]: the trait bound `isize: FromUriParam<Query, &str>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:60:25
|
57 | uri!(simple_q: id = "hi");
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
60 | uri!(simple_q: id = "hi");
| ^^^^ the trait `FromUriParam<Query, &str>` is not implemented for `isize`
|
= help: the following implementations were found:
<isize as FromUriParam<P, &'x isize>>
@ -94,82 +94,48 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &st
<isize as FromUriParam<P, isize>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:59:24
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:62:24
|
59 | uri!(other_q: 100, S);
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
62 | uri!(other_q: 100, S);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:61:26
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:64:26
|
61 | uri!(other_q: rest = S, id = 100);
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
64 | uri!(other_q: rest = S, id = 100);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:36:29
error[E0277]: the trait bound `S: Ignorable<Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:66:26
|
36 | fn other_q(id: usize, rest: S) { }
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `S`
...
63 | uri!(other_q: rest = _, id = 100);
| ---------------------------------- in this macro invocation
66 | uri!(other_q: rest = _, id = 100);
| ^ the trait `Ignorable<Query>` is not implemented for `S`
|
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
| ------------ required by this bound in `assert_ignorable`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:36:16
error[E0277]: the trait bound `usize: Ignorable<Query>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:68:34
|
36 | fn other_q(id: usize, rest: S) { }
| ^^^^^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `usize`
...
65 | uri!(other_q: rest = S, id = _);
| -------------------------------- in this macro invocation
68 | uri!(other_q: rest = S, id = _);
| ^ the trait `Ignorable<Query>` is not implemented for `usize`
|
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
| ------------ required by this bound in `assert_ignorable`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:65:26
error[E0277]: the trait bound `S: FromUriParam<Query, _>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:68:26
|
65 | uri!(other_q: rest = S, id = _);
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
68 | uri!(other_q: rest = S, id = _);
| ^ the trait `FromUriParam<Query, _>` is not implemented for `S`
|
= note: required by `from_uri_param`
error[E0277]: the trait bound `std::option::Option<i32>: FromUriParam<rocket::http::uri::Query, {integer}>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:69:28
|
69 | uri!(optionals_q: id = 10, name = "Bob".to_string());
| ^^ the trait `FromUriParam<rocket::http::uri::Query, {integer}>` is not implemented for `std::option::Option<i32>`
|
= help: the following implementations were found:
<std::option::Option<T> as FromUriParam<rocket::http::uri::Path, A>>
<std::option::Option<T> as FromUriParam<rocket::http::uri::Query, std::option::Option<A>>>
<std::option::Option<T> as FromUriParam<rocket::http::uri::Query, std::result::Result<A, E>>>
= note: required by `from_uri_param`
error[E0277]: the trait bound `std::result::Result<std::string::String, &RawStr>: FromUriParam<rocket::http::uri::Query, std::string::String>` is not satisfied
--> $DIR/typed-uri-bad-type.rs:69:39
|
69 | uri!(optionals_q: id = 10, name = "Bob".to_string());
| ^^^^^ the trait `FromUriParam<rocket::http::uri::Query, std::string::String>` is not implemented for `std::result::Result<std::string::String, &RawStr>`
|
= help: the following implementations were found:
<std::result::Result<T, E> as FromUriParam<rocket::http::uri::Path, A>>
<std::result::Result<T, E> as FromUriParam<rocket::http::uri::Query, std::option::Option<A>>>
<std::result::Result<T, E> as FromUriParam<rocket::http::uri::Query, std::result::Result<A, E>>>
= note: required by `from_uri_param`

View File

@ -1,8 +1,8 @@
error: fieldless structs or variants are not supported
--> $DIR/uri_display.rs:4:8
--> $DIR/uri_display.rs:4:1
|
4 | struct Foo1;
| ^^^^
| ^^^^^^
error: [note] error occurred while deriving `UriDisplay`
--> $DIR/uri_display.rs:3:10
@ -13,10 +13,10 @@ error: [note] error occurred while deriving `UriDisplay`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: fieldless structs or variants are not supported
--> $DIR/uri_display.rs:7:8
--> $DIR/uri_display.rs:7:1
|
7 | struct Foo2();
| ^^^^
| ^^^^^^
error: [note] error occurred while deriving `UriDisplay`
--> $DIR/uri_display.rs:6:10
@ -71,7 +71,7 @@ error: [note] error occurred while deriving `UriDisplay`
error: invalid value: expected string literal
--> $DIR/uri_display.rs:22:20
|
22 | #[form(field = 123)]
22 | #[field(name = 123)]
| ^^^
error: [note] error occurred while deriving `UriDisplay`
@ -97,10 +97,10 @@ error: [note] error occurred while deriving `UriDisplay`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: struct must have exactly one field
--> $DIR/uri_display.rs:30:8
--> $DIR/uri_display.rs:30:1
|
30 | struct Foo8;
| ^^^^
| ^^^^^^
error: [note] error occurred while deriving `UriDisplay`
--> $DIR/uri_display.rs:29:10

View File

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

View File

@ -1,7 +1,5 @@
#[macro_use] extern crate rocket;
use rocket::http::RawStr;
#[derive(FromForm)]
enum Thing { }
@ -17,127 +15,127 @@ struct Foo3(usize);
#[derive(FromForm)]
struct NextTodoTask<'f, 'a> {
description: String,
raw_description: &'f RawStr,
other: &'a RawStr,
raw_description: &'f str,
other: &'a str,
completed: bool,
}
#[derive(FromForm)]
struct BadName1 {
#[form(field = "isindex")]
#[field(name = "isindex")]
field: String,
}
#[derive(FromForm)]
struct Demo2 {
#[form(field = "foo")]
#[field(name = "foo")]
field: String,
foo: usize,
}
#[derive(FromForm)]
struct MyForm9 {
#[form(field = "hello")]
#[field(name = "hello")]
first: String,
#[form(field = "hello")]
#[field(name = "hello")]
other: String,
}
#[derive(FromForm)]
struct MyForm10 {
first: String,
#[form(field = "first")]
#[field(name = "first")]
other: String,
}
#[derive(FromForm)]
struct MyForm {
#[form(field = "blah", field = "bloo")]
#[field(name = "blah", field = "bloo")]
my_field: String,
}
#[derive(FromForm)]
struct MyForm1 {
#[form]
#[field]
my_field: String,
}
#[derive(FromForm)]
struct MyForm2 {
#[form("blah")]
#[field("blah")]
my_field: String,
}
#[derive(FromForm)]
struct MyForm3 {
#[form(123)]
#[field(123)]
my_field: String,
}
#[derive(FromForm)]
struct MyForm4 {
#[form(beep = "bop")]
#[field(beep = "bop")]
my_field: String,
}
#[derive(FromForm)]
struct MyForm5 {
#[form(field = "blah")]
#[form(field = "bleh")]
#[field(name = "blah")]
#[field(name = "blah")]
my_field: String,
}
#[derive(FromForm)]
struct MyForm6 {
#[form(field = true)]
#[field(name = true)]
my_field: String,
}
#[derive(FromForm)]
struct MyForm7 {
#[form(field)]
#[field(name)]
my_field: String,
}
#[derive(FromForm)]
struct MyForm8 {
#[form(field = 123)]
#[field(name = 123)]
my_field: String,
}
#[derive(FromForm)]
struct MyForm11 {
#[form(field = "hello&world")]
#[field(name = "hello&world")]
first: String,
}
#[derive(FromForm)]
struct MyForm12 {
#[form(field = "!@#$%^&*()_")]
#[field(name = "!@#$%^&*()_")]
first: String,
}
#[derive(FromForm)]
struct MyForm13 {
#[form(field = "?")]
#[field(name = "?")]
first: String,
}
#[derive(FromForm)]
struct MyForm14 {
#[form(field = "")]
#[field(name = "")]
first: String,
}
#[derive(FromForm)]
struct BadName2 {
#[form(field = "a&b")]
#[field(name = "a&b")]
field: String,
}
#[derive(FromForm)]
struct BadName3 {
#[form(field = "a=")]
#[field(name = "a=")]
field: String,
}

View File

@ -0,0 +1,39 @@
#[macro_use] extern crate rocket;
#[derive(FromFormField)]
struct Foo1;
#[derive(FromFormField)]
struct Foo2(usize);
#[derive(FromFormField)]
struct Foo3 {
foo: usize,
}
#[derive(FromFormField)]
enum Foo4 {
A(usize),
}
#[derive(FromFormField)]
enum Foo5 { }
#[derive(FromFormField)]
enum Foo6<T> {
A(T),
}
#[derive(FromFormField)]
enum Bar1 {
#[field(value = 123)]
A,
}
#[derive(FromFormField)]
enum Bar2 {
#[field(value)]
A,
}
fn main() { }

View File

@ -1,39 +0,0 @@
#[macro_use] extern crate rocket;
#[derive(FromFormValue)]
struct Foo1;
#[derive(FromFormValue)]
struct Foo2(usize);
#[derive(FromFormValue)]
struct Foo3 {
foo: usize,
}
#[derive(FromFormValue)]
enum Foo4 {
A(usize),
}
#[derive(FromFormValue)]
enum Foo5 { }
#[derive(FromFormValue)]
enum Foo6<T> {
A(T),
}
#[derive(FromFormValue)]
enum Bar1 {
#[form(value = 123)]
A,
}
#[derive(FromFormValue)]
enum Bar2 {
#[form(value)]
A,
}
fn main() { }

View File

@ -1,13 +1,12 @@
#[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) }
fn from_param(param: &'a str) -> Result<Self, Self::Error> { Ok(S) }
}
#[post("/<id>")]
@ -20,13 +19,17 @@ fn not_uri_display(id: i32, name: S) { }
fn not_uri_display_but_unused(id: i32, name: S) { }
#[post("/<id>/<name>")]
fn optionals(id: Option<i32>, name: Result<String, &RawStr>) { }
fn optionals(id: Option<i32>, name: Result<String, &str>) { }
use rocket::request::{Query, FromQuery};
use rocket::form::{FromFormField, Errors, ValueField, DataField};
impl<'q> FromQuery<'q> for S {
type Error = ();
fn from_query(query: Query<'q>) -> Result<Self, Self::Error> { Ok(S) }
#[rocket::async_trait]
impl<'v> FromFormField<'v> for S {
fn default() -> Option<Self> { None }
fn from_value(_: ValueField<'v>) -> Result<Self, Errors<'v>> { Ok(S) }
async fn from_data(_: DataField<'v, '_>) -> Result<Self, Errors<'v>> { Ok(S) }
}
#[post("/?<id>")]
@ -36,7 +39,7 @@ fn simple_q(id: isize) { }
fn other_q(id: usize, rest: S) { }
#[post("/?<id>&<name>")]
fn optionals_q(id: Option<i32>, name: Result<String, &RawStr>) { }
fn optionals_q(id: Option<i32>, name: Result<String, Errors<'_>>) { }
fn main() {
uri!(simple: id = "hi");
@ -66,7 +69,7 @@ fn main() {
// These are all okay.
uri!(optionals_q: _, _);
uri!(optionals_q: id = 10, name = "Bob".to_string());
uri!(optionals_q: _, "Bob".into());
uri!(optionals_q: id = Some(10), name = Some("Bob".to_string()));
uri!(optionals_q: _, Some("Bob".into()));
uri!(optionals_q: id = _, name = _);
}

View File

@ -1,6 +1,6 @@
#[macro_use] extern crate rocket;
use rocket::http::{CookieJar, RawStr};
use rocket::http::CookieJar;
#[post("/<id>")]
fn has_one(id: i32) { }
@ -12,7 +12,7 @@ fn has_one_guarded(cookies: &CookieJar<'_>, id: i32) { }
fn has_two(cookies: &CookieJar<'_>, id: i32, name: String) { }
#[post("/<id>/<name>")]
fn optionals(id: Option<i32>, name: Result<String, &RawStr>) { }
fn optionals(id: Option<i32>, name: Result<String, &str>) { }
fn main() {
uri!(has_one);

View File

@ -19,7 +19,7 @@ struct Foo5(String, String);
#[derive(UriDisplayQuery)]
struct Foo6 {
#[form(field = 123)]
#[field(name = 123)]
field: String,
}

View File

@ -1,6 +1,5 @@
#[macro_use] extern crate rocket;
use rocket::http::RawStr;
use rocket::http::uri::{UriDisplay, Query, Path};
macro_rules! assert_uri_display_query {
@ -12,13 +11,13 @@ macro_rules! assert_uri_display_query {
#[derive(UriDisplayQuery, Clone)]
enum Foo<'r> {
First(&'r RawStr),
First(&'r str),
Second {
inner: &'r RawStr,
inner: &'r str,
other: usize,
},
Third {
#[form(field = "type")]
#[field(name = "type")]
kind: String,
},
}
@ -95,7 +94,7 @@ fn uri_display_baz() {
struct Bam<'a> {
foo: &'a str,
bar: Option<usize>,
baz: Result<&'a RawStr, usize>,
baz: Result<&'a str, usize>,
}
#[test]

View File

@ -18,6 +18,7 @@ edition = "2018"
default = []
tls = ["tokio-rustls"]
private-cookies = ["cookie/private", "cookie/key-expansion"]
serde = ["uncased/with-serde-alloc", "_serde"]
[dependencies]
smallvec = "1.0"
@ -27,22 +28,31 @@ http = "0.2"
mime = "0.3.13"
time = "0.2.11"
indexmap = { version = "1.5.2", features = ["std"] }
state = "0.4"
tokio-rustls = { version = "0.22.0", optional = true }
tokio = { version = "1.0", features = ["net", "sync", "time"] }
unicode-xid = "0.2"
log = "0.4"
ref-cast = "1.0"
uncased = "0.9"
uncased = "0.9.4"
parking_lot = "0.11"
either = "1"
pear = "0.2"
pear = "0.2.1"
pin-project-lite = "0.2"
memchr = "2"
stable-pattern = "0.1"
cookie = { version = "0.15", features = ["percent-encode"] }
[dependencies.state]
git = "https://github.com/SergioBenitez/state.git"
rev = "7576652"
[dependencies._serde]
package = "serde"
version = "1.0"
optional = true
default-features = false
features = ["std"]
[dependencies.cookie]
git = "https://github.com/SergioBenitez/cookie-rs.git"
rev = "1c3ca83"
features = ["percent-encode"]
[dev-dependencies]
rocket = { version = "0.5.0-dev", path = "../lib" }

View File

@ -15,6 +15,7 @@ mod key {
/// Types and methods to manage a `Key` when private cookies are disabled.
#[cfg(not(feature = "private-cookies"))]
#[allow(missing_docs)]
mod key {
#[derive(Copy, Clone)]
pub struct Key;

View File

@ -1,6 +1,7 @@
//! Extension traits implemented by several HTTP types.
use smallvec::{Array, SmallVec};
use state::Storage;
// TODO: It would be nice if we could somehow have one trait that could give us
// either SmallVec or Vec.
@ -96,6 +97,38 @@ impl<T: IntoOwned> IntoOwned for Option<T> {
}
}
impl<T: IntoOwned> IntoOwned for Vec<T> {
type Owned = Vec<T::Owned>;
#[inline(always)]
fn into_owned(self) -> Self::Owned {
self.into_iter()
.map(|inner| inner.into_owned())
.collect()
}
}
impl<T: IntoOwned + Send + Sync> IntoOwned for Storage<T>
where T::Owned: Send + Sync
{
type Owned = Storage<T::Owned>;
#[inline(always)]
fn into_owned(self) -> Self::Owned {
self.map(|inner| inner.into_owned())
}
}
impl<A: IntoOwned, B: IntoOwned> IntoOwned for (A, B) {
type Owned = (A::Owned, B::Owned);
#[inline(always)]
fn into_owned(self) -> Self::Owned {
(self.0.into_owned(), self.1.into_owned())
}
}
impl<B: 'static + ToOwned + ?Sized> IntoOwned for Cow<'_, B> {
type Owned = Cow<'static, B>;

View File

@ -3,8 +3,8 @@ use std::ops::Deref;
use std::str::FromStr;
use std::fmt;
use crate::header::Header;
use crate::media_type::{MediaType, Source};
use crate::header::{Header, MediaType};
use crate::uncased::UncasedStr;
use crate::ext::IntoCollection;
/// Representation of HTTP Content-Types.
@ -102,6 +102,47 @@ macro_rules! from_extension {
);)
}
macro_rules! extension {
($($ext:expr => $name:ident,)*) => (
docify!([
Returns the most common file extension associated with the
@[Content-Type] @code{self} if it is known. Otherwise, returns
@code{None}. The currently recognized extensions are identical to those
in @{"[`ContentType::from_extension()`]"} with the @{"most common"}
extension being the first extension appearing in the list for a given
@[Content-Type].
];
/// # Example
///
/// Known extension:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentType;
///
/// assert_eq!(ContentType::JSON.extension().unwrap(), "json");
/// assert_eq!(ContentType::JPEG.extension().unwrap(), "jpeg");
/// assert_eq!(ContentType::JPEG.extension().unwrap(), "JPEG");
/// assert_eq!(ContentType::PDF.extension().unwrap(), "pdf");
/// ```
///
/// An unknown extension:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentType;
///
/// let foo = ContentType::new("foo", "bar");
/// assert!(foo.extension().is_none());
/// ```
#[inline]
pub fn extension(&self) -> Option<&UncasedStr> {
$(if self == &ContentType::$name { return Some($ext.into()) })*
None
}
);)
}
macro_rules! parse_flexible {
($($short:expr => $name:ident,)*) => (
docify!([
@ -245,6 +286,8 @@ impl ContentType {
&self.0
}
known_extensions!(extension);
known_media_types!(content_types);
}
@ -336,14 +379,7 @@ impl fmt::Display for ContentType {
impl Into<Header<'static>> for ContentType {
#[inline(always)]
fn into(self) -> Header<'static> {
// FIXME: For known media types, don't do `to_string`. Store the whole
// string as a `source` and have a way to know that the source is
// everything. That removes the allocation here. Then, in
// `MediaType::fmt`, write the source string out directly as well.
//
// We could also use an `enum` for MediaType. But that kinda sucks. But
// maybe it's what we want.
if let Source::Known(src) = self.0.source {
if let Some(src) = self.known_source() {
Header::new("Content-Type", src)
} else {
Header::new("Content-Type", self.to_string())

View File

@ -104,5 +104,6 @@ macro_rules! known_shorthands {
"css" => CSS,
"multipart" => FormData,
"xml" => XML,
"pdf" => PDF,
})
}

View File

@ -7,7 +7,7 @@ use either::Either;
use crate::ext::IntoCollection;
use crate::uncased::UncasedStr;
use crate::parse::{Indexed, IndexedString, parse_media_type};
use crate::parse::{Indexed, IndexedStr, parse_media_type};
use smallvec::SmallVec;
@ -54,24 +54,18 @@ pub struct MediaType {
/// Storage for the entire media type string.
pub(crate) source: Source,
/// The top-level type.
pub(crate) top: IndexedString,
pub(crate) top: IndexedStr<'static>,
/// The subtype.
pub(crate) sub: IndexedString,
pub(crate) sub: IndexedStr<'static>,
/// The parameters, if any.
pub(crate) params: MediaParams
}
#[derive(Debug, Clone)]
struct MediaParam {
key: IndexedString,
value: IndexedString,
}
// FIXME: `Static` is needed for `const` items. Need `const SmallVec::new`.
// FIXME: `Static` variant is needed for `const`. Need `const SmallVec::new`.
#[derive(Debug, Clone)]
pub(crate) enum MediaParams {
Static(&'static [(&'static str, &'static str)]),
Dynamic(SmallVec<[(IndexedString, IndexedString); 2]>)
Dynamic(SmallVec<[(IndexedStr<'static>, IndexedStr<'static>); 2]>)
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -81,6 +75,12 @@ pub(crate) enum Source {
None
}
impl From<Cow<'static, str>> for Source {
fn from(custom: Cow<'static, str>) -> Source {
Source::Custom(custom)
}
}
macro_rules! media_types {
($($name:ident ($check:ident): $str:expr, $t:expr,
$s:expr $(; $k:expr => $v:expr)*,)+) => {
@ -169,6 +169,47 @@ macro_rules! from_extension {
);)
}
macro_rules! extension {
($($ext:expr => $name:ident,)*) => (
docify!([
Returns the most common file extension associated with the @[Media-Type]
@code{self} if it is known. Otherwise, returns @code{None}. The
currently recognized extensions are identical to those in
@{"[`MediaType::from_extension()`]"} with the @{"most common"} extension
being the first extension appearing in the list for a given
@[Media-Type].
];
/// # Example
///
/// Known extension:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::MediaType;
///
/// assert_eq!(MediaType::JSON.extension().unwrap(), "json");
/// assert_eq!(MediaType::JPEG.extension().unwrap(), "jpeg");
/// assert_eq!(MediaType::JPEG.extension().unwrap(), "JPEG");
/// assert_eq!(MediaType::PDF.extension().unwrap(), "pdf");
/// ```
///
/// An unknown extension:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::MediaType;
///
/// let foo = MediaType::new("foo", "bar");
/// assert!(foo.extension().is_none());
/// ```
#[inline]
pub fn extension(&self) -> Option<&UncasedStr> {
$(if self == &MediaType::$name { return Some($ext.into()) })*
None
}
);)
}
macro_rules! parse_flexible {
($($short:expr => $name:ident,)*) => (
docify!([
@ -353,6 +394,14 @@ impl MediaType {
}
}
pub(crate) fn known_source(&self) -> Option<&'static str> {
match self.source {
Source::Known(string) => Some(string),
Source::Custom(Cow::Borrowed(string)) => Some(string),
_ => None
}
}
known_shorthands!(parse_flexible);
known_extensions!(from_extension);
@ -482,8 +531,9 @@ impl MediaType {
/// use rocket::http::MediaType;
///
/// let plain = MediaType::Plain;
/// let plain_params: Vec<_> = plain.params().collect();
/// assert_eq!(plain_params, vec![("charset", "utf-8")]);
/// let (key, val) = plain.params().next().unwrap();
/// assert_eq!(key, "charset");
/// assert_eq!(val, "utf-8");
/// ```
///
/// The `MediaType::PNG` type has no parameters:
@ -496,8 +546,8 @@ impl MediaType {
/// assert_eq!(png.params().count(), 0);
/// ```
#[inline]
pub fn params<'a>(&'a self) -> impl Iterator<Item=(&'a str, &'a str)> + 'a {
match self.params {
pub fn params<'a>(&'a self) -> impl Iterator<Item=(&'a UncasedStr, &'a str)> + 'a {
let raw = match self.params {
MediaParams::Static(ref slice) => Either::Left(slice.iter().cloned()),
MediaParams::Dynamic(ref vec) => {
Either::Right(vec.iter().map(move |&(ref key, ref val)| {
@ -505,9 +555,22 @@ impl MediaType {
(key.from_source(source_str), val.from_source(source_str))
}))
}
}
};
raw.map(|(k, v)| (k.into(), v))
}
/// Returns the first parameter with name `name`, if there is any.
#[inline]
pub fn param<'a>(&'a self, name: &str) -> Option<&'a str> {
self.params()
.filter(|(k, _)| *k == name)
.map(|(_, v)| v)
.next()
}
known_extensions!(extension);
known_media_types!(media_types);
}
@ -544,7 +607,7 @@ impl Hash for MediaType {
impl fmt::Display for MediaType {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Source::Known(src) = self.source {
if let Some(src) = self.known_source() {
src.fmt(f)
} else {
write!(f, "{}/{}", self.top(), self.sub())?;
@ -563,8 +626,10 @@ impl Default for MediaParams {
}
}
impl Extend<(IndexedString, IndexedString)> for MediaParams {
fn extend<T: IntoIterator<Item = (IndexedString, IndexedString)>>(&mut self, iter: T) {
impl Extend<(IndexedStr<'static>, IndexedStr<'static>)> for MediaParams {
fn extend<T>(&mut self, iter: T)
where T: IntoIterator<Item = (IndexedStr<'static>, IndexedStr<'static>)>
{
match self {
MediaParams::Static(..) => panic!("can't add to static collection!"),
MediaParams::Dynamic(ref mut v) => v.extend(iter)

View File

@ -0,0 +1,13 @@
#[macro_use]
mod known_media_types;
mod media_type;
mod content_type;
mod accept;
mod header;
pub use self::content_type::ContentType;
pub use self::accept::{Accept, QMediaType};
pub use self::media_type::MediaType;
pub use self::header::{Header, HeaderMap};
pub(crate) use self::media_type::Source;

View File

@ -3,6 +3,7 @@
#![cfg_attr(nightly, feature(doc_cfg))]
#![warn(rust_2018_idioms)]
#![warn(missing_docs)]
//! Types that map to concepts in HTTP.
//!
@ -12,12 +13,16 @@
//!
//! [#17]: https://github.com/SergioBenitez/Rocket/issues/17
#[macro_use] extern crate pear;
#[macro_use]
extern crate pear;
pub mod hyper;
pub mod uri;
pub mod ext;
#[macro_use]
mod docify;
#[doc(hidden)]
#[cfg(feature = "tls")]
pub mod tls;
@ -26,16 +31,10 @@ pub mod tls;
pub mod route;
#[macro_use]
mod docify;
#[macro_use]
mod known_media_types;
mod header;
mod cookies;
mod method;
mod media_type;
mod content_type;
mod status;
mod header;
mod accept;
mod raw_str;
mod parse;
mod listener;
@ -64,10 +63,7 @@ pub mod private {
}
pub use crate::method::Method;
pub use crate::content_type::ContentType;
pub use crate::accept::{Accept, QMediaType};
pub use crate::status::{Status, StatusClass};
pub use crate::header::{Header, HeaderMap};
pub use crate::raw_str::RawStr;
pub use crate::media_type::MediaType;
pub use crate::cookies::{Cookie, CookieJar, SameSite};
pub use crate::header::*;

View File

@ -18,6 +18,7 @@ use tokio::net::{TcpListener, TcpStream};
// that they could be introduced in upstream libraries.
/// A 'Listener' yields incoming connections
pub trait Listener {
/// The connection type returned by this listener.
type Connection: Connection;
/// Return the actual address this listener bound to.
@ -29,6 +30,7 @@ pub trait Listener {
/// A 'Connection' represents an open connection to a client
pub trait Connection: AsyncRead + AsyncWrite {
/// The remote address, i.e. the client's socket address.
fn remote_addr(&self) -> Option<SocketAddr>;
}
@ -150,6 +152,7 @@ impl<L: fmt::Debug> fmt::Debug for Incoming<L> {
}
}
/// Binds a TCP listener to `address` and returns it.
pub async fn bind_tcp(address: SocketAddr) -> io::Result<TcpListener> {
Ok(TcpListener::bind(address).await?)
}

View File

@ -8,14 +8,23 @@ use self::Method::*;
/// Representation of HTTP methods.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Method {
/// The `GET` variant.
Get,
/// The `PUT` variant.
Put,
/// The `POST` variant.
Post,
/// The `DELETE` variant.
Delete,
/// The `OPTIONS` variant.
Options,
/// The `HEAD` variant.
Head,
/// The `TRACE` variant.
Trace,
/// The `CONNECT` variant.
Connect,
/// The `PATCH` variant.
Patch
}

View File

@ -11,8 +11,13 @@ type Result<'a, T> = pear::input::Result<T, Input<'a>>;
#[parser]
fn weighted_media_type<'a>(input: &mut Input<'a>) -> Result<'a, QMediaType> {
let media_type = media_type()?;
let weight = match media_type.params().next() {
Some(("q", value)) if value.len() <= 5 => match value.parse::<f32>().ok() {
let q = match media_type.params().next() {
Some((name, value)) if name == "q" => Some(value),
_ => None
};
let weight = match q {
Some(value) if value.len() <= 5 => match value.parse::<f32>().ok() {
Some(q) if q > 1. => parse_error!("q value must be <= 1")?,
Some(q) if q < 0. => parse_error!("q value must be > 0")?,
Some(q) => Some(q),

View File

@ -10,7 +10,6 @@ use crate::ext::IntoOwned;
pub use pear::input::Extent;
pub type IndexedString = Indexed<'static, str>;
pub type IndexedStr<'a> = Indexed<'a, str>;
pub type IndexedBytes<'a> = Indexed<'a, [u8]>;
@ -32,9 +31,12 @@ impl AsPtr for [u8] {
}
}
/// Either a concrete string or indices to the start and end of a string.
#[derive(PartialEq)]
pub enum Indexed<'a, T: ?Sized + ToOwned> {
/// The start and end index of a string.
Indexed(usize, usize),
/// A conrete string.
Concrete(Cow<'a, T>)
}
@ -111,16 +113,18 @@ impl<'a, T: ?Sized + ToOwned + 'a> Add for Indexed<'a, T> {
impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T>
where T: Length + AsPtr + Index<Range<usize>, Output = T>
{
// Returns `None` if `needle` is not a substring of `haystack`.
/// Returns `None` if `needle` is not a substring of `haystack`. Otherwise
/// returns an `Indexed` with the indices of `needle` in `haystack`.
pub fn checked_from(needle: &T, haystack: &T) -> Option<Indexed<'a, T>> {
let haystack_start = haystack.as_ptr() as usize;
let needle_start = needle.as_ptr() as usize;
let haystack_start = haystack.as_ptr() as usize;
if needle_start < haystack_start {
return None;
}
if (needle_start + needle.len()) > (haystack_start + haystack.len()) {
let needle_end = needle_start + needle.len();
let haystack_end = haystack_start + haystack.len();
if needle_end > haystack_end {
return None;
}
@ -129,7 +133,13 @@ impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T>
Some(Indexed::Indexed(start, end))
}
// Caller must ensure that `needle` is a substring of `haystack`.
/// Like `checked_from` but without checking if `needle` is indeed a
/// substring of `haystack`.
///
/// # Safety
///
/// The caller must ensure that `needle` is indeed a substring of
/// `haystack`.
pub unsafe fn unchecked_from(needle: &T, haystack: &T) -> Indexed<'a, T> {
let haystack_start = haystack.as_ptr() as usize;
let needle_start = needle.as_ptr() as usize;
@ -148,7 +158,7 @@ impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T>
}
}
/// Whether this string is derived from indexes or not.
/// Whether this string is empty.
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0

View File

@ -5,7 +5,7 @@ use pear::combinators::{prefixed_series, surrounded};
use pear::macros::{parser, switch, parse};
use pear::parsers::*;
use crate::media_type::{MediaType, Source};
use crate::header::{MediaType, Source};
use crate::parse::checkers::{is_whitespace, is_valid_token};
type Input<'a> = pear::input::Pear<pear::input::Cursor<&'a str>>;

View File

@ -14,8 +14,8 @@ use crate::ext::IntoOwned;
/// `Display` implementation. In other words, by printing a value of this type.
#[derive(Debug)]
pub struct Error<'a> {
expected: Expected<u8, Cow<'a, [u8]>>,
index: usize,
pub(crate) expected: Expected<u8, Cow<'a, [u8]>>,
pub(crate) index: usize,
}
impl<'a> From<ParseError<RawInput<'a>>> for Error<'a> {

View File

@ -6,7 +6,7 @@ pub(crate) mod tables;
use crate::uri::{Uri, Origin, Absolute, Authority};
use self::parser::{uri, origin, authority_only, absolute_only, rocket_route_origin};
use self::parser::{uri, origin, authority_only, absolute_only};
pub use self::error::Error;
@ -22,11 +22,6 @@ pub fn origin_from_str(s: &str) -> Result<Origin<'_>, Error<'_>> {
Ok(parse!(origin: RawInput::new(s.as_bytes()))?)
}
#[inline]
pub fn route_origin_from_str(s: &str) -> Result<Origin<'_>, Error<'_>> {
Ok(parse!(rocket_route_origin: RawInput::new(s.as_bytes()))?)
}
#[inline]
pub fn authority_from_str(s: &str) -> Result<Authority<'_>, Error<'_>> {
Ok(parse!(authority_only: RawInput::new(s.as_bytes()))?)

View File

@ -3,7 +3,7 @@ use pear::input::{Extent, Rewind};
use pear::macros::{parser, switch, parse_current_marker, parse_error, parse_try};
use crate::uri::{Uri, Origin, Authority, Absolute, Host};
use crate::parse::uri::tables::{is_reg_name_char, is_pchar, is_qchar, is_rchar};
use crate::parse::uri::tables::{is_reg_name_char, is_pchar, is_qchar};
use crate::parse::uri::RawInput;
type Result<'a, T> = pear::input::Result<T, RawInput<'a>>;
@ -35,13 +35,6 @@ pub fn origin<'a>(input: &mut RawInput<'a>) -> Result<'a, Origin<'a>> {
(peek(b'/')?, path_and_query(is_pchar, is_qchar)?).1
}
#[parser]
pub fn rocket_route_origin<'a>(input: &mut RawInput<'a>) -> Result<'a, Origin<'a>> {
fn is_pchar_or_rchar(c: &u8) -> bool { is_pchar(c) || is_rchar(c) }
fn is_qchar_or_rchar(c: &u8) -> bool { is_qchar(c) || is_rchar(c) }
(peek(b'/')?, path_and_query(is_pchar_or_rchar, is_qchar_or_rchar)?).1
}
#[parser]
fn path_and_query<'a, F, Q>(
input: &mut RawInput<'a>,
@ -57,7 +50,7 @@ fn path_and_query<'a, F, Q>(
parse_error!("expected path or query, found neither")?
} else {
// We know the string is ASCII because of the `is_char` checks above.
Ok(unsafe {Origin::raw(input.start.into(), path.into(), query.map(|q| q.into())) })
Ok(unsafe { Origin::raw(input.start.into(), path.into(), query.map(|q| q.into())) })
}
}

View File

@ -1,3 +1,5 @@
/// Takes a set of sets of byte characters, return a 2^8 array with non-zero
/// values at the indices corresponding to the character byte values.
const fn char_table(sets: &[&[u8]]) -> [u8; 256] {
let mut table = [0u8; 256];
@ -40,10 +42,6 @@ pub const PATH_CHARS: [u8; 256] = char_table(&[
UNRESERVED, PCT_ENCODED, SUB_DELIMS, &[b':', b'@', b'/']
]);
const ROUTE_CHARS: [u8; 256] = char_table(&[&[
b'<', b'>'
]]);
const QUERY_CHARS: [u8; 256] = char_table(&[
&PATH_CHARS, &[b'/', b'?'],
@ -59,9 +57,6 @@ const REG_NAME_CHARS: [u8; 256] = char_table(&[
#[inline(always)]
pub const fn is_pchar(&c: &u8) -> bool { PATH_CHARS[c as usize] != 0 }
#[inline(always)]
pub const fn is_rchar(&c: &u8) -> bool { ROUTE_CHARS[c as usize] != 0 }
#[inline(always)]
pub const fn is_qchar(&c: &u8) -> bool { QUERY_CHARS[c as usize] != 0 }
@ -82,7 +77,6 @@ mod tests {
fn check_tables() {
test_char_table(&super::PATH_CHARS[..]);
test_char_table(&super::QUERY_CHARS[..]);
test_char_table(&super::ROUTE_CHARS[..]);
test_char_table(&super::REG_NAME_CHARS[..]);
}
}

View File

@ -1,4 +1,3 @@
use std::ops::{Deref, DerefMut};
use std::borrow::Cow;
use std::convert::AsRef;
use std::cmp::Ordering;
@ -6,6 +5,7 @@ use std::str::Utf8Error;
use std::fmt;
use ref_cast::RefCast;
use stable_pattern::{Pattern, ReverseSearcher, Split, SplitInternal};
use crate::uncased::UncasedStr;
@ -52,11 +52,11 @@ use crate::uncased::UncasedStr;
/// [`FromParam`]: rocket::request::FromParam
/// [`FromFormValue`]: rocket::request::FromFormValue
#[repr(transparent)]
#[derive(RefCast, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(RefCast, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawStr(str);
impl RawStr {
/// Constructs an `&RawStr` from an `&str` at no cost.
/// Constructs an `&RawStr` from a string-like type at no cost.
///
/// # Example
///
@ -64,14 +64,18 @@ impl RawStr {
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Hello, world!");
/// let raw_str = RawStr::new("Hello, world!");
///
/// // `into` can also be used; note that the type must be specified
/// let raw_str: &RawStr = "Hello, world!".into();
/// ```
#[inline(always)]
pub fn from_str(string: &str) -> &RawStr {
string.into()
pub fn new<S: AsRef<str> + ?Sized>(string: &S) -> &RawStr {
RawStr::ref_cast(string.as_ref())
}
/// Performs percent decoding.
fn _percent_decode(&self) -> percent_encoding::PercentDecode<'_> {
percent_encoding::percent_decode(self.as_bytes())
}
/// Returns a percent-decoded version of the string.
@ -88,7 +92,7 @@ impl RawStr {
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Hello%21");
/// let raw_str = RawStr::new("Hello%21");
/// let decoded = raw_str.percent_decode();
/// assert_eq!(decoded, Ok("Hello!".into()));
/// ```
@ -101,12 +105,12 @@ impl RawStr {
///
/// // Note: Rocket should never hand you a bad `&RawStr`.
/// let bad_str = unsafe { std::str::from_utf8_unchecked(b"a=\xff") };
/// let bad_raw_str = RawStr::from_str(bad_str);
/// let bad_raw_str = RawStr::new(bad_str);
/// assert!(bad_raw_str.percent_decode().is_err());
/// ```
#[inline(always)]
pub fn percent_decode(&self) -> Result<Cow<'_, str>, Utf8Error> {
percent_encoding::percent_decode(self.as_bytes()).decode_utf8()
self._percent_decode().decode_utf8()
}
/// Returns a percent-decoded version of the string. Any invalid UTF-8
@ -121,7 +125,7 @@ impl RawStr {
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Hello%21");
/// let raw_str = RawStr::new("Hello%21");
/// let decoded = raw_str.percent_decode_lossy();
/// assert_eq!(decoded, "Hello!");
/// ```
@ -134,12 +138,30 @@ impl RawStr {
///
/// // Note: Rocket should never hand you a bad `&RawStr`.
/// let bad_str = unsafe { std::str::from_utf8_unchecked(b"a=\xff") };
/// let bad_raw_str = RawStr::from_str(bad_str);
/// let bad_raw_str = RawStr::new(bad_str);
/// assert_eq!(bad_raw_str.percent_decode_lossy(), "a=<3D>");
/// ```
#[inline(always)]
pub fn percent_decode_lossy(&self) -> Cow<'_, str> {
percent_encoding::percent_decode(self.as_bytes()).decode_utf8_lossy()
self._percent_decode().decode_utf8_lossy()
}
/// Replaces '+' with ' ' in `self`, allocating only when necessary.
fn _replace_plus(&self) -> Cow<'_, str> {
let string = self.as_str();
let mut allocated = String::new(); // this is allocation free
for i in memchr::memchr_iter(b'+', string.as_bytes()) {
if allocated.is_empty() {
allocated = string.into();
}
unsafe { allocated.as_bytes_mut()[i] = b' '; }
}
match allocated.is_empty() {
true => Cow::Borrowed(string),
false => Cow::Owned(allocated)
}
}
/// Returns a URL-decoded version of the string. This is identical to
@ -156,16 +178,16 @@ impl RawStr {
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str: &RawStr = "Hello%2C+world%21".into();
/// let raw_str = RawStr::new("Hello%2C+world%21");
/// let decoded = raw_str.url_decode();
/// assert_eq!(decoded, Ok("Hello, world!".to_string()));
/// assert_eq!(decoded.unwrap(), "Hello, world!");
/// ```
pub fn url_decode(&self) -> Result<String, Utf8Error> {
// TODO: Make this more efficient!
let replaced = self.replace("+", " ");
RawStr::from_str(replaced.as_str())
.percent_decode()
.map(|cow| cow.into_owned())
pub fn url_decode(&self) -> Result<Cow<'_, str>, Utf8Error> {
let string = self._replace_plus();
match percent_encoding::percent_decode(string.as_bytes()).decode_utf8()? {
Cow::Owned(s) => Ok(Cow::Owned(s)),
Cow::Borrowed(_) => Ok(string)
}
}
/// Returns a URL-decoded version of the string.
@ -196,14 +218,15 @@ impl RawStr {
///
/// // Note: Rocket should never hand you a bad `&RawStr`.
/// let bad_str = unsafe { std::str::from_utf8_unchecked(b"a+b=\xff") };
/// let bad_raw_str = RawStr::from_str(bad_str);
/// let bad_raw_str = RawStr::new(bad_str);
/// assert_eq!(bad_raw_str.url_decode_lossy(), "a b=<3D>");
/// ```
pub fn url_decode_lossy(&self) -> String {
let replaced = self.replace("+", " ");
RawStr::from_str(replaced.as_str())
.percent_decode_lossy()
.into_owned()
pub fn url_decode_lossy(&self) -> Cow<'_, str> {
let string = self._replace_plus();
match percent_encoding::percent_decode(string.as_bytes()).decode_utf8_lossy() {
Cow::Owned(s) => Cow::Owned(s),
Cow::Borrowed(_) => string
}
}
/// Returns an HTML escaped version of `self`. Allocates only when
@ -247,6 +270,9 @@ impl RawStr {
/// let escaped = raw_str.html_escape();
/// assert_eq!(escaped, "大阪");
/// ```
// NOTE: This is the ~fastest (a table-based implementation is slightly
// faster) implementation benchmarked for dense-ish escaping. For sparser
// texts, a regex-based-find solution is much faster.
pub fn html_escape(&self) -> Cow<'_, str> {
let mut escaped = false;
let mut allocated = Vec::new(); // this is allocation free
@ -312,6 +338,44 @@ impl RawStr {
}
}
/// Returns the length of `self`.
///
/// This length is in bytes, not [`char`]s or graphemes. In other words,
/// it may not be what a human considers the length of the string.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::new("Hello, world!");
/// assert_eq!(raw_str.len(), 13);
/// ```
#[inline]
pub const fn len(&self) -> usize {
self.0.len()
}
/// Returns `true` if `self` has a length of zero bytes.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::new("Hello, world!");
/// assert!(!raw_str.is_empty());
///
/// let raw_str = RawStr::new("");
/// assert!(raw_str.is_empty());
/// ```
#[inline]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
/// Converts `self` into an `&str`.
///
/// This method should be used sparingly. **Only use this method when you
@ -323,12 +387,54 @@ impl RawStr {
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Hello, world!");
/// let raw_str = RawStr::new("Hello, world!");
/// assert_eq!(raw_str.as_str(), "Hello, world!");
/// ```
#[inline(always)]
pub fn as_str(&self) -> &str {
self
pub const fn as_str(&self) -> &str {
&self.0
}
/// Converts `self` into an `&[u8]`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::new("hi");
/// assert_eq!(raw_str.as_bytes(), &[0x68, 0x69]);
/// ```
#[inline(always)]
pub const fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
/// Converts a string slice to a raw pointer.
///
/// As string slices are a slice of bytes, the raw pointer points to a
/// [`u8`]. This pointer will be pointing to the first byte of the string
/// slice.
///
/// The caller must ensure that the returned pointer is never written to.
/// If you need to mutate the contents of the string slice, use [`as_mut_ptr`].
///
/// [`as_mut_ptr`]: str::as_mut_ptr
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::new("hi");
/// let ptr = raw_str.as_ptr();
/// ```
pub const fn as_ptr(&self) -> *const u8 {
self.as_str().as_ptr()
}
/// Converts `self` into an `&UncasedStr`.
@ -342,54 +448,317 @@ impl RawStr {
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Content-Type");
/// let raw_str = RawStr::new("Content-Type");
/// assert!(raw_str.as_uncased_str() == "content-TYPE");
/// ```
#[inline(always)]
pub fn as_uncased_str(&self) -> &UncasedStr {
self.as_str().into()
}
/// Returns `true` if the given pattern matches a sub-slice of
/// this string slice.
///
/// Returns `false` if it does not.
///
/// The pattern can be a `&str`, [`char`], a slice of [`char`]s, or a
/// function or closure that determines if a character matches.
///
/// [`char`]: prim@char
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let bananas = RawStr::new("bananas");
///
/// assert!(bananas.contains("nana"));
/// assert!(!bananas.contains("apples"));
/// ```
#[inline]
pub fn contains<'a, P: Pattern<'a>>(&'a self, pat: P) -> bool {
pat.is_contained_in(self.as_str())
}
/// Returns `true` if the given pattern matches a prefix of this
/// string slice.
///
/// Returns `false` if it does not.
///
/// The pattern can be a `&str`, [`char`], a slice of [`char`]s, or a
/// function or closure that determines if a character matches.
///
/// [`char`]: prim@char
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let bananas = RawStr::new("bananas");
///
/// assert!(bananas.starts_with("bana"));
/// assert!(!bananas.starts_with("nana"));
/// ```
pub fn starts_with<'a, P: Pattern<'a>>(&'a self, pat: P) -> bool {
pat.is_prefix_of(self.as_str())
}
/// Returns `true` if the given pattern matches a suffix of this
/// string slice.
///
/// Returns `false` if it does not.
///
/// The pattern can be a `&str`, [`char`], a slice of [`char`]s, or a
/// function or closure that determines if a character matches.
///
/// [`char`]: prim@char
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let bananas = RawStr::new("bananas");
///
/// assert!(bananas.ends_with("anas"));
/// assert!(!bananas.ends_with("nana"));
/// ```
pub fn ends_with<'a, P>(&'a self, pat: P) -> bool
where P: Pattern<'a>, <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>
{
pat.is_suffix_of(self.as_str())
}
/// An iterator over substrings of this string slice, separated by
/// characters matched by a pattern.
///
/// The pattern can be a `&str`, [`char`], a slice of [`char`]s, or a
/// function or closure that determines if a character matches.
///
/// [`char`]: prim@char
///
/// # Examples
///
/// Simple patterns:
///
/// ```
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let v: Vec<_> = RawStr::new("Mary had a little lamb")
/// .split(' ')
/// .map(|r| r.as_str())
/// .collect();
///
/// assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
/// ```
#[inline]
pub fn split<'a, P>(&'a self, pat: P) -> impl Iterator<Item = &'a RawStr>
where P: Pattern<'a>
{
let split: Split<'_, P> = Split(SplitInternal {
start: 0,
end: self.len(),
matcher: pat.into_searcher(self.as_str()),
allow_trailing_empty: true,
finished: false,
});
split.map(|s| s.into())
}
/// Splits `self` into two pieces: the piece _before_ the first byte `b` and
/// the piece _after_ (not including `b`). Returns the tuple (`before`,
/// `after`). If `b` is not in `self`, or `b` is not an ASCII characters,
/// returns the entire string `self` as `before` and the empty string as
/// `after`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let haystack = RawStr::new("a good boy!");
///
/// let (before, after) = haystack.split_at_byte(b'a');
/// assert_eq!(before, "");
/// assert_eq!(after, " good boy!");
///
/// let (before, after) = haystack.split_at_byte(b' ');
/// assert_eq!(before, "a");
/// assert_eq!(after, "good boy!");
///
/// let (before, after) = haystack.split_at_byte(b'o');
/// assert_eq!(before, "a g");
/// assert_eq!(after, "od boy!");
///
/// let (before, after) = haystack.split_at_byte(b'!');
/// assert_eq!(before, "a good boy");
/// assert_eq!(after, "");
///
/// let (before, after) = haystack.split_at_byte(b'?');
/// assert_eq!(before, "a good boy!");
/// assert_eq!(after, "");
///
/// let haystack = RawStr::new("");
/// let (before, after) = haystack.split_at_byte(b' ');
/// assert_eq!(before, "");
/// assert_eq!(after, "");
/// ```
#[inline]
pub fn split_at_byte(&self, b: u8) -> (&RawStr, &RawStr) {
if !b.is_ascii() {
return (self, &self[0..0]);
}
match memchr::memchr(b, self.as_bytes()) {
// SAFETY: `b` is a character boundary since it's ASCII, `i` is in
// bounds in `self` (or else None), and i is at most len - 1, so i +
// 1 is at most len.
Some(i) => unsafe {
let s = self.as_str();
let start = s.get_unchecked(0..i);
let end = s.get_unchecked((i + 1)..self.len());
(start.into(), end.into())
},
None => (self, &self[0..0])
}
}
/// Parses this string slice into another type.
///
/// Because `parse` is so general, it can cause problems with type
/// inference. As such, `parse` is one of the few times you'll see
/// the syntax affectionately known as the 'turbofish': `::<>`. This
/// helps the inference algorithm understand specifically which type
/// you're trying to parse into.
///
/// `parse` can parse any type that implements the [`FromStr`] trait.
///
/// # Errors
///
/// Will return [`Err`] if it's not possible to parse this string slice into
/// the desired type.
///
/// [`Err`]: FromStr::Err
///
/// # Examples
///
/// Basic usage
///
/// ```
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let four: u32 = RawStr::new("4").parse().unwrap();
///
/// assert_eq!(4, four);
/// ```
#[inline]
pub fn parse<F: std::str::FromStr>(&self) -> Result<F, F::Err> {
std::str::FromStr::from_str(self.as_str())
}
}
#[cfg(feature = "serde")]
mod serde {
use _serde::{ser, de, Serialize, Deserialize};
use super::*;
impl Serialize for RawStr {
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where S: ser::Serializer
{
self.as_str().serialize(ser)
}
}
impl<'de: 'a, 'a> Deserialize<'de> for &'a RawStr {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where D: de::Deserializer<'de>
{
<&'a str as Deserialize<'de>>::deserialize(de).map(RawStr::new)
}
}
}
impl fmt::Debug for RawStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> From<&'a str> for &'a RawStr {
#[inline(always)]
fn from(string: &'a str) -> &'a RawStr {
RawStr::ref_cast(string)
RawStr::new(string)
}
}
impl PartialEq<str> for RawStr {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
macro_rules! impl_partial {
($A:ty : $B:ty) => (
impl PartialEq<$A> for $B {
#[inline(always)]
fn eq(&self, other: &$A) -> bool {
let left: &str = self.as_ref();
let right: &str = other.as_ref();
left == right
}
}
impl PartialOrd<$A> for $B {
#[inline(always)]
fn partial_cmp(&self, other: &$A) -> Option<Ordering> {
let left: &str = self.as_ref();
let right: &str = other.as_ref();
left.partial_cmp(right)
}
}
)
}
impl PartialEq<String> for RawStr {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl_partial!(RawStr : &RawStr);
impl_partial!(&RawStr : RawStr);
impl PartialEq<String> for &'_ RawStr {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl_partial!(str : RawStr);
impl_partial!(str : &RawStr);
impl_partial!(&str : RawStr);
impl_partial!(&&str : RawStr);
impl PartialOrd<str> for RawStr {
#[inline(always)]
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
(self as &str).partial_cmp(other)
}
}
impl_partial!(Cow<'_, str> : RawStr);
impl_partial!(Cow<'_, str> : &RawStr);
impl_partial!(RawStr : Cow<'_, str>);
impl_partial!(&RawStr : Cow<'_, str>);
impl_partial!(String : RawStr);
impl_partial!(String : &RawStr);
impl_partial!(RawStr : String);
impl_partial!(&RawStr : String);
impl_partial!(RawStr : str);
impl_partial!(RawStr : &str);
impl_partial!(RawStr : &&str);
impl_partial!(&RawStr : str);
impl AsRef<str> for RawStr {
#[inline(always)]
fn as_ref(&self) -> &str {
self
self.as_str()
}
}
@ -400,19 +769,26 @@ impl AsRef<[u8]> for RawStr {
}
}
impl Deref for RawStr {
type Target = str;
impl<I: core::slice::SliceIndex<str, Output=str>> core::ops::Index<I> for RawStr {
type Output = RawStr;
#[inline(always)]
fn deref(&self) -> &str {
&self.0
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.as_str()[index].into()
}
}
impl DerefMut for RawStr {
impl std::borrow::Borrow<str> for RawStr {
#[inline(always)]
fn deref_mut(&mut self) -> &mut str {
&mut self.0
fn borrow(&self) -> &str {
self.as_str()
}
}
impl std::borrow::Borrow<RawStr> for &str {
#[inline(always)]
fn borrow(&self) -> &RawStr {
(*self).into()
}
}
@ -429,10 +805,10 @@ mod tests {
#[test]
fn can_compare() {
let raw_str = RawStr::from_str("abc");
let raw_str = RawStr::new("abc");
assert_eq!(raw_str, "abc");
assert_eq!("abc", raw_str.as_str());
assert_eq!(raw_str, RawStr::from_str("abc"));
assert_eq!(raw_str, RawStr::new("abc"));
assert_eq!(raw_str, "abc".to_string());
assert_eq!("abc".to_string(), raw_str.as_str());
}

View File

@ -5,7 +5,6 @@ use unicode_xid::UnicodeXID;
use crate::ext::IntoOwned;
use crate::uri::{Origin, UriPart, Path, Query};
use crate::uri::encoding::unsafe_percent_encode;
use self::Error::*;
@ -120,8 +119,6 @@ impl<'a, P: UriPart> RouteSegment<'a, P> {
return Err(MissingClose);
} else if segment.contains('>') || segment.contains('<') {
return Err(Malformed);
} else if unsafe_percent_encode::<P>(segment) != segment {
return Err(Uri);
}
Ok(RouteSegment {
@ -132,12 +129,13 @@ impl<'a, P: UriPart> RouteSegment<'a, P> {
})
}
pub fn parse_many(
string: &'a str,
pub fn parse_many<S: AsRef<str> + ?Sized> (
string: &'a S,
) -> impl Iterator<Item = SResult<'_, P>> {
let mut last_multi_seg: Option<&str> = None;
// We check for empty segments when we parse an `Origin` in `FromMeta`.
string.split(P::DELIMITER)
string.as_ref()
.split(P::DELIMITER)
.filter(|s| !s.is_empty())
.enumerate()
.map(move |(i, seg)| {
@ -158,12 +156,12 @@ impl<'a, P: UriPart> RouteSegment<'a, P> {
impl<'a> RouteSegment<'a, Path> {
pub fn parse(uri: &'a Origin<'_>) -> impl Iterator<Item = SResult<'a, Path>> {
Self::parse_many(uri.path())
Self::parse_many(uri.path().as_str())
}
}
impl<'a> RouteSegment<'a, Query> {
pub fn parse(uri: &'a Origin<'_>) -> Option<impl Iterator<Item = SResult<'a, Query>>> {
uri.query().map(|q| Self::parse_many(q))
uri.query().map(|q| Self::parse_many(q.as_str()))
}
}

View File

@ -50,6 +50,26 @@ impl StatusClass {
/// constant should be used; one is declared for every status defined
/// in the HTTP standard.
///
/// # Responding
///
/// To set a custom `Status` on a response, use a [`response::status`]
/// responder. Alternatively, respond with `(Status, T)` where `T: Responder`, but
/// note that the response may require additional headers to be valid as
/// enforced by the types in [`response::status`].
///
/// ```rust
/// # extern crate rocket;
/// # use rocket::get;
/// use rocket::http::Status;
///
/// #[get("/")]
/// fn index() -> (Status, &'static str) {
/// (Status::NotFound, "Hey, there's no index!")
/// }
/// ```
///
/// [`response::status`]: ../response/status/index.html
///
/// ## Example
///
/// A status of `200 OK` can be instantiated via the `Ok` constant:
@ -84,7 +104,7 @@ impl StatusClass {
/// assert_eq!(not_found.reason, "Not Found");
/// assert_eq!(not_found.to_string(), "404 Not Found".to_string());
/// ```
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[derive(Debug, Clone, Copy)]
pub struct Status {
/// The HTTP status code associated with this status.
pub code: u16,
@ -92,6 +112,12 @@ pub struct Status {
pub reason: &'static str
}
impl Default for Status {
fn default() -> Self {
Status::Ok
}
}
macro_rules! ctrs {
($($code:expr, $code_str:expr, $name:ident => $reason:expr),+) => {
$(
@ -277,3 +303,29 @@ impl fmt::Display for Status {
write!(f, "{} {}", self.code, self.reason)
}
}
impl std::hash::Hash for Status {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.code.hash(state)
}
}
impl PartialEq for Status {
fn eq(&self, other: &Self) -> bool {
self.code.eq(&other.code)
}
}
impl Eq for Status { }
impl PartialOrd for Status {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.code.partial_cmp(&other.code)
}
}
impl Ord for Status {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.code.cmp(&other.code)
}
}

View File

@ -139,7 +139,7 @@ impl<'a> Absolute<'a> {
/// assert_eq!(uri.scheme(), "file");
/// let origin = uri.origin().unwrap();
/// assert_eq!(origin.path(), "/web/home.html");
/// assert_eq!(origin.query(), Some("new"));
/// assert_eq!(origin.query().unwrap(), "new");
///
/// let uri = Absolute::parse("https://rocket.rs").expect("valid URI");
/// assert_eq!(uri.origin(), None);
@ -148,9 +148,107 @@ impl<'a> Absolute<'a> {
pub fn origin(&self) -> Option<&Origin<'a>> {
self.origin.as_ref()
}
/// Sets the authority in `self` to `authority` and returns `self`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::{Absolute, Authority};
///
/// let uri = Absolute::parse("https://rocket.rs:80").expect("valid URI");
/// let authority = uri.authority().unwrap();
/// assert_eq!(authority.host(), "rocket.rs");
/// assert_eq!(authority.port(), Some(80));
///
/// let new_authority = Authority::parse("google.com").unwrap();
/// let uri = uri.with_authority(new_authority);
/// let authority = uri.authority().unwrap();
/// assert_eq!(authority.host(), "google.com");
/// assert_eq!(authority.port(), None);
/// ```
#[inline(always)]
pub fn with_authority(mut self, authority: Authority<'a>) -> Self {
self.set_authority(authority);
self
}
/// Sets the authority in `self` to `authority`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::{Absolute, Authority};
///
/// let mut uri = Absolute::parse("https://rocket.rs:80").expect("valid URI");
/// let authority = uri.authority().unwrap();
/// assert_eq!(authority.host(), "rocket.rs");
/// assert_eq!(authority.port(), Some(80));
///
/// let new_authority = Authority::parse("google.com:443").unwrap();
/// uri.set_authority(new_authority);
/// let authority = uri.authority().unwrap();
/// assert_eq!(authority.host(), "google.com");
/// assert_eq!(authority.port(), Some(443));
/// ```
#[inline(always)]
pub fn set_authority(&mut self, authority: Authority<'a>) {
self.authority = Some(authority);
}
/// Sets the origin in `self` to `origin` and returns `self`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::{Absolute, Origin};
///
/// let mut uri = Absolute::parse("http://rocket.rs/web/?new").unwrap();
/// let origin = uri.origin().unwrap();
/// assert_eq!(origin.path(), "/web/");
/// assert_eq!(origin.query().unwrap(), "new");
///
/// let new_origin = Origin::parse("/launch").unwrap();
/// let uri = uri.with_origin(new_origin);
/// let origin = uri.origin().unwrap();
/// assert_eq!(origin.path(), "/launch");
/// assert_eq!(origin.query(), None);
/// ```
#[inline(always)]
pub fn with_origin(mut self, origin: Origin<'a>) -> Self {
self.set_origin(origin);
self
}
/// Sets the origin in `self` to `origin`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::{Absolute, Origin};
///
/// let mut uri = Absolute::parse("http://rocket.rs/web/?new").unwrap();
/// let origin = uri.origin().unwrap();
/// assert_eq!(origin.path(), "/web/");
/// assert_eq!(origin.query().unwrap(), "new");
///
/// let new_origin = Origin::parse("/launch?when=now").unwrap();
/// uri.set_origin(new_origin);
/// let origin = uri.origin().unwrap();
/// assert_eq!(origin.path(), "/launch");
/// assert_eq!(origin.query().unwrap(), "when=now");
/// ```
#[inline(always)]
pub fn set_origin(&mut self, origin: Origin<'a>) {
self.origin = Some(origin);
}
}
impl<'b> PartialEq<Absolute<'b>> for Absolute<'_> {
impl<'a, 'b> PartialEq<Absolute<'b>> for Absolute<'a> {
fn eq(&self, other: &Absolute<'b>) -> bool {
self.scheme() == other.scheme()
&& self.authority() == other.authority()
@ -173,4 +271,3 @@ impl Display for Absolute<'_> {
Ok(())
}
}

View File

@ -3,6 +3,7 @@ use std::borrow::Cow;
use percent_encoding::{AsciiSet, utf8_percent_encode};
use crate::RawStr;
use crate::uri::{UriPart, Path, Query};
use crate::parse::uri::tables::PATH_CHARS;
@ -79,14 +80,6 @@ impl EncodeSet for DEFAULT_ENCODE_SET {
.add(b'=');
}
pub fn unsafe_percent_encode<P: UriPart>(string: &str) -> Cow<'_, str> {
match P::DELIMITER {
'/' => percent_encode::<UNSAFE_ENCODE_SET<Path>>(string),
'&' => percent_encode::<UNSAFE_ENCODE_SET<Query>>(string),
_ => percent_encode::<DEFAULT_ENCODE_SET>(string)
}
}
pub fn percent_encode<S: EncodeSet + Default>(string: &str) -> Cow<'_, str> {
utf8_percent_encode(string, &S::SET).into()
pub fn percent_encode<S: EncodeSet + Default>(string: &RawStr) -> Cow<'_, str> {
utf8_percent_encode(string.as_str(), &S::SET).into()
}

View File

@ -1,6 +1,5 @@
use std::path::{Path, PathBuf};
use crate::RawStr;
use crate::uri::{self, UriPart, UriDisplay};
/// Conversion trait for parameters used in [`uri!`] invocations.
@ -52,19 +51,7 @@ use crate::uri::{self, UriPart, UriDisplay};
///
/// Because the [`FromUriParam::Target`] type is the same as the input type, the
/// conversion is a no-op and free of cost, allowing an `&str` to be used in
/// place of a `String` without penalty. A similar no-op conversion exists for
/// [`&RawStr`](RawStr):
///
/// ```rust
/// # extern crate rocket;
/// # use rocket::http::uri::{FromUriParam, UriPart};
/// # struct S;
/// # type RawStr = S;
/// impl<'a, 'b, P: UriPart> FromUriParam<P, &'a str> for &'b RawStr {
/// type Target = &'a str;
/// # fn from_uri_param(s: &'a str) -> Self::Target { "hi" }
/// }
/// ```
/// place of a `String` without penalty.
///
/// # Provided Implementations
///
@ -72,7 +59,7 @@ use crate::uri::{self, UriPart, UriDisplay};
///
/// * `String`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `u8`, `u16`,
/// `u32`, `u64`, `u128`, `usize`, `f32`, `f64`, `bool`, `IpAddr`,
/// `Ipv4Addr`, `Ipv6Addr`, `&str`, `&RawStr`, `Cow<str>`
/// `Ipv4Addr`, `Ipv6Addr`, `&str`, `Cow<str>`
///
/// The following types have _identity_ implementations _only in [`Path`]_:
///
@ -87,9 +74,7 @@ use crate::uri::{self, UriPart, UriDisplay};
/// is expected by a route:
///
/// * `&str` to `String`
/// * `&str` to `RawStr`
/// * `String` to `&str`
/// * `String` to `RawStr`
/// * `T` to `Form<T>`
///
/// The following conversions are implemented _only in [`Path`]_:
@ -136,12 +121,11 @@ use crate::uri::{self, UriPart, UriDisplay};
/// # #[macro_use] extern crate rocket;
/// use std::fmt;
///
/// use rocket::http::RawStr;
/// use rocket::http::uri::{Formatter, UriDisplay, FromUriParam, Query};
///
/// #[derive(FromForm)]
/// struct User<'a> {
/// name: &'a RawStr,
/// name: &'a str,
/// nickname: String,
/// }
///
@ -166,12 +150,10 @@ use crate::uri::{self, UriPart, UriDisplay};
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # use std::fmt;
/// use rocket::http::RawStr;
/// use rocket::request::Form;
/// # use rocket::http::uri::{Formatter, UriDisplay, FromUriParam, Query};
/// #
/// # #[derive(FromForm)]
/// # struct User<'a> { name: &'a RawStr, nickname: String, }
/// # struct User<'a> { name: &'a str, nickname: String, }
/// #
/// # impl UriDisplay<Query> for User<'_> {
/// # fn fmt(&self, f: &mut Formatter<Query>) -> fmt::Result {
@ -186,13 +168,13 @@ use crate::uri::{self, UriPart, UriDisplay};
/// # User { name: name.into(), nickname: nickname.to_string() }
/// # }
/// # }
///
/// #
/// #[post("/<name>?<user..>")]
/// fn some_route(name: &RawStr, user: Form<User>) { /* .. */ }
/// fn some_route(name: &str, user: User<'_>) { /* .. */ }
///
/// let uri = uri!(some_route: name = "hey", user = ("Robert Mike", "Bob"));
/// assert_eq!(uri.path(), "/hey");
/// assert_eq!(uri.query(), Some("name=Robert%20Mike&nickname=Bob"));
/// assert_eq!(uri.query().unwrap(), "name=Robert%20Mike&nickname=Bob");
/// ```
///
/// [`uri!`]: crate::uri
@ -306,16 +288,13 @@ impl_from_uri_param_identity! {
impl_from_uri_param_identity! {
('a) &'a str,
('a) &'a RawStr,
('a) Cow<'a, str>
}
impl_conversion_ref! {
('a) &'a str => String,
('a, 'b) &'a str => &'b RawStr,
('a) String => &'a str,
('a) String => &'a RawStr
('a) String => &'a str
}
impl_from_uri_param_identity!([uri::Path] ('a) &'a Path);

View File

@ -53,6 +53,9 @@ mod private {
/// [`UriDisplay`]: crate::uri::UriDisplay
/// [`Formatter`]: crate::uri::Formatter
pub trait UriPart: private::Sealed {
/// The delimiter used to separate components of this URI part.
/// Specifically, `/` for `Path` and `&` for `Query`.
#[doc(hidden)]
const DELIMITER: char;
}

View File

@ -3,7 +3,8 @@ use std::borrow::Cow;
use crate::ext::IntoOwned;
use crate::parse::{Indexed, Extent, IndexedStr};
use crate::uri::{as_utf8_unchecked, Error, Segments};
use crate::uri::{as_utf8_unchecked, Error, UriPart, Query, Path, Segments, QuerySegments};
use crate::RawStr;
use state::Storage;
@ -88,10 +89,12 @@ pub struct Origin<'a> {
pub(crate) source: Option<Cow<'a, str>>,
pub(crate) path: IndexedStr<'a>,
pub(crate) query: Option<IndexedStr<'a>>,
pub(crate) segment_count: Storage<usize>,
pub(crate) decoded_path_segs: Storage<Vec<IndexedStr<'static>>>,
pub(crate) decoded_query_segs: Storage<Vec<(IndexedStr<'static>, IndexedStr<'static>)>>,
}
impl<'b> PartialEq<Origin<'b>> for Origin<'_> {
impl<'a, 'b> PartialEq<Origin<'b>> for Origin<'a> {
fn eq(&self, other: &Origin<'b>) -> bool {
self.path() == other.path() && self.query() == other.query()
}
@ -105,12 +108,34 @@ impl IntoOwned for Origin<'_> {
source: self.source.into_owned(),
path: self.path.into_owned(),
query: self.query.into_owned(),
segment_count: self.segment_count
decoded_path_segs: self.decoded_path_segs.map(|v| v.into_owned()),
decoded_query_segs: self.decoded_query_segs.map(|v| v.into_owned()),
}
}
}
fn decode_to_indexed_str<P: UriPart>(
value: &RawStr,
(indexed, source): (&IndexedStr<'_>, &RawStr)
) -> IndexedStr<'static> {
let decoded = match P::DELIMITER {
Query::DELIMITER => value.url_decode_lossy(),
Path::DELIMITER => value.percent_decode_lossy(),
_ => unreachable!("sealed trait admits only path and query")
};
match decoded {
Cow::Borrowed(b) if indexed.is_indexed() => {
let indexed = IndexedStr::checked_from(b, source.as_str());
debug_assert!(indexed.is_some());
indexed.unwrap_or(IndexedStr::from(Cow::Borrowed("")))
}
cow => IndexedStr::from(Cow::Owned(cow.into_owned())),
}
}
impl<'a> Origin<'a> {
/// SAFETY: `source` must be UTF-8.
#[inline]
pub(crate) unsafe fn raw(
source: Cow<'a, [u8]>,
@ -121,7 +146,9 @@ impl<'a> Origin<'a> {
source: Some(as_utf8_unchecked(source)),
path: path.into(),
query: query.map(|q| q.into()),
segment_count: Storage::new()
decoded_path_segs: Storage::new(),
decoded_query_segs: Storage::new(),
}
}
@ -136,7 +163,8 @@ impl<'a> Origin<'a> {
source: None,
path: Indexed::from(path.into()),
query: query.map(|q| Indexed::from(q.into())),
segment_count: Storage::new()
decoded_path_segs: Storage::new(),
decoded_query_segs: Storage::new(),
}
}
@ -160,7 +188,7 @@ impl<'a> Origin<'a> {
/// // Parse a valid origin URI.
/// let uri = Origin::parse("/a/b/c?query").expect("valid URI");
/// assert_eq!(uri.path(), "/a/b/c");
/// assert_eq!(uri.query(), Some("query"));
/// assert_eq!(uri.query().unwrap(), "query");
///
/// // Invalid URIs fail to parse.
/// Origin::parse("foo bar").expect_err("invalid URI");
@ -169,12 +197,26 @@ impl<'a> Origin<'a> {
crate::parse::uri::origin_from_str(string)
}
// Parses an `Origin` that may contain `<` or `>` characters which are
// invalid according to the RFC but used by Rocket's routing URIs.
// Don't use this outside of Rocket!
// Parses an `Origin` which is allowed to contain _any_ `UTF-8` character.
// The path must still be absolute `/..`. Don't use this outside of Rocket!
#[doc(hidden)]
pub fn parse_route(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
crate::parse::uri::route_origin_from_str(string)
use pear::error::Expected;
if !string.starts_with('/') {
return Err(Error {
expected: Expected::token(Some(&b'/'), string.as_bytes().get(0).cloned()),
index: 0,
});
}
let (path, query) = RawStr::new(string).split_at_byte(b'?');
let query = match query.is_empty() {
false => Some(query.as_str()),
true => None,
};
Ok(Origin::new(path.as_str(), query))
}
/// Parses the string `string` into an `Origin`. Parsing will never
@ -206,25 +248,24 @@ impl<'a> Origin<'a> {
// These two facts can be easily verified. An `&mut` can't be created
// because `string` isn't `mut`. Then, `string` is clearly not dropped
// since it's passed in to `source`.
// let copy_of_str = unsafe { &*(string.as_str() as *const str) };
let copy_of_str = unsafe { &*(string.as_str() as *const str) };
let origin = Origin::parse(copy_of_str)?;
debug_assert!(origin.source.is_some(), "Origin source parsed w/o source");
let uri = match origin {
Origin { source: Some(_), path, query, segment_count } => Origin {
segment_count,
path: path.into_owned(),
query: query.into_owned(),
// At this point, it's impossible for anything to be borrowing
// `string` except for `source`, even though Rust doesn't know
// it. Because we're replacing `source` here, there can't
// possibly be a borrow remaining, it's safe to "move out of the
// borrow".
source: Some(Cow::Owned(string)),
},
_ => unreachable!("parser always parses with a source")
let origin = Origin {
path: origin.path.into_owned(),
query: origin.query.into_owned(),
decoded_path_segs: origin.decoded_path_segs.into_owned(),
decoded_query_segs: origin.decoded_query_segs.into_owned(),
// At this point, it's impossible for anything to be borrowing
// `string` except for `source`, even though Rust doesn't know it.
// Because we're replacing `source` here, there can't possibly be a
// borrow remaining, it's safe to "move out of the borrow".
source: Some(Cow::Owned(string)),
};
Ok(uri)
Ok(origin)
}
/// Returns `true` if `self` is normalized. Otherwise, returns `false`.
@ -248,9 +289,10 @@ impl<'a> Origin<'a> {
/// assert!(!abnormal.is_normalized());
/// ```
pub fn is_normalized(&self) -> bool {
self.path().starts_with('/') &&
!self.path().contains("//") &&
!(self.path().len() > 1 && self.path().ends_with('/'))
let path_str = self.path().as_str();
path_str.starts_with('/') &&
!path_str.contains("//") &&
!(path_str.len() > 1 && path_str.ends_with('/'))
}
/// Normalizes `self`.
@ -276,7 +318,7 @@ impl<'a> Origin<'a> {
self
} else {
let mut new_path = String::with_capacity(self.path().len());
for segment in self.segments() {
for segment in self.raw_path_segments() {
use std::fmt::Write;
let _ = write!(new_path, "/{}", segment);
}
@ -315,8 +357,8 @@ impl<'a> Origin<'a> {
/// assert_eq!(uri.path(), "/a/b/c");
/// ```
#[inline]
pub fn path(&self) -> &str {
self.path.from_cow_source(&self.source)
pub fn path(&self) -> &RawStr {
self.path.from_cow_source(&self.source).into()
}
/// Applies the function `f` to the internal `path` and returns a new
@ -334,14 +376,17 @@ impl<'a> Origin<'a> {
///
/// let old_uri = Origin::parse("/a/b/c").unwrap();
/// let expected_uri = Origin::parse("/a/b/c/").unwrap();
/// assert_eq!(old_uri.map_path(|p| p.to_owned() + "/"), Some(expected_uri));
/// assert_eq!(old_uri.map_path(|p| format!("{}/", p)), Some(expected_uri));
///
/// let old_uri = Origin::parse("/a/b/c/").unwrap();
/// let expected_uri = Origin::parse("/a/b/c//").unwrap();
/// assert_eq!(old_uri.map_path(|p| p.to_owned() + "/"), Some(expected_uri));
/// assert_eq!(old_uri.map_path(|p| format!("{}/", p)), Some(expected_uri));
///
/// let old_uri = Origin::parse("/a/b/c/").unwrap();
/// assert_eq!(old_uri.map_path(|p| format!("hi/{}", p)), None);
/// ```
#[inline]
pub fn map_path<F: FnOnce(&str) -> String>(&self, f: F) -> Option<Self> {
pub fn map_path<F: FnOnce(&RawStr) -> String>(&self, f: F) -> Option<Self> {
let path = f(self.path());
if !path.starts_with('/')
|| !path.bytes().all(|b| crate::parse::uri::tables::is_pchar(&b))
@ -353,7 +398,8 @@ impl<'a> Origin<'a> {
source: self.source.clone(),
path: Cow::from(path).into(),
query: self.query.clone(),
segment_count: Storage::new(),
decoded_path_segs: Storage::new(),
decoded_query_segs: Storage::new(),
})
}
@ -369,7 +415,7 @@ impl<'a> Origin<'a> {
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/a/b/c?alphabet=true").unwrap();
/// assert_eq!(uri.query(), Some("alphabet=true"));
/// assert_eq!(uri.query().unwrap(), "alphabet=true");
/// ```
///
/// A URI without the query part:
@ -382,8 +428,8 @@ impl<'a> Origin<'a> {
/// assert_eq!(uri.query(), None);
/// ```
#[inline]
pub fn query(&self) -> Option<&str> {
self.query.as_ref().map(|q| q.from_cow_source(&self.source))
pub fn query(&self) -> Option<&RawStr> {
self.query.as_ref().map(|q| q.from_cow_source(&self.source).into())
}
/// Removes the query part of this URI, if there is any.
@ -395,7 +441,7 @@ impl<'a> Origin<'a> {
/// use rocket::http::uri::Origin;
///
/// let mut uri = Origin::parse("/a/b/c?query=some").unwrap();
/// assert_eq!(uri.query(), Some("query=some"));
/// assert_eq!(uri.query().unwrap(), "query=some");
///
/// uri.clear_query();
/// assert_eq!(uri.query(), None);
@ -404,8 +450,69 @@ impl<'a> Origin<'a> {
self.query = None;
}
/// Returns an iterator over the segments of the path in this URI. Skips
/// empty segments.
/// Returns a (smart) iterator over the non-empty, percent-decoded segments
/// of the path of this URI.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/a%20b/b%2Fc/d//e?query=some").unwrap();
/// let path_segs: Vec<&str> = uri.path_segments().collect();
/// assert_eq!(path_segs, &["a b", "b/c", "d", "e"]);
/// ```
pub fn path_segments(&self) -> Segments<'_> {
let cached = self.decoded_path_segs.get_or_set(|| {
let (indexed, path) = (&self.path, self.path());
self.raw_path_segments()
.map(|s| decode_to_indexed_str::<Path>(s, (indexed, path)))
.collect()
});
Segments { source: self.path(), segments: cached, pos: 0 }
}
/// Returns a (smart) iterator over the non-empty, url-decoded `(name,
/// value)` pairs of the query of this URI. If there is no query, the
/// iterator is empty.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/").unwrap();
/// let query_segs: Vec<_> = uri.query_segments().collect();
/// assert!(query_segs.is_empty());
///
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
/// let query_segs: Vec<_> = uri.query_segments().collect();
/// assert_eq!(query_segs, &[("a b/", "some one@gmail.com"), ("&=2", "")]);
/// ```
pub fn query_segments(&self) -> QuerySegments<'_> {
let cached = self.decoded_query_segs.get_or_set(|| {
let (indexed, query) = match (self.query.as_ref(), self.query()) {
(Some(i), Some(q)) => (i, q),
_ => return vec![],
};
self.raw_query_segments()
.map(|(name, val)| {
let name = decode_to_indexed_str::<Query>(name, (indexed, query));
let val = decode_to_indexed_str::<Query>(val, (indexed, query));
(name, val)
})
.collect()
});
QuerySegments { source: self.query(), segments: cached, pos: 0 }
}
/// Returns an iterator over the raw, undecoded segments of the path in this
/// URI.
///
/// ### Examples
///
@ -416,7 +523,10 @@ impl<'a> Origin<'a> {
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/a/b/c?a=true").unwrap();
/// for (i, segment) in uri.segments().enumerate() {
/// # let segments: Vec<_> = uri.raw_path_segments().collect();
/// # assert_eq!(segments, &["a", "b", "c"]);
///
/// for (i, segment) in uri.raw_path_segments().enumerate() {
/// match i {
/// 0 => assert_eq!(segment, "a"),
/// 1 => assert_eq!(segment, "b"),
@ -433,7 +543,10 @@ impl<'a> Origin<'a> {
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("///a//b///c////d?query&param").unwrap();
/// for (i, segment) in uri.segments().enumerate() {
/// # let segments: Vec<_> = uri.raw_path_segments().collect();
/// # assert_eq!(segments, &["a", "b", "c", "d"]);
///
/// for (i, segment) in uri.raw_path_segments().enumerate() {
/// match i {
/// 0 => assert_eq!(segment, "a"),
/// 1 => assert_eq!(segment, "b"),
@ -444,40 +557,38 @@ impl<'a> Origin<'a> {
/// }
/// ```
#[inline(always)]
pub fn segments(&self) -> Segments<'_> {
Segments(self.path())
pub fn raw_path_segments(&self) -> impl Iterator<Item = &RawStr> {
self.path().split(Path::DELIMITER).filter(|s| !s.is_empty())
}
/// Returns the number of segments in the URI. Empty segments, which are
/// invalid according to RFC#3986, are not counted.
/// Returns a (smart) iterator over the non-empty, non-url-decoded `(name,
/// value)` pairs of the query of this URI. If there is no query, the
/// iterator is empty.
///
/// The segment count is cached after the first invocation. As a result,
/// this function is O(1) after the first invocation, and O(n) before.
///
/// ### Examples
///
/// A valid URI with only non-empty segments:
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/a/b/c").unwrap();
/// assert_eq!(uri.segment_count(), 3);
/// let uri = Origin::parse("/").unwrap();
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
/// assert!(query_segs.is_empty());
///
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
/// let query_segs: Vec<_> = uri.raw_query_segments()
/// .map(|(name, val)| (name.as_str(), val.as_str()))
/// .collect();
///
/// assert_eq!(query_segs, &[("a+b%2F", "some+one%40gmail.com"), ("%26%3D2", "")]);
/// ```
///
/// A URI with empty segments:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/a/b//c/d///e").unwrap();
/// assert_eq!(uri.segment_count(), 5);
/// ```
#[inline]
pub fn segment_count(&self) -> usize {
*self.segment_count.get_or_set(|| self.segments().count())
#[inline(always)]
pub fn raw_query_segments(&self) -> impl Iterator<Item = (&RawStr, &RawStr)> {
self.query().into_iter().flat_map(|q| {
q.split(Query::DELIMITER)
.filter(|s| !s.is_empty())
.map(|q| q.split_at_byte(b'='))
})
}
}
@ -497,12 +608,14 @@ mod tests {
use super::Origin;
fn seg_count(path: &str, expected: usize) -> bool {
let actual = Origin::parse(path).unwrap().segment_count();
let origin = Origin::parse(path).unwrap();
let segments = origin.path_segments();
let actual = segments.len();
if actual != expected {
eprintln!("Count mismatch: expected {}, got {}.", expected, actual);
eprintln!("{}", if actual != expected { "lifetime" } else { "buf" });
eprintln!("Segments (for {}):", path);
for (i, segment) in Origin::parse(path).unwrap().segments().enumerate() {
for (i, segment) in segments.enumerate() {
eprintln!("{}: {}", i, segment);
}
}
@ -516,7 +629,7 @@ mod tests {
Err(e) => panic!("failed to parse {}: {}", path, e)
};
let actual: Vec<&str> = uri.segments().collect();
let actual: Vec<&str> = uri.path_segments().collect();
actual == expected
}
@ -601,7 +714,7 @@ mod tests {
fn test_query(uri: &str, query: Option<&str>) {
let uri = Origin::parse(uri).unwrap();
assert_eq!(uri.query(), query);
assert_eq!(uri.query().map(|s| s.as_str()), query);
}
#[test]

View File

@ -1,9 +1,11 @@
use std::path::PathBuf;
use std::str::Utf8Error;
use crate::uri::Uri;
use crate::RawStr;
use crate::parse::IndexedStr;
/// Iterator over the segments of an absolute URI path. Skips empty segments.
/// Iterator over the non-empty, percent-decoded segments of a URI path.
///
/// Returned by [`Origin::path_segments()`].
///
/// ### Examples
///
@ -11,27 +13,32 @@ use crate::uri::Uri;
/// # extern crate rocket;
/// use rocket::http::uri::Origin;
///
/// let uri = Origin::parse("/a/////b/c////////d").unwrap();
/// let segments = uri.segments();
/// let uri = Origin::parse("/a%20z/////b/c////////d").unwrap();
/// let segments = uri.path_segments();
/// for (i, segment) in segments.enumerate() {
/// match i {
/// 0 => assert_eq!(segment, "a"),
/// 0 => assert_eq!(segment, "a z"),
/// 1 => assert_eq!(segment, "b"),
/// 2 => assert_eq!(segment, "c"),
/// 3 => assert_eq!(segment, "d"),
/// _ => panic!("only four segments")
/// }
/// }
/// # assert_eq!(uri.path_segments().len(), 4);
/// # assert_eq!(uri.path_segments().count(), 4);
/// # assert_eq!(uri.path_segments().next(), Some("a z"));
/// ```
#[derive(Clone, Debug)]
pub struct Segments<'a>(pub &'a str);
#[derive(Debug, Clone, Copy)]
pub struct Segments<'o> {
pub(super) source: &'o RawStr,
pub(super) segments: &'o [IndexedStr<'static>],
pub(super) pos: usize,
}
/// Errors which can occur when attempting to interpret a segment string as a
/// valid path segment.
/// An error interpreting a segment as a [`PathBuf`] component in
/// [`Segments::to_path_buf()`].
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SegmentError {
/// The segment contained invalid UTF8 characters when percent decoded.
Utf8(Utf8Error),
pub enum PathError {
/// The segment started with the wrapped invalid character.
BadStart(char),
/// The segment contained the wrapped invalid character.
@ -40,10 +47,37 @@ pub enum SegmentError {
BadEnd(char),
}
impl Segments<'_> {
/// Creates a `PathBuf` from a `Segments` iterator. The returned `PathBuf`
/// is percent-decoded. If a segment is equal to "..", the previous segment
/// (if any) is skipped.
impl<'o> Segments<'o> {
/// Returns the number of path segments left.
#[inline]
pub fn len(&self) -> usize {
let max_pos = std::cmp::min(self.pos, self.segments.len());
self.segments.len() - max_pos
}
/// Returns `true` if there are no segments left.
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Skips `n` segments.
#[inline]
pub fn skip(mut self, n: usize) -> Self {
self.pos = std::cmp::min(self.pos + n, self.segments.len());
self
}
/// Get the `n`th segment from the current position.
#[inline]
pub fn get(&self, n: usize) -> Option<&'o str> {
self.segments.get(self.pos + n)
.map(|i| i.from_source(Some(self.source.as_str())))
}
/// Creates a `PathBuf` from `self`. The returned `PathBuf` is
/// percent-decoded. If a segment is equal to "..", the previous segment (if
/// any) is skipped.
///
/// For security purposes, if a segment meets any of the following
/// conditions, an `Err` is returned indicating the condition met:
@ -62,30 +96,27 @@ impl Segments<'_> {
/// As a result of these conditions, a `PathBuf` derived via `FromSegments`
/// is safe to interpolate within, or use as a suffix of, a path without
/// additional checks.
pub fn into_path_buf(self, allow_dotfiles: bool) -> Result<PathBuf, SegmentError> {
pub fn to_path_buf(&self, allow_dotfiles: bool) -> Result<PathBuf, PathError> {
let mut buf = PathBuf::new();
for segment in self {
let decoded = Uri::percent_decode(segment.as_bytes())
.map_err(SegmentError::Utf8)?;
if decoded == ".." {
for segment in self.clone() {
if segment == ".." {
buf.pop();
} else if !allow_dotfiles && decoded.starts_with('.') {
return Err(SegmentError::BadStart('.'))
} else if decoded.starts_with('*') {
return Err(SegmentError::BadStart('*'))
} else if decoded.ends_with(':') {
return Err(SegmentError::BadEnd(':'))
} else if decoded.ends_with('>') {
return Err(SegmentError::BadEnd('>'))
} else if decoded.ends_with('<') {
return Err(SegmentError::BadEnd('<'))
} else if decoded.contains('/') {
return Err(SegmentError::BadChar('/'))
} else if cfg!(windows) && decoded.contains('\\') {
return Err(SegmentError::BadChar('\\'))
} else if !allow_dotfiles && segment.starts_with('.') {
return Err(PathError::BadStart('.'))
} else if segment.starts_with('*') {
return Err(PathError::BadStart('*'))
} else if segment.ends_with(':') {
return Err(PathError::BadEnd(':'))
} else if segment.ends_with('>') {
return Err(PathError::BadEnd('>'))
} else if segment.ends_with('<') {
return Err(PathError::BadEnd('<'))
} else if segment.contains('/') {
return Err(PathError::BadChar('/'))
} else if cfg!(windows) && segment.contains('\\') {
return Err(PathError::BadChar('\\'))
} else {
buf.push(&*decoded)
buf.push(&*segment)
}
}
@ -93,30 +124,70 @@ impl Segments<'_> {
}
}
impl<'a> Iterator for Segments<'a> {
type Item = &'a str;
impl<'o> Iterator for Segments<'o> {
type Item = &'o str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// Find the start of the next segment (first that's not '/').
let i = self.0.find(|c| c != '/')?;
// Get the index of the first character that _is_ a '/' after start.
// j = index of first character after i (hence the i +) that's not a '/'
let j = self.0[i..].find('/').map_or(self.0.len(), |j| i + j);
// Save the result, update the iterator, and return!
let result = Some(&self.0[i..j]);
self.0 = &self.0[j..];
result
let item = self.get(0)?;
self.pos += 1;
Some(item)
}
// TODO: Potentially take a second parameter with Option<cached count> and
// return it here if it's Some. The downside is that a decision has to be
// made about -when- to compute and cache that count. A place to do it is in
// the segments() method. But this means that the count will always be
// computed regardless of whether it's needed. Maybe this is ok. We'll see.
// fn count(self) -> usize where Self: Sized {
// self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1))
// }
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len(), Some(self.len()))
}
fn count(self) -> usize {
self.len()
}
}
/// Decoded query segments iterator.
#[derive(Debug, Clone, Copy)]
pub struct QuerySegments<'o> {
pub(super) source: Option<&'o RawStr>,
pub(super) segments: &'o [(IndexedStr<'static>, IndexedStr<'static>)],
pub(super) pos: usize,
}
impl<'o> QuerySegments<'o> {
/// Returns the number of query segments left.
pub fn len(&self) -> usize {
let max_pos = std::cmp::min(self.pos, self.segments.len());
self.segments.len() - max_pos
}
/// Skip `n` segments.
pub fn skip(mut self, n: usize) -> Self {
self.pos = std::cmp::min(self.pos + n, self.segments.len());
self
}
/// Get the `n`th segment from the current position.
#[inline]
pub fn get(&self, n: usize) -> Option<(&'o str, &'o str)> {
let (name, val) = self.segments.get(self.pos + n)?;
let source = self.source.map(|s| s.as_str());
let name = name.from_source(source);
let val = val.from_source(source);
Some((name, val))
}
}
impl<'o> Iterator for QuerySegments<'o> {
type Item = (&'o str, &'o str);
fn next(&mut self) -> Option<Self::Item> {
let item = self.get(0)?;
self.pos += 1;
Some(item)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len(), Some(self.len()))
}
fn count(self) -> usize {
self.len()
}
}

View File

@ -4,6 +4,7 @@ use std::borrow::Cow;
use std::str::Utf8Error;
use std::convert::TryFrom;
use crate::RawStr;
use crate::ext::IntoOwned;
use crate::parse::Extent;
use crate::uri::{Origin, Authority, Absolute, Error};
@ -96,7 +97,7 @@ impl<'a> Uri<'a> {
/// let uri = Uri::parse("/a/b/c?query").expect("valid URI");
/// let origin = uri.origin().expect("origin URI");
/// assert_eq!(origin.path(), "/a/b/c");
/// assert_eq!(origin.query(), Some("query"));
/// assert_eq!(origin.query().unwrap(), "query");
///
/// // Invalid URIs fail to parse.
/// Uri::parse("foo bar").expect_err("invalid URI");
@ -105,20 +106,6 @@ impl<'a> Uri<'a> {
crate::parse::uri::from_str(string)
}
// pub fn from_hyp(uri: &'a hyper::Uri) -> Uri<'a> {
// match uri.is_absolute() {
// true => Uri::Absolute(Absolute::new(
// uri.scheme().unwrap(),
// match uri.host() {
// Some(host) => Some(Authority::new(None, Host::Raw(host), uri.port())),
// None => None
// },
// None
// )),
// false => Uri::Asterisk
// }
// }
/// Returns the internal instance of `Origin` if `self` is a `Uri::Origin`.
/// Otherwise, returns `None`.
///
@ -197,8 +184,10 @@ impl<'a> Uri<'a> {
/// let encoded = Uri::percent_encode("hello?a=<b>hi</b>");
/// assert_eq!(encoded, "hello%3Fa%3D%3Cb%3Ehi%3C%2Fb%3E");
/// ```
pub fn percent_encode(string: &str) -> Cow<'_, str> {
percent_encode::<DEFAULT_ENCODE_SET>(string)
pub fn percent_encode<S>(string: &S) -> Cow<'_, str>
where S: AsRef<str> + ?Sized
{
percent_encode::<DEFAULT_ENCODE_SET>(RawStr::new(string))
}
/// Returns a URL-decoded version of the string. If the percent encoded
@ -213,8 +202,10 @@ impl<'a> Uri<'a> {
/// let decoded = Uri::percent_decode("/Hello%2C%20world%21".as_bytes());
/// assert_eq!(decoded.unwrap(), "/Hello, world!");
/// ```
pub fn percent_decode(string: &[u8]) -> Result<Cow<'_, str>, Utf8Error> {
let decoder = percent_encoding::percent_decode(string);
pub fn percent_decode<S>(bytes: &S) -> Result<Cow<'_, str>, Utf8Error>
where S: AsRef<[u8]> + ?Sized
{
let decoder = percent_encoding::percent_decode(bytes.as_ref());
decoder.decode_utf8()
}
@ -231,8 +222,10 @@ impl<'a> Uri<'a> {
/// let decoded = Uri::percent_decode_lossy("/Hello%2C%20world%21".as_bytes());
/// assert_eq!(decoded, "/Hello, world!");
/// ```
pub fn percent_decode_lossy(string: &[u8]) -> Cow<'_, str> {
let decoder = percent_encoding::percent_decode(string);
pub fn percent_decode_lossy<S>(bytes: &S) -> Cow<'_, str>
where S: AsRef<[u8]> + ?Sized
{
let decoder = percent_encoding::percent_decode(bytes.as_ref());
decoder.decode_utf8_lossy()
}
}

View File

@ -1,7 +1,6 @@
use std::{fmt, path};
use std::borrow::Cow;
use crate::RawStr;
use crate::uri::{Uri, UriPart, Path, Query, Formatter};
/// Trait implemented by types that can be displayed as part of a URI in
@ -119,7 +118,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
/// The implementation of `UriDisplay` for these types is identical to the
/// `Display` implementation.
///
/// * **[`&RawStr`](RawStr), `String`, `&str`, `Cow<str>`**
/// * **`String`, `&str`, `Cow<str>`**
///
/// The string is percent encoded.
///
@ -237,25 +236,23 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::http::RawStr;
/// use rocket::request::FromParam;
///
/// struct Name(String);
/// struct Name<'r>(&'r str);
///
/// const PREFIX: &str = "name:";
///
/// impl<'r> FromParam<'r> for Name {
/// type Error = &'r RawStr;
/// impl<'r> FromParam<'r> for Name<'r> {
/// type Error = &'r str;
///
/// /// Validates parameters that start with 'name:', extracting the text
/// /// after 'name:' as long as there is at least one character.
/// fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
/// let decoded = param.percent_decode().map_err(|_| param)?;
/// if !decoded.starts_with(PREFIX) || decoded.len() < (PREFIX.len() + 1) {
/// fn from_param(param: &'r str) -> Result<Self, Self::Error> {
/// if !param.starts_with(PREFIX) || param.len() < (PREFIX.len() + 1) {
/// return Err(param);
/// }
///
/// let real_name = decoded[PREFIX.len()..].to_string();
/// let real_name = &param[PREFIX.len()..];
/// Ok(Name(real_name))
/// }
/// }
@ -265,25 +262,25 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
/// use rocket::http::uri::{Formatter, FromUriParam, UriDisplay, Path};
/// use rocket::response::Redirect;
///
/// impl UriDisplay<Path> for Name {
/// // Delegates to the `UriDisplay` implementation for `String` via the
/// // call to `write_value` to ensure that the written string is
/// // URI-safe. In this case, the string will be percent encoded.
/// // Prefixes the inner name with `name:`.
/// impl UriDisplay<Path> for Name<'_> {
/// // Delegates to the `UriDisplay` implementation for `str` via the call
/// // to `write_value` to ensure that the written string is URI-safe. In
/// // this case, the string will be percent encoded. Prefixes the inner
/// // name with `name:`.
/// fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result {
/// f.write_value(&format!("name:{}", self.0))
/// }
/// }
///
/// impl_from_uri_param_identity!([Path] Name);
/// impl_from_uri_param_identity!([Path] ('a) Name<'a>);
///
/// #[get("/name/<name>")]
/// fn redirector(name: Name) -> Redirect {
/// fn redirector(name: Name<'_>) -> Redirect {
/// Redirect::to(uri!(real: name))
/// }
///
/// #[get("/<name>")]
/// fn real(name: Name) -> String {
/// fn real(name: Name<'_>) -> String {
/// format!("Hello, {}!", name.0)
/// }
///
@ -353,14 +350,6 @@ impl_with_display! {
// These are second level implementations: they all defer to an existing
// implementation.
/// Percent-encodes the raw string. Defers to `str`.
impl<P: UriPart> UriDisplay<P> for RawStr {
#[inline(always)]
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
self.as_str().fmt(f)
}
}
/// Percent-encodes the raw string. Defers to `str`.
impl<P: UriPart> UriDisplay<P> for String {
#[inline(always)]

View File

@ -24,18 +24,14 @@ tls = ["rocket_http/tls"]
secrets = ["rocket_http/private-cookies"]
[dependencies]
rocket_codegen = { version = "0.5.0-dev", path = "../codegen" }
rocket_http = { version = "0.5.0-dev", path = "../http" }
futures = "0.3.0"
yansi = "0.5"
log = { version = "0.4", features = ["std"] }
num_cpus = "1.0"
state = "0.4.1"
time = "0.2.11"
memchr = "2" # TODO: Use pear instead.
binascii = "0.1"
atty = "0.2"
async-trait = "0.1"
ref-cast = "1.0"
atomic = "0.5"
parking_lot = "0.11"
@ -44,6 +40,31 @@ serde = { version = "1.0", features = ["derive"] }
figment = { version = "0.10.2", features = ["toml", "env"] }
rand = "0.8"
either = "1"
pin-project-lite = "0.1"
indexmap = { version = "1.0", features = ["serde"] }
tempfile = "3"
[dependencies.state]
git = "https://github.com/SergioBenitez/state.git"
rev = "7576652"
[dependencies.async-trait]
git = "https://github.com/SergioBenitez/async-trait.git"
rev = "0cda89bd"
[dependencies.rocket_codegen]
version = "0.5.0-dev"
path = "../codegen"
[dependencies.rocket_http]
version = "0.5.0-dev"
path = "../http"
features = ["serde"]
[dependencies.multer]
git = "https://github.com/rousan/multer-rs.git"
rev = "0620e54"
features = ["tokio-io"]
[dependencies.tokio]
version = "1.0"

View File

@ -1,8 +1,6 @@
#[macro_use] extern crate rocket;
#[macro_use] extern crate bencher;
use rocket::http::RawStr;
#[get("/")]
fn hello_world() -> &'static str { "Hello, world!" }
@ -22,7 +20,7 @@ fn index_b() -> &'static str { "index" }
fn index_c() -> &'static str { "index" }
#[get("/<_a>")]
fn index_dyn_a(_a: &RawStr) -> &'static str { "index" }
fn index_dyn_a(_a: &str) -> &'static str { "index" }
fn hello_world_rocket() -> rocket::Rocket {
let config = rocket::Config::figment().merge(("log_level", "off"));
@ -93,7 +91,7 @@ fn bench_simple_routing(b: &mut Bencher) {
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];
for route in client.rocket().routes() {
let request = client.req(route.method, route.uri.path());
let request = client.req(route.method, route.uri.path().as_str());
requests.push(request);
}

View File

@ -285,25 +285,24 @@ impl fmt::Debug for Catcher {
macro_rules! html_error_template {
($code:expr, $reason:expr, $description:expr) => (
concat!(r#"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>"#, $code, " ", $reason, r#"</title>
</head>
<body align="center">
<div role="main" align="center">
<h1>"#, $code, ": ", $reason, r#"</h1>
<p>"#, $description, r#"</p>
<hr />
</div>
<div role="contentinfo" align="center">
<small>Rocket</small>
</div>
</body>
</html>
"#
concat!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>"#, $code, " ", $reason, r#"</title>
</head>
<body align="center">
<div role="main" align="center">
<h1>"#, $code, ": ", $reason, r#"</h1>
<p>"#, $description, r#"</p>
<hr />
</div>
<div role="contentinfo" align="center">
<small>Rocket</small>
</div>
</body>
</html>"#
)
)
}

View File

@ -1,3 +1,4 @@
use std::path::PathBuf;
use std::net::{IpAddr, Ipv4Addr};
use figment::{Figment, Profile, Provider, Metadata, error::Result};
@ -7,6 +8,7 @@ use serde::{Deserialize, Serialize};
use yansi::Paint;
use crate::config::{SecretKey, TlsConfig, LogLevel};
use crate::request::{self, Request, FromRequest};
use crate::data::Limits;
/// Rocket server configuration.
@ -57,21 +59,24 @@ pub struct Config {
pub address: IpAddr,
/// Port to serve on. **(default: `8000`)**
pub port: u16,
/// Number of future-executing threads. **(default: `num cores`)**
/// Number of threads to use for executing futures. **(default: `num_cores`)**
pub workers: usize,
/// Keep-alive timeout in seconds; disabled when `0`. **(default: `5`)**
pub keep_alive: u32,
/// Streaming read size limits. **(default: [`Limits::default()`])**
pub limits: Limits,
/// The TLS configuration, if any. **(default: `None`)**
pub tls: Option<TlsConfig>,
/// The secret key for signing and encrypting. **(default: `0`)**
pub secret_key: SecretKey,
/// The directory to store temporary files in. **(default:
/// [`std::env::temp_dir`]).
pub temp_dir: PathBuf,
/// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)**
pub log_level: LogLevel,
/// Whether to use colors and emoji when logging. **(default: `true`)**
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
pub cli_colors: bool,
/// The secret key for signing and encrypting. **(default: `0`)**
pub secret_key: SecretKey,
/// The TLS configuration, if any. **(default: `None`)**
pub tls: Option<TlsConfig>,
/// Streaming read size limits. **(default: [`Limits::default()`])**
pub limits: Limits,
/// Whether `ctrl-c` initiates a server shutdown. **(default: `true`)**
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
pub ctrlc: bool,
@ -141,6 +146,7 @@ impl Config {
secret_key: SecretKey::zero(),
tls: None,
limits: Limits::default(),
temp_dir: std::env::temp_dir(),
ctrlc: true,
}
}
@ -270,10 +276,6 @@ impl Config {
launch_info_!("address: {}", Paint::default(&self.address).bold());
launch_info_!("port: {}", Paint::default(&self.port).bold());
launch_info_!("workers: {}", Paint::default(self.workers).bold());
launch_info_!("log level: {}", Paint::default(self.log_level).bold());
launch_info_!("secret key: {:?}", Paint::default(&self.secret_key).bold());
launch_info_!("limits: {}", Paint::default(&self.limits).bold());
launch_info_!("cli colors: {}", Paint::default(&self.cli_colors).bold());
let ka = self.keep_alive;
if ka > 0 {
@ -282,11 +284,14 @@ impl Config {
launch_info_!("keep-alive: {}", Paint::default("disabled").bold());
}
launch_info_!("limits: {}", Paint::default(&self.limits).bold());
match self.tls_enabled() {
true => launch_info_!("tls: {}", Paint::default("enabled").bold()),
false => launch_info_!("tls: {}", Paint::default("disabled").bold()),
}
launch_info_!("secret key: {:?}", Paint::default(&self.secret_key).bold());
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))]
if !self.secret_key.is_provided() {
warn!("secrets enabled without a configured `secret_key`");
@ -294,6 +299,10 @@ impl Config {
info_!("this becomes a {} in non-debug profiles", Paint::red("hard error").bold());
}
launch_info_!("temp dir: {}", Paint::default(&self.temp_dir.display()).bold());
launch_info_!("log level: {}", Paint::default(self.log_level).bold());
launch_info_!("cli colors: {}", Paint::default(&self.cli_colors).bold());
// Check for now depreacted config values.
for (key, replacement) in Self::DEPRECATED_KEYS {
if let Some(md) = figment.find_metadata(key) {
@ -346,6 +355,15 @@ impl Provider for Config {
}
}
#[crate::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for &'r Config {
type Error = std::convert::Infallible;
async fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
request::Outcome::Success(req.config())
}
}
#[doc(hidden)]
pub fn pretty_print_error(error: figment::Error) {
use figment::error::{Kind, OneOf};

270
core/lib/src/data/capped.rs Normal file
View File

@ -0,0 +1,270 @@
/// Number of bytes read/written and whether that consisted of the entire
/// stream.
#[derive(Debug, Copy, Clone)]
pub struct N {
/// The number of bytes written out.
pub written: u64,
/// Whether the entire stream was read and written out.
pub complete: bool,
}
impl std::fmt::Display for N {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.written.fmt(f)
}
}
impl std::ops::Deref for N {
type Target = u64;
fn deref(&self) -> &Self::Target {
&self.written
}
}
/// Encapsulates a value capped to a data limit.
///
/// A `Capped<T>` type represents a `T` that has been limited (capped) to some
/// number of bytes. The internal [`N`] specifies whether the value is complete
/// (also [`Capped::is_complete()`]) or whether it was capped prematurely. A
/// [`Capped`] is returned by various methods of [`DataStream`]. Some
/// `Capped<T>` types, like `Capped<String>` and `Capped<TempFile>`, implement
/// traits like [`FromData`] and [`FromForm`].
///
/// # Example
///
/// Since `Capped<TempFile>` implements `FromData`, it can be used as a data
/// guard. The following Rocket route accepts a raw upload and stores the upload
/// in a different directory depending on whether the file exceeded the data
/// limit or not. See [`TempFile`] for details on temporary file storage
/// locations and limits.
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::data::{Capped, TempFile};
///
/// #[post("/upload", data = "<file>")]
/// async fn upload(mut file: Capped<TempFile<'_>>) -> std::io::Result<()> {
/// if file.is_complete() {
/// file.persist_to("/tmp/complete/file.txt").await?;
/// } else {
/// file.persist_to("/tmp/incomplete/file.txt").await?;
/// }
///
/// Ok(())
/// }
/// ```
///
/// [`DataStream`]: crate::data::DataStream
/// [`FromData`]: crate::data::FromData
/// [`FromForm`]: crate::form::FromForm
/// [`TempFile`]: crate::data::TempFile
// TODO: `Capped` not particularly usable outside Rocket due to coherence.
#[derive(Debug, Copy, Clone)]
pub struct Capped<T> {
/// The capped value itself.
pub value: T,
/// The number of bytes written and whether `value` is complete.
pub n: N
}
impl<T> Capped<T> {
/// Creates a new `Capped` from a `value` and an `n`. Prefer to use
/// [`Capped::from()`] when possible.
///
/// # Example
///
/// ```rust
/// use rocket::data::{Capped, N};
///
/// let n = N { written: 2, complete: true };
/// let capped = Capped::new("hi".to_string(), n);
/// ```
#[inline(always)]
pub fn new(value: T, n: N) -> Self {
Capped { value, n, }
}
/// Creates a new `Capped` from a `value` and the length of `value` `n`,
/// marking `value` as complete. Prefer to use [`Capped::from()`] when
/// possible.
///
/// # Example
///
/// ```rust
/// use rocket::data::{Capped, N};
///
/// let string = "hi";
/// let capped = Capped::complete("hi", string.len());
/// ```
#[inline(always)]
pub fn complete(value: T, len: usize) -> Self {
Capped { value, n: N { written: len as u64, complete: true } }
}
/// Converts a `Capped<T>` to `Capped<U>` by applying `f` to the contained
/// value.
///
/// # Example
///
/// ```rust
/// use rocket::data::{Capped, N};
///
/// let n = N { written: 2, complete: true };
/// let capped: Capped<usize> = Capped::new(10usize, n);
/// let mapped: Capped<String> = capped.map(|n| n.to_string());
/// ```
#[inline(always)]
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Capped<U> {
Capped { value: f(self.value), n: self.n }
}
/// Returns `true` if `self.n.written` is `0`, that is, no bytes were
/// written to `value`.
///
/// # Example
///
/// ```rust
/// use rocket::data::{Capped, N};
///
/// let n = N { written: 2, complete: true };
/// let capped = Capped::new("hi".to_string(), n);
/// assert!(!capped.is_empty());
///
/// let n = N { written: 0, complete: true };
/// let capped = Capped::new("".to_string(), n);
/// assert!(capped.is_empty());
/// ```
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.n.written == 0
}
/// Returns `true` if `self.n.complete`, that is, `value` represents the
/// entire data stream.
///
/// # Example
///
/// ```rust
/// use rocket::data::{Capped, N};
///
/// let n = N { written: 2, complete: true };
/// let capped = Capped::new("hi".to_string(), n);
/// assert!(capped.is_complete());
///
/// let n = N { written: 4, complete: false };
/// let capped = Capped::new("hell".to_string(), n);
/// assert!(!capped.is_complete());
/// ```
#[inline(always)]
pub fn is_complete(&self) -> bool {
self.n.complete
}
/// Returns the internal value.
///
/// # Example
///
/// ```rust
/// use rocket::data::{Capped, N};
///
/// let n = N { written: 2, complete: true };
/// let capped = Capped::new("hi".to_string(), n);
/// assert_eq!(capped.into_inner(), "hi");
/// ```
#[inline(always)]
pub fn into_inner(self) -> T {
self.value
}
}
impl<T> std::ops::Deref for Capped<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T> std::ops::DerefMut for Capped<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl<T: AsRef<[u8]>> From<T> for Capped<T> {
/// Creates a `Capped<T>` from a `value`, setting `complete` to `true`.
fn from(value: T) -> Self {
let len = value.as_ref().len();
Capped::complete(value, len)
}
}
use crate::response::{self, Responder};
use crate::request::Request;
impl<'r, 'o: 'r, T: Responder<'r, 'o>> Responder<'r, 'o> for Capped<T> {
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o> {
self.value.respond_to(request)
}
}
macro_rules! impl_strict_from_form_field_from_capped {
($T:ty) => (const _: () = {
use $crate::form::{FromFormField, ValueField, DataField, Result};
use $crate::data::Capped;
#[crate::async_trait]
impl<'v> FromFormField<'v> for $T {
fn default() -> Option<Self> {
<Capped<$T> as FromFormField<'v>>::default().map(|c| c.value)
}
fn from_value(f: ValueField<'v>) -> Result<'v, Self> {
let capped = <Capped<$T> as FromFormField<'v>>::from_value(f)?;
if capped.is_complete() {
Ok(capped.value)
} else {
Err((None, Some(capped.n.written)))?
}
}
async fn from_data(field: DataField<'v, '_>) -> Result<'v, Self> {
let capped = <Capped<$T> as FromFormField<'v>>::from_data(field);
let capped = capped.await?;
if capped.is_complete() {
Ok(capped.value)
} else {
Err((None, Some(capped.n.written)))?
}
}
}
};)
}
macro_rules! impl_strict_from_data_from_capped {
($T:ty) => (
#[crate::async_trait]
impl<'r> $crate::data::FromData<'r> for $T {
type Error = <$crate::data::Capped<Self> as $crate::data::FromData<'r>>::Error;
async fn from_data(
r: &'r $crate::Request<'_>,
d: $crate::Data
) -> $crate::data::Outcome<Self, Self::Error> {
use $crate::outcome::Outcome::*;
use std::io::{Error, ErrorKind::UnexpectedEof};
match <$crate::data::Capped<$T> as FromData>::from_data(r, d).await {
Success(p) if p.is_complete() => Success(p.into_inner()),
Success(_) => {
let e = Error::new(UnexpectedEof, "data limit exceeded");
Failure((Status::BadRequest, e.into()))
},
Forward(d) => Forward(d),
Failure((s, e)) => Failure((s, e)),
}
}
}
)
}

View File

@ -1,20 +1,17 @@
use std::io::Cursor;
use crate::http::hyper;
use crate::ext::AsyncReadBody;
use crate::tokio::io::AsyncReadExt;
use crate::data::data_stream::DataStream;
use crate::data::ByteUnit;
use crate::data::{ByteUnit, StreamReader};
/// The number of bytes to read into the "peek" buffer.
pub const PEEK_BYTES: usize = 512;
/// Type representing the data in the body of an incoming request.
/// Type representing the body data of a request.
///
/// This type is the only means by which the body of a request can be retrieved.
/// This type is not usually used directly. Instead, types that implement
/// [`FromTransformedData`](crate::data::FromTransformedData) are used via code
/// generation by specifying the `data = "<var>"` route parameter as follows:
/// This type is not usually used directly. Instead, data guards (types that
/// implement [`FromData`](crate::data::FromData)) are created indirectly via
/// code generation by specifying the `data = "<var>"` route parameter as
/// follows:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
@ -24,9 +21,8 @@ pub const PEEK_BYTES: usize = 512;
/// # fn main() { }
/// ```
///
/// Above, `DataGuard` can be any type that implements `FromTransformedData` (or
/// equivalently, `FromData`). Note that `Data` itself implements
/// `FromTransformedData`.
/// Above, `DataGuard` can be any type that implements `FromData`. Note that
/// `Data` itself implements `FromData`.
///
/// # Reading Data
///
@ -44,16 +40,17 @@ pub const PEEK_BYTES: usize = 512;
pub struct Data {
buffer: Vec<u8>,
is_complete: bool,
stream: AsyncReadBody,
stream: StreamReader,
}
impl Data {
pub(crate) async fn from_hyp(body: hyper::Body) -> Data {
/// Create a `Data` from a recognized `stream`.
pub(crate) fn from<S: Into<StreamReader>>(stream: S) -> Data {
// TODO.async: This used to also set the read timeout to 5 seconds.
// Such a short read timeout is likely no longer necessary, but some
// kind of idle timeout should be implemented.
let stream = AsyncReadBody::from(body);
let stream = stream.into();
let buffer = Vec::with_capacity(PEEK_BYTES / 8);
Data { buffer, stream, is_complete: false }
}
@ -63,7 +60,7 @@ impl Data {
pub(crate) fn local(data: Vec<u8>) -> Data {
Data {
buffer: data,
stream: AsyncReadBody::empty(),
stream: StreamReader::empty(),
is_complete: true,
}
}
@ -86,11 +83,7 @@ impl Data {
/// }
/// ```
pub fn open(self, limit: ByteUnit) -> DataStream {
let buffer_limit = std::cmp::min(self.buffer.len().into(), limit);
let stream_limit = limit - buffer_limit;
let buffer = Cursor::new(self.buffer).take(buffer_limit.into());
let stream = self.stream.take(stream_limit.into());
DataStream { buffer, stream }
DataStream::new(self.buffer, self.stream, limit.into())
}
/// Retrieve at most `num` bytes from the `peek` buffer without consuming
@ -113,10 +106,13 @@ impl Data {
/// # type MyError = String;
///
/// #[rocket::async_trait]
/// impl FromData for MyType {
/// impl<'r> FromData<'r> for MyType {
/// type Error = MyError;
///
/// async fn from_data(req: &Request<'_>, mut data: Data) -> data::Outcome<Self, MyError> {
/// async fn from_data(
/// req: &'r Request<'_>,
/// mut data: Data
/// ) -> data::Outcome<Self, Self::Error> {
/// if data.peek(2).await != b"hi" {
/// return data::Outcome::Forward(data)
/// }

View File

@ -3,26 +3,107 @@ use std::task::{Context, Poll};
use std::path::Path;
use std::io::{self, Cursor};
use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncWrite, AsyncReadExt, ReadBuf, Take};
use futures::stream::Stream;
use futures::ready;
use crate::ext::AsyncReadBody;
use crate::http::hyper;
use crate::ext::{PollExt, Chain};
use crate::data::{Capped, N};
/// Raw data stream of a request body.
///
/// This stream can only be obtained by calling
/// [`Data::open()`](crate::data::Data::open()). The stream contains all of the
/// data in the body of the request. It exposes no methods directly. Instead, it
/// must be used as an opaque [`AsyncRead`] structure.
/// [`Data::open()`](crate::data::Data::open()) with a data limit. The stream
/// contains all of the data in the body of the request.
///
/// Reading from a `DataStream` is accomplished via the various methods on the
/// structure. In general, methods exists in two variants: those that _check_
/// whether the entire stream was read and those that don't. The former either
/// directly or indirectly (via [`Capped`]) return an [`N`] which allows
/// checking if the stream was read to completion while the latter do not.
///
/// | Read Into | Method | Notes |
/// |-----------|--------------------------------------|----------------------------------|
/// | `String` | [`DataStream::into_string()`] | Completeness checked. Preferred. |
/// | `String` | [`AsyncReadExt::read_to_string()`] | Unchecked w/existing `String`. |
/// | `Vec<u8>` | [`DataStream::into_bytes()`] | Checked. Preferred. |
/// | `Vec<u8>` | [`DataStream::stream_to(&mut vec)`] | Checked w/existing `Vec`. |
/// | `Vec<u8>` | [`DataStream::stream_precise_to()`] | Unchecked w/existing `Vec`. |
/// | `File` | [`DataStream::into_file()`] | Checked. Preferred. |
/// | `File` | [`DataStream::stream_to(&mut file)`] | Checked w/ existing `File`. |
/// | `File` | [`DataStream::stream_precise_to()`] | Unchecked w/ existing `File`. |
/// | `T` | [`DataStream::stream_to()`] | Checked. Any `T: AsyncWrite`. |
/// | `T` | [`DataStream::stream_precise_to()`] | Unchecked. Any `T: AsyncWrite`. |
///
/// [`DataStream::stream_to(&mut vec)`]: DataStream::stream_to()
/// [`DataStream::stream_to(&mut file)`]: DataStream::stream_to()
pub struct DataStream {
pub(crate) buffer: Take<Cursor<Vec<u8>>>,
pub(crate) stream: Take<AsyncReadBody>
pub(crate) chain: Take<Chain<Cursor<Vec<u8>>, StreamReader>>,
}
/// An adapter: turns a `T: Stream` (in `StreamKind`) into a `tokio::AsyncRead`.
pub struct StreamReader {
state: State,
inner: StreamKind,
}
/// The current state of `StreamReader` `AsyncRead` adapter.
enum State {
Pending,
Partial(Cursor<hyper::Bytes>),
Done,
}
/// The kinds of streams we accept as `Data`.
enum StreamKind {
Body(hyper::Body),
Multipart(multer::Field)
}
impl DataStream {
/// A helper method to write the body of the request to any `AsyncWrite`
/// type.
pub(crate) fn new(buf: Vec<u8>, stream: StreamReader, limit: u64) -> Self {
let chain = Chain::new(Cursor::new(buf), stream).take(limit);
Self { chain }
}
/// Whether a previous read exhausted the set limit _and then some_.
async fn limit_exceeded(&mut self) -> io::Result<bool> {
#[cold]
async fn _limit_exceeded(stream: &mut DataStream) -> io::Result<bool> {
stream.chain.set_limit(1);
let mut buf = [0u8; 1];
Ok(stream.read(&mut buf).await? != 0)
}
Ok(self.chain.limit() == 0 && _limit_exceeded(self).await?)
}
/// Number of bytes a full read from `self` will _definitely_ read.
///
/// This method is identical to `tokio::io::copy(&mut self, &mut writer)`.
/// # Example
///
/// ```rust
/// use rocket::data::{Data, ToByteUnit};
///
/// async fn f(data: Data) {
/// let definitely_have_n_bytes = data.open(1.kibibytes()).hint();
/// }
/// ```
pub fn hint(&self) -> usize {
let buf_len = self.chain.get_ref().get_ref().0.get_ref().len();
std::cmp::min(buf_len, self.chain.limit() as usize)
}
/// A helper method to write the body of the request to any `AsyncWrite`
/// type. Returns an [`N`] which indicates how many bytes were written and
/// whether the entire stream was read. An additional read from `self` may
/// be required to check if all of the sream has been read. If that
/// information is not needed, use [`DataStream::stream_precise_to()`].
///
/// This method is identical to `tokio::io::copy(&mut self, &mut writer)`
/// except in that it returns an `N` to check for completeness.
///
/// # Example
///
@ -30,24 +111,24 @@ impl DataStream {
/// use std::io;
/// use rocket::data::{Data, ToByteUnit};
///
/// async fn handler(mut data: Data) -> io::Result<String> {
/// async fn data_guard(mut data: Data) -> io::Result<String> {
/// // write all of the data to stdout
/// let written = data.open(512.kibibytes()).stream_to(tokio::io::stdout()).await?;
/// let written = data.open(512.kibibytes())
/// .stream_to(tokio::io::stdout()).await?;
///
/// Ok(format!("Wrote {} bytes.", written))
/// }
/// ```
#[inline(always)]
pub async fn stream_to<W>(mut self, mut writer: W) -> io::Result<u64>
pub async fn stream_to<W>(mut self, mut writer: W) -> io::Result<N>
where W: AsyncWrite + Unpin
{
tokio::io::copy(&mut self, &mut writer).await
let written = tokio::io::copy(&mut self, &mut writer).await?;
Ok(N { written, complete: !self.limit_exceeded().await? })
}
/// A helper method to write the body of the request to a file at the path
/// determined by `path`.
///
/// This method is identical to `self.stream_to(&mut
/// File::create(path).await?)`.
/// Like [`DataStream::stream_to()`] except that no end-of-stream check is
/// conducted and thus read/write completeness is unknown.
///
/// # Example
///
@ -55,36 +136,19 @@ impl DataStream {
/// use std::io;
/// use rocket::data::{Data, ToByteUnit};
///
/// async fn handler(mut data: Data) -> io::Result<String> {
/// let written = data.open(1.megabytes()).stream_to_file("/static/file").await?;
/// Ok(format!("Wrote {} bytes to /static/file", written))
/// async fn data_guard(mut data: Data) -> io::Result<String> {
/// // write all of the data to stdout
/// let written = data.open(512.kibibytes())
/// .stream_precise_to(tokio::io::stdout()).await?;
///
/// Ok(format!("Wrote {} bytes.", written))
/// }
/// ```
#[inline(always)]
pub async fn stream_to_file<P: AsRef<Path>>(self, path: P) -> io::Result<u64> {
let mut file = tokio::fs::File::create(path).await?;
self.stream_to(&mut file).await
}
/// A helper method to write the body of the request to a `String`.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use rocket::data::{Data, ToByteUnit};
///
/// async fn handler(data: Data) -> io::Result<String> {
/// data.open(10.bytes()).stream_to_string().await
/// }
/// ```
pub async fn stream_to_string(mut self) -> io::Result<String> {
let buf_len = self.buffer.get_ref().get_ref().len();
let max_from_buf = std::cmp::min(buf_len, self.buffer.limit() as usize);
let capacity = std::cmp::min(max_from_buf, 1024);
let mut string = String::with_capacity(capacity);
self.read_to_string(&mut string).await?;
Ok(string)
pub async fn stream_precise_to<W>(mut self, mut writer: W) -> io::Result<u64>
where W: AsyncWrite + Unpin
{
tokio::io::copy(&mut self, &mut writer).await
}
/// A helper method to write the body of the request to a `Vec<u8>`.
@ -95,22 +159,91 @@ impl DataStream {
/// use std::io;
/// use rocket::data::{Data, ToByteUnit};
///
/// async fn handler(data: Data) -> io::Result<Vec<u8>> {
/// data.open(4.kibibytes()).stream_to_vec().await
/// async fn data_guard(data: Data) -> io::Result<Vec<u8>> {
/// let bytes = data.open(4.kibibytes()).into_bytes().await?;
/// if !bytes.is_complete() {
/// println!("there are bytes remaining in the stream");
/// }
///
/// Ok(bytes.into_inner())
/// }
/// ```
pub async fn stream_to_vec(mut self) -> io::Result<Vec<u8>> {
let buf_len = self.buffer.get_ref().get_ref().len();
let max_from_buf = std::cmp::min(buf_len, self.buffer.limit() as usize);
let capacity = std::cmp::min(max_from_buf, 1024);
let mut vec = Vec::with_capacity(capacity);
self.read_to_end(&mut vec).await?;
Ok(vec)
pub async fn into_bytes(self) -> io::Result<Capped<Vec<u8>>> {
let mut vec = Vec::with_capacity(self.hint());
let n = self.stream_to(&mut vec).await?;
Ok(Capped { value: vec, n })
}
/// A helper method to write the body of the request to a `String`.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use rocket::data::{Data, ToByteUnit};
///
/// async fn data_guard(data: Data) -> io::Result<String> {
/// let string = data.open(10.bytes()).into_string().await?;
/// if !string.is_complete() {
/// println!("there are bytes remaining in the stream");
/// }
///
/// Ok(string.into_inner())
/// }
/// ```
pub async fn into_string(mut self) -> io::Result<Capped<String>> {
let mut string = String::with_capacity(self.hint());
let written = self.read_to_string(&mut string).await?;
let n = N { written: written as u64, complete: !self.limit_exceeded().await? };
Ok(Capped { value: string, n })
}
/// A helper method to write the body of the request to a file at the path
/// determined by `path`. If a file at the path already exists, it is
/// overwritten. The opened file is returned.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use rocket::data::{Data, ToByteUnit};
///
/// async fn data_guard(mut data: Data) -> io::Result<String> {
/// let file = data.open(1.megabytes()).into_file("/static/file").await?;
/// if !file.is_complete() {
/// println!("there are bytes remaining in the stream");
/// }
///
/// Ok(format!("Wrote {} bytes to /static/file", file.n))
/// }
/// ```
pub async fn into_file<P: AsRef<Path>>(self, path: P) -> io::Result<Capped<File>> {
let mut file = File::create(path).await?;
let n = self.stream_to(&mut tokio::io::BufWriter::new(&mut file)).await?;
Ok(Capped { value: file, n })
}
}
// TODO.async: Consider implementing `AsyncBufRead`.
impl StreamReader {
pub fn empty() -> Self {
Self { inner: StreamKind::Body(hyper::Body::empty()), state: State::Done }
}
}
impl From<hyper::Body> for StreamReader {
fn from(body: hyper::Body) -> Self {
Self { inner: StreamKind::Body(body), state: State::Pending }
}
}
impl From<multer::Field> for StreamReader {
fn from(field: multer::Field) -> Self {
Self { inner: StreamKind::Multipart(field), state: State::Pending }
}
}
impl AsyncRead for DataStream {
#[inline(always)]
fn poll_read(
@ -118,15 +251,57 @@ impl AsyncRead for DataStream {
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
if self.buffer.limit() > 0 {
trace_!("DataStream::buffer_read()");
match Pin::new(&mut self.buffer).poll_read(cx, buf) {
Poll::Ready(Ok(())) if buf.filled().is_empty() => { /* fall through */ },
poll => return poll,
}
}
trace_!("DataStream::stream_read()");
Pin::new(&mut self.stream).poll_read(cx, buf)
Pin::new(&mut self.chain).poll_read(cx, buf)
}
}
impl Stream for StreamKind {
type Item = io::Result<hyper::Bytes>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
match self.get_mut() {
StreamKind::Body(body) => Pin::new(body).poll_next(cx)
.map_err_ext(|e| io::Error::new(io::ErrorKind::Other, e)),
StreamKind::Multipart(mp) => Pin::new(mp).poll_next(cx)
.map_err_ext(|e| io::Error::new(io::ErrorKind::Other, e)),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
StreamKind::Body(body) => body.size_hint(),
StreamKind::Multipart(mp) => mp.size_hint(),
}
}
}
impl AsyncRead for StreamReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
loop {
self.state = match self.state {
State::Pending => {
match ready!(Pin::new(&mut self.inner).poll_next(cx)) {
Some(Err(e)) => return Poll::Ready(Err(e)),
Some(Ok(bytes)) => State::Partial(Cursor::new(bytes)),
None => State::Done,
}
},
State::Partial(ref mut cursor) => {
let rem = buf.remaining();
match ready!(Pin::new(cursor).poll_read(cx, buf)) {
Ok(()) if rem == buf.remaining() => State::Pending,
result => return Poll::Ready(result),
}
}
State::Done => return Poll::Ready(Ok(())),
}
}
}
}

View File

@ -1,15 +1,11 @@
use std::borrow::Borrow;
use crate::http::{RawStr, Status};
use crate::request::{Request, local_cache};
use crate::data::{Data, Limits};
use crate::outcome::{self, IntoOutcome, Outcome::*};
use futures::future::BoxFuture;
use futures::future::{ready, FutureExt};
use crate::outcome::{self, IntoOutcome};
use crate::outcome::Outcome::*;
use crate::http::Status;
use crate::request::Request;
use crate::data::Data;
/// Type alias for the `Outcome` of a `FromTransformedData` conversion.
/// Type alias for the `Outcome` of [`FromData`].
///
/// [`FromData`]: crate::data::FromData
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Data>;
impl<S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
@ -33,397 +29,27 @@ impl<S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
}
}
/// Indicates how incoming data should be transformed before being parsed and
/// validated by a data guard.
///
/// See the documentation for [`FromTransformedData`] for usage details.
pub enum Transform<T, B = T> {
/// Indicates that data should be or has been transformed into the
/// [`FromTransformedData::Owned`] variant.
Owned(T),
/// Indicates that data should be or has been transformed into the
/// [`FromTransformedData::Borrowed`] variant.
Borrowed(B)
}
impl<T, B> Transform<T, B> {
/// Returns the `Owned` value if `self` is `Owned`.
///
/// # Panics
///
/// Panics if `self` is `Borrowed`.
///
///
/// # Example
///
/// ```rust
/// use rocket::data::Transform;
///
/// let owned: Transform<usize, &[usize]> = Transform::Owned(10);
/// assert_eq!(owned.owned(), 10);
/// ```
#[inline]
pub fn owned(self) -> T {
match self {
Transform::Owned(val) => val,
Transform::Borrowed(_) => panic!("Transform::owned() called on Borrowed"),
}
}
/// Returns the `Borrowed` value if `self` is `Borrowed`.
///
/// # Panics
///
/// Panics if `self` is `Owned`.
///
/// ```rust
/// use rocket::data::Transform;
///
/// let borrowed: Transform<usize, &[usize]> = Transform::Borrowed(&[10]);
/// assert_eq!(borrowed.borrowed(), &[10]);
/// ```
#[inline]
pub fn borrowed(self) -> B {
match self {
Transform::Borrowed(val) => val,
Transform::Owned(_) => panic!("Transform::borrowed() called on Owned"),
}
}
}
/// Type alias to the `outcome` input type of [`FromTransformedData::from_data`].
///
/// This is a hairy type, but the gist is that this is a [`Transform`] where,
/// for a given `T: FromTransformedData`:
///
/// * The `Owned` variant is an `Outcome` whose `Success` value is of type
/// [`FromTransformedData::Owned`].
///
/// * The `Borrowed` variant is an `Outcome` whose `Success` value is a borrow
/// of type [`FromTransformedData::Borrowed`].
///
/// * In either case, the `Outcome`'s `Failure` variant is a value of type
/// [`FromTransformedData::Error`].
pub type Transformed<'a, T> =
Transform<
Outcome<<T as FromTransformedData<'a>>::Owned, <T as FromTransformedData<'a>>::Error>,
Outcome<&'a <T as FromTransformedData<'a>>::Borrowed, <T as FromTransformedData<'a>>::Error>
>;
/// Type alias to the `Future` returned by [`FromTransformedData::transform`].
pub type TransformFuture<'fut, T, E> = BoxFuture<'fut, Transform<Outcome<T, E>>>;
/// Type alias to the `Future` returned by [`FromTransformedData::from_data`].
pub type FromDataFuture<'fut, T, E> = BoxFuture<'fut, Outcome<T, E>>;
/// Trait implemented by data guards to derive a value from request body data.
///
/// # Data Guards
///
/// A data guard is a [request guard] that operates on a request's body data.
/// Data guards validate, parse, and optionally convert request body data.
/// Validation and parsing/conversion is implemented through
/// `FromTransformedData`. In other words, every type that implements
/// `FromTransformedData` is a data guard.
/// A data guard is a guard that operates on a request's body data. Data guards
/// validate and parse request body data via implementations of `FromData`. In
/// other words, a type is a data guard _iff_ it implements `FromData`.
///
/// Data guards are used as the target of the `data` route attribute parameter.
/// A handler can have at most one data guard.
///
/// For many data guards, implementing [`FromData`] will be simpler and
/// sufficient. All types that implement `FromData` automatically implement
/// `FromTransformedData`. Thus, when possible, prefer to implement [`FromData`]
/// instead of `FromTransformedData`.
///
/// [request guard]: crate::request::FromRequest
///
/// ## Example
///
/// In the example below, `var` is used as the argument name for the data guard
/// type `DataGuard`. When the `submit` route matches, Rocket will call the
/// `FromTransformedData` implementation for the type `T`. The handler will only be called
/// if the guard returns successfully.
/// Data guards are the target of the `data` route attribute parameter:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # type DataGuard = rocket::data::Data;
/// #[post("/submit", data = "<var>")]
/// fn submit(var: DataGuard) { /* ... */ }
/// # fn main() { }
/// ```
///
/// # Transforming
///
/// Data guards can optionally _transform_ incoming data before processing it
/// via an implementation of the [`FromTransformedData::transform()`] method.
/// This is useful when a data guard requires or could benefit from a reference
/// to body data as opposed to an owned version. If a data guard has no need to
/// operate on a reference to body data, [`FromData`] should be implemented
/// instead; it is simpler to implement and less error prone. All types that
/// implement `FromData` automatically implement `FromTransformedData`.
///
/// When exercising a data guard, Rocket first calls the guard's
/// [`FromTransformedData::transform()`] method and awaits on the returned
/// future, then calls the guard's [`FromTransformedData::from_data()`] method
/// and awaits on that returned future. Rocket stores data returned by
/// [`FromTransformedData::transform()`] on the stack. If `transform` returns a
/// [`Transform::Owned`], Rocket moves the data back to the data guard in the
/// subsequent `from_data` call as a `Transform::Owned`. If instead `transform`
/// returns a [`Transform::Borrowed`] variant, Rocket calls `borrow()` on the
/// owned value, producing a borrow of the associated
/// [`FromTransformedData::Borrowed`] type and passing it as a
/// `Transform::Borrowed`.
///
/// ## Example
///
/// Consider a data guard type that wishes to hold a slice to two different
/// parts of the incoming data:
///
/// ```rust
/// struct Name<'a> {
/// first: &'a str,
/// last: &'a str
/// }
/// ```
///
/// Without the ability to transform into a borrow, implementing such a data
/// guard would be impossible. With transformation, however, we can instruct
/// Rocket to produce a borrow to a `Data` that has been transformed into a
/// `String` (an `&str`).
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # #[derive(Debug)]
/// # struct Name<'a> { first: &'a str, last: &'a str, }
/// use std::io::{self, Read};
///
/// use rocket::Request;
/// use rocket::data::{Data, Outcome, FromDataFuture, ByteUnit};
/// use rocket::data::{FromTransformedData, Transform, Transformed, TransformFuture};
/// use rocket::http::Status;
///
/// const NAME_LIMIT: ByteUnit = ByteUnit::Byte(256);
///
/// enum NameError {
/// Io(io::Error),
/// Parse
/// }
///
/// impl<'r> FromTransformedData<'r> for Name<'r> {
/// type Error = NameError;
/// type Owned = String;
/// type Borrowed = str;
///
/// fn transform(_: &'r Request, data: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
/// Box::pin(async move {
/// let outcome = match data.open(NAME_LIMIT).stream_to_string().await {
/// Ok(string) => Outcome::Success(string),
/// Err(e) => Outcome::Failure((Status::InternalServerError, NameError::Io(e)))
/// };
///
/// // Returning `Borrowed` here means we get `Borrowed` in `from_data`.
/// Transform::Borrowed(outcome)
/// })
/// }
///
/// fn from_data(_: &'r Request, outcome: Transformed<'r, Self>) -> FromDataFuture<'r, Self, Self::Error> {
/// Box::pin(async move {
/// // Retrieve a borrow to the now transformed `String` (an &str).
/// // This is only correct because we know we _always_ return a
/// // `Borrowed` from `transform` above.
/// let string = try_outcome!(outcome.borrowed());
///
/// // Perform a crude, inefficient parse.
/// let splits: Vec<&str> = string.split(" ").collect();
/// if splits.len() != 2 || splits.iter().any(|s| s.is_empty()) {
/// return Outcome::Failure((Status::UnprocessableEntity, NameError::Parse));
/// }
///
/// // Return successfully.
/// Outcome::Success(Name { first: splits[0], last: splits[1] })
/// })
/// }
/// }
/// # #[post("/person", data = "<person>")]
/// # fn person(person: Name) { }
/// # #[post("/person", data = "<person>")]
/// # fn person2(person: Result<Name, NameError>) { }
/// # fn main() { }
/// ```
///
/// # Outcomes
///
/// The returned [`Outcome`] of a `from_data` call determines how the incoming
/// request will be processed.
///
/// * **Success**(S)
///
/// If the `Outcome` is [`Success`], then the `Success` value will be used as
/// the value for the data parameter. As long as all other parsed types
/// succeed, the request will be handled by the requesting handler.
///
/// * **Failure**(Status, E)
///
/// If the `Outcome` is [`Failure`], the request will fail with the given
/// status code and error. The designated error [`Catcher`](crate::Catcher) will be
/// used to respond to the request. Note that users can request types of
/// `Result<S, E>` and `Option<S>` to catch `Failure`s and retrieve the error
/// value.
///
/// * **Forward**(Data)
///
/// If the `Outcome` is [`Forward`], the request will be forwarded to the next
/// matching request. This requires that no data has been read from the `Data`
/// parameter. Note that users can request an `Option<S>` to catch `Forward`s.
///
/// # Provided Implementations
///
/// Rocket implements `FromTransformedData` for several built-in types. Their behavior is
/// documented here.
///
/// * **Data**
///
/// The identity implementation; simply returns [`Data`] directly.
///
/// _This implementation always returns successfully._
///
/// * **Option&lt;T>** _where_ **T: FromTransformedData**
///
/// The type `T` is derived from the incoming data using `T`'s `FromTransformedData`
/// implementation. If the derivation is a `Success`, the derived value is
/// returned in `Some`. Otherwise, a `None` is returned.
///
/// _This implementation always returns successfully._
///
/// * **Result&lt;T, T::Error>** _where_ **T: FromTransformedData**
///
/// The type `T` is derived from the incoming data using `T`'s `FromTransformedData`
/// implementation. If derivation is a `Success`, the value is returned in
/// `Ok`. If the derivation is a `Failure`, the error value is returned in
/// `Err`. If the derivation is a `Forward`, the request is forwarded.
///
/// * **String**
///
/// **Note:** _An implementation of `FromTransformedData` for `String` is only available
/// when compiling in debug mode!_
///
/// Reads the entire request body into a `String`. If reading fails, returns
/// a `Failure` with the corresponding `io::Error`.
///
/// **WARNING:** Do **not** use this implementation for anything _but_
/// debugging. This is because the implementation reads the entire body into
/// memory; since the user controls the size of the body, this is an obvious
/// vector for a denial of service attack.
///
/// * **Vec&lt;u8>**
///
/// **Note:** _An implementation of `FromTransformedData` for `Vec<u8>` is only
/// available when compiling in debug mode!_
///
/// Reads the entire request body into a `Vec<u8>`. If reading fails,
/// returns a `Failure` with the corresponding `io::Error`.
///
/// **WARNING:** Do **not** use this implementation for anything _but_
/// debugging. This is because the implementation reads the entire body into
/// memory; since the user controls the size of the body, this is an obvious
/// vector for a denial of service attack.
///
/// # Simplified `FromTransformedData`
///
/// For an example of a type that wouldn't require transformation, see the
/// [`FromData`] documentation.
pub trait FromTransformedData<'r>: Sized {
/// The associated error to be returned when the guard fails.
type Error: Send;
/// The owned type returned from [`FromTransformedData::transform()`].
///
/// The trait bounds ensures that it is is possible to borrow an
/// `&Self::Borrowed` from a value of this type.
type Owned: Borrow<Self::Borrowed>;
/// The _borrowed_ type consumed by [`FromTransformedData::from_data()`] when
/// [`FromTransformedData::transform()`] returns a [`Transform::Borrowed`].
///
/// If [`FromTransformedData::from_data()`] returns a [`Transform::Owned`], this
/// associated type should be set to `Self::Owned`.
type Borrowed: ?Sized;
/// Asynchronously transforms `data` into a value of type `Self::Owned`.
///
/// If the returned future resolves to `Transform::Owned(Self::Owned)`, then
/// `from_data` should subsequently be called with a `data` value of
/// `Transform::Owned(Self::Owned)`. If the future resolves to
/// `Transform::Borrowed(Self::Owned)`, `from_data` should subsequently be
/// called with a `data` value of `Transform::Borrowed(&Self::Borrowed)`. In
/// other words, the variant of `Transform` returned from this method is
/// used to determine which variant of `Transform` should be passed to the
/// `from_data` method. Rocket _always_ makes the subsequent call correctly.
///
/// It is very unlikely that a correct implementation of this method is
/// capable of returning either of an `Owned` or `Borrowed` variant.
/// Instead, this method should return exactly _one_ of these variants.
///
/// If transformation succeeds, an outcome of `Success` is returned.
/// If the data is not appropriate given the type of `Self`, `Forward` is
/// returned. On failure, `Failure` is returned.
fn transform(request: &'r Request<'_>, data: Data) -> TransformFuture<'r, Self::Owned, Self::Error>;
/// Asynchronously validates, parses, and converts the incoming request body
/// data into an instance of `Self`.
///
/// If validation and parsing succeeds, an outcome of `Success` is returned.
/// If the data is not appropriate given the type of `Self`, `Forward` is
/// returned. If parsing or validation fails, `Failure` is returned.
///
/// # Example
///
/// When implementing this method, you rarely need to destruct the `outcome`
/// parameter. Instead, the first line of the method should be one of the
/// following:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # use rocket::data::{Data, FromTransformedData, Transformed, Outcome};
/// # fn f<'a>(outcome: Transformed<'a, Data>) -> Outcome<Data, <Data as FromTransformedData<'a>>::Error> {
/// // If `Owned` was returned from `transform`:
/// let data = try_outcome!(outcome.owned());
/// # unimplemented!()
/// # }
///
/// # fn g<'a>(outcome: Transformed<'a, Data>) -> Outcome<Data, <Data as FromTransformedData<'a>>::Error> {
/// // If `Borrowed` was returned from `transform`:
/// let data = try_outcome!(outcome.borrowed());
/// # unimplemented!()
/// # }
/// ```
fn from_data(request: &'r Request<'_>, outcome: Transformed<'r, Self>) -> FromDataFuture<'r, Self, Self::Error>;
}
/// The identity implementation of `FromTransformedData`. Always returns `Success`.
impl<'r> FromTransformedData<'r> for Data {
type Error = std::convert::Infallible;
type Owned = Data;
type Borrowed = Data;
#[inline(always)]
fn transform(_: &'r Request<'_>, data: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
Box::pin(ready(Transform::Owned(Success(data))))
}
#[inline(always)]
fn from_data(_: &'r Request<'_>, outcome: Transformed<'r, Self>) -> FromDataFuture<'r, Self, Self::Error> {
Box::pin(ready(outcome.owned()))
}
}
/// A variant of [`FromTransformedData`] for data guards that don't require
/// transformations.
///
/// When transformation of incoming data isn't required, data guards should
/// implement this trait instead of [`FromTransformedData`]. Any type that
/// implements `FromData` automatically implements `FromTransformedData`. For a
/// description of data guards, see the [`FromTransformedData`] documentation.
/// A route can have at most one data guard. Above, `var` is used as the
/// argument name for the data guard type `DataGuard`. When the `submit` route
/// matches, Rocket will call the `FromData` implementation for the type `T`.
/// The handler will only be called if the guard returns successfully.
///
/// ## Async Trait
///
@ -437,10 +63,10 @@ impl<'r> FromTransformedData<'r> for Data {
/// # type MyError = String;
///
/// #[rocket::async_trait]
/// impl FromData for MyType {
/// impl<'r> FromData<'r> for MyType {
/// type Error = MyError;
///
/// async fn from_data(req: &Request<'_>, data: Data) -> data::Outcome<Self, MyError> {
/// async fn from_data(req: &'r Request<'_>, data: Data) -> data::Outcome<Self, MyError> {
/// /* .. */
/// # unimplemented!()
/// }
@ -452,88 +78,110 @@ impl<'r> FromTransformedData<'r> for Data {
/// Say that you have a custom type, `Person`:
///
/// ```rust
/// struct Person {
/// name: String,
/// struct Person<'r> {
/// name: &'r str,
/// age: u16
/// }
/// ```
///
/// `Person` has a custom serialization format, so the built-in `Json` type
/// doesn't suffice. The format is `<name>:<age>` with `Content-Type:
/// application/x-person`. You'd like to use `Person` as a `FromTransformedData` type so
/// that you can retrieve it directly from a client's request body:
/// application/x-person`. You'd like to use `Person` as a data guard, so that
/// you can retrieve it directly from a client's request body:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # type Person = rocket::data::Data;
/// # use rocket::post;
/// # type Person<'r> = &'r rocket::http::RawStr;
/// #[post("/person", data = "<person>")]
/// fn person(person: Person) -> &'static str {
/// fn person(person: Person<'_>) -> &'static str {
/// "Saved the new person to the database!"
/// }
/// ```
///
/// A `FromData` implementation allowing this looks like:
/// A `FromData` implementation for such a type might look like:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// #
/// # #[derive(Debug)]
/// # struct Person { name: String, age: u16 }
/// # struct Person<'r> { name: &'r str, age: u16 }
/// #
/// use std::io::Read;
///
/// use rocket::{Request, Data};
/// use rocket::data::{self, Outcome, FromData, FromDataFuture, ByteUnit};
/// use rocket::request::{self, Request};
/// use rocket::data::{self, Data, FromData, ToByteUnit};
/// use rocket::http::{Status, ContentType};
/// use rocket::tokio::io::AsyncReadExt;
///
/// // Always use a limit to prevent DoS attacks.
/// const LIMIT: ByteUnit = ByteUnit::Byte(256);
/// enum Error {
/// TooLarge,
/// NoColon,
/// InvalidAge,
/// Io(std::io::Error),
/// }
///
/// #[rocket::async_trait]
/// impl FromData for Person {
/// type Error = String;
/// impl<'r> FromData<'r> for Person<'r> {
/// type Error = Error;
///
/// async fn from_data(req: &'r Request<'_>, data: Data) -> data::Outcome<Self, Error> {
/// use Error::*;
/// use rocket::outcome::Outcome::*;
///
/// async fn from_data(req: &Request<'_>, data: Data) -> Outcome<Self, String> {
/// // Ensure the content type is correct before opening the data.
/// let person_ct = ContentType::new("application", "x-person");
/// if req.content_type() != Some(&person_ct) {
/// return Outcome::Forward(data);
/// return Forward(data);
/// }
///
/// // Read the data into a String.
/// let limit = req.limits().get("person").unwrap_or(LIMIT);
/// let string = match data.open(limit).stream_to_string().await {
/// Ok(string) => string,
/// Err(e) => return Outcome::Failure((Status::InternalServerError, format!("{}", e)))
/// // Use a configured limit with name 'person' or fallback to default.
/// let limit = req.limits().get("person").unwrap_or(256.bytes());
///
/// // Read the data into a string.
/// let string = match data.open(limit).into_string().await {
/// Ok(string) if string.is_complete() => string.into_inner(),
/// Ok(_) => return Failure((Status::PayloadTooLarge, TooLarge)),
/// Err(e) => return Failure((Status::InternalServerError, Io(e))),
/// };
///
/// // We store `string` in request-local cache for long-lived borrows.
/// let string = request::local_cache!(req, string);
///
/// // Split the string into two pieces at ':'.
/// let (name, age) = match string.find(':') {
/// Some(i) => (string[..i].to_string(), &string[(i + 1)..]),
/// None => return Outcome::Failure((Status::UnprocessableEntity, "':'".into()))
/// Some(i) => (&string[..i], &string[(i + 1)..]),
/// None => return Failure((Status::UnprocessableEntity, NoColon)),
/// };
///
/// // Parse the age.
/// let age: u16 = match age.parse() {
/// Ok(age) => age,
/// Err(_) => return Outcome::Failure((Status::UnprocessableEntity, "Age".into()))
/// Err(_) => return Failure((Status::UnprocessableEntity, InvalidAge)),
/// };
///
/// // Return successfully.
/// Outcome::Success(Person { name, age })
/// Success(Person { name, age })
/// }
/// }
/// # #[post("/person", data = "<person>")]
/// # fn person(person: Person) { }
/// # #[post("/person", data = "<person>")]
/// # fn person2(person: Result<Person, String>) { }
/// # fn main() { }
///
/// // The following routes now typecheck...
///
/// #[post("/person", data = "<person>")]
/// fn person(person: Person<'_>) { /* .. */ }
///
/// #[post("/person", data = "<person>")]
/// fn person2(person: Result<Person<'_>, Error>) { /* .. */ }
///
/// #[post("/person", data = "<person>")]
/// fn person3(person: Option<Person<'_>>) { /* .. */ }
///
/// #[post("/person", data = "<person>")]
/// fn person4(person: Person<'_>) -> &str {
/// // Note that this is only possible because the data in `person` live
/// // as long as the request through request-local cache.
/// person.name
/// }
/// ```
#[crate::async_trait]
pub trait FromData: Sized {
pub trait FromData<'r>: Sized {
/// The associated error to be returned when the guard fails.
type Error: Send + 'static;
type Error: Send;
/// Asynchronously validates, parses, and converts an instance of `Self`
/// from the incoming request body data.
@ -541,98 +189,119 @@ pub trait FromData: Sized {
/// If validation and parsing succeeds, an outcome of `Success` is returned.
/// If the data is not appropriate given the type of `Self`, `Forward` is
/// returned. If parsing fails, `Failure` is returned.
async fn from_data(request: &Request<'_>, data: Data) -> Outcome<Self, Self::Error>;
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error>;
}
impl<'r, T: FromData + 'r> FromTransformedData<'r> for T {
type Error = T::Error;
type Owned = Data;
type Borrowed = Data;
use crate::data::Capped;
#[inline(always)]
fn transform(_: &'r Request<'_>, d: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
Box::pin(ready(Transform::Owned(Success(d))))
}
#[crate::async_trait]
impl<'r> FromData<'r> for Capped<String> {
type Error = std::io::Error;
#[inline(always)]
fn from_data(req: &'r Request<'_>, o: Transformed<'r, Self>) -> FromDataFuture<'r, Self, Self::Error> {
match o.owned() {
Success(data) => T::from_data(req, data),
_ => unreachable!(),
}
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
let limit = req.limits().get("string").unwrap_or(Limits::STRING);
data.open(limit).into_string().await.into_outcome(Status::BadRequest)
}
}
impl<'r, T: FromTransformedData<'r> + 'r> FromTransformedData<'r> for Result<T, T::Error> {
type Error = T::Error;
type Owned = T::Owned;
type Borrowed = T::Borrowed;
impl_strict_from_data_from_capped!(String);
#[inline(always)]
fn transform(r: &'r Request<'_>, d: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
T::transform(r, d)
#[crate::async_trait]
impl<'r> FromData<'r> for Capped<&'r str> {
type Error = std::io::Error;
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
let capped = try_outcome!(<Capped<String>>::from_data(req, data).await);
let string = capped.map(|s| local_cache!(req, s).as_str());
Success(string)
}
}
#[inline(always)]
fn from_data(r: &'r Request<'_>, o: Transformed<'r, Self>) -> FromDataFuture<'r, Self, Self::Error> {
Box::pin(T::from_data(r, o).map(|x| match x {
Success(val) => Success(Ok(val)),
Forward(data) => Forward(data),
impl_strict_from_data_from_capped!(&'r str);
#[crate::async_trait]
impl<'r> FromData<'r> for Capped<&'r RawStr> {
type Error = std::io::Error;
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
let capped = try_outcome!(<Capped<String>>::from_data(req, data).await);
let raw = capped.map(|s| RawStr::new(local_cache!(req, s)));
Success(raw)
}
}
impl_strict_from_data_from_capped!(&'r RawStr);
#[crate::async_trait]
impl<'r> FromData<'r> for Capped<std::borrow::Cow<'_, str>> {
type Error = std::io::Error;
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
let capped = try_outcome!(<Capped<String>>::from_data(req, data).await);
Success(capped.map(|s| s.into()))
}
}
impl_strict_from_data_from_capped!(std::borrow::Cow<'_, str>);
#[crate::async_trait]
impl<'r> FromData<'r> for Capped<&'r [u8]> {
type Error = std::io::Error;
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
let capped = try_outcome!(<Capped<Vec<u8>>>::from_data(req, data).await);
let raw = capped.map(|b| local_cache!(req, b).as_slice());
Success(raw)
}
}
impl_strict_from_data_from_capped!(&'r [u8]);
#[crate::async_trait]
impl<'r> FromData<'r> for Capped<Vec<u8>> {
type Error = std::io::Error;
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
let limit = req.limits().get("bytes").unwrap_or(Limits::BYTES);
data.open(limit).into_bytes().await.into_outcome(Status::BadRequest)
}
}
impl_strict_from_data_from_capped!(Vec<u8>);
#[crate::async_trait]
impl<'r> FromData<'r> for Data {
type Error = std::convert::Infallible;
async fn from_data(_: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
Success(data)
}
}
#[crate::async_trait]
impl<'r, T: FromData<'r> + 'r> FromData<'r> for Result<T, T::Error> {
type Error = std::convert::Infallible;
async fn from_data(
req: &'r Request<'_>,
data: Data
) -> Outcome<Result<T, <T as FromData<'r>>::Error>, Self::Error> {
match T::from_data(req, data).await {
Success(v) => Success(Ok(v)),
Failure((_, e)) => Success(Err(e)),
}))
}
}
impl<'r, T: FromTransformedData<'r> + 'r> FromTransformedData<'r> for Option<T> {
type Error = T::Error;
type Owned = T::Owned;
type Borrowed = T::Borrowed;
#[inline(always)]
fn transform(r: &'r Request<'_>, d: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
T::transform(r, d)
}
#[inline(always)]
fn from_data(r: &'r Request<'_>, o: Transformed<'r, Self>) -> FromDataFuture<'r, Self, Self::Error> {
Box::pin(T::from_data(r, o).map(|x| match x {
Success(val) => Success(Some(val)),
Failure(_) | Forward(_) => Success(None),
}))
}
}
#[cfg(debug_assertions)]
use crate::data::ByteUnit;
#[crate::async_trait]
#[cfg(debug_assertions)]
impl FromData for String {
type Error = std::io::Error;
#[inline(always)]
async fn from_data(_: &Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
match data.open(ByteUnit::max_value()).stream_to_string().await {
Ok(string) => Success(string),
Err(e) => Failure((Status::BadRequest, e)),
Forward(d) => Forward(d),
}
}
}
#[crate::async_trait]
#[cfg(debug_assertions)]
impl FromData for Vec<u8> {
type Error = std::io::Error;
impl<'r, T: FromData<'r>> FromData<'r> for Option<T> {
type Error = std::convert::Infallible;
#[inline(always)]
async fn from_data(_: &Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
use tokio::io::AsyncReadExt;
let mut stream = data.open(ByteUnit::max_value());
let mut buf = Vec::new();
match stream.read_to_end(&mut buf).await {
Ok(_) => Success(buf),
Err(e) => Failure((Status::BadRequest, e)),
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
match T::from_data(req, data).await {
Success(v) => Success(Some(v)),
Failure(..) | Forward(..) => Success(None),
}
}
}

View File

@ -3,21 +3,69 @@ use std::fmt;
use serde::{Serialize, Deserialize};
use crate::request::{Request, FromRequest, Outcome};
use crate::data::{ByteUnit, ToByteUnit};
use crate::data::ByteUnit;
use crate::http::uncased::Uncased;
/// Mapping from data types to read limits.
/// Mapping from (hierarchical) data types to size limits.
///
/// A `Limits` structure contains a mapping from a given data type ("forms",
/// "json", and so on) to the maximum size in bytes that should be accepted by a
/// Rocket application for that data type. For instance, if the limit for
/// "forms" is set to `256`, only 256 bytes from an incoming form request will
/// be read.
/// A `Limits` structure contains a mapping from a given hierarchical data type
/// ("form", "data-form", "ext/pdf", and so on) to the maximum size in bytes
/// that should be accepted by Rocket for said data type. For instance, if the
/// limit for "form" is set to `256`, only 256 bytes from an incoming non-data
/// form (that is, url-encoded) will be accepted.
///
/// # Defaults
/// To help in preventing DoS attacks, all incoming data reads must capped by a
/// limit. As such, all data guards impose a limit. The _name_ of the limit is
/// dictated by the data guard or type itself. For instance, [`Form`] imposes
/// the `form` limit for value-based forms and `data-form` limit for data-based
/// forms.
///
/// The default limits are:
/// If a limit is exceeded, a guard will typically fail. The [`Capped`] type
/// allows retrieving some data types even when the limit is exceeded.
///
/// * **forms**: 32KiB
/// [`Capped`]: crate::data::Capped
/// [`Form`]: crate::form::Form
///
/// # Hierarchy
///
/// Data limits are hierarchical. The `/` (forward slash) character delimits the
/// levels, or layers, of a given limit. To obtain a limit value for a given
/// name, layers are peeled from right to left until a match is found, if any.
/// For example, fetching the limit named `pet/dog/bingo` will return the first
/// of `pet/dog/bingo`, `pet/dog` or `pet`:
///
/// ```rust
/// use rocket::data::{Limits, ToByteUnit};
///
/// let limits = Limits::default()
/// .limit("pet", 64.kibibytes())
/// .limit("pet/dog", 128.kibibytes())
/// .limit("pet/dog/bingo", 96.kibibytes());
///
/// assert_eq!(limits.get("pet/dog/bingo"), Some(96.kibibytes()));
/// assert_eq!(limits.get("pet/dog/ralph"), Some(128.kibibytes()));
/// assert_eq!(limits.get("pet/cat/bingo"), Some(64.kibibytes()));
///
/// assert_eq!(limits.get("pet/dog/bingo/hat"), Some(96.kibibytes()));
/// ```
///
/// # Built-in Limits
///
/// The following table details recognized built-in limits used by Rocket. Note
/// that this table _does not_ include limits for types outside of Rocket's
/// core. In particular, this does not include limits applicable to contrib
/// types like `json` and `msgpack`.
///
/// | Limit Name | Default | Type | Description |
/// |-------------------|---------|--------------|---------------------------------------|
/// | `form` | 32KiB | [`Form`] | entire non-data-based form |
/// | `data-form` | 2MiB | [`Form`] | entire data-based form |
/// | `file` | 1MiB | [`TempFile`] | [`TempFile`] data guard or form field |
/// | `file/$ext` | _N/A_ | [`TempFile`] | file form field with extension `$ext` |
/// | `string` | 8KiB | [`String`] | data guard or data form field |
/// | `bytes` | 8KiB | [`Vec<u8>`] | data guard |
///
/// [`TempFile`]: crate::data::TempFile
///
/// # Usage
///
@ -26,13 +74,16 @@ use crate::data::{ByteUnit, ToByteUnit};
/// ```rust
/// use rocket::data::{Limits, ToByteUnit};
///
/// // Set a limit of 64KiB for forms and 3MiB for JSON.
/// // Set a limit of 64KiB for forms, 3MiB for PDFs, and 1MiB for JSON.
/// let limits = Limits::default()
/// .limit("forms", 64.kibibytes())
/// .limit("json", 3.mebibytes());
/// .limit("form", 64.kibibytes())
/// .limit("file/pdf", 3.mebibytes())
/// .limit("json", 1.mebibytes());
/// ```
///
/// The configured limits can be retrieved via the `&Limits` request guard:
/// The [`Limits::default()`](#impl-Default) method populates the `Limits`
/// structure with default limits in the [table above](#built-in-limits). A
/// configured limit can be retrieved via the `&Limits` request guard:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
@ -44,7 +95,7 @@ use crate::data::{ByteUnit, ToByteUnit};
/// #[post("/echo", data = "<data>")]
/// async fn echo(data: Data, limits: &Limits) -> Result<String, Debug<io::Error>> {
/// let limit = limits.get("data").unwrap_or(1.mebibytes());
/// Ok(data.open(limit).stream_to_string().await?)
/// Ok(data.open(limit).into_string().await?.value)
/// }
/// ```
///
@ -58,10 +109,10 @@ use crate::data::{ByteUnit, ToByteUnit};
/// # struct MyType;
/// # type MyError = ();
/// #[rocket::async_trait]
/// impl FromData for MyType {
/// impl<'r> FromData<'r> for MyType {
/// type Error = MyError;
///
/// async fn from_data(req: &Request<'_>, data: Data) -> data::Outcome<Self, MyError> {
/// async fn from_data(req: &'r Request<'_>, data: Data) -> data::Outcome<Self, MyError> {
/// let limit = req.limits().get("my-data-type");
/// /* .. */
/// # unimplemented!()
@ -71,22 +122,37 @@ use crate::data::{ByteUnit, ToByteUnit};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Limits {
// We cache this internally but don't share that fact in the API.
#[serde(with = "figment::util::vec_tuple_map")]
limits: Vec<(String, ByteUnit)>
limits: Vec<(Uncased<'static>, ByteUnit)>
}
/// The default limits are:
///
/// * **forms**: 32KiB
impl Default for Limits {
fn default() -> Limits {
// Default limit for forms is 32KiB.
Limits { limits: vec![("forms".into(), 32.kibibytes())] }
Limits::new()
.limit("form", Limits::FORM)
.limit("data-form", Limits::DATA_FORM)
.limit("file", Limits::FILE)
.limit("string", Limits::STRING)
.limit("bytes", Limits::BYTES)
}
}
impl Limits {
/// Default limit for value-based forms.
pub const FORM: ByteUnit = ByteUnit::Kibibyte(32);
/// Default limit for data-based forms.
pub const DATA_FORM: ByteUnit = ByteUnit::Mebibyte(2);
/// Default limit for temporary files.
pub const FILE: ByteUnit = ByteUnit::Mebibyte(1);
/// Default limit for strings.
pub const STRING: ByteUnit = ByteUnit::Kibibyte(8);
/// Default limit for bytes.
pub const BYTES: ByteUnit = ByteUnit::Kibibyte(8);
/// Construct a new `Limits` structure with no limits set.
///
/// # Example
@ -95,10 +161,10 @@ impl Limits {
/// use rocket::data::{Limits, ToByteUnit};
///
/// let limits = Limits::default();
/// assert_eq!(limits.get("forms"), Some(32.kibibytes()));
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
///
/// let limits = Limits::new();
/// assert_eq!(limits.get("forms"), None);
/// assert_eq!(limits.get("form"), None);
/// ```
#[inline]
pub fn new() -> Self {
@ -113,42 +179,112 @@ impl Limits {
/// ```rust
/// use rocket::data::{Limits, ToByteUnit};
///
/// let limits = Limits::default().limit("json", 1.mebibytes());
/// let limits = Limits::default();
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
/// assert_eq!(limits.get("json"), None);
///
/// assert_eq!(limits.get("forms"), Some(32.kibibytes()));
/// let limits = limits.limit("json", 1.mebibytes());
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
/// assert_eq!(limits.get("json"), Some(1.mebibytes()));
///
/// let new_limits = limits.limit("json", 64.mebibytes());
/// assert_eq!(new_limits.get("json"), Some(64.mebibytes()));
/// let limits = limits.limit("json", 64.mebibytes());
/// assert_eq!(limits.get("json"), Some(64.mebibytes()));
/// ```
pub fn limit<S: Into<String>>(mut self, name: S, limit: ByteUnit) -> Self {
pub fn limit<S: Into<Uncased<'static>>>(mut self, name: S, limit: ByteUnit) -> Self {
let name = name.into();
match self.limits.iter_mut().find(|(k, _)| *k == name) {
Some((_, v)) => *v = limit,
None => self.limits.push((name, limit)),
match self.limits.binary_search_by(|(k, _)| k.cmp(&name)) {
Ok(i) => self.limits[i].1 = limit,
Err(i) => self.limits.insert(i, (name.into(), limit))
}
self.limits.sort_by(|a, b| a.0.cmp(&b.0));
self
}
/// Retrieve the set limit, if any, for the data type with name `name`.
/// Returns the limit named `name`, proceeding hierarchically from right
/// to left until one is found, or returning `None` if none is found.
///
/// # Example
///
/// ```rust
/// use rocket::data::{Limits, ToByteUnit};
///
/// let limits = Limits::default().limit("json", 64.mebibytes());
/// let limits = Limits::default()
/// .limit("json", 1.mebibytes())
/// .limit("file/jpeg", 4.mebibytes());
///
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
/// assert_eq!(limits.get("json"), Some(1.mebibytes()));
/// assert_eq!(limits.get("data-form"), Some(Limits::DATA_FORM));
///
/// assert_eq!(limits.get("file"), Some(1.mebibytes()));
/// assert_eq!(limits.get("file/png"), Some(1.mebibytes()));
/// assert_eq!(limits.get("file/jpeg"), Some(4.mebibytes()));
/// assert_eq!(limits.get("file/jpeg/inner"), Some(4.mebibytes()));
///
/// assert_eq!(limits.get("forms"), Some(32.kibibytes()));
/// assert_eq!(limits.get("json"), Some(64.mebibytes()));
/// assert!(limits.get("msgpack").is_none());
/// ```
pub fn get(&self, name: &str) -> Option<ByteUnit> {
self.limits.iter()
.find(|(k, _)| *k == name)
.map(|(_, v)| *v)
pub fn get<S: AsRef<str>>(&self, name: S) -> Option<ByteUnit> {
let mut name = name.as_ref();
let mut indices = name.rmatch_indices('/');
loop {
let exact_limit = self.limits
.binary_search_by(|(k, _)| k.as_uncased_str().cmp(name.into()))
.map(|i| self.limits[i].1);
if let Ok(exact) = exact_limit {
return Some(exact);
}
let (i, _) = indices.next()?;
name = &name[..i];
}
}
/// Returns the limit for the name created by joining the strings in
/// `layers` with `/` as a separator, then proceeding like
/// [`Limits::get()`], hierarchically from right to left until one is found,
/// or returning `None` if none is found.
///
/// This methods exists to allow finding hierarchical limits without
/// constructing a string to call `get()` with but otherwise returns the
/// same results.
///
/// # Example
///
/// ```rust
/// use rocket::data::{Limits, ToByteUnit};
///
/// let limits = Limits::default()
/// .limit("json", 2.mebibytes())
/// .limit("file/jpeg", 4.mebibytes());
///
/// assert_eq!(limits.find(["json"]), Some(2.mebibytes()));
/// assert_eq!(limits.find(["json", "person"]), Some(2.mebibytes()));
///
/// assert_eq!(limits.find(["file"]), Some(1.mebibytes()));
/// assert_eq!(limits.find(["file", "png"]), Some(1.mebibytes()));
/// assert_eq!(limits.find(["file", "jpeg"]), Some(4.mebibytes()));
/// assert_eq!(limits.find(["file", "jpeg", "inner"]), Some(4.mebibytes()));
///
/// # let s: &[&str] = &[]; assert_eq!(limits.find(s), None);
/// ```
pub fn find<S: AsRef<str>, L: AsRef<[S]>>(&self, layers: L) -> Option<ByteUnit> {
let layers = layers.as_ref();
for j in (1..=layers.len()).rev() {
let layers = &layers[..j];
let opt = self.limits
.binary_search_by(|(k, _)| {
let k_layers = k.as_str().split('/');
k_layers.cmp(layers.iter().map(|s| s.as_ref()))
})
.map(|i| self.limits[i].1);
if let Ok(byte_unit) = opt {
return Some(byte_unit);
}
}
None
}
}

Some files were not shown because too many files have changed in this diff Show More