mirror of https://github.com/rwf2/Rocket.git
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:
parent
93e62c86ed
commit
63a14525d8
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
|
||||
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<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 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)))
|
||||
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))
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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> {
|
||||
impl<'r, T: Deserialize<'r>> MsgPack<T> {
|
||||
fn from_bytes(buf: &'r [u8]) -> Result<Self, Error> {
|
||||
rmp_serde::from_slice(buf).map(MsgPack)
|
||||
}
|
||||
|
||||
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)),
|
||||
};
|
||||
|
||||
Self::from_bytes(local_cache!(req, bytes))
|
||||
}
|
||||
}
|
||||
#[rocket::async_trait]
|
||||
impl<'r, T: Deserialize<'r>> FromData<'r> 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)))),
|
||||
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)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn from_data(_: &'a Request<'_>, o: Transformed<'a, Self>) -> FromDataFuture<'a, Self, Self::Error> {
|
||||
use self::Error::*;
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
|
|
@ -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) {
|
||||
match <#ty as #_request::FromSegments>::from_segments(#__req.routed_segments(#i..)) {
|
||||
#_Ok(__v) => __v,
|
||||
#_Err(#error) => return #parse_error,
|
||||
},
|
||||
#_None => return #internal_error
|
||||
#_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())
|
||||
// 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();
|
||||
|
||||
// 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)
|
||||
};
|
||||
|
||||
let decl = match segment.kind {
|
||||
Kind::Single => quote_spanned! { span =>
|
||||
let span = ty.span();
|
||||
define_spanned_export!(span => FromForm, _form);
|
||||
|
||||
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)]
|
||||
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),
|
||||
}
|
||||
};
|
||||
|
||||
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!()
|
||||
};
|
||||
|
||||
decls.push(decl);
|
||||
matchers.push(matcher);
|
||||
builders.push(builder);
|
||||
}
|
||||
|
||||
matchers.push(quote!(_ => continue));
|
||||
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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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))
|
||||
});
|
||||
|
||||
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>>>()?;
|
||||
|
||||
Ok(quote! {
|
||||
__c.__parent = __f.name.parent();
|
||||
|
||||
match __f.name.key_lossy().as_str() {
|
||||
#(#matchers,)*
|
||||
_k if _k == "_method" || !__c.__opts.strict => { /* ok */ },
|
||||
_ => __c.__errors.push(__f.unexpected()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct FormField {
|
||||
pub span: Span,
|
||||
pub name: NameSource,
|
||||
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()));
|
||||
}
|
||||
|
||||
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
|
||||
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())
|
||||
}
|
||||
|
||||
// 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"));
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
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() {
|
||||
.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(())
|
||||
})
|
||||
.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
|
||||
.fields_validate(|_, fields| {
|
||||
if fields.is_empty() {
|
||||
return Err(fields.span().error("at least one field is required"));
|
||||
}
|
||||
|
||||
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(())
|
||||
})
|
||||
.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();
|
||||
|
||||
)
|
||||
.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! {
|
||||
#(#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));
|
||||
/// 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
|
||||
}
|
||||
_ => { /* lenient or "method"; let it pass */ }
|
||||
}
|
||||
}
|
||||
|
||||
#_Ok(Self { #(#builders)* })
|
||||
})
|
||||
})
|
||||
.to_tokens2()
|
||||
.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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,33 +18,31 @@ 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")),
|
||||
.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(())
|
||||
})
|
||||
.validate_fields(|_, fields| match fields.is_empty() {
|
||||
.fields_validate(|_, 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
|
||||
)
|
||||
.inner_mapper(MapperBuild::new()
|
||||
.with_output(|_, output| quote! {
|
||||
fn respond_to(self, __req: &'__r #Request) -> #_response::Result<'__o> {
|
||||
#output
|
||||
}
|
||||
})
|
||||
.try_map_fields(|_, fields| {
|
||||
define_vars_and_mods!(_Ok);
|
||||
.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::from_attrs("response", fields.parent.attrs())
|
||||
.unwrap_or_else(|| Ok(Default::default()))?;
|
||||
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());
|
||||
|
@ -57,8 +55,8 @@ pub fn derive_responder(input: proc_macro::TokenStream) -> TokenStream {
|
|||
|
||||
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()))?;
|
||||
let attr = FieldAttr::one_from_attrs("response", &field.attrs)?
|
||||
.unwrap_or_default();
|
||||
|
||||
if !attr.ignore {
|
||||
headers.push(set_header_tokens(field.accessor()));
|
||||
|
@ -78,5 +76,6 @@ pub fn derive_responder(input: proc_macro::TokenStream) -> TokenStream {
|
|||
#_Ok(__res)
|
||||
})
|
||||
})
|
||||
.to_tokens2()
|
||||
)
|
||||
.to_tokens()
|
||||
}
|
||||
|
|
|
@ -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,58 +12,51 @@ 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
|
||||
const URI_DISPLAY: StaticTokens = quote_static!(#_uri::UriDisplay<#_uri::Query>);
|
||||
const FORMATTER: StaticTokens = quote_static!(#_uri::Formatter<#_uri::Query>);
|
||||
|
||||
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_map_field(|_, field| {
|
||||
.try_field_map(|_, 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()))?;
|
||||
|
||||
let name = name_source.name();
|
||||
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)?;)
|
||||
|
@ -69,54 +64,17 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
|
|||
|
||||
Ok(tokens)
|
||||
})
|
||||
.try_to_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() {
|
||||
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))
|
||||
_ => Err(fields.span().error(EXACTLY_ONE_FIELD))
|
||||
})
|
||||
.function(move |_, inner| quote! {
|
||||
fn fmt(&self, f: &mut #Formatter) -> ::std::fmt::Result {
|
||||
#inner
|
||||
)
|
||||
.inner_mapper(MapperBuild::new()
|
||||
.with_output(|_, output| quote! {
|
||||
fn fmt(&self, f: &mut #FORMATTER) -> ::std::fmt::Result {
|
||||
#output
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.map_field(|_, field| {
|
||||
let span = field.span().into();
|
||||
.field_map(|_, field| {
|
||||
let accessor = field.accessor();
|
||||
quote_spanned!(span => f.write_value(&#accessor)?;)
|
||||
quote_spanned!(field.span() => f.write_value(&#accessor)?;)
|
||||
})
|
||||
.try_to_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<#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()
|
||||
}
|
||||
|
|
|
@ -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);)*)
|
||||
}
|
|
@ -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)
|
||||
};
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,19 @@
|
|||
#[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.");
|
||||
fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
|
||||
Form::<Strict<T>>::parse(string).map(|s| s.into_inner())
|
||||
}
|
||||
|
||||
result
|
||||
fn lenient<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
|
||||
Form::<T>::parse(string)
|
||||
}
|
||||
|
||||
fn strict<'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, true)
|
||||
}
|
||||
|
||||
fn lenient<'f, T>(string: &'f str) -> Result<T, FormParseError<'f>>
|
||||
where T: FromForm<'f, Error = FormParseError<'f>>
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_renaming() {
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct RenamedForm {
|
||||
single: usize,
|
||||
#[form(field = "camelCase")]
|
||||
#[field(name = "camelCase")]
|
||||
camel_case: String,
|
||||
#[form(field = "TitleCase")]
|
||||
#[field(name = "TitleCase")]
|
||||
title_case: String,
|
||||
#[form(field = "type")]
|
||||
#[field(name = "type")]
|
||||
field_type: isize,
|
||||
#[form(field = "DOUBLE")]
|
||||
#[field(name = "DOUBLE")]
|
||||
double: String,
|
||||
#[form(field = "a.b")]
|
||||
dot: isize,
|
||||
#[form(field = "some space")]
|
||||
some_space: String,
|
||||
#[field(name = "a:b")]
|
||||
colon: isize,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_renaming() {
|
||||
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,25 +217,20 @@ 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,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generics() {
|
||||
#[derive(FromForm, Debug, PartialEq)]
|
||||
struct Oops<A, B, C> {
|
||||
base: String,
|
||||
|
@ -251,8 +239,12 @@ struct Oops<A, B, C> {
|
|||
c: C,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generics() {
|
||||
#[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,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn form_errors() {
|
||||
use rocket::form::error::{ErrorKind, Entity};
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct WhoopsForm {
|
||||
complete: bool,
|
||||
other: usize,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn form_errors() {
|
||||
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())));
|
||||
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,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_ident_form() {
|
||||
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);
|
||||
// }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}));
|
||||
}
|
|
@ -2,36 +2,33 @@
|
|||
|
||||
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() {
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../ui-fail/from_form_field.rs
|
|
@ -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)
|
|
@ -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>`
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../ui-fail/from_form_value.rs
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
||||
|
|
|
@ -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
|
||||
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`
|
||||
| ^^^^^^ the trait `FromData<'_>` is not implemented for `Q`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `FromTransformedData<'_>` 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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
--> $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);
|
||||
| ^^^^^^
|
||||
|
||||
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:29:10
|
||||
|
|
||||
29 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: duplicate form field
|
||||
--> $DIR/from_form.rs:40:5
|
||||
|
|
||||
40 | #[field(name = "hello")]
|
||||
| ^
|
||||
|
||||
error: [note] previously defined here
|
||||
--> $DIR/from_form.rs:38:5
|
||||
|
|
||||
38 | #[field(name = "hello")]
|
||||
| ^
|
||||
|
||||
error: [note] error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:36:10
|
||||
|
|
||||
36 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: duplicate form field
|
||||
--> $DIR/from_form.rs:47:5
|
||||
|
|
||||
47 | #[field(name = "first")]
|
||||
| ^
|
||||
|
||||
error: [note] previously defined here
|
||||
--> $DIR/from_form.rs:46:5
|
||||
|
|
||||
46 | first: String,
|
||||
| ^^^^^
|
||||
|
||||
error: [note] error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:31:10
|
||||
--> $DIR/from_form.rs:44:10
|
||||
|
|
||||
31 | #[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 field name
|
||||
--> $DIR/from_form.rs:42:20
|
||||
error: unexpected attribute parameter: `field`
|
||||
--> $DIR/from_form.rs:53:28
|
||||
|
|
||||
42 | #[form(field = "hello")]
|
||||
| ^^^^^^^
|
||||
|
||||
error: [note] previous definition here
|
||||
--> $DIR/from_form.rs:40:20
|
||||
|
|
||||
40 | #[form(field = "hello")]
|
||||
| ^^^^^^^
|
||||
|
||||
error: [note] error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:38:10
|
||||
|
|
||||
38 | #[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
|
||||
|
|
||||
49 | #[form(field = "first")]
|
||||
| ^^^^^^^
|
||||
|
||||
error: [note] previous definition here
|
||||
--> $DIR/from_form.rs:48:5
|
||||
|
|
||||
48 | first: String,
|
||||
53 | #[field(name = "blah", field = "bloo")]
|
||||
| ^^^^^
|
||||
|
||||
error: [note] error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:46:10
|
||||
--> $DIR/from_form.rs:51:10
|
||||
|
|
||||
46 | #[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: duplicate attribute parameter: field
|
||||
--> $DIR/from_form.rs:55:28
|
||||
error: expected list `#[field(..)]`, found bare path "field"
|
||||
--> $DIR/from_form.rs:59:7
|
||||
|
|
||||
55 | #[form(field = "blah", field = "bloo")]
|
||||
59 | #[field]
|
||||
| ^^^^^
|
||||
|
||||
error: [note] error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:53:10
|
||||
--> $DIR/from_form.rs:57:10
|
||||
|
|
||||
53 | #[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: malformed attribute: expected list
|
||||
--- help: expected syntax: #[form(key = value, ..)]
|
||||
--> $DIR/from_form.rs:61:7
|
||||
error: expected key/value `key = value`
|
||||
--> $DIR/from_form.rs:65:13
|
||||
|
|
||||
61 | #[form]
|
||||
| ^^^^
|
||||
|
||||
error: [note] error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:59:10
|
||||
|
|
||||
59 | #[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
|
||||
|
|
||||
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)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../ui-fail/from_form_field.rs
|
|
@ -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)
|
|
@ -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>`
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../ui-fail/from_form_value.rs
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
|
||||
|
|
|
@ -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
|
||||
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`
|
||||
| ^ the trait `FromData<'_>` is not implemented for `Q`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `FromTransformedData<'_>` 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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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() { }
|
|
@ -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() { }
|
|
@ -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 = _);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -19,7 +19,7 @@ struct Foo5(String, String);
|
|||
|
||||
#[derive(UriDisplayQuery)]
|
||||
struct Foo6 {
|
||||
#[form(field = 123)]
|
||||
#[field(name = 123)]
|
||||
field: String,
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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())
|
|
@ -104,5 +104,6 @@ macro_rules! known_shorthands {
|
|||
"css" => CSS,
|
||||
"multipart" => FormData,
|
||||
"xml" => XML,
|
||||
"pdf" => PDF,
|
||||
})
|
||||
}
|
|
@ -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)
|
|
@ -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;
|
|
@ -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::*;
|
||||
|
|
|
@ -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?)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>>;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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()))?)
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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[..]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
macro_rules! impl_partial {
|
||||
($A:ty : $B:ty) => (
|
||||
impl PartialEq<$A> for $B {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_str() == other
|
||||
fn eq(&self, other: &$A) -> bool {
|
||||
let left: &str = self.as_ref();
|
||||
let right: &str = other.as_ref();
|
||||
left == right
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for RawStr {
|
||||
impl PartialOrd<$A> for $B {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
self.as_str() == other.as_str()
|
||||
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 PartialOrd<str> for RawStr {
|
||||
#[inline(always)]
|
||||
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
|
||||
(self as &str).partial_cmp(other)
|
||||
}
|
||||
}
|
||||
impl_partial!(str : RawStr);
|
||||
impl_partial!(str : &RawStr);
|
||||
impl_partial!(&str : RawStr);
|
||||
impl_partial!(&&str : RawStr);
|
||||
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
impl<'b> PartialEq<Absolute<'b>> for Absolute<'_> {
|
||||
/// 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<'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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
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".
|
||||
// `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")
|
||||
};
|
||||
|
||||
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¶m").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]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = ¶m[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)]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -285,8 +285,8 @@ impl fmt::Debug for Catcher {
|
|||
|
||||
macro_rules! html_error_template {
|
||||
($code:expr, $reason:expr, $description:expr) => (
|
||||
concat!(r#"
|
||||
<!DOCTYPE html>
|
||||
concat!(
|
||||
r#"<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -302,8 +302,7 @@ macro_rules! html_error_template {
|
|||
<small>Rocket</small>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"#
|
||||
</html>"#
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
/// }
|
||||
|
|
|
@ -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,
|
||||
Pin::new(&mut self.chain).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
trace_!("DataStream::stream_read()");
|
||||
Pin::new(&mut self.stream).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(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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<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<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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue