Skip to content

Commit 88fa27b

Browse files
committed
Insane number of changes.
Routing: * All UTF-8 characters are accepted anywhere 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 body forms and queries. (resolves #205) * Nested forms and structures are supported. (resolves #313) * Form fields can be ad-hoc validated with `#[field(value = 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. * 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` as `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.
1 parent 93e62c8 commit 88fa27b

File tree

191 files changed

+11401
-5572
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

191 files changed

+11401
-5572
lines changed

Cargo.toml

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
[profile.dev]
2-
codegen-units = 4
3-
41
[workspace]
52
members = [
63
"core/lib/",
@@ -11,7 +8,7 @@ members = [
118
"site/tests",
129
"examples/cookies",
1310
"examples/errors",
14-
"examples/form_validation",
11+
"examples/forms",
1512
"examples/hello_person",
1613
"examples/query_params",
1714
"examples/hello_world",
@@ -30,7 +27,6 @@ members = [
3027
"examples/msgpack",
3128
"examples/handlebars_templates",
3229
"examples/tera_templates",
33-
"examples/form_kitchen_sink",
3430
"examples/config",
3531
"examples/raw_upload",
3632
"examples/pastebin",

contrib/codegen/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ proc-macro = true
1919

2020
[dependencies]
2121
quote = "1.0"
22-
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "3648468" }
22+
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "bd221a4" }
2323

2424
[dev-dependencies]
2525
rocket = { version = "0.5.0-dev", path = "../../core/lib" }

contrib/lib/src/json.rs

+105-56
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,41 @@
1414
//! features = ["json"]
1515
//! ```
1616
17-
use std::ops::{Deref, DerefMut};
1817
use std::io;
18+
use std::ops::{Deref, DerefMut};
1919
use std::iter::FromIterator;
2020

21-
use rocket::request::Request;
22-
use rocket::outcome::Outcome::*;
23-
use rocket::data::{Data, ByteUnit, Transform::*, Transformed};
24-
use rocket::data::{FromTransformedData, TransformFuture, FromDataFuture};
25-
use rocket::http::Status;
21+
use rocket::request::{Request, local_cache};
22+
use rocket::data::{ByteUnit, Data, FromData, Outcome};
2623
use rocket::response::{self, Responder, content};
24+
use rocket::http::Status;
25+
use rocket::form::prelude as form;
2726

2827
use serde::{Serialize, Serializer};
29-
use serde::de::{Deserialize, Deserializer};
28+
use serde::de::{Deserialize, DeserializeOwned, Deserializer};
3029

3130
#[doc(hidden)]
3231
pub use serde_json::{json_internal, json_internal_vec};
3332

34-
/// The JSON type: implements [`FromTransformedData`] and [`Responder`], allowing you to
35-
/// easily consume and respond with JSON.
33+
/// The JSON data guard: easily consume and respond with JSON.
3634
///
3735
/// ## Receiving JSON
3836
///
39-
/// If you're receiving JSON data, simply add a `data` parameter to your route
40-
/// arguments and ensure the type of the parameter is a `Json<T>`, where `T` is
41-
/// some type you'd like to parse from JSON. `T` must implement [`Deserialize`]
42-
/// from [`serde`]. The data is parsed from the HTTP request body.
37+
/// `Json` is both a data guard and a form guard.
38+
///
39+
/// ### Data Guard
40+
///
41+
/// To parse request body data as JSON , add a `data` route argument with a
42+
/// target type of `Json<T>`, where `T` is some type you'd like to parse from
43+
/// JSON. `T` must implement [`serde::Deserialize`].
4344
///
4445
/// ```rust
4546
/// # #[macro_use] extern crate rocket;
4647
/// # extern crate rocket_contrib;
4748
/// # type User = usize;
4849
/// use rocket_contrib::json::Json;
4950
///
50-
/// #[post("/users", format = "json", data = "<user>")]
51+
/// #[post("/user", format = "json", data = "<user>")]
5152
/// fn new_user(user: Json<User>) {
5253
/// /* ... */
5354
/// }
@@ -58,6 +59,30 @@ pub use serde_json::{json_internal, json_internal_vec};
5859
/// "application/json" as its `Content-Type` header value will not be routed to
5960
/// the handler.
6061
///
62+
/// ### Form Guard
63+
///
64+
/// `Json<T>`, as a form guard, accepts value and data fields and parses the
65+
/// data as a `T`. Simple use `Json<T>`:
66+
///
67+
/// ```rust
68+
/// # #[macro_use] extern crate rocket;
69+
/// # extern crate rocket_contrib;
70+
/// # type Metadata = usize;
71+
/// use rocket::form::{Form, FromForm};
72+
/// use rocket_contrib::json::Json;
73+
///
74+
/// #[derive(FromForm)]
75+
/// struct User<'r> {
76+
/// name: &'r str,
77+
/// metadata: Json<Metadata>
78+
/// }
79+
///
80+
/// #[post("/user", data = "<form>")]
81+
/// fn new_user(form: Form<User<'_>>) {
82+
/// /* ... */
83+
/// }
84+
/// ```
85+
///
6186
/// ## Sending JSON
6287
///
6388
/// If you're responding with JSON data, return a `Json<T>` type, where `T`
@@ -94,6 +119,22 @@ pub use serde_json::{json_internal, json_internal_vec};
94119
#[derive(Debug)]
95120
pub struct Json<T>(pub T);
96121

122+
/// An error returned by the [`Json`] data guard when incoming data fails to
123+
/// serialize as JSON.
124+
#[derive(Debug)]
125+
pub enum JsonError<'a> {
126+
/// An I/O error occurred while reading the incoming request data.
127+
Io(io::Error),
128+
129+
/// The client's data was received successfully but failed to parse as valid
130+
/// JSON or as the requested type. The `&str` value in `.0` is the raw data
131+
/// received from the user, while the `Error` in `.1` is the deserialization
132+
/// error from `serde`.
133+
Parse(&'a str, serde_json::error::Error),
134+
}
135+
136+
const DEFAULT_LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
137+
97138
impl<T> Json<T> {
98139
/// Consumes the JSON wrapper and returns the wrapped item.
99140
///
@@ -110,52 +151,38 @@ impl<T> Json<T> {
110151
}
111152
}
112153

113-
/// An error returned by the [`Json`] data guard when incoming data fails to
114-
/// serialize as JSON.
115-
#[derive(Debug)]
116-
pub enum JsonError<'a> {
117-
/// An I/O error occurred while reading the incoming request data.
118-
Io(io::Error),
119-
120-
/// The client's data was received successfully but failed to parse as valid
121-
/// JSON or as the requested type. The `&str` value in `.0` is the raw data
122-
/// received from the user, while the `Error` in `.1` is the deserialization
123-
/// error from `serde`.
124-
Parse(&'a str, serde_json::error::Error),
125-
}
154+
impl<'r, T: Deserialize<'r>> Json<T> {
155+
fn from_str(s: &'r str) -> Result<Self, JsonError<'r>> {
156+
serde_json::from_str(s).map(Json).map_err(|e| JsonError::Parse(s, e))
157+
}
126158

127-
const DEFAULT_LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
159+
async fn from_data(req: &'r Request<'_>, data: Data) -> Result<Self, JsonError<'r>> {
160+
let size_limit = req.limits().get("json").unwrap_or(DEFAULT_LIMIT);
161+
let string = match data.open(size_limit).into_string().await {
162+
Ok(s) if s.is_complete() => s.into_inner(),
163+
Ok(_) => {
164+
let eof = io::ErrorKind::UnexpectedEof;
165+
return Err(JsonError::Io(io::Error::new(eof, "data limit exceeded")));
166+
},
167+
Err(e) => return Err(JsonError::Io(e)),
168+
};
128169

129-
impl<'a, T: Deserialize<'a>> FromTransformedData<'a> for Json<T> {
130-
type Error = JsonError<'a>;
131-
type Owned = String;
132-
type Borrowed = str;
133-
134-
fn transform<'r>(r: &'r Request<'_>, d: Data) -> TransformFuture<'r, Self::Owned, Self::Error> {
135-
Box::pin(async move {
136-
let size_limit = r.limits().get("json").unwrap_or(DEFAULT_LIMIT);
137-
match d.open(size_limit).stream_to_string().await {
138-
Ok(s) => Borrowed(Success(s)),
139-
Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(e))))
140-
}
141-
})
170+
Self::from_str(local_cache!(req, string))
142171
}
172+
}
173+
174+
#[rocket::async_trait]
175+
impl<'r, T: Deserialize<'r>> FromData<'r> for Json<T> {
176+
type Error = JsonError<'r>;
143177

144-
fn from_data(_: &'a Request<'_>, o: Transformed<'a, Self>) -> FromDataFuture<'a, Self, Self::Error> {
145-
Box::pin(async move {
146-
let string = try_outcome!(o.borrowed());
147-
match serde_json::from_str(&string) {
148-
Ok(v) => Success(Json(v)),
149-
Err(e) => {
150-
error_!("Couldn't parse JSON body: {:?}", e);
151-
if e.is_data() {
152-
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
153-
} else {
154-
Failure((Status::BadRequest, JsonError::Parse(string, e)))
155-
}
156-
}
157-
}
158-
})
178+
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
179+
match Self::from_data(req, data).await {
180+
Ok(value) => Outcome::Success(value),
181+
Err(JsonError::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
182+
Outcome::Failure((Status::PayloadTooLarge, JsonError::Io(e)))
183+
},
184+
Err(e) => Outcome::Failure((Status::BadRequest, e)),
185+
}
159186
}
160187
}
161188

@@ -190,6 +217,26 @@ impl<T> DerefMut for Json<T> {
190217
}
191218
}
192219

220+
impl From<JsonError<'_>> for form::Error<'_> {
221+
fn from(e: JsonError<'_>) -> Self {
222+
match e {
223+
JsonError::Io(e) => e.into(),
224+
JsonError::Parse(_, e) => form::Error::custom(e)
225+
}
226+
}
227+
}
228+
229+
#[rocket::async_trait]
230+
impl<'v, T: DeserializeOwned + Send> form::FromFormField<'v> for Json<T> {
231+
fn from_value(field: form::ValueField<'v>) -> Result<Self, form::Errors<'v>> {
232+
Ok(Self::from_str(field.value)?)
233+
}
234+
235+
async fn from_data(f: form::DataField<'v, '_>) -> Result<Self, form::Errors<'v>> {
236+
Ok(Self::from_data(f.request, f.data).await?)
237+
}
238+
}
239+
193240
/// An arbitrary JSON value.
194241
///
195242
/// This structure wraps `serde`'s [`Value`] type. Importantly, unlike `Value`,
@@ -397,3 +444,5 @@ macro_rules! json {
397444
$crate::json::JsonValue($crate::json::json_internal!($($json)+))
398445
};
399446
}
447+
448+
pub use json;

0 commit comments

Comments
 (0)