Graduate contrib 'json' and 'msgpack' into core.

This has the following nice benefits:

  * The 'JsonValue' wrapper type is gone.
  * 'Local{Request, Response}' natively support JSON/MessagePack.
  * The 'json' and 'msgpack' limits are officially recognized.
  * Soon, Rocket application will not require an explicit 'serde' dep.

This marks the beginning of the end of 'rocket_contrib'.
This commit is contained in:
Sergio Benitez 2021-04-29 05:19:24 -07:00
parent 7fb18cbe0f
commit c74bcfd40a
38 changed files with 619 additions and 468 deletions

View File

@ -20,9 +20,7 @@ databases = [
]
# User-facing features.
default = ["json", "serve"]
json = ["serde", "serde_json", "tokio/io-util"]
msgpack = ["serde", "rmp-serde", "tokio/io-util"]
default = ["serve"]
tera_templates = ["tera", "templates"]
handlebars_templates = ["handlebars", "templates"]
helmet = ["time"]
@ -50,7 +48,6 @@ log = "0.4"
# Serialization and templating dependencies.
serde = { version = "1.0", optional = true, features = ["derive"] }
serde_json = { version = "1.0.26", optional = true }
rmp-serde = { version = "0.15.0", optional = true }
# Templating dependencies.
handlebars = { version = "3.0", optional = true }

View File

@ -16,9 +16,7 @@
//! common modules exposed by default. The present feature list is below, with
//! an asterisk next to the features that are enabled by default:
//!
//! * [json*](type@json) - JSON (de)serialization
//! * [serve*](serve) - Static File Serving
//! * [msgpack](msgpack) - MessagePack (de)serialization
//! * [handlebars_templates](templates) - Handlebars Templating
//! * [tera_templates](templates) - Tera Templating
//! * [uuid](uuid) - UUID (de)serialization
@ -28,13 +26,14 @@
//! The recommend way to include features from this crate via Rocket in your
//! project is by adding a `[dependencies.rocket_contrib]` section to your
//! `Cargo.toml` file, setting `default-features` to false, and specifying
//! features manually. For example, to use the JSON module, you would add:
//! features manually. For example, to use the `tera_templates` module, you
//! would add:
//!
//! ```toml
//! [dependencies.rocket_contrib]
//! version = "0.5.0-dev"
//! default-features = false
//! features = ["json"]
//! features = ["tera_templates"]
//! ```
//!
//! This crate is expected to grow with time, bringing in outside crates to be
@ -42,9 +41,7 @@
#[allow(unused_imports)] #[macro_use] extern crate rocket;
#[cfg(feature="json")] #[macro_use] pub mod json;
#[cfg(feature="serve")] pub mod serve;
#[cfg(feature="msgpack")] pub mod msgpack;
#[cfg(feature="templates")] pub mod templates;
#[cfg(feature="uuid")] pub mod uuid;
#[cfg(feature="databases")] pub mod databases;

View File

@ -22,8 +22,15 @@ all-features = true
default = []
tls = ["rocket_http/tls"]
secrets = ["rocket_http/private-cookies"]
json = ["serde_json", "tokio/io-util"]
msgpack = ["rmp-serde", "tokio/io-util"]
[dependencies]
# Serialization dependencies.
serde_json = { version = "1.0.26", optional = true }
rmp-serde = { version = "0.15.0", optional = true }
# Non-optional, core dependencies from here on out.
futures = "0.3.0"
yansi = "0.5"
log = { version = "0.4", features = ["std"] }

View File

@ -64,8 +64,12 @@ use crate::http::uncased::Uncased;
/// | `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 |
/// | `json` | 1MiB | [`Json`] | JSON data and form payloads |
/// | `msgpack` | 1MiB | [`MsgPack`] | MessagePack data and form payloads |
///
/// [`TempFile`]: crate::data::TempFile
/// [`Json`]: crate::serde::json::Json
/// [`MsgPack`]: crate::serde::msgpack::MsgPack
///
/// # Usage
///
@ -78,7 +82,7 @@ use crate::http::uncased::Uncased;
/// let limits = Limits::default()
/// .limit("form", 64.kibibytes())
/// .limit("file/pdf", 3.mebibytes())
/// .limit("json", 1.mebibytes());
/// .limit("json", 2.mebibytes());
/// ```
///
/// The [`Limits::default()`](#impl-Default) method populates the `Limits`
@ -134,6 +138,8 @@ impl Default for Limits {
.limit("file", Limits::FILE)
.limit("string", Limits::STRING)
.limit("bytes", Limits::BYTES)
.limit("json", Limits::JSON)
.limit("msgpack", Limits::MESSAGE_PACK)
}
}
@ -153,6 +159,12 @@ impl Limits {
/// Default limit for bytes.
pub const BYTES: ByteUnit = ByteUnit::Kibibyte(8);
/// Default limit for JSON payloads.
pub const JSON: ByteUnit = ByteUnit::Mebibyte(1);
/// Default limit for MessagePack payloads.
pub const MESSAGE_PACK: ByteUnit = ByteUnit::Mebibyte(1);
/// Construct a new `Limits` structure with no limits set.
///
/// # Example
@ -181,11 +193,12 @@ impl Limits {
///
/// let limits = Limits::default();
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
/// assert_eq!(limits.get("json"), None);
///
/// let limits = limits.limit("json", 1.mebibytes());
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
/// assert_eq!(limits.get("json"), Some(1.mebibytes()));
/// assert_eq!(limits.get("cat"), None);
///
/// let limits = limits.limit("cat", 1.mebibytes());
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
/// assert_eq!(limits.get("cat"), Some(1.mebibytes()));
///
/// let limits = limits.limit("json", 64.mebibytes());
/// assert_eq!(limits.get("json"), Some(64.mebibytes()));
@ -209,12 +222,12 @@ impl Limits {
/// use rocket::data::{Limits, ToByteUnit};
///
/// let limits = Limits::default()
/// .limit("json", 1.mebibytes())
/// .limit("json", 2.mebibytes())
/// .limit("file/jpeg", 4.mebibytes())
/// .limit("file/jpeg/special", 8.mebibytes());
///
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
/// assert_eq!(limits.get("json"), Some(1.mebibytes()));
/// assert_eq!(limits.get("json"), Some(2.mebibytes()));
/// assert_eq!(limits.get("data-form"), Some(Limits::DATA_FORM));
///
/// assert_eq!(limits.get("file"), Some(1.mebibytes()));
@ -223,7 +236,7 @@ impl Limits {
/// assert_eq!(limits.get("file/jpeg/inner"), Some(4.mebibytes()));
/// assert_eq!(limits.get("file/jpeg/special"), Some(8.mebibytes()));
///
/// assert!(limits.get("msgpack").is_none());
/// assert!(limits.get("cats").is_none());
/// ```
pub fn get<S: AsRef<str>>(&self, name: S) -> Option<ByteUnit> {
let mut name = name.as_ref();

View File

@ -27,14 +27,6 @@
//! [quickstart]: https://rocket.rs/master/guide/quickstart
//! [getting started]: https://rocket.rs/master/guide/getting-started
//!
//! ## Libraries
//!
//! Rocket's functionality is split into two crates:
//!
//! 1. Core - This core library. Needed by every Rocket application.
//! 2. [Contrib](../rocket_contrib) - Provides useful functionality for many
//! Rocket applications. Completely optional.
//!
//! ## Usage
//!
//! Depend on `rocket` in `Rocket.toml`:
@ -68,39 +60,44 @@
//!
//! ## Features
//!
//! There are two optional, disabled-by-default features:
//! To avoid unused dependencies, Rocket _feaure-gates_ functionalities, all of
//! which are disabled-by-default:
//!
//! * **secrets:** Enables support for [private cookies].
//! * **tls:** Enables support for [TLS].
//! | Feature | Description |
//! |-----------|---------------------------------------------------------|
//! | `secrets` | Support for authenticated, encrypted [private cookies]. |
//! | `tls` | Support for [TLS] encrypted connections. |
//! | `json` | Support for [JSON (de)serialization]. |
//! | `msgpack` | Support for [MessagePack (de)serialization]. |
//!
//! The features can be enabled in `Rocket.toml`:
//! Features can be selectively enabled in `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! rocket = { version = "0.5.0-dev", features = ["secrets", "tls"] }
//! rocket = { version = "0.5.0-dev", features = ["secrets", "tls", "json"] }
//! ```
//!
//! [JSON (de)serialization]: crate::serde::json
//! [MessagePack (de)serialization]: crate::serde::msgpack
//! [private cookies]: https://rocket.rs/master/guide/requests/#private-cookies
//! [TLS]: https://rocket.rs/master/guide/configuration/#tls
//!
//! ## Configuration
//!
//! By default, Rocket applications are configured via a `Rocket.toml` file
//! and/or `ROCKET_{PARAM}` environment variables. For more information on how
//! to configure Rocket, including how to completely customize configuration
//! sources, see the [configuration section] of the guide as well as the
//! [`config`] module documentation.
//!
//! [configuration section]: https://rocket.rs/master/guide/configuration/
//! Rocket offers a rich, extensible configuration system built on [Figment]. By
//! default, Rocket applications are configured via a `Rocket.toml` file
//! and/or `ROCKET_{PARAM}` environment variables, but applications may
//! configure their own sources. See the [configuration guide] for full details.
//!
//! ## Testing
//!
//! The [`local`] module contains structures that facilitate unit and
//! integration testing of a Rocket application. The top-level [`local`] module
//! documentation and the [testing chapter of the guide] include detailed
//! examples.
//! documentation and the [testing guide] include detailed examples.
//!
//! [testing chapter of the guide]: https://rocket.rs/master/guide/testing/#testing
//! [configuration guide]: https://rocket.rs/master/guide/configuration/
//! [testing guide]: https://rocket.rs/master/guide/testing/#testing
//! [Figment]: https://docs.rs/figment
/// These are public dependencies! Update docs if these are changed, especially
/// figment's version number in docs.
@ -124,6 +121,7 @@ pub mod fairing;
pub mod error;
pub mod catcher;
pub mod route;
pub mod serde;
// Reexport of HTTP everything.
pub mod http {

View File

@ -115,6 +115,73 @@ impl LocalResponse<'_> {
self.response.body_mut().to_bytes().await
}
#[cfg(feature = "json")]
async fn _into_json<T: Send + 'static>(self) -> Option<T>
where T: serde::de::DeserializeOwned
{
self.blocking_read(|r| serde_json::from_reader(r)).await?.ok()
}
#[cfg(feature = "msgpack")]
async fn _into_msgpack<T: Send + 'static>(self) -> Option<T>
where T: serde::de::DeserializeOwned
{
self.blocking_read(|r| rmp_serde::from_read(r)).await?.ok()
}
#[cfg(any(feature = "json", feature = "msgpack"))]
async fn blocking_read<T, F>(mut self, f: F) -> Option<T>
where T: Send + 'static,
F: FnOnce(&mut dyn io::Read) -> T + Send + 'static
{
use tokio::sync::mpsc;
use tokio::io::AsyncReadExt;
struct ChanReader {
last: Option<io::Cursor<Vec<u8>>>,
rx: mpsc::Receiver<io::Result<Vec<u8>>>,
}
impl std::io::Read for ChanReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
loop {
if let Some(ref mut cursor) = self.last {
if cursor.position() < cursor.get_ref().len() as u64 {
return std::io::Read::read(cursor, buf);
}
}
if let Some(buf) = self.rx.blocking_recv() {
self.last = Some(io::Cursor::new(buf?));
} else {
return Ok(0);
}
}
}
}
let (tx, rx) = mpsc::channel(2);
let reader = tokio::task::spawn_blocking(move || {
let mut reader = ChanReader { last: None, rx };
f(&mut reader)
});
loop {
let mut buf = Vec::with_capacity(1024);
// TODO: Try to fill as much as the buffer before send it off?
match self.read_buf(&mut buf).await {
Ok(n) if n == 0 => break,
Ok(_) => tx.send(Ok(buf)).await.ok()?,
Err(e) => {
tx.send(Err(e)).await.ok()?;
break;
}
}
}
reader.await.ok()
}
// Generates the public API methods, which call the private methods above.
pub_response_impl!("# use rocket::local::asynchronous::Client;\n\
use rocket::local::asynchronous::LocalResponse;" async await);

View File

@ -71,6 +71,20 @@ impl LocalResponse<'_> {
self.client.block_on(self.inner._into_bytes())
}
#[cfg(feature = "json")]
fn _into_json<T: Send + 'static>(self) -> Option<T>
where T: serde::de::DeserializeOwned
{
serde_json::from_reader(self).ok()
}
#[cfg(feature = "msgpack")]
fn _into_msgpack<T: Send + 'static>(self) -> Option<T>
where T: serde::de::DeserializeOwned
{
rmp_serde::from_read(self).ok()
}
// Generates the public API methods, which call the private methods above.
pub_response_impl!("# use rocket::local::blocking::Client;\n\
use rocket::local::blocking::LocalResponse;");

View File

@ -51,8 +51,9 @@
//! // Using the preferred `blocking` API.
//! #[test]
//! fn test_hello_world_blocking() {
//! // Construct a client to use for dispatching requests.
//! use rocket::local::blocking::Client;
//!
//! // Construct a client to use for dispatching requests.
//! let client = Client::tracked(super::rocket())
//! .expect("valid `Rocket`");
//!
@ -64,8 +65,9 @@
//! // Using the `asynchronous` API.
//! #[rocket::async_test]
//! async fn test_hello_world_async() {
//! // Construct a client to use for dispatching requests.
//! use rocket::local::asynchronous::Client;
//!
//! // Construct a client to use for dispatching requests.
//! let client = Client::tracked(super::rocket()).await
//! .expect("valid `Rocket`");
//!

View File

@ -191,12 +191,10 @@ macro_rules! pub_request_impl {
self
}
/// Set the body (data) of the request.
/// Sets the body data of the request.
///
/// # Examples
///
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
#[doc = $import]
/// use rocket::http::ContentType;
@ -204,8 +202,8 @@ macro_rules! pub_request_impl {
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let req = request
/// .header(ContentType::JSON)
/// .body(r#"{ "key": "value", "array": [1, 2, 3] }"#);
/// .header(ContentType::Text)
/// .body("Hello, world!");
/// # });
/// ```
#[inline]
@ -219,6 +217,74 @@ macro_rules! pub_request_impl {
self
}
/// Sets the body to `value` serialized as JSON with `Content-Type`
/// [`ContentType::JSON`](crate::http::ContentType::JSON).
///
/// If `value` fails to serialize, the body is set to empty. The
/// `Content-Type` header is _always_ set.
///
/// # Examples
///
/// ```rust
#[doc = $import]
/// use rocket::serde::Serialize;
/// use rocket::http::ContentType;
///
/// #[derive(Serialize)]
/// struct Task {
/// id: usize,
/// complete: bool,
/// }
///
/// # Client::_test(|_, request, _| {
/// let task = Task { id: 10, complete: false };
///
/// let request: LocalRequest = request;
/// let req = request.json(&task);
/// assert_eq!(req.content_type(), Some(&ContentType::JSON));
/// # });
/// ```
#[cfg(feature = "json")]
#[cfg_attr(nightly, doc(cfg(feature = "json")))]
pub fn json<T: crate::serde::Serialize>(self, value: &T) -> Self {
let json = serde_json::to_vec(&value).unwrap_or_default();
self.header(crate::http::ContentType::JSON).body(json)
}
/// Sets the body to `value` serialized as MessagePack with `Content-Type`
/// [`ContentType::MsgPack`](crate::http::ContentType::MsgPack).
///
/// If `value` fails to serialize, the body is set to empty. The
/// `Content-Type` header is _always_ set.
///
/// # Examples
///
/// ```rust
#[doc = $import]
/// use rocket::serde::Serialize;
/// use rocket::http::ContentType;
///
/// #[derive(Serialize)]
/// struct Task {
/// id: usize,
/// complete: bool,
/// }
///
/// # Client::_test(|_, request, _| {
/// let task = Task { id: 10, complete: false };
///
/// let request: LocalRequest = request;
/// let req = request.msgpack(&task);
/// assert_eq!(req.content_type(), Some(&ContentType::MsgPack));
/// # });
/// ```
#[cfg(feature = "msgpack")]
#[cfg_attr(nightly, doc(cfg(feature = "msgpack")))]
pub fn msgpack<T: crate::serde::Serialize>(self, value: &T) -> Self {
let msgpack = rmp_serde::to_vec(value).unwrap_or_default();
self.header(crate::http::ContentType::MsgPack).body(msgpack)
}
/// Set the body (data) of the request without consuming `self`.
///
/// # Examples

View File

@ -85,7 +85,7 @@ macro_rules! pub_response_impl {
/// Consumes `self` and reads the entirety of its body into a `Vec` of
/// bytes.
///
/// If reading fails or the body is unset in the response, return `None`.
/// If reading fails or the body is unset in the response, returns `None`.
/// Otherwise, returns `Some`. The returned vector may be empty if the body
/// is empty.
///
@ -108,6 +108,78 @@ macro_rules! pub_response_impl {
self._into_bytes() $(.$suffix)? .ok()
}
/// Consumes `self` and deserializes its body as JSON without buffering in
/// memory.
///
/// If deserialization fails or the body is unset in the response, returns
/// `None`. Otherwise, returns `Some`.
///
/// # Example
///
/// ```rust
#[doc = $doc_prelude]
/// use rocket::serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Task {
/// id: usize,
/// complete: bool,
/// text: String,
/// }
///
/// # Client::_test(|_, _, response| {
/// let response: LocalResponse = response;
/// let task = response.into_json::<Task>();
/// # });
/// ```
#[cfg(feature = "json")]
#[cfg_attr(nightly, doc(cfg(feature = "json")))]
pub $($prefix)? fn into_json<T>(self) -> Option<T>
where T: Send + serde::de::DeserializeOwned + 'static
{
if self._response().body().is_none() {
return None;
}
self._into_json() $(.$suffix)?
}
/// Consumes `self` and deserializes its body as MessagePack without
/// buffering in memory.
///
/// If deserialization fails or the body is unset in the response, returns
/// `None`. Otherwise, returns `Some`.
///
/// # Example
///
/// ```rust
#[doc = $doc_prelude]
/// use rocket::serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Task {
/// id: usize,
/// complete: bool,
/// text: String,
/// }
///
/// # Client::_test(|_, _, response| {
/// let response: LocalResponse = response;
/// let task = response.into_msgpack::<Task>();
/// # });
/// ```
#[cfg(feature = "msgpack")]
#[cfg_attr(nightly, doc(cfg(feature = "msgpack")))]
pub $($prefix)? fn into_msgpack<T>(self) -> Option<T>
where T: Send + serde::de::DeserializeOwned + 'static
{
if self._response().body().is_none() {
return None;
}
self._into_msgpack() $(.$suffix)?
}
#[cfg(test)]
#[allow(dead_code)]
fn _ensure_impls_exist() {

View File

@ -1,6 +1,6 @@
//! Automatic JSON (de)serialization support.
//!
//! See the [`Json`](crate::json::Json) type for further details.
//! See [`Json`](Json) for details.
//!
//! # Enabling
//!
@ -8,28 +8,37 @@
//! in `Cargo.toml` as follows:
//!
//! ```toml
//! [dependencies.rocket_contrib]
//! [dependencies.rocket]
//! version = "0.5.0-dev"
//! default-features = false
//! features = ["json"]
//! ```
//!
//! # Testing
//!
//! The [`LocalRequest`] and [`LocalResponse`] types provide [`json()`] and
//! [`into_json()`] methods to create a request with serialized JSON and
//! deserialize a response as JSON, respectively.
//!
//! [`LocalRequest`]: crate::local::blocking::LocalRequest
//! [`LocalResponse`]: crate::local::blocking::LocalResponse
//! [`json()`]: crate::local::blocking::LocalRequest::json()
//! [`into_json()`]: crate::local::blocking::LocalResponse::into_json()
use std::io;
use std::ops::{Deref, DerefMut};
use std::iter::FromIterator;
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 crate::request::{Request, local_cache};
use crate::data::{Limits, Data, FromData, Outcome};
use crate::response::{self, Responder, content};
use crate::http::Status;
use crate::form::prelude as form;
use serde::{Serialize, Serializer, Deserialize, Deserializer};
use serde::{Serialize, Deserialize};
#[doc(hidden)]
pub use serde_json::{json_internal, json_internal_vec};
pub use serde_json;
/// The JSON data guard: easily consume and respond with JSON.
/// The JSON guard: easily consume and return JSON.
///
/// ## Receiving JSON
///
@ -43,9 +52,8 @@ pub use serde_json::{json_internal, json_internal_vec};
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type User = usize;
/// use rocket_contrib::json::Json;
/// use rocket::serde::json::Json;
///
/// #[post("/user", format = "json", data = "<user>")]
/// fn new_user(user: Json<User>) {
@ -65,10 +73,9 @@ pub use serde_json::{json_internal, json_internal_vec};
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type Metadata = usize;
/// use rocket::form::{Form, FromForm};
/// use rocket_contrib::json::Json;
/// use rocket::serde::json::Json;
///
/// #[derive(FromForm)]
/// struct User<'r> {
@ -82,27 +89,7 @@ pub use serde_json::{json_internal, json_internal_vec};
/// }
/// ```
///
/// ## Sending JSON
///
/// If you're responding with JSON data, return a `Json<T>` type, where `T`
/// implements [`Serialize`] from [`serde`]. The content type of the response is
/// set to `application/json` automatically.
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type User = usize;
/// use rocket_contrib::json::Json;
///
/// #[get("/users/<id>")]
/// fn user(id: usize) -> Json<User> {
/// let user_from_id = User::from(id);
/// /* ... */
/// Json(user_from_id)
/// }
/// ```
///
/// ## Incoming Data Limits
/// ### Incoming Data Limits
///
/// The default size limit for incoming JSON data is 1MiB. Setting a limit
/// protects your application from denial of service (DoS) attacks and from
@ -115,13 +102,31 @@ pub use serde_json::{json_internal, json_internal_vec};
/// [global.limits]
/// json = 5242880
/// ```
///
/// ## Sending JSON
///
/// If you're responding with JSON data, return a `Json<T>` type, where `T`
/// implements [`Serialize`] from [`serde`]. The content type of the response is
/// set to `application/json` automatically.
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # type User = usize;
/// use rocket::serde::json::Json;
///
/// #[get("/users/<id>")]
/// fn user(id: usize) -> Json<User> {
/// let user_from_id = User::from(id);
/// /* ... */
/// Json(user_from_id)
/// }
/// ```
#[derive(Debug)]
pub struct Json<T>(pub T);
/// An error returned by the [`Json`] data guard when incoming data fails to
/// serialize as JSON.
/// Error returned by the [`Json`] guard when JSON deserialization fails.
#[derive(Debug)]
pub enum JsonError<'a> {
pub enum Error<'a> {
/// An I/O error occurred while reading the incoming request data.
Io(io::Error),
@ -132,14 +137,12 @@ pub enum JsonError<'a> {
Parse(&'a str, serde_json::error::Error),
}
const DEFAULT_LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
impl<T> Json<T> {
/// Consumes the JSON wrapper and returns the wrapped item.
///
/// # Example
/// ```rust
/// # use rocket_contrib::json::Json;
/// # use rocket::serde::json::Json;
/// let string = "Hello".to_string();
/// let my_json = Json(string);
/// assert_eq!(my_json.into_inner(), "Hello".to_string());
@ -151,37 +154,37 @@ impl<T> Json<T> {
}
impl<'r, T: Deserialize<'r>> Json<T> {
fn from_str(s: &'r str) -> Result<Self, JsonError<'r>> {
serde_json::from_str(s).map(Json).map_err(|e| JsonError::Parse(s, e))
fn from_str(s: &'r str) -> Result<Self, Error<'r>> {
serde_json::from_str(s).map(Json).map_err(|e| Error::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 {
async fn from_data(req: &'r Request<'_>, data: Data) -> Result<Self, Error<'r>> {
let limit = req.limits().get("json").unwrap_or(Limits::JSON);
let string = match data.open(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")));
return Err(Error::Io(io::Error::new(eof, "data limit exceeded")));
},
Err(e) => return Err(JsonError::Io(e)),
Err(e) => return Err(Error::Io(e)),
};
Self::from_str(local_cache!(req, string))
}
}
#[rocket::async_trait]
#[crate::async_trait]
impl<'r, T: Deserialize<'r>> FromData<'r> for Json<T> {
type Error = JsonError<'r>;
type Error = Error<'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(Error::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
Outcome::Failure((Status::PayloadTooLarge, Error::Io(e)))
},
Err(JsonError::Parse(s, e)) if e.classify() == serde_json::error::Category::Data => {
Outcome::Failure((Status::UnprocessableEntity, JsonError::Parse(s, e)))
Err(Error::Parse(s, e)) if e.classify() == serde_json::error::Category::Data => {
Outcome::Failure((Status::UnprocessableEntity, Error::Parse(s, e)))
},
Err(e) => Outcome::Failure((Status::BadRequest, e)),
@ -226,16 +229,16 @@ impl<T> DerefMut for Json<T> {
}
}
impl From<JsonError<'_>> for form::Error<'_> {
fn from(e: JsonError<'_>) -> Self {
impl From<Error<'_>> for form::Error<'_> {
fn from(e: Error<'_>) -> Self {
match e {
JsonError::Io(e) => e.into(),
JsonError::Parse(_, e) => form::Error::custom(e)
Error::Io(e) => e.into(),
Error::Parse(_, e) => form::Error::custom(e)
}
}
}
#[rocket::async_trait]
#[crate::async_trait]
impl<'v, T: Deserialize<'v> + 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)?)
@ -253,11 +256,11 @@ impl<'v, T: Deserialize<'v> + Send> form::FromFormField<'v> for Json<T> {
/// returned directly from a handler.
///
/// [`Value`]: serde_json::value
/// [`Responder`]: rocket::response::Responder
/// [`Responder`]: crate::response::Responder
///
/// # `Responder`
///
/// The `Responder` implementation for `JsonValue` serializes the represented
/// The `Responder` implementation for `Value` serializes the represented
/// value into a JSON string and sets the string as the body of a fixed-sized
/// response with a `Content-Type` of `application/json`.
///
@ -269,105 +272,39 @@ impl<'v, T: Deserialize<'v> + Send> form::FromFormField<'v> for Json<T> {
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib;
/// use rocket_contrib::json::JsonValue;
/// use rocket::serde::json::{json, Value};
///
/// #[get("/json")]
/// fn get_json() -> JsonValue {
/// fn get_json() -> Value {
/// json!({
/// "id": 83,
/// "values": [1, 2, 3, 4]
/// })
/// }
/// ```
#[derive(Debug, Clone, PartialEq, Default)]
pub struct JsonValue(pub serde_json::Value);
impl Serialize for JsonValue {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for JsonValue {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
serde_json::Value::deserialize(deserializer).map(JsonValue)
}
}
impl JsonValue {
#[inline(always)]
fn into_inner(self) -> serde_json::Value {
self.0
}
}
impl Deref for JsonValue {
type Target = serde_json::Value;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for JsonValue {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Into<serde_json::Value> for JsonValue {
#[inline(always)]
fn into(self) -> serde_json::Value {
self.into_inner()
}
}
impl From<serde_json::Value> for JsonValue {
#[inline(always)]
fn from(value: serde_json::Value) -> JsonValue {
JsonValue(value)
}
}
impl<T> FromIterator<T> for JsonValue where serde_json::Value: FromIterator<T> {
fn from_iter<I: IntoIterator<Item=T>>(iter: I) -> Self {
JsonValue(serde_json::Value::from_iter(iter))
}
}
#[doc(inline)]
pub use serde_json::Value;
/// Serializes the value into JSON. Returns a response with Content-Type JSON
/// and a fixed-size body with the serialized value.
impl<'r> Responder<'r, 'static> for JsonValue {
impl<'r> Responder<'r, 'static> for Value {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
content::Json(self.0.to_string()).respond_to(req)
content::Json(self.to_string()).respond_to(req)
}
}
crate::export! {
/// A macro to create ad-hoc JSON serializable values using JSON syntax.
///
/// # Usage
///
/// To import the macro, add the `#[macro_use]` attribute to the `extern crate
/// rocket_contrib` invocation:
///
/// ```rust
/// #[macro_use] extern crate rocket_contrib;
/// ```
///
/// The return type of a `json!` invocation is
/// [`JsonValue`](crate::json::JsonValue). A value created with this macro can
/// be returned from a handler as follows:
/// The return type of a `json!` invocation is [`Value`](Value). A value
/// created with this macro can be returned from a handler as follows:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib;
/// use rocket_contrib::json::JsonValue;
/// use rocket::serde::json::{json, Value};
///
/// #[get("/json")]
/// fn get_json() -> JsonValue {
/// fn get_json() -> Value {
/// json!({
/// "key": "value",
/// "array": [1, 2, 3, 4]
@ -375,8 +312,8 @@ impl<'r> Responder<'r, 'static> for JsonValue {
/// }
/// ```
///
/// The [`Responder`](rocket::response::Responder) implementation for
/// `JsonValue` serializes the value into a JSON string and sets it as the body
/// The [`Responder`](crate::response::Responder) implementation for
/// `Value` serializes the value into a JSON string and sets it as the body
/// of the response with a `Content-Type` of `application/json`.
///
/// # Examples
@ -384,22 +321,18 @@ impl<'r> Responder<'r, 'static> for JsonValue {
/// Create a simple JSON object with two keys: `"username"` and `"id"`:
///
/// ```rust
/// # #![allow(unused_variables)]
/// # #[macro_use] extern crate rocket_contrib;
/// # fn main() {
/// use rocket::serde::json::json;
///
/// let value = json!({
/// "username": "mjordan",
/// "id": 23
/// });
/// # }
/// ```
///
/// Create a more complex object with a nested object and array:
///
/// ```rust
/// # #![allow(unused_variables)]
/// # #[macro_use] extern crate rocket_contrib;
/// # fn main() {
/// # use rocket::serde::json::json;
/// let value = json!({
/// "code": 200,
/// "success": true,
@ -408,7 +341,6 @@ impl<'r> Responder<'r, 'static> for JsonValue {
/// "ids": [12, 121],
/// },
/// });
/// # }
/// ```
///
/// Variables or expressions can be interpolated into the JSON literal. Any type
@ -417,9 +349,7 @@ impl<'r> Responder<'r, 'static> for JsonValue {
/// implement `Into<String>`.
///
/// ```rust
/// # #![allow(unused_variables)]
/// # #[macro_use] extern crate rocket_contrib;
/// # fn main() {
/// # use rocket::serde::json::json;
/// let code = 200;
/// let features = vec!["serde", "json"];
///
@ -430,29 +360,20 @@ impl<'r> Responder<'r, 'static> for JsonValue {
/// features[0]: features[1]
/// }
/// });
/// # }
/// ```
///
/// Trailing commas are allowed inside both arrays and objects.
///
/// ```rust
/// # #![allow(unused_variables)]
/// # #[macro_use] extern crate rocket_contrib;
/// # fn main() {
/// # use rocket::serde::json::json;
/// let value = json!([
/// "notice",
/// "the",
/// "trailing",
/// "comma -->",
/// ]);
/// # }
/// ```
#[macro_export]
macro_rules! json {
($($json:tt)+) => {
$crate::json::JsonValue($crate::json::json_internal!($($json)+))
};
($($json:tt)+) => ($crate::serde::json::serde_json::json!($($json)*));
}
}
#[doc(inline)]
pub use json;

18
core/lib/src/serde/mod.rs Normal file
View File

@ -0,0 +1,18 @@
//! Automatic serialization and deserialization support.
#[doc(inline)]
pub use serde::ser::{Serialize, Serializer};
#[doc(inline)]
pub use serde::de::{Deserialize, DeserializeOwned, Deserializer};
#[doc(hidden)]
pub use serde::*;
#[cfg(feature = "json")]
#[cfg_attr(nightly, doc(cfg(feature = "json")))]
pub mod json;
#[cfg(feature = "msgpack")]
#[cfg_attr(nightly, doc(cfg(feature = "msgpack")))]
pub mod msgpack;

View File

@ -1,35 +1,44 @@
//! Automatic MessagePack (de)serialization support.
//!
//! See the [`MsgPack`](crate::msgpack::MsgPack) type for further details.
//! See [`MsgPack`](crate::serde::msgpack::MsgPack) for further details.
//!
//! # Enabling
//!
//! This module is only available when the `msgpack` feature is enabled. Enable
//! it in `Cargo.toml` as follows:
//! This module is only available when the `json` feature is enabled. Enable it
//! in `Cargo.toml` as follows:
//!
//! ```toml
//! [dependencies.rocket_contrib]
//! [dependencies.rocket]
//! version = "0.5.0-dev"
//! default-features = false
//! features = ["msgpack"]
//! ```
//!
//! # Testing
//!
//! The [`LocalRequest`] and [`LocalResponse`] types provide [`msgpack()`] and
//! [`into_msgpack()`] methods to create a request with serialized MessagePack
//! and deserialize a response as MessagePack, respectively.
//!
//! [`LocalRequest`]: crate::local::blocking::LocalRequest
//! [`LocalResponse`]: crate::local::blocking::LocalResponse
//! [`msgpack()`]: crate::local::blocking::LocalRequest::msgpack()
//! [`into_msgpack()`]: crate::local::blocking::LocalResponse::into_msgpack()
use std::io;
use std::ops::{Deref, DerefMut};
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 crate::request::{Request, local_cache};
use crate::data::{Limits, Data, FromData, Outcome};
use crate::response::{self, Responder, content};
use crate::http::Status;
use crate::form::prelude as form;
use serde::{Serialize, Deserialize};
#[doc(inline)]
pub use rmp_serde::decode::Error;
/// The `MsgPack` data guard and responder: easily consume and respond with
/// MessagePack.
/// The MessagePack guard: easily consume and return MessagePack.
///
/// ## Receiving MessagePack
///
@ -43,9 +52,8 @@ pub use rmp_serde::decode::Error;
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type User = usize;
/// use rocket_contrib::msgpack::MsgPack;
/// use rocket::serde::msgpack::MsgPack;
///
/// #[post("/users", format = "msgpack", data = "<user>")]
/// fn new_user(user: MsgPack<User>) {
@ -65,10 +73,9 @@ pub use rmp_serde::decode::Error;
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type Metadata = usize;
/// use rocket::form::{Form, FromForm};
/// use rocket_contrib::msgpack::MsgPack;
/// use rocket::serde::msgpack::MsgPack;
///
/// #[derive(FromForm)]
/// struct User<'r> {
@ -82,27 +89,7 @@ pub use rmp_serde::decode::Error;
/// }
/// ```
///
/// ## Sending MessagePack
///
/// If you're responding with MessagePack data, return a `MsgPack<T>` type,
/// where `T` implements [`Serialize`] from [`serde`]. The content type of the
/// response is set to `application/msgpack` automatically.
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type User = usize;
/// use rocket_contrib::msgpack::MsgPack;
///
/// #[get("/users/<id>")]
/// fn user(id: usize) -> MsgPack<User> {
/// let user_from_id = User::from(id);
/// /* ... */
/// MsgPack(user_from_id)
/// }
/// ```
///
/// ## Incoming Data Limits
/// ### Incoming Data Limits
///
/// The default size limit for incoming MessagePack data is 1MiB. Setting a
/// limit protects your application from denial of service (DOS) attacks and
@ -115,6 +102,25 @@ pub use rmp_serde::decode::Error;
/// [global.limits]
/// msgpack = 5242880
/// ```
///
/// ## Sending MessagePack
///
/// If you're responding with MessagePack data, return a `MsgPack<T>` type,
/// where `T` implements [`Serialize`] from [`serde`]. The content type of the
/// response is set to `application/msgpack` automatically.
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # type User = usize;
/// use rocket::serde::msgpack::MsgPack;
///
/// #[get("/users/<id>")]
/// fn user(id: usize) -> MsgPack<User> {
/// let user_from_id = User::from(id);
/// /* ... */
/// MsgPack(user_from_id)
/// }
/// ```
#[derive(Debug)]
pub struct MsgPack<T>(pub T);
@ -124,7 +130,7 @@ impl<T> MsgPack<T> {
/// # Example
///
/// ```rust
/// # use rocket_contrib::msgpack::MsgPack;
/// # use rocket::serde::msgpack::MsgPack;
/// let string = "Hello".to_string();
/// let my_msgpack = MsgPack(string);
/// assert_eq!(my_msgpack.into_inner(), "Hello".to_string());
@ -135,16 +141,14 @@ impl<T> MsgPack<T> {
}
}
const DEFAULT_LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
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 {
let limit = req.limits().get("msgpack").unwrap_or(Limits::MESSAGE_PACK);
let bytes = match data.open(limit).into_bytes().await {
Ok(buf) if buf.is_complete() => buf.into_inner(),
Ok(_) => {
let eof = io::ErrorKind::UnexpectedEof;
@ -156,7 +160,7 @@ impl<'r, T: Deserialize<'r>> MsgPack<T> {
Self::from_bytes(local_cache!(req, bytes))
}
}
#[rocket::async_trait]
#[crate::async_trait]
impl<'r, T: Deserialize<'r>> FromData<'r> for MsgPack<T> {
type Error = Error;
@ -192,7 +196,7 @@ impl<'r, T: Serialize> Responder<'r, 'static> for MsgPack<T> {
}
}
#[rocket::async_trait]
#[crate::async_trait]
impl<'v, T: Deserialize<'v> + 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| {

View File

@ -59,8 +59,8 @@ This directory contains projects showcasing Rocket's features.
derived `Responder`.
* **[`serialization`](./serialization)** - Showcases JSON and MessagePack
(de)serialization support in `contrib` by implementing a CRUD-like message
API in JSON and a simply read/echo API in MessagePack.
(de)serialization support by implementing a CRUD-like message API in JSON
and a simply read/echo API in MessagePack.
* **[`state`](./state)** - Illustrates the use of request-local state and
managed state. Uses request-local state to cache "expensive" per-request

View File

@ -7,4 +7,3 @@ publish = false
[dependencies]
rocket = { path = "../../core/lib", features = ["secrets"] }
serde = { version = "1", features = ["derive"] }

View File

@ -4,10 +4,10 @@
use rocket::{State, Config};
use rocket::fairing::AdHoc;
use serde::Deserialize;
use rocket::serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
struct AppConfig {
key: String,
port: u16

View File

@ -6,9 +6,7 @@ edition = "2018"
publish = false
[dependencies]
rocket = { path = "../../core/lib" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rocket = { path = "../../core/lib", features = ["json"] }
diesel = { version = "1.3", features = ["sqlite", "r2d2"] }
diesel_migrations = "1.3"
@ -20,4 +18,4 @@ features = ["runtime-tokio-rustls", "sqlite", "macros", "offline", "migrate"]
[dependencies.rocket_contrib]
path = "../../contrib/lib"
default-features = false
features = ["diesel_sqlite_pool", "sqlite_pool", "json"]
features = ["diesel_sqlite_pool", "sqlite_pool"]

View File

@ -1,12 +1,11 @@
use rocket::{Rocket, Build};
use rocket::fairing::AdHoc;
use rocket::response::{Debug, status::Created};
use rocket::serde::{Serialize, Deserialize, json::Json};
use rocket_contrib::databases::diesel;
use rocket_contrib::json::Json;
use self::diesel::prelude::*;
use serde::{Serialize, Deserialize};
#[database("diesel")]
struct Db(diesel::SqliteConnection);
@ -14,6 +13,7 @@ struct Db(diesel::SqliteConnection);
type Result<T, E = Debug<diesel::result::Error>> = std::result::Result<T, E>;
#[derive(Debug, Clone, Deserialize, Serialize, Queryable, Insertable)]
#[serde(crate = "rocket::serde")]
#[table_name="posts"]
struct Post {
#[serde(skip_deserializing)]

View File

@ -1,16 +1,17 @@
use rocket::{Rocket, Build};
use rocket::fairing::AdHoc;
use rocket_contrib::databases::rusqlite;
use rocket::serde::{Serialize, Deserialize, json::Json};
use rocket::response::{Debug, status::Created};
use rocket_contrib::json::Json;
use rocket_contrib::databases::rusqlite;
use self::rusqlite::params;
use serde::{Serialize, Deserialize};
#[database("rusqlite")]
struct Db(rusqlite::Connection);
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct Post {
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
id: Option<i64>,

View File

@ -1,11 +1,10 @@
use rocket::{Rocket, State, Build, futures};
use rocket::fairing::{self, AdHoc};
use rocket::response::status::Created;
use rocket_contrib::json::Json;
use rocket::serde::{Serialize, Deserialize, json::Json};
use futures::stream::TryStreamExt;
use futures::future::TryFutureExt;
use serde::{Serialize, Deserialize};
use sqlx::ConnectOptions;
type Db = sqlx::SqlitePool;
@ -13,6 +12,7 @@ type Db = sqlx::SqlitePool;
type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct Post {
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
id: Option<i64>,

View File

@ -1,31 +1,10 @@
use rocket::fairing::AdHoc;
use rocket::local::blocking::{Client, LocalResponse, LocalRequest};
use rocket::http::{Status, ContentType};
use serde::{Serialize, Deserialize};
// Make it easier to work with JSON.
trait LocalResponseExt {
fn into_json<T: serde::de::DeserializeOwned>(self) -> Option<T>;
}
trait LocalRequestExt {
fn json<T: serde::Serialize>(self, value: &T) -> Self;
}
impl LocalResponseExt for LocalResponse<'_> {
fn into_json<T: serde::de::DeserializeOwned>(self) -> Option<T> {
serde_json::from_reader(self).ok()
}
}
impl LocalRequestExt for LocalRequest<'_> {
fn json<T: serde::Serialize>(self, value: &T) -> Self {
let json = serde_json::to_string(value).expect("JSON serialization");
self.header(ContentType::JSON).body(json)
}
}
use rocket::local::blocking::Client;
use rocket::serde::{Serialize, Deserialize};
use rocket::http::Status;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct Post {
title: String,
text: String,

View File

@ -91,7 +91,7 @@ use rocket::response::content;
// NOTE: This example explicitly uses the `Json` type from `response::content`
// for demonstration purposes. In a real application, _always_ prefer to use
// `rocket_contrib::json::Json` instead!
// `rocket::serde::json::Json` instead!
// In a `GET` request and all other non-payload supporting request types, the
// preferred media type in the Accept header is matched against the `format` in
@ -128,7 +128,7 @@ use rocket::response::content::{Json, MsgPack};
use rocket::http::uncased::AsUncased;
// NOTE: In a real application, we'd use `Json` and `MsgPack` from
// `rocket_contrib`, which perform automatic serialization of responses and
// `rocket::serde`, which perform automatic serialization of responses and
// automatically set the `Content-Type`.
#[get("/content/<kind>")]
fn json_or_msgpack(kind: &str) -> Either<Json<&'static str>, MsgPack<&'static [u8]>> {

View File

@ -5,11 +5,6 @@ workspace = "../"
edition = "2018"
publish = false
[dependencies]
rocket = { path = "../../core/lib" }
serde = "1"
[dependencies.rocket_contrib]
path = "../../contrib/lib"
default-features = false
[dependencies.rocket]
path = "../../core/lib"
features = ["json", "msgpack"]

View File

@ -2,9 +2,8 @@ use std::borrow::Cow;
use rocket::State;
use rocket::tokio::sync::Mutex;
use rocket_contrib::json::{Json, JsonValue, json};
use serde::{Serialize, Deserialize};
use rocket::serde::json::{Json, Value, json};
use rocket::serde::{Serialize, Deserialize};
// The type to represent the ID of a message.
type Id = usize;
@ -14,13 +13,14 @@ type MessageList = Mutex<Vec<String>>;
type Messages<'r> = &'r State<MessageList>;
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct Message<'r> {
id: Option<Id>,
message: Cow<'r, str>
}
#[post("/", format = "json", data = "<message>")]
async fn new(message: Json<Message<'_>>, list: Messages<'_>) -> JsonValue {
async fn new(message: Json<Message<'_>>, list: Messages<'_>) -> Value {
let mut list = list.lock().await;
let id = list.len();
list.push(message.message.to_string());
@ -28,7 +28,7 @@ async fn new(message: Json<Message<'_>>, list: Messages<'_>) -> JsonValue {
}
#[put("/<id>", format = "json", data = "<message>")]
async fn update(id: Id, message: Json<Message<'_>>, list: Messages<'_>) -> Option<JsonValue> {
async fn update(id: Id, message: Json<Message<'_>>, list: Messages<'_>) -> Option<Value> {
match list.lock().await.get_mut(id) {
Some(existing) => {
*existing = message.message.to_string();
@ -49,7 +49,7 @@ async fn get<'r>(id: Id, list: Messages<'r>) -> Option<Json<Message<'r>>> {
}
#[catch(404)]
fn not_found() -> JsonValue {
fn not_found() -> Value {
json!({
"status": "error",
"reason": "Resource was not found."

View File

@ -1,21 +1,20 @@
use rocket_contrib::msgpack::MsgPack;
use serde::{Serialize, Deserialize};
use rocket::serde::{Serialize, Deserialize, msgpack::MsgPack};
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct Message<'r> {
id: usize,
contents: &'r str
message: &'r str
}
#[get("/<id>", format = "msgpack")]
fn get(id: usize) -> MsgPack<Message<'static>> {
MsgPack(Message { id, contents: "Hello, world!", })
MsgPack(Message { id, message: "Hello, world!", })
}
#[post("/", data = "<data>", format = "msgpack")]
fn echo<'r>(data: MsgPack<Message<'r>>) -> &'r str {
data.contents
data.message
}
pub fn stage() -> rocket::fairing::AdHoc {

View File

@ -1,5 +1,24 @@
use rocket::local::blocking::Client;
use rocket::http::{Status, ContentType, Accept};
use rocket::serde::{Serialize, Deserialize};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct Message {
id: Option<usize>,
message: String
}
impl Message {
fn new(message: impl Into<String>) -> Self {
Message { message: message.into(), id: None }
}
fn with_id(mut self, id: usize) -> Self {
self.id = Some(id);
self
}
}
#[test]
fn json_bad_get_put() {
@ -32,8 +51,7 @@ fn json_bad_get_put() {
// Try to put a message for an ID that doesn't exist.
let res = client.put("/json/80")
.header(ContentType::JSON)
.body(r#"{ "message": "Bye bye, world!" }"#)
.json(&Message::new("hi"))
.dispatch();
assert_eq!(res.status(), Status::NotFound);
@ -46,40 +64,30 @@ fn json_post_get_put_get() {
// Create/read/update/read a few messages.
for id in 0..10 {
let uri = format!("/json/{}", id);
let message = format!("Hello, JSON {}!", id);
// Check that a message with doesn't exist.
let res = client.get(&uri).header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::NotFound);
// Add a new message. This should be ID 0.
let res = client.post("/json")
.header(ContentType::JSON)
.body(format!(r#"{{ "message": "{}" }}"#, message))
.dispatch();
let message = Message::new(format!("Hello, JSON {}!", id));
let res = client.post("/json").json(&message).dispatch();
assert_eq!(res.status(), Status::Ok);
// Check that the message exists with the correct contents.
let res = client.get(&uri).header(Accept::JSON).dispatch();
assert_eq!(res.status(), Status::Ok);
let body = res.into_string().unwrap();
assert!(body.contains(&message));
assert_eq!(res.into_json::<Message>().unwrap(), message.with_id(id));
// Change the message contents.
let res = client.put(&uri)
.header(ContentType::JSON)
.body(r#"{ "message": "Bye bye, world!" }"#)
.dispatch();
let message = Message::new("Bye bye, world!");
let res = client.put(&uri).json(&message).dispatch();
assert_eq!(res.status(), Status::Ok);
// Check that the message exists with the updated contents.
let res = client.get(&uri).header(Accept::JSON).dispatch();
assert_eq!(res.status(), Status::Ok);
let body = res.into_string().unwrap();
assert!(!body.contains(&message));
assert!(body.contains("Bye bye, world!"));
assert_eq!(res.into_json::<Message>().unwrap(), message.with_id(id));
}
}
@ -91,8 +99,8 @@ fn msgpack_get() {
assert_eq!(res.content_type(), Some(ContentType::MsgPack));
// Check that the message is `[1, "Hello, world!"]`
assert_eq!(&res.into_bytes().unwrap(), &[146, 1, 173, 72, 101, 108, 108,
111, 44, 32, 119, 111, 114, 108, 100, 33]);
let msg = Message::new("Hello, world!").with_id(1);
assert_eq!(res.into_msgpack::<Message>().unwrap(), msg);
}
#[test]
@ -100,11 +108,9 @@ fn msgpack_post() {
// Dispatch request with a message of `[2, "Goodbye, world!"]`.
let client = Client::tracked(super::rocket()).unwrap();
let res = client.post("/msgpack")
.header(ContentType::MsgPack)
.body(&[146, 2, 175, 71, 111, 111, 100, 98, 121, 101, 44, 32, 119, 111,
114, 108, 100, 33])
.msgpack(&Message::new("Goodbye, world!").with_id(2))
.dispatch();
assert_eq!(res.status(), Status::Ok);
assert_eq!(res.into_string(), Some("Goodbye, world!".into()));
assert_eq!(res.into_string().unwrap(), "Goodbye, world!");
}

View File

@ -7,8 +7,6 @@ publish = false
[dependencies]
rocket = { path = "../../core/lib" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dependencies.rocket_contrib]
path = "../../contrib/lib"

View File

@ -1,9 +1,13 @@
use rocket::Request;
use rocket::response::Redirect;
use rocket::serde::Serialize;
use rocket_contrib::templates::{Template, handlebars};
use self::handlebars::{Handlebars, JsonRender};
#[derive(serde::Serialize)]
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct TemplateContext<'r> {
title: &'r str,
name: Option<&'r str>,

View File

@ -2,9 +2,12 @@ use std::collections::HashMap;
use rocket::Request;
use rocket::response::Redirect;
use rocket::serde::Serialize;
use rocket_contrib::templates::{Template, tera::Tera};
#[derive(serde::Serialize)]
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct TemplateContext<'r> {
title: &'r str,
name: Option<&'r str>,

View File

@ -7,8 +7,6 @@ publish = false
[dependencies]
rocket = { path = "../../core/lib" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
diesel = { version = "1.3", features = ["sqlite", "r2d2"] }
diesel_migrations = "1.3"

View File

@ -11,6 +11,7 @@ use rocket::{Rocket, Build};
use rocket::fairing::AdHoc;
use rocket::request::FlashMessage;
use rocket::response::{Flash, Redirect};
use rocket::serde::Serialize;
use rocket::form::Form;
use rocket_contrib::templates::Template;
@ -21,7 +22,8 @@ use crate::task::{Task, Todo};
#[database("sqlite_database")]
pub struct DbConn(diesel::SqliteConnection);
#[derive(Debug, serde::Serialize)]
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct Context {
flash: Option<(String, String)>,
tasks: Vec<Task>

View File

@ -1,3 +1,4 @@
use rocket::serde::Serialize;
use diesel::{self, result::QueryResult, prelude::*};
mod schema {
@ -15,7 +16,8 @@ use self::schema::tasks::dsl::{tasks as all_tasks, completed as task_completed};
use crate::DbConn;
#[derive(serde::Serialize, Queryable, Insertable, Debug, Clone)]
#[derive(Serialize, Queryable, Insertable, Debug, Clone)]
#[serde(crate = "rocket::serde")]
#[table_name="tasks"]
pub struct Task {
pub id: Option<i32>,

View File

@ -60,8 +60,6 @@ function check_style() {
function test_contrib() {
FEATURES=(
json
msgpack
tera_templates
handlebars_templates
serve
@ -92,6 +90,8 @@ function test_core() {
FEATURES=(
secrets
tls
json
msgpack
)
pushd "${CORE_LIB_ROOT}" > /dev/null 2>&1

View File

@ -612,18 +612,14 @@ Any type that implements [`FromData`] is also known as _a data guard_.
### JSON
The [`Json<T>`](@api/rocket_contrib/json/struct.Json.html) type from
[`rocket_contrib`] is a data guard that parses the deserialzies body data as
JSON. The only condition is that the generic type `T` implements the
`Deserialize` trait from [Serde](https://github.com/serde-rs/json).
The [`Json<T>`](@api/rocket/serde/json/struct.Json.html) guard deserialzies body
data as JSON. The only condition is that the generic type `T` implements the
`Deserialize` trait from [`serde`](https://serde.rs).
```rust
# #[macro_use] extern crate rocket;
# extern crate rocket_contrib;
# fn main() {}
use serde::Deserialize;
use rocket_contrib::json::Json;
use rocket::serde::{Deserialize, json::Json};
#[derive(Deserialize)]
struct Task<'r> {

View File

@ -63,7 +63,7 @@ fn json() -> content::Json<&'static str> {
}
```
! warning: This is _not_ the same as the [`Json`] in [`rocket_contrib`]!
! warning: This is _not_ the same as the [`Json`] in [`serde`]!
[`Accepted`]: @api/rocket/response/status/struct.Accepted.html
[`content::Json`]: @api/rocket/response/content/struct.Json.html
@ -346,21 +346,18 @@ how to detect and handle graceful shutdown requests.
### JSON
The [`Json`] responder in [`rocket_contrib`] allows you to easily respond with
well-formed JSON data: simply return a value of type `Json<T>` where `T` is the
type of a structure to serialize into JSON. The type `T` must implement the
[`Serialize`] trait from [`serde`], which can be automatically derived.
The [`Json`] responder in allows you to easily respond with well-formed JSON
data: simply return a value of type `Json<T>` where `T` is the type of a
structure to serialize into JSON. The type `T` must implement the [`Serialize`]
trait from [`serde`], which can be automatically derived.
As an example, to respond with the JSON value of a `Task` structure, we might
write:
```rust
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
use serde::Serialize;
use rocket_contrib::json::Json;
use rocket::serde::{Serialize, json::Json};
#[derive(Serialize)]
struct Task { /* .. */ }
@ -377,9 +374,9 @@ fails, a **500 - Internal Server Error** is returned.
The [serialization example] provides further illustration.
[`Json`]: @api/rocket_contrib/json/struct.Json.html
[`Json`]: @api/rocket/serde/json/struct.Json.html
[`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
[`serde`]: https://docs.serde.rs/serde/
[`serde`]: https://serde.rs
[serialization example]: @example/serialization
## Templates

View File

@ -69,6 +69,8 @@ a few below:
* [`headers`]: returns a map of all of the headers in the response.
* [`into_string`]: reads the body data into a `String`.
* [`into_bytes`]: reads the body data into a `Vec<u8>`.
* [`into_json`]: deserializes the body data on-the-fly as JSON.
* [`into_msgpack`]: deserializes the body data on-the-fly as MessagePack.
[`LocalResponse`]: @api/rocket/local/blocking/struct.LocalResponse.html
[`status`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.status
@ -76,6 +78,8 @@ a few below:
[`headers`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.headers
[`into_string`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.into_string
[`into_bytes`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.into_bytes
[`into_json`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.into_json
[`into_msgpack`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.into_msgpack
These methods are typically used in combination with the `assert_eq!` or
`assert!` macros as follows:
@ -301,24 +305,26 @@ note: emitting Rocket code generation debug output
| ^^^^^^^^^^^^^^^^
|
= note:
impl From<world> for rocket::StaticRouteInfo {
fn from(_: world) -> rocket::StaticRouteInfo {
impl world {
fn into_info(self) -> rocket::StaticRouteInfo {
fn monomorphized_function<'_b>(
__req: &'_b rocket::request::Request<'_>,
__data: rocket::data::Data,
) -> rocket::handler::HandlerFuture<'_b> {
) -> ::rocket::route::BoxFuture<'_b> {
::std::boxed::Box::pin(async move {
let ___responder = world();
rocket::handler::Outcome::from(__req, ___responder)
::rocket::handler::Outcome::from(__req, ___responder)
})
}
rocket::StaticRouteInfo {
::rocket::StaticRouteInfo {
name: "world",
method: ::rocket::http::Method::Get,
path: "/world",
handler: monomorphized_function,
format: ::std::option::Option::None,
rank: ::std::option::Option::None,
sentinels: sentinels![&'static str],
}
}
}

View File

@ -57,7 +57,7 @@ selected profile doesn't contain a requested values, while values in the
[`Toml`]: @figment/providers/struct.Toml.html
[`Json`]: @figment/providers/struct.Json.html
[`Figment`]: @api/rocket/struct.Figment.html
[`Deserialize`]: @serde/trait.Deserialize.html
[`Deserialize`]: @api/rocket/serde/trait.Deserialize.html
[`Limits::default()`]: @api/rocket/data/struct.Limits.html#impl-Default
### Secret Key
@ -205,10 +205,8 @@ from the configured provider, which is exposed via [`Rocket::figment()`]:
```rust
# #[macro_use] extern crate rocket;
# extern crate serde;
use serde::Deserialize;
use rocket::serde::Deserialize;
#[launch]
fn rocket() -> _ {
@ -242,8 +240,7 @@ Because it is common to store configuration in managed state, Rocket provides an
```rust
# #[macro_use] extern crate rocket;
# extern crate serde;
# use serde::Deserialize;
# use rocket::serde::Deserialize;
# #[derive(Deserialize)]
# struct Config {
# port: u16,
@ -278,21 +275,15 @@ more complex cases.
! note: You may need to depend on `figment` and `serde` directly.
Rocket reexports `figment` from its crate root, so you can refer to `figment`
types via `rocket::figment`. However, Rocket does not enable all features from
the figment crate. As such, you may need to import `figment` directly:
Rocket reexports `figment` and `serde` from its crate root, so you can refer
to `figment` types via `rocket::figment` and `serde` types via
`rocket::serde`. However, Rocket does not enable all features from either
crate. As such, you may need to import crates directly:
`
figment = { version = "0.9", features = ["env", "toml", "json"] }
`
Furthermore, you should directly depend on `serde` when using its `derive`
feature, which is also not enabled by Rocket:
`
serde = { version = "1", features = ["derive"] }
`
As a first example, we override configuration values at runtime by merging
figment's tuple providers with Rocket's default provider:
@ -320,10 +311,11 @@ and `APP_PROFILE` to configure the selected profile:
```rust
# #[macro_use] extern crate rocket;
use serde::{Serialize, Deserialize};
use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
use rocket::serde::{Serialize, Deserialize};
use rocket::fairing::AdHoc;
use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
#[derive(Debug, Deserialize, Serialize)]
struct Config {
app_value: usize,

View File

@ -9,8 +9,8 @@ publish = false
rocket = { path = "../../core/lib", features = ["secrets"] }
[dev-dependencies]
rocket = { path = "../../core/lib", features = ["secrets"] }
rocket_contrib = { path = "../../contrib/lib", features = ["json", "tera_templates", "diesel_sqlite_pool"] }
rocket = { path = "../../core/lib", features = ["secrets", "json"] }
rocket_contrib = { path = "../../contrib/lib", features = ["tera_templates", "diesel_sqlite_pool"] }
serde = { version = "1.0", features = ["derive"] }
rand = "0.8"
figment = { version = "0.10", features = ["toml", "env"] }