mirror of https://github.com/rwf2/Rocket.git
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:
parent
7fb18cbe0f
commit
c74bcfd40a
|
@ -20,9 +20,7 @@ databases = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# User-facing features.
|
# User-facing features.
|
||||||
default = ["json", "serve"]
|
default = ["serve"]
|
||||||
json = ["serde", "serde_json", "tokio/io-util"]
|
|
||||||
msgpack = ["serde", "rmp-serde", "tokio/io-util"]
|
|
||||||
tera_templates = ["tera", "templates"]
|
tera_templates = ["tera", "templates"]
|
||||||
handlebars_templates = ["handlebars", "templates"]
|
handlebars_templates = ["handlebars", "templates"]
|
||||||
helmet = ["time"]
|
helmet = ["time"]
|
||||||
|
@ -50,7 +48,6 @@ log = "0.4"
|
||||||
# Serialization and templating dependencies.
|
# Serialization and templating dependencies.
|
||||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||||
serde_json = { version = "1.0.26", optional = true }
|
serde_json = { version = "1.0.26", optional = true }
|
||||||
rmp-serde = { version = "0.15.0", optional = true }
|
|
||||||
|
|
||||||
# Templating dependencies.
|
# Templating dependencies.
|
||||||
handlebars = { version = "3.0", optional = true }
|
handlebars = { version = "3.0", optional = true }
|
||||||
|
|
|
@ -16,9 +16,7 @@
|
||||||
//! common modules exposed by default. The present feature list is below, with
|
//! common modules exposed by default. The present feature list is below, with
|
||||||
//! an asterisk next to the features that are enabled by default:
|
//! an asterisk next to the features that are enabled by default:
|
||||||
//!
|
//!
|
||||||
//! * [json*](type@json) - JSON (de)serialization
|
|
||||||
//! * [serve*](serve) - Static File Serving
|
//! * [serve*](serve) - Static File Serving
|
||||||
//! * [msgpack](msgpack) - MessagePack (de)serialization
|
|
||||||
//! * [handlebars_templates](templates) - Handlebars Templating
|
//! * [handlebars_templates](templates) - Handlebars Templating
|
||||||
//! * [tera_templates](templates) - Tera Templating
|
//! * [tera_templates](templates) - Tera Templating
|
||||||
//! * [uuid](uuid) - UUID (de)serialization
|
//! * [uuid](uuid) - UUID (de)serialization
|
||||||
|
@ -28,13 +26,14 @@
|
||||||
//! The recommend way to include features from this crate via Rocket in your
|
//! The recommend way to include features from this crate via Rocket in your
|
||||||
//! project is by adding a `[dependencies.rocket_contrib]` section to your
|
//! project is by adding a `[dependencies.rocket_contrib]` section to your
|
||||||
//! `Cargo.toml` file, setting `default-features` to false, and specifying
|
//! `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
|
//! ```toml
|
||||||
//! [dependencies.rocket_contrib]
|
//! [dependencies.rocket_contrib]
|
||||||
//! version = "0.5.0-dev"
|
//! version = "0.5.0-dev"
|
||||||
//! default-features = false
|
//! default-features = false
|
||||||
//! features = ["json"]
|
//! features = ["tera_templates"]
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! This crate is expected to grow with time, bringing in outside crates to be
|
//! 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;
|
#[allow(unused_imports)] #[macro_use] extern crate rocket;
|
||||||
|
|
||||||
#[cfg(feature="json")] #[macro_use] pub mod json;
|
|
||||||
#[cfg(feature="serve")] pub mod serve;
|
#[cfg(feature="serve")] pub mod serve;
|
||||||
#[cfg(feature="msgpack")] pub mod msgpack;
|
|
||||||
#[cfg(feature="templates")] pub mod templates;
|
#[cfg(feature="templates")] pub mod templates;
|
||||||
#[cfg(feature="uuid")] pub mod uuid;
|
#[cfg(feature="uuid")] pub mod uuid;
|
||||||
#[cfg(feature="databases")] pub mod databases;
|
#[cfg(feature="databases")] pub mod databases;
|
||||||
|
|
|
@ -22,8 +22,15 @@ all-features = true
|
||||||
default = []
|
default = []
|
||||||
tls = ["rocket_http/tls"]
|
tls = ["rocket_http/tls"]
|
||||||
secrets = ["rocket_http/private-cookies"]
|
secrets = ["rocket_http/private-cookies"]
|
||||||
|
json = ["serde_json", "tokio/io-util"]
|
||||||
|
msgpack = ["rmp-serde", "tokio/io-util"]
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
futures = "0.3.0"
|
||||||
yansi = "0.5"
|
yansi = "0.5"
|
||||||
log = { version = "0.4", features = ["std"] }
|
log = { version = "0.4", features = ["std"] }
|
||||||
|
|
|
@ -64,8 +64,12 @@ use crate::http::uncased::Uncased;
|
||||||
/// | `file/$ext` | _N/A_ | [`TempFile`] | file form field with extension `$ext` |
|
/// | `file/$ext` | _N/A_ | [`TempFile`] | file form field with extension `$ext` |
|
||||||
/// | `string` | 8KiB | [`String`] | data guard or data form field |
|
/// | `string` | 8KiB | [`String`] | data guard or data form field |
|
||||||
/// | `bytes` | 8KiB | [`Vec<u8>`] | data guard |
|
/// | `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
|
/// [`TempFile`]: crate::data::TempFile
|
||||||
|
/// [`Json`]: crate::serde::json::Json
|
||||||
|
/// [`MsgPack`]: crate::serde::msgpack::MsgPack
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
///
|
///
|
||||||
|
@ -78,7 +82,7 @@ use crate::http::uncased::Uncased;
|
||||||
/// let limits = Limits::default()
|
/// let limits = Limits::default()
|
||||||
/// .limit("form", 64.kibibytes())
|
/// .limit("form", 64.kibibytes())
|
||||||
/// .limit("file/pdf", 3.mebibytes())
|
/// .limit("file/pdf", 3.mebibytes())
|
||||||
/// .limit("json", 1.mebibytes());
|
/// .limit("json", 2.mebibytes());
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The [`Limits::default()`](#impl-Default) method populates the `Limits`
|
/// The [`Limits::default()`](#impl-Default) method populates the `Limits`
|
||||||
|
@ -134,6 +138,8 @@ impl Default for Limits {
|
||||||
.limit("file", Limits::FILE)
|
.limit("file", Limits::FILE)
|
||||||
.limit("string", Limits::STRING)
|
.limit("string", Limits::STRING)
|
||||||
.limit("bytes", Limits::BYTES)
|
.limit("bytes", Limits::BYTES)
|
||||||
|
.limit("json", Limits::JSON)
|
||||||
|
.limit("msgpack", Limits::MESSAGE_PACK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +159,12 @@ impl Limits {
|
||||||
/// Default limit for bytes.
|
/// Default limit for bytes.
|
||||||
pub const BYTES: ByteUnit = ByteUnit::Kibibyte(8);
|
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.
|
/// Construct a new `Limits` structure with no limits set.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -181,11 +193,12 @@ impl Limits {
|
||||||
///
|
///
|
||||||
/// let limits = Limits::default();
|
/// let limits = Limits::default();
|
||||||
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
|
/// 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("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());
|
/// let limits = limits.limit("json", 64.mebibytes());
|
||||||
/// assert_eq!(limits.get("json"), Some(64.mebibytes()));
|
/// assert_eq!(limits.get("json"), Some(64.mebibytes()));
|
||||||
|
@ -209,12 +222,12 @@ impl Limits {
|
||||||
/// use rocket::data::{Limits, ToByteUnit};
|
/// use rocket::data::{Limits, ToByteUnit};
|
||||||
///
|
///
|
||||||
/// let limits = Limits::default()
|
/// let limits = Limits::default()
|
||||||
/// .limit("json", 1.mebibytes())
|
/// .limit("json", 2.mebibytes())
|
||||||
/// .limit("file/jpeg", 4.mebibytes())
|
/// .limit("file/jpeg", 4.mebibytes())
|
||||||
/// .limit("file/jpeg/special", 8.mebibytes());
|
/// .limit("file/jpeg/special", 8.mebibytes());
|
||||||
///
|
///
|
||||||
/// assert_eq!(limits.get("form"), Some(32.kibibytes()));
|
/// 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("data-form"), Some(Limits::DATA_FORM));
|
||||||
///
|
///
|
||||||
/// assert_eq!(limits.get("file"), Some(1.mebibytes()));
|
/// 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/inner"), Some(4.mebibytes()));
|
||||||
/// assert_eq!(limits.get("file/jpeg/special"), Some(8.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> {
|
pub fn get<S: AsRef<str>>(&self, name: S) -> Option<ByteUnit> {
|
||||||
let mut name = name.as_ref();
|
let mut name = name.as_ref();
|
||||||
|
|
|
@ -27,14 +27,6 @@
|
||||||
//! [quickstart]: https://rocket.rs/master/guide/quickstart
|
//! [quickstart]: https://rocket.rs/master/guide/quickstart
|
||||||
//! [getting started]: https://rocket.rs/master/guide/getting-started
|
//! [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
|
//! ## Usage
|
||||||
//!
|
//!
|
||||||
//! Depend on `rocket` in `Rocket.toml`:
|
//! Depend on `rocket` in `Rocket.toml`:
|
||||||
|
@ -68,39 +60,44 @@
|
||||||
//!
|
//!
|
||||||
//! ## Features
|
//! ## 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].
|
//! | Feature | Description |
|
||||||
//! * **tls:** Enables support for [TLS].
|
//! |-----------|---------------------------------------------------------|
|
||||||
|
//! | `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
|
//! ```toml
|
||||||
//! [dependencies]
|
//! [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
|
//! [private cookies]: https://rocket.rs/master/guide/requests/#private-cookies
|
||||||
//! [TLS]: https://rocket.rs/master/guide/configuration/#tls
|
//! [TLS]: https://rocket.rs/master/guide/configuration/#tls
|
||||||
//!
|
//!
|
||||||
//! ## Configuration
|
//! ## Configuration
|
||||||
//!
|
//!
|
||||||
//! By default, Rocket applications are configured via a `Rocket.toml` file
|
//! Rocket offers a rich, extensible configuration system built on [Figment]. By
|
||||||
//! and/or `ROCKET_{PARAM}` environment variables. For more information on how
|
//! default, Rocket applications are configured via a `Rocket.toml` file
|
||||||
//! to configure Rocket, including how to completely customize configuration
|
//! and/or `ROCKET_{PARAM}` environment variables, but applications may
|
||||||
//! sources, see the [configuration section] of the guide as well as the
|
//! configure their own sources. See the [configuration guide] for full details.
|
||||||
//! [`config`] module documentation.
|
|
||||||
//!
|
|
||||||
//! [configuration section]: https://rocket.rs/master/guide/configuration/
|
|
||||||
//!
|
//!
|
||||||
//! ## Testing
|
//! ## Testing
|
||||||
//!
|
//!
|
||||||
//! The [`local`] module contains structures that facilitate unit and
|
//! The [`local`] module contains structures that facilitate unit and
|
||||||
//! integration testing of a Rocket application. The top-level [`local`] module
|
//! integration testing of a Rocket application. The top-level [`local`] module
|
||||||
//! documentation and the [testing chapter of the guide] include detailed
|
//! documentation and the [testing guide] include detailed examples.
|
||||||
//! 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
|
/// These are public dependencies! Update docs if these are changed, especially
|
||||||
/// figment's version number in docs.
|
/// figment's version number in docs.
|
||||||
|
@ -124,6 +121,7 @@ pub mod fairing;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod catcher;
|
pub mod catcher;
|
||||||
pub mod route;
|
pub mod route;
|
||||||
|
pub mod serde;
|
||||||
|
|
||||||
// Reexport of HTTP everything.
|
// Reexport of HTTP everything.
|
||||||
pub mod http {
|
pub mod http {
|
||||||
|
|
|
@ -115,6 +115,73 @@ impl LocalResponse<'_> {
|
||||||
self.response.body_mut().to_bytes().await
|
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.
|
// Generates the public API methods, which call the private methods above.
|
||||||
pub_response_impl!("# use rocket::local::asynchronous::Client;\n\
|
pub_response_impl!("# use rocket::local::asynchronous::Client;\n\
|
||||||
use rocket::local::asynchronous::LocalResponse;" async await);
|
use rocket::local::asynchronous::LocalResponse;" async await);
|
||||||
|
|
|
@ -71,6 +71,20 @@ impl LocalResponse<'_> {
|
||||||
self.client.block_on(self.inner._into_bytes())
|
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.
|
// Generates the public API methods, which call the private methods above.
|
||||||
pub_response_impl!("# use rocket::local::blocking::Client;\n\
|
pub_response_impl!("# use rocket::local::blocking::Client;\n\
|
||||||
use rocket::local::blocking::LocalResponse;");
|
use rocket::local::blocking::LocalResponse;");
|
||||||
|
|
|
@ -51,8 +51,9 @@
|
||||||
//! // Using the preferred `blocking` API.
|
//! // Using the preferred `blocking` API.
|
||||||
//! #[test]
|
//! #[test]
|
||||||
//! fn test_hello_world_blocking() {
|
//! fn test_hello_world_blocking() {
|
||||||
//! // Construct a client to use for dispatching requests.
|
|
||||||
//! use rocket::local::blocking::Client;
|
//! use rocket::local::blocking::Client;
|
||||||
|
//!
|
||||||
|
//! // Construct a client to use for dispatching requests.
|
||||||
//! let client = Client::tracked(super::rocket())
|
//! let client = Client::tracked(super::rocket())
|
||||||
//! .expect("valid `Rocket`");
|
//! .expect("valid `Rocket`");
|
||||||
//!
|
//!
|
||||||
|
@ -64,8 +65,9 @@
|
||||||
//! // Using the `asynchronous` API.
|
//! // Using the `asynchronous` API.
|
||||||
//! #[rocket::async_test]
|
//! #[rocket::async_test]
|
||||||
//! async fn test_hello_world_async() {
|
//! async fn test_hello_world_async() {
|
||||||
//! // Construct a client to use for dispatching requests.
|
|
||||||
//! use rocket::local::asynchronous::Client;
|
//! use rocket::local::asynchronous::Client;
|
||||||
|
//!
|
||||||
|
//! // Construct a client to use for dispatching requests.
|
||||||
//! let client = Client::tracked(super::rocket()).await
|
//! let client = Client::tracked(super::rocket()).await
|
||||||
//! .expect("valid `Rocket`");
|
//! .expect("valid `Rocket`");
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -191,12 +191,10 @@ macro_rules! pub_request_impl {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the body (data) of the request.
|
/// Sets the body data of the request.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Set the body to be a JSON structure; also sets the Content-Type.
|
|
||||||
///
|
|
||||||
/// ```rust
|
/// ```rust
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
/// use rocket::http::ContentType;
|
/// use rocket::http::ContentType;
|
||||||
|
@ -204,8 +202,8 @@ macro_rules! pub_request_impl {
|
||||||
/// # Client::_test(|_, request, _| {
|
/// # Client::_test(|_, request, _| {
|
||||||
/// let request: LocalRequest = request;
|
/// let request: LocalRequest = request;
|
||||||
/// let req = request
|
/// let req = request
|
||||||
/// .header(ContentType::JSON)
|
/// .header(ContentType::Text)
|
||||||
/// .body(r#"{ "key": "value", "array": [1, 2, 3] }"#);
|
/// .body("Hello, world!");
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -219,6 +217,74 @@ macro_rules! pub_request_impl {
|
||||||
self
|
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`.
|
/// Set the body (data) of the request without consuming `self`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
|
@ -85,7 +85,7 @@ macro_rules! pub_response_impl {
|
||||||
/// Consumes `self` and reads the entirety of its body into a `Vec` of
|
/// Consumes `self` and reads the entirety of its body into a `Vec` of
|
||||||
/// bytes.
|
/// 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
|
/// Otherwise, returns `Some`. The returned vector may be empty if the body
|
||||||
/// is empty.
|
/// is empty.
|
||||||
///
|
///
|
||||||
|
@ -108,6 +108,78 @@ macro_rules! pub_response_impl {
|
||||||
self._into_bytes() $(.$suffix)? .ok()
|
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)]
|
#[cfg(test)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn _ensure_impls_exist() {
|
fn _ensure_impls_exist() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Automatic JSON (de)serialization support.
|
//! Automatic JSON (de)serialization support.
|
||||||
//!
|
//!
|
||||||
//! See the [`Json`](crate::json::Json) type for further details.
|
//! See [`Json`](Json) for details.
|
||||||
//!
|
//!
|
||||||
//! # Enabling
|
//! # Enabling
|
||||||
//!
|
//!
|
||||||
|
@ -8,28 +8,37 @@
|
||||||
//! in `Cargo.toml` as follows:
|
//! in `Cargo.toml` as follows:
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies.rocket_contrib]
|
//! [dependencies.rocket]
|
||||||
//! version = "0.5.0-dev"
|
//! version = "0.5.0-dev"
|
||||||
//! default-features = false
|
|
||||||
//! features = ["json"]
|
//! 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::io;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::iter::FromIterator;
|
|
||||||
|
|
||||||
use rocket::request::{Request, local_cache};
|
use crate::request::{Request, local_cache};
|
||||||
use rocket::data::{ByteUnit, Data, FromData, Outcome};
|
use crate::data::{Limits, Data, FromData, Outcome};
|
||||||
use rocket::response::{self, Responder, content};
|
use crate::response::{self, Responder, content};
|
||||||
use rocket::http::Status;
|
use crate::http::Status;
|
||||||
use rocket::form::prelude as form;
|
use crate::form::prelude as form;
|
||||||
|
|
||||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[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
|
/// ## Receiving JSON
|
||||||
///
|
///
|
||||||
|
@ -43,9 +52,8 @@ pub use serde_json::{json_internal, json_internal_vec};
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket_contrib;
|
|
||||||
/// # type User = usize;
|
/// # type User = usize;
|
||||||
/// use rocket_contrib::json::Json;
|
/// use rocket::serde::json::Json;
|
||||||
///
|
///
|
||||||
/// #[post("/user", format = "json", data = "<user>")]
|
/// #[post("/user", format = "json", data = "<user>")]
|
||||||
/// fn new_user(user: Json<User>) {
|
/// fn new_user(user: Json<User>) {
|
||||||
|
@ -65,10 +73,9 @@ pub use serde_json::{json_internal, json_internal_vec};
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket_contrib;
|
|
||||||
/// # type Metadata = usize;
|
/// # type Metadata = usize;
|
||||||
/// use rocket::form::{Form, FromForm};
|
/// use rocket::form::{Form, FromForm};
|
||||||
/// use rocket_contrib::json::Json;
|
/// use rocket::serde::json::Json;
|
||||||
///
|
///
|
||||||
/// #[derive(FromForm)]
|
/// #[derive(FromForm)]
|
||||||
/// struct User<'r> {
|
/// struct User<'r> {
|
||||||
|
@ -82,27 +89,7 @@ pub use serde_json::{json_internal, json_internal_vec};
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Sending JSON
|
/// ### Incoming Data Limits
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
///
|
///
|
||||||
/// The default size limit for incoming JSON data is 1MiB. Setting a limit
|
/// The default size limit for incoming JSON data is 1MiB. Setting a limit
|
||||||
/// protects your application from denial of service (DoS) attacks and from
|
/// 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]
|
/// [global.limits]
|
||||||
/// json = 5242880
|
/// 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)]
|
#[derive(Debug)]
|
||||||
pub struct Json<T>(pub T);
|
pub struct Json<T>(pub T);
|
||||||
|
|
||||||
/// An error returned by the [`Json`] data guard when incoming data fails to
|
/// Error returned by the [`Json`] guard when JSON deserialization fails.
|
||||||
/// serialize as JSON.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum JsonError<'a> {
|
pub enum Error<'a> {
|
||||||
/// An I/O error occurred while reading the incoming request data.
|
/// An I/O error occurred while reading the incoming request data.
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
|
|
||||||
|
@ -132,14 +137,12 @@ pub enum JsonError<'a> {
|
||||||
Parse(&'a str, serde_json::error::Error),
|
Parse(&'a str, serde_json::error::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
|
|
||||||
|
|
||||||
impl<T> Json<T> {
|
impl<T> Json<T> {
|
||||||
/// Consumes the JSON wrapper and returns the wrapped item.
|
/// Consumes the JSON wrapper and returns the wrapped item.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rocket_contrib::json::Json;
|
/// # use rocket::serde::json::Json;
|
||||||
/// let string = "Hello".to_string();
|
/// let string = "Hello".to_string();
|
||||||
/// let my_json = Json(string);
|
/// let my_json = Json(string);
|
||||||
/// assert_eq!(my_json.into_inner(), "Hello".to_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> {
|
impl<'r, T: Deserialize<'r>> Json<T> {
|
||||||
fn from_str(s: &'r str) -> Result<Self, JsonError<'r>> {
|
fn from_str(s: &'r str) -> Result<Self, Error<'r>> {
|
||||||
serde_json::from_str(s).map(Json).map_err(|e| JsonError::Parse(s, e))
|
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>> {
|
async fn from_data(req: &'r Request<'_>, data: Data) -> Result<Self, Error<'r>> {
|
||||||
let size_limit = req.limits().get("json").unwrap_or(DEFAULT_LIMIT);
|
let limit = req.limits().get("json").unwrap_or(Limits::JSON);
|
||||||
let string = match data.open(size_limit).into_string().await {
|
let string = match data.open(limit).into_string().await {
|
||||||
Ok(s) if s.is_complete() => s.into_inner(),
|
Ok(s) if s.is_complete() => s.into_inner(),
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let eof = io::ErrorKind::UnexpectedEof;
|
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))
|
Self::from_str(local_cache!(req, string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[crate::async_trait]
|
||||||
impl<'r, T: Deserialize<'r>> FromData<'r> for Json<T> {
|
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> {
|
async fn from_data(req: &'r Request<'_>, data: Data) -> Outcome<Self, Self::Error> {
|
||||||
match Self::from_data(req, data).await {
|
match Self::from_data(req, data).await {
|
||||||
Ok(value) => Outcome::Success(value),
|
Ok(value) => Outcome::Success(value),
|
||||||
Err(JsonError::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
|
Err(Error::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
|
||||||
Outcome::Failure((Status::PayloadTooLarge, JsonError::Io(e)))
|
Outcome::Failure((Status::PayloadTooLarge, Error::Io(e)))
|
||||||
},
|
},
|
||||||
Err(JsonError::Parse(s, e)) if e.classify() == serde_json::error::Category::Data => {
|
Err(Error::Parse(s, e)) if e.classify() == serde_json::error::Category::Data => {
|
||||||
Outcome::Failure((Status::UnprocessableEntity, JsonError::Parse(s, e)))
|
Outcome::Failure((Status::UnprocessableEntity, Error::Parse(s, e)))
|
||||||
},
|
},
|
||||||
Err(e) => Outcome::Failure((Status::BadRequest, e)),
|
Err(e) => Outcome::Failure((Status::BadRequest, e)),
|
||||||
|
|
||||||
|
@ -226,16 +229,16 @@ impl<T> DerefMut for Json<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<JsonError<'_>> for form::Error<'_> {
|
impl From<Error<'_>> for form::Error<'_> {
|
||||||
fn from(e: JsonError<'_>) -> Self {
|
fn from(e: Error<'_>) -> Self {
|
||||||
match e {
|
match e {
|
||||||
JsonError::Io(e) => e.into(),
|
Error::Io(e) => e.into(),
|
||||||
JsonError::Parse(_, e) => form::Error::custom(e)
|
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> {
|
impl<'v, T: Deserialize<'v> + Send> form::FromFormField<'v> for Json<T> {
|
||||||
fn from_value(field: form::ValueField<'v>) -> Result<Self, form::Errors<'v>> {
|
fn from_value(field: form::ValueField<'v>) -> Result<Self, form::Errors<'v>> {
|
||||||
Ok(Self::from_str(field.value)?)
|
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.
|
/// returned directly from a handler.
|
||||||
///
|
///
|
||||||
/// [`Value`]: serde_json::value
|
/// [`Value`]: serde_json::value
|
||||||
/// [`Responder`]: rocket::response::Responder
|
/// [`Responder`]: crate::response::Responder
|
||||||
///
|
///
|
||||||
/// # `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
|
/// value into a JSON string and sets the string as the body of a fixed-sized
|
||||||
/// response with a `Content-Type` of `application/json`.
|
/// 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
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # #[macro_use] extern crate rocket_contrib;
|
/// use rocket::serde::json::{json, Value};
|
||||||
/// use rocket_contrib::json::JsonValue;
|
|
||||||
///
|
///
|
||||||
/// #[get("/json")]
|
/// #[get("/json")]
|
||||||
/// fn get_json() -> JsonValue {
|
/// fn get_json() -> Value {
|
||||||
/// json!({
|
/// json!({
|
||||||
/// "id": 83,
|
/// "id": 83,
|
||||||
/// "values": [1, 2, 3, 4]
|
/// "values": [1, 2, 3, 4]
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
#[doc(inline)]
|
||||||
pub struct JsonValue(pub serde_json::Value);
|
pub use 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serializes the value into JSON. Returns a response with Content-Type JSON
|
/// Serializes the value into JSON. Returns a response with Content-Type JSON
|
||||||
/// and a fixed-size body with the serialized value.
|
/// 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> {
|
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.
|
/// A macro to create ad-hoc JSON serializable values using JSON syntax.
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// The return type of a `json!` invocation is [`Value`](Value). A value
|
||||||
///
|
/// created with this macro can be returned from a handler as follows:
|
||||||
/// 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:
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # #[macro_use] extern crate rocket_contrib;
|
/// use rocket::serde::json::{json, Value};
|
||||||
/// use rocket_contrib::json::JsonValue;
|
|
||||||
///
|
///
|
||||||
/// #[get("/json")]
|
/// #[get("/json")]
|
||||||
/// fn get_json() -> JsonValue {
|
/// fn get_json() -> Value {
|
||||||
/// json!({
|
/// json!({
|
||||||
/// "key": "value",
|
/// "key": "value",
|
||||||
/// "array": [1, 2, 3, 4]
|
/// "array": [1, 2, 3, 4]
|
||||||
|
@ -375,8 +312,8 @@ impl<'r> Responder<'r, 'static> for JsonValue {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The [`Responder`](rocket::response::Responder) implementation for
|
/// The [`Responder`](crate::response::Responder) implementation for
|
||||||
/// `JsonValue` serializes the value into a JSON string and sets it as the body
|
/// `Value` serializes the value into a JSON string and sets it as the body
|
||||||
/// of the response with a `Content-Type` of `application/json`.
|
/// of the response with a `Content-Type` of `application/json`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -384,22 +321,18 @@ impl<'r> Responder<'r, 'static> for JsonValue {
|
||||||
/// Create a simple JSON object with two keys: `"username"` and `"id"`:
|
/// Create a simple JSON object with two keys: `"username"` and `"id"`:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![allow(unused_variables)]
|
/// use rocket::serde::json::json;
|
||||||
/// # #[macro_use] extern crate rocket_contrib;
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let value = json!({
|
/// let value = json!({
|
||||||
/// "username": "mjordan",
|
/// "username": "mjordan",
|
||||||
/// "id": 23
|
/// "id": 23
|
||||||
/// });
|
/// });
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Create a more complex object with a nested object and array:
|
/// Create a more complex object with a nested object and array:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![allow(unused_variables)]
|
/// # use rocket::serde::json::json;
|
||||||
/// # #[macro_use] extern crate rocket_contrib;
|
|
||||||
/// # fn main() {
|
|
||||||
/// let value = json!({
|
/// let value = json!({
|
||||||
/// "code": 200,
|
/// "code": 200,
|
||||||
/// "success": true,
|
/// "success": true,
|
||||||
|
@ -408,7 +341,6 @@ impl<'r> Responder<'r, 'static> for JsonValue {
|
||||||
/// "ids": [12, 121],
|
/// "ids": [12, 121],
|
||||||
/// },
|
/// },
|
||||||
/// });
|
/// });
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Variables or expressions can be interpolated into the JSON literal. Any type
|
/// 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>`.
|
/// implement `Into<String>`.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![allow(unused_variables)]
|
/// # use rocket::serde::json::json;
|
||||||
/// # #[macro_use] extern crate rocket_contrib;
|
|
||||||
/// # fn main() {
|
|
||||||
/// let code = 200;
|
/// let code = 200;
|
||||||
/// let features = vec!["serde", "json"];
|
/// let features = vec!["serde", "json"];
|
||||||
///
|
///
|
||||||
|
@ -430,29 +360,20 @@ impl<'r> Responder<'r, 'static> for JsonValue {
|
||||||
/// features[0]: features[1]
|
/// features[0]: features[1]
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Trailing commas are allowed inside both arrays and objects.
|
/// Trailing commas are allowed inside both arrays and objects.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![allow(unused_variables)]
|
/// # use rocket::serde::json::json;
|
||||||
/// # #[macro_use] extern crate rocket_contrib;
|
|
||||||
/// # fn main() {
|
|
||||||
/// let value = json!([
|
/// let value = json!([
|
||||||
/// "notice",
|
/// "notice",
|
||||||
/// "the",
|
/// "the",
|
||||||
/// "trailing",
|
/// "trailing",
|
||||||
/// "comma -->",
|
/// "comma -->",
|
||||||
/// ]);
|
/// ]);
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
|
||||||
macro_rules! json {
|
macro_rules! json {
|
||||||
($($json:tt)+) => {
|
($($json:tt)+) => ($crate::serde::json::serde_json::json!($($json)*));
|
||||||
$crate::json::JsonValue($crate::json::json_internal!($($json)+))
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use json;
|
|
|
@ -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;
|
|
@ -1,35 +1,44 @@
|
||||||
//! Automatic MessagePack (de)serialization support.
|
//! 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
|
//! # Enabling
|
||||||
//!
|
//!
|
||||||
//! This module is only available when the `msgpack` feature is enabled. Enable
|
//! This module is only available when the `json` feature is enabled. Enable it
|
||||||
//! it in `Cargo.toml` as follows:
|
//! in `Cargo.toml` as follows:
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies.rocket_contrib]
|
//! [dependencies.rocket]
|
||||||
//! version = "0.5.0-dev"
|
//! version = "0.5.0-dev"
|
||||||
//! default-features = false
|
|
||||||
//! features = ["msgpack"]
|
//! 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::io;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use rocket::request::{Request, local_cache};
|
use crate::request::{Request, local_cache};
|
||||||
use rocket::data::{ByteUnit, Data, FromData, Outcome};
|
use crate::data::{Limits, Data, FromData, Outcome};
|
||||||
use rocket::response::{self, Responder, content};
|
use crate::response::{self, Responder, content};
|
||||||
use rocket::http::Status;
|
use crate::http::Status;
|
||||||
use rocket::form::prelude as form;
|
use crate::form::prelude as form;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use rmp_serde::decode::Error;
|
pub use rmp_serde::decode::Error;
|
||||||
|
|
||||||
/// The `MsgPack` data guard and responder: easily consume and respond with
|
/// The MessagePack guard: easily consume and return MessagePack.
|
||||||
/// MessagePack.
|
|
||||||
///
|
///
|
||||||
/// ## Receiving MessagePack
|
/// ## Receiving MessagePack
|
||||||
///
|
///
|
||||||
|
@ -43,9 +52,8 @@ pub use rmp_serde::decode::Error;
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket_contrib;
|
|
||||||
/// # type User = usize;
|
/// # type User = usize;
|
||||||
/// use rocket_contrib::msgpack::MsgPack;
|
/// use rocket::serde::msgpack::MsgPack;
|
||||||
///
|
///
|
||||||
/// #[post("/users", format = "msgpack", data = "<user>")]
|
/// #[post("/users", format = "msgpack", data = "<user>")]
|
||||||
/// fn new_user(user: MsgPack<User>) {
|
/// fn new_user(user: MsgPack<User>) {
|
||||||
|
@ -65,10 +73,9 @@ pub use rmp_serde::decode::Error;
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket_contrib;
|
|
||||||
/// # type Metadata = usize;
|
/// # type Metadata = usize;
|
||||||
/// use rocket::form::{Form, FromForm};
|
/// use rocket::form::{Form, FromForm};
|
||||||
/// use rocket_contrib::msgpack::MsgPack;
|
/// use rocket::serde::msgpack::MsgPack;
|
||||||
///
|
///
|
||||||
/// #[derive(FromForm)]
|
/// #[derive(FromForm)]
|
||||||
/// struct User<'r> {
|
/// struct User<'r> {
|
||||||
|
@ -82,27 +89,7 @@ pub use rmp_serde::decode::Error;
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Sending MessagePack
|
/// ### Incoming Data Limits
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
///
|
///
|
||||||
/// The default size limit for incoming MessagePack data is 1MiB. Setting a
|
/// The default size limit for incoming MessagePack data is 1MiB. Setting a
|
||||||
/// limit protects your application from denial of service (DOS) attacks and
|
/// limit protects your application from denial of service (DOS) attacks and
|
||||||
|
@ -115,6 +102,25 @@ pub use rmp_serde::decode::Error;
|
||||||
/// [global.limits]
|
/// [global.limits]
|
||||||
/// msgpack = 5242880
|
/// 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)]
|
#[derive(Debug)]
|
||||||
pub struct MsgPack<T>(pub T);
|
pub struct MsgPack<T>(pub T);
|
||||||
|
|
||||||
|
@ -124,7 +130,7 @@ impl<T> MsgPack<T> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rocket_contrib::msgpack::MsgPack;
|
/// # use rocket::serde::msgpack::MsgPack;
|
||||||
/// let string = "Hello".to_string();
|
/// let string = "Hello".to_string();
|
||||||
/// let my_msgpack = MsgPack(string);
|
/// let my_msgpack = MsgPack(string);
|
||||||
/// assert_eq!(my_msgpack.into_inner(), "Hello".to_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> {
|
impl<'r, T: Deserialize<'r>> MsgPack<T> {
|
||||||
fn from_bytes(buf: &'r [u8]) -> Result<Self, Error> {
|
fn from_bytes(buf: &'r [u8]) -> Result<Self, Error> {
|
||||||
rmp_serde::from_slice(buf).map(MsgPack)
|
rmp_serde::from_slice(buf).map(MsgPack)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_data(req: &'r Request<'_>, data: Data) -> Result<Self, Error> {
|
async fn from_data(req: &'r Request<'_>, data: Data) -> Result<Self, Error> {
|
||||||
let size_limit = req.limits().get("msgpack").unwrap_or(DEFAULT_LIMIT);
|
let limit = req.limits().get("msgpack").unwrap_or(Limits::MESSAGE_PACK);
|
||||||
let bytes = match data.open(size_limit).into_bytes().await {
|
let bytes = match data.open(limit).into_bytes().await {
|
||||||
Ok(buf) if buf.is_complete() => buf.into_inner(),
|
Ok(buf) if buf.is_complete() => buf.into_inner(),
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let eof = io::ErrorKind::UnexpectedEof;
|
let eof = io::ErrorKind::UnexpectedEof;
|
||||||
|
@ -156,7 +160,7 @@ impl<'r, T: Deserialize<'r>> MsgPack<T> {
|
||||||
Self::from_bytes(local_cache!(req, bytes))
|
Self::from_bytes(local_cache!(req, bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[rocket::async_trait]
|
#[crate::async_trait]
|
||||||
impl<'r, T: Deserialize<'r>> FromData<'r> for MsgPack<T> {
|
impl<'r, T: Deserialize<'r>> FromData<'r> for MsgPack<T> {
|
||||||
type Error = Error;
|
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> {
|
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>> {
|
async fn from_data(f: form::DataField<'v, '_>) -> Result<Self, form::Errors<'v>> {
|
||||||
Self::from_data(f.request, f.data).await.map_err(|e| {
|
Self::from_data(f.request, f.data).await.map_err(|e| {
|
|
@ -59,8 +59,8 @@ This directory contains projects showcasing Rocket's features.
|
||||||
derived `Responder`.
|
derived `Responder`.
|
||||||
|
|
||||||
* **[`serialization`](./serialization)** - Showcases JSON and MessagePack
|
* **[`serialization`](./serialization)** - Showcases JSON and MessagePack
|
||||||
(de)serialization support in `contrib` by implementing a CRUD-like message
|
(de)serialization support by implementing a CRUD-like message API in JSON
|
||||||
API in JSON and a simply read/echo API in MessagePack.
|
and a simply read/echo API in MessagePack.
|
||||||
|
|
||||||
* **[`state`](./state)** - Illustrates the use of request-local state and
|
* **[`state`](./state)** - Illustrates the use of request-local state and
|
||||||
managed state. Uses request-local state to cache "expensive" per-request
|
managed state. Uses request-local state to cache "expensive" per-request
|
||||||
|
|
|
@ -7,4 +7,3 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib", features = ["secrets"] }
|
rocket = { path = "../../core/lib", features = ["secrets"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
|
|
||||||
use rocket::{State, Config};
|
use rocket::{State, Config};
|
||||||
use rocket::fairing::AdHoc;
|
use rocket::fairing::AdHoc;
|
||||||
|
use rocket::serde::Deserialize;
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct AppConfig {
|
struct AppConfig {
|
||||||
key: String,
|
key: String,
|
||||||
port: u16
|
port: u16
|
||||||
|
|
|
@ -6,9 +6,7 @@ edition = "2018"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib" }
|
rocket = { path = "../../core/lib", features = ["json"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
diesel = { version = "1.3", features = ["sqlite", "r2d2"] }
|
diesel = { version = "1.3", features = ["sqlite", "r2d2"] }
|
||||||
diesel_migrations = "1.3"
|
diesel_migrations = "1.3"
|
||||||
|
|
||||||
|
@ -20,4 +18,4 @@ features = ["runtime-tokio-rustls", "sqlite", "macros", "offline", "migrate"]
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_contrib]
|
||||||
path = "../../contrib/lib"
|
path = "../../contrib/lib"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["diesel_sqlite_pool", "sqlite_pool", "json"]
|
features = ["diesel_sqlite_pool", "sqlite_pool"]
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use rocket::{Rocket, Build};
|
use rocket::{Rocket, Build};
|
||||||
use rocket::fairing::AdHoc;
|
use rocket::fairing::AdHoc;
|
||||||
use rocket::response::{Debug, status::Created};
|
use rocket::response::{Debug, status::Created};
|
||||||
|
use rocket::serde::{Serialize, Deserialize, json::Json};
|
||||||
|
|
||||||
use rocket_contrib::databases::diesel;
|
use rocket_contrib::databases::diesel;
|
||||||
use rocket_contrib::json::Json;
|
|
||||||
|
|
||||||
use self::diesel::prelude::*;
|
use self::diesel::prelude::*;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
#[database("diesel")]
|
#[database("diesel")]
|
||||||
struct Db(diesel::SqliteConnection);
|
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>;
|
type Result<T, E = Debug<diesel::result::Error>> = std::result::Result<T, E>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Queryable, Insertable)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Queryable, Insertable)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
#[table_name="posts"]
|
#[table_name="posts"]
|
||||||
struct Post {
|
struct Post {
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
use rocket::{Rocket, Build};
|
use rocket::{Rocket, Build};
|
||||||
use rocket::fairing::AdHoc;
|
use rocket::fairing::AdHoc;
|
||||||
use rocket_contrib::databases::rusqlite;
|
use rocket::serde::{Serialize, Deserialize, json::Json};
|
||||||
use rocket::response::{Debug, status::Created};
|
use rocket::response::{Debug, status::Created};
|
||||||
use rocket_contrib::json::Json;
|
|
||||||
|
use rocket_contrib::databases::rusqlite;
|
||||||
|
|
||||||
use self::rusqlite::params;
|
use self::rusqlite::params;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
#[database("rusqlite")]
|
#[database("rusqlite")]
|
||||||
struct Db(rusqlite::Connection);
|
struct Db(rusqlite::Connection);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct Post {
|
struct Post {
|
||||||
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
|
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
|
||||||
id: Option<i64>,
|
id: Option<i64>,
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use rocket::{Rocket, State, Build, futures};
|
use rocket::{Rocket, State, Build, futures};
|
||||||
use rocket::fairing::{self, AdHoc};
|
use rocket::fairing::{self, AdHoc};
|
||||||
use rocket::response::status::Created;
|
use rocket::response::status::Created;
|
||||||
use rocket_contrib::json::Json;
|
use rocket::serde::{Serialize, Deserialize, json::Json};
|
||||||
|
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
use futures::future::TryFutureExt;
|
use futures::future::TryFutureExt;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use sqlx::ConnectOptions;
|
use sqlx::ConnectOptions;
|
||||||
|
|
||||||
type Db = sqlx::SqlitePool;
|
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>;
|
type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct Post {
|
struct Post {
|
||||||
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
|
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
|
||||||
id: Option<i64>,
|
id: Option<i64>,
|
||||||
|
|
|
@ -1,31 +1,10 @@
|
||||||
use rocket::fairing::AdHoc;
|
use rocket::fairing::AdHoc;
|
||||||
use rocket::local::blocking::{Client, LocalResponse, LocalRequest};
|
use rocket::local::blocking::Client;
|
||||||
use rocket::http::{Status, ContentType};
|
use rocket::serde::{Serialize, Deserialize};
|
||||||
use serde::{Serialize, Deserialize};
|
use rocket::http::Status;
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct Post {
|
struct Post {
|
||||||
title: String,
|
title: String,
|
||||||
text: String,
|
text: String,
|
||||||
|
|
|
@ -91,7 +91,7 @@ use rocket::response::content;
|
||||||
|
|
||||||
// NOTE: This example explicitly uses the `Json` type from `response::content`
|
// NOTE: This example explicitly uses the `Json` type from `response::content`
|
||||||
// for demonstration purposes. In a real application, _always_ prefer to use
|
// 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
|
// 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
|
// 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;
|
use rocket::http::uncased::AsUncased;
|
||||||
|
|
||||||
// NOTE: In a real application, we'd use `Json` and `MsgPack` from
|
// 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`.
|
// automatically set the `Content-Type`.
|
||||||
#[get("/content/<kind>")]
|
#[get("/content/<kind>")]
|
||||||
fn json_or_msgpack(kind: &str) -> Either<Json<&'static str>, MsgPack<&'static [u8]>> {
|
fn json_or_msgpack(kind: &str) -> Either<Json<&'static str>, MsgPack<&'static [u8]>> {
|
||||||
|
|
|
@ -5,11 +5,6 @@ workspace = "../"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies.rocket]
|
||||||
rocket = { path = "../../core/lib" }
|
path = "../../core/lib"
|
||||||
serde = "1"
|
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
|
||||||
path = "../../contrib/lib"
|
|
||||||
default-features = false
|
|
||||||
features = ["json", "msgpack"]
|
features = ["json", "msgpack"]
|
||||||
|
|
|
@ -2,9 +2,8 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket::tokio::sync::Mutex;
|
use rocket::tokio::sync::Mutex;
|
||||||
use rocket_contrib::json::{Json, JsonValue, json};
|
use rocket::serde::json::{Json, Value, json};
|
||||||
|
use rocket::serde::{Serialize, Deserialize};
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
// The type to represent the ID of a message.
|
// The type to represent the ID of a message.
|
||||||
type Id = usize;
|
type Id = usize;
|
||||||
|
@ -14,13 +13,14 @@ type MessageList = Mutex<Vec<String>>;
|
||||||
type Messages<'r> = &'r State<MessageList>;
|
type Messages<'r> = &'r State<MessageList>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct Message<'r> {
|
struct Message<'r> {
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
message: Cow<'r, str>
|
message: Cow<'r, str>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/", format = "json", data = "<message>")]
|
#[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 mut list = list.lock().await;
|
||||||
let id = list.len();
|
let id = list.len();
|
||||||
list.push(message.message.to_string());
|
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>")]
|
#[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) {
|
match list.lock().await.get_mut(id) {
|
||||||
Some(existing) => {
|
Some(existing) => {
|
||||||
*existing = message.message.to_string();
|
*existing = message.message.to_string();
|
||||||
|
@ -49,7 +49,7 @@ async fn get<'r>(id: Id, list: Messages<'r>) -> Option<Json<Message<'r>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
fn not_found() -> JsonValue {
|
fn not_found() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"reason": "Resource was not found."
|
"reason": "Resource was not found."
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
use rocket_contrib::msgpack::MsgPack;
|
use rocket::serde::{Serialize, Deserialize, msgpack::MsgPack};
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct Message<'r> {
|
struct Message<'r> {
|
||||||
id: usize,
|
id: usize,
|
||||||
contents: &'r str
|
message: &'r str
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<id>", format = "msgpack")]
|
#[get("/<id>", format = "msgpack")]
|
||||||
fn get(id: usize) -> MsgPack<Message<'static>> {
|
fn get(id: usize) -> MsgPack<Message<'static>> {
|
||||||
MsgPack(Message { id, contents: "Hello, world!", })
|
MsgPack(Message { id, message: "Hello, world!", })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/", data = "<data>", format = "msgpack")]
|
#[post("/", data = "<data>", format = "msgpack")]
|
||||||
fn echo<'r>(data: MsgPack<Message<'r>>) -> &'r str {
|
fn echo<'r>(data: MsgPack<Message<'r>>) -> &'r str {
|
||||||
data.contents
|
data.message
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stage() -> rocket::fairing::AdHoc {
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
|
|
|
@ -1,5 +1,24 @@
|
||||||
use rocket::local::blocking::Client;
|
use rocket::local::blocking::Client;
|
||||||
use rocket::http::{Status, ContentType, Accept};
|
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]
|
#[test]
|
||||||
fn json_bad_get_put() {
|
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.
|
// Try to put a message for an ID that doesn't exist.
|
||||||
let res = client.put("/json/80")
|
let res = client.put("/json/80")
|
||||||
.header(ContentType::JSON)
|
.json(&Message::new("hi"))
|
||||||
.body(r#"{ "message": "Bye bye, world!" }"#)
|
|
||||||
.dispatch();
|
.dispatch();
|
||||||
|
|
||||||
assert_eq!(res.status(), Status::NotFound);
|
assert_eq!(res.status(), Status::NotFound);
|
||||||
|
@ -46,40 +64,30 @@ fn json_post_get_put_get() {
|
||||||
// Create/read/update/read a few messages.
|
// Create/read/update/read a few messages.
|
||||||
for id in 0..10 {
|
for id in 0..10 {
|
||||||
let uri = format!("/json/{}", id);
|
let uri = format!("/json/{}", id);
|
||||||
let message = format!("Hello, JSON {}!", id);
|
|
||||||
|
|
||||||
// Check that a message with doesn't exist.
|
// Check that a message with doesn't exist.
|
||||||
let res = client.get(&uri).header(ContentType::JSON).dispatch();
|
let res = client.get(&uri).header(ContentType::JSON).dispatch();
|
||||||
assert_eq!(res.status(), Status::NotFound);
|
assert_eq!(res.status(), Status::NotFound);
|
||||||
|
|
||||||
// Add a new message. This should be ID 0.
|
// Add a new message. This should be ID 0.
|
||||||
let res = client.post("/json")
|
let message = Message::new(format!("Hello, JSON {}!", id));
|
||||||
.header(ContentType::JSON)
|
let res = client.post("/json").json(&message).dispatch();
|
||||||
.body(format!(r#"{{ "message": "{}" }}"#, message))
|
|
||||||
.dispatch();
|
|
||||||
|
|
||||||
assert_eq!(res.status(), Status::Ok);
|
assert_eq!(res.status(), Status::Ok);
|
||||||
|
|
||||||
// Check that the message exists with the correct contents.
|
// Check that the message exists with the correct contents.
|
||||||
let res = client.get(&uri).header(Accept::JSON).dispatch();
|
let res = client.get(&uri).header(Accept::JSON).dispatch();
|
||||||
assert_eq!(res.status(), Status::Ok);
|
assert_eq!(res.status(), Status::Ok);
|
||||||
let body = res.into_string().unwrap();
|
assert_eq!(res.into_json::<Message>().unwrap(), message.with_id(id));
|
||||||
assert!(body.contains(&message));
|
|
||||||
|
|
||||||
// Change the message contents.
|
// Change the message contents.
|
||||||
let res = client.put(&uri)
|
let message = Message::new("Bye bye, world!");
|
||||||
.header(ContentType::JSON)
|
let res = client.put(&uri).json(&message).dispatch();
|
||||||
.body(r#"{ "message": "Bye bye, world!" }"#)
|
|
||||||
.dispatch();
|
|
||||||
|
|
||||||
assert_eq!(res.status(), Status::Ok);
|
assert_eq!(res.status(), Status::Ok);
|
||||||
|
|
||||||
// Check that the message exists with the updated contents.
|
// Check that the message exists with the updated contents.
|
||||||
let res = client.get(&uri).header(Accept::JSON).dispatch();
|
let res = client.get(&uri).header(Accept::JSON).dispatch();
|
||||||
assert_eq!(res.status(), Status::Ok);
|
assert_eq!(res.status(), Status::Ok);
|
||||||
let body = res.into_string().unwrap();
|
assert_eq!(res.into_json::<Message>().unwrap(), message.with_id(id));
|
||||||
assert!(!body.contains(&message));
|
|
||||||
assert!(body.contains("Bye bye, world!"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +99,8 @@ fn msgpack_get() {
|
||||||
assert_eq!(res.content_type(), Some(ContentType::MsgPack));
|
assert_eq!(res.content_type(), Some(ContentType::MsgPack));
|
||||||
|
|
||||||
// Check that the message is `[1, "Hello, world!"]`
|
// Check that the message is `[1, "Hello, world!"]`
|
||||||
assert_eq!(&res.into_bytes().unwrap(), &[146, 1, 173, 72, 101, 108, 108,
|
let msg = Message::new("Hello, world!").with_id(1);
|
||||||
111, 44, 32, 119, 111, 114, 108, 100, 33]);
|
assert_eq!(res.into_msgpack::<Message>().unwrap(), msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -100,11 +108,9 @@ fn msgpack_post() {
|
||||||
// Dispatch request with a message of `[2, "Goodbye, world!"]`.
|
// Dispatch request with a message of `[2, "Goodbye, world!"]`.
|
||||||
let client = Client::tracked(super::rocket()).unwrap();
|
let client = Client::tracked(super::rocket()).unwrap();
|
||||||
let res = client.post("/msgpack")
|
let res = client.post("/msgpack")
|
||||||
.header(ContentType::MsgPack)
|
.msgpack(&Message::new("Goodbye, world!").with_id(2))
|
||||||
.body(&[146, 2, 175, 71, 111, 111, 100, 98, 121, 101, 44, 32, 119, 111,
|
|
||||||
114, 108, 100, 33])
|
|
||||||
.dispatch();
|
.dispatch();
|
||||||
|
|
||||||
assert_eq!(res.status(), Status::Ok);
|
assert_eq!(res.status(), Status::Ok);
|
||||||
assert_eq!(res.into_string(), Some("Goodbye, world!".into()));
|
assert_eq!(res.into_string().unwrap(), "Goodbye, world!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib" }
|
rocket = { path = "../../core/lib" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_contrib]
|
||||||
path = "../../contrib/lib"
|
path = "../../contrib/lib"
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use rocket::Request;
|
use rocket::Request;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
|
use rocket::serde::Serialize;
|
||||||
|
|
||||||
use rocket_contrib::templates::{Template, handlebars};
|
use rocket_contrib::templates::{Template, handlebars};
|
||||||
|
|
||||||
use self::handlebars::{Handlebars, JsonRender};
|
use self::handlebars::{Handlebars, JsonRender};
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct TemplateContext<'r> {
|
struct TemplateContext<'r> {
|
||||||
title: &'r str,
|
title: &'r str,
|
||||||
name: Option<&'r str>,
|
name: Option<&'r str>,
|
||||||
|
|
|
@ -2,9 +2,12 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use rocket::Request;
|
use rocket::Request;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
|
use rocket::serde::Serialize;
|
||||||
|
|
||||||
use rocket_contrib::templates::{Template, tera::Tera};
|
use rocket_contrib::templates::{Template, tera::Tera};
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct TemplateContext<'r> {
|
struct TemplateContext<'r> {
|
||||||
title: &'r str,
|
title: &'r str,
|
||||||
name: Option<&'r str>,
|
name: Option<&'r str>,
|
||||||
|
|
|
@ -7,8 +7,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib" }
|
rocket = { path = "../../core/lib" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
diesel = { version = "1.3", features = ["sqlite", "r2d2"] }
|
diesel = { version = "1.3", features = ["sqlite", "r2d2"] }
|
||||||
diesel_migrations = "1.3"
|
diesel_migrations = "1.3"
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ use rocket::{Rocket, Build};
|
||||||
use rocket::fairing::AdHoc;
|
use rocket::fairing::AdHoc;
|
||||||
use rocket::request::FlashMessage;
|
use rocket::request::FlashMessage;
|
||||||
use rocket::response::{Flash, Redirect};
|
use rocket::response::{Flash, Redirect};
|
||||||
|
use rocket::serde::Serialize;
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
|
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
|
@ -21,7 +22,8 @@ use crate::task::{Task, Todo};
|
||||||
#[database("sqlite_database")]
|
#[database("sqlite_database")]
|
||||||
pub struct DbConn(diesel::SqliteConnection);
|
pub struct DbConn(diesel::SqliteConnection);
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
struct Context {
|
struct Context {
|
||||||
flash: Option<(String, String)>,
|
flash: Option<(String, String)>,
|
||||||
tasks: Vec<Task>
|
tasks: Vec<Task>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use rocket::serde::Serialize;
|
||||||
use diesel::{self, result::QueryResult, prelude::*};
|
use diesel::{self, result::QueryResult, prelude::*};
|
||||||
|
|
||||||
mod schema {
|
mod schema {
|
||||||
|
@ -15,7 +16,8 @@ use self::schema::tasks::dsl::{tasks as all_tasks, completed as task_completed};
|
||||||
|
|
||||||
use crate::DbConn;
|
use crate::DbConn;
|
||||||
|
|
||||||
#[derive(serde::Serialize, Queryable, Insertable, Debug, Clone)]
|
#[derive(Serialize, Queryable, Insertable, Debug, Clone)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
#[table_name="tasks"]
|
#[table_name="tasks"]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
pub id: Option<i32>,
|
pub id: Option<i32>,
|
||||||
|
|
|
@ -60,8 +60,6 @@ function check_style() {
|
||||||
|
|
||||||
function test_contrib() {
|
function test_contrib() {
|
||||||
FEATURES=(
|
FEATURES=(
|
||||||
json
|
|
||||||
msgpack
|
|
||||||
tera_templates
|
tera_templates
|
||||||
handlebars_templates
|
handlebars_templates
|
||||||
serve
|
serve
|
||||||
|
@ -92,6 +90,8 @@ function test_core() {
|
||||||
FEATURES=(
|
FEATURES=(
|
||||||
secrets
|
secrets
|
||||||
tls
|
tls
|
||||||
|
json
|
||||||
|
msgpack
|
||||||
)
|
)
|
||||||
|
|
||||||
pushd "${CORE_LIB_ROOT}" > /dev/null 2>&1
|
pushd "${CORE_LIB_ROOT}" > /dev/null 2>&1
|
||||||
|
|
|
@ -612,18 +612,14 @@ Any type that implements [`FromData`] is also known as _a data guard_.
|
||||||
|
|
||||||
### JSON
|
### JSON
|
||||||
|
|
||||||
The [`Json<T>`](@api/rocket_contrib/json/struct.Json.html) type from
|
The [`Json<T>`](@api/rocket/serde/json/struct.Json.html) guard deserialzies body
|
||||||
[`rocket_contrib`] is a data guard that parses the deserialzies body data as
|
data as JSON. The only condition is that the generic type `T` implements the
|
||||||
JSON. The only condition is that the generic type `T` implements the
|
`Deserialize` trait from [`serde`](https://serde.rs).
|
||||||
`Deserialize` trait from [Serde](https://github.com/serde-rs/json).
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
# extern crate rocket_contrib;
|
|
||||||
# fn main() {}
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
use rocket::serde::{Deserialize, json::Json};
|
||||||
use rocket_contrib::json::Json;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Task<'r> {
|
struct Task<'r> {
|
||||||
|
|
|
@ -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
|
[`Accepted`]: @api/rocket/response/status/struct.Accepted.html
|
||||||
[`content::Json`]: @api/rocket/response/content/struct.Json.html
|
[`content::Json`]: @api/rocket/response/content/struct.Json.html
|
||||||
|
@ -346,21 +346,18 @@ how to detect and handle graceful shutdown requests.
|
||||||
|
|
||||||
### JSON
|
### JSON
|
||||||
|
|
||||||
The [`Json`] responder in [`rocket_contrib`] allows you to easily respond with
|
The [`Json`] responder in allows you to easily respond with well-formed JSON
|
||||||
well-formed JSON data: simply return a value of type `Json<T>` where `T` is the
|
data: simply return a value of type `Json<T>` where `T` is the type of a
|
||||||
type of a structure to serialize into JSON. The type `T` must implement the
|
structure to serialize into JSON. The type `T` must implement the [`Serialize`]
|
||||||
[`Serialize`] trait from [`serde`], which can be automatically derived.
|
trait from [`serde`], which can be automatically derived.
|
||||||
|
|
||||||
As an example, to respond with the JSON value of a `Task` structure, we might
|
As an example, to respond with the JSON value of a `Task` structure, we might
|
||||||
write:
|
write:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
# #[macro_use] extern crate rocket_contrib;
|
|
||||||
# fn main() {}
|
|
||||||
|
|
||||||
use serde::Serialize;
|
use rocket::serde::{Serialize, json::Json};
|
||||||
use rocket_contrib::json::Json;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Task { /* .. */ }
|
struct Task { /* .. */ }
|
||||||
|
@ -377,9 +374,9 @@ fails, a **500 - Internal Server Error** is returned.
|
||||||
|
|
||||||
The [serialization example] provides further illustration.
|
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
|
[`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
|
||||||
[`serde`]: https://docs.serde.rs/serde/
|
[`serde`]: https://serde.rs
|
||||||
[serialization example]: @example/serialization
|
[serialization example]: @example/serialization
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|
|
@ -69,6 +69,8 @@ a few below:
|
||||||
* [`headers`]: returns a map of all of the headers in the response.
|
* [`headers`]: returns a map of all of the headers in the response.
|
||||||
* [`into_string`]: reads the body data into a `String`.
|
* [`into_string`]: reads the body data into a `String`.
|
||||||
* [`into_bytes`]: reads the body data into a `Vec<u8>`.
|
* [`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
|
[`LocalResponse`]: @api/rocket/local/blocking/struct.LocalResponse.html
|
||||||
[`status`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.status
|
[`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
|
[`headers`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.headers
|
||||||
[`into_string`]: @api/rocket/local/blocking/struct.LocalResponse.html#method.into_string
|
[`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_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
|
These methods are typically used in combination with the `assert_eq!` or
|
||||||
`assert!` macros as follows:
|
`assert!` macros as follows:
|
||||||
|
@ -301,24 +305,26 @@ note: emitting Rocket code generation debug output
|
||||||
| ^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= note:
|
= note:
|
||||||
impl From<world> for rocket::StaticRouteInfo {
|
impl world {
|
||||||
fn from(_: world) -> rocket::StaticRouteInfo {
|
fn into_info(self) -> rocket::StaticRouteInfo {
|
||||||
fn monomorphized_function<'_b>(
|
fn monomorphized_function<'_b>(
|
||||||
__req: &'_b rocket::request::Request<'_>,
|
__req: &'_b rocket::request::Request<'_>,
|
||||||
__data: rocket::data::Data,
|
__data: rocket::data::Data,
|
||||||
) -> rocket::handler::HandlerFuture<'_b> {
|
) -> ::rocket::route::BoxFuture<'_b> {
|
||||||
::std::boxed::Box::pin(async move {
|
::std::boxed::Box::pin(async move {
|
||||||
let ___responder = world();
|
let ___responder = world();
|
||||||
rocket::handler::Outcome::from(__req, ___responder)
|
::rocket::handler::Outcome::from(__req, ___responder)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
rocket::StaticRouteInfo {
|
|
||||||
|
::rocket::StaticRouteInfo {
|
||||||
name: "world",
|
name: "world",
|
||||||
method: ::rocket::http::Method::Get,
|
method: ::rocket::http::Method::Get,
|
||||||
path: "/world",
|
path: "/world",
|
||||||
handler: monomorphized_function,
|
handler: monomorphized_function,
|
||||||
format: ::std::option::Option::None,
|
format: ::std::option::Option::None,
|
||||||
rank: ::std::option::Option::None,
|
rank: ::std::option::Option::None,
|
||||||
|
sentinels: sentinels![&'static str],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ selected profile doesn't contain a requested values, while values in the
|
||||||
[`Toml`]: @figment/providers/struct.Toml.html
|
[`Toml`]: @figment/providers/struct.Toml.html
|
||||||
[`Json`]: @figment/providers/struct.Json.html
|
[`Json`]: @figment/providers/struct.Json.html
|
||||||
[`Figment`]: @api/rocket/struct.Figment.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
|
[`Limits::default()`]: @api/rocket/data/struct.Limits.html#impl-Default
|
||||||
|
|
||||||
### Secret Key
|
### Secret Key
|
||||||
|
@ -205,10 +205,8 @@ from the configured provider, which is exposed via [`Rocket::figment()`]:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
# extern crate serde;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
|
use rocket::serde::Deserialize;
|
||||||
|
|
||||||
#[launch]
|
#[launch]
|
||||||
fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
|
@ -242,8 +240,7 @@ Because it is common to store configuration in managed state, Rocket provides an
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
# extern crate serde;
|
# use rocket::serde::Deserialize;
|
||||||
# use serde::Deserialize;
|
|
||||||
# #[derive(Deserialize)]
|
# #[derive(Deserialize)]
|
||||||
# struct Config {
|
# struct Config {
|
||||||
# port: u16,
|
# port: u16,
|
||||||
|
@ -278,21 +275,15 @@ more complex cases.
|
||||||
|
|
||||||
! note: You may need to depend on `figment` and `serde` directly.
|
! note: You may need to depend on `figment` and `serde` directly.
|
||||||
|
|
||||||
Rocket reexports `figment` from its crate root, so you can refer to `figment`
|
Rocket reexports `figment` and `serde` from its crate root, so you can refer
|
||||||
types via `rocket::figment`. However, Rocket does not enable all features from
|
to `figment` types via `rocket::figment` and `serde` types via
|
||||||
the figment crate. As such, you may need to import `figment` directly:
|
`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"] }
|
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
|
As a first example, we override configuration values at runtime by merging
|
||||||
figment's tuple providers with Rocket's default provider:
|
figment's tuple providers with Rocket's default provider:
|
||||||
|
|
||||||
|
@ -320,10 +311,11 @@ and `APP_PROFILE` to configure the selected profile:
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use rocket::serde::{Serialize, Deserialize};
|
||||||
use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
|
|
||||||
use rocket::fairing::AdHoc;
|
use rocket::fairing::AdHoc;
|
||||||
|
|
||||||
|
use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
app_value: usize,
|
app_value: usize,
|
||||||
|
|
|
@ -9,8 +9,8 @@ publish = false
|
||||||
rocket = { path = "../../core/lib", features = ["secrets"] }
|
rocket = { path = "../../core/lib", features = ["secrets"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rocket = { path = "../../core/lib", features = ["secrets"] }
|
rocket = { path = "../../core/lib", features = ["secrets", "json"] }
|
||||||
rocket_contrib = { path = "../../contrib/lib", features = ["json", "tera_templates", "diesel_sqlite_pool"] }
|
rocket_contrib = { path = "../../contrib/lib", features = ["tera_templates", "diesel_sqlite_pool"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
figment = { version = "0.10", features = ["toml", "env"] }
|
figment = { version = "0.10", features = ["toml", "env"] }
|
||||||
|
|
Loading…
Reference in New Issue