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