Rocket/contrib/src/json.rs

242 lines
6.7 KiB
Rust
Raw Normal View History

use std::ops::{Deref, DerefMut};
use std::io::Read;
use rocket::config;
use rocket::outcome::{Outcome, IntoOutcome};
use rocket::request::Request;
use rocket::data::{self, Data, FromData};
use rocket::response::{self, Responder, content};
use rocket::http::Status;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json;
pub use serde_json::Value;
pub use serde_json::error::Error as SerdeError;
/// The JSON type: implements `FromData` and `Responder`, allowing you to easily
/// consume and respond with JSON.
///
2017-04-18 07:36:39 +00:00
/// ## Receiving JSON
///
2017-02-09 08:16:13 +00:00
/// If you're receiving JSON data, simply add a `data` parameter to your route
/// arguments and ensure the type of the parameter is a `JSON<T>`, where `T` is
/// some type you'd like to parse from JSON. `T` must implement `Deserialize` or
/// `DeserializeOwned` from [Serde](https://github.com/serde-rs/json). The data
/// is parsed from the HTTP request body.
///
/// ```rust,ignore
/// #[post("/users/", format = "application/json", data = "<user>")]
/// fn new_user(user: JSON<User>) {
/// ...
/// }
/// ```
///
/// You don't _need_ to use `format = "application/json"`, but it _may_ be what
/// you want. Using `format = application/json` means that any request that
2017-02-09 08:16:13 +00:00
/// doesn't specify "application/json" as its `Content-Type` header value will
/// not be routed to the handler.
///
2017-04-18 07:36:39 +00:00
/// ## Sending JSON
///
/// If you're responding with JSON data, return a `JSON<T>` type, where `T`
/// implements `Serialize` from [Serde](https://github.com/serde-rs/json). The
/// content type of the response is set to `application/json` automatically.
///
/// ```rust,ignore
/// #[get("/users/<id>")]
/// fn user(id: usize) -> JSON<User> {
/// let user_from_id = User::from(id);
/// ...
/// JSON(user_from_id)
/// }
/// ```
2017-04-18 07:36:39 +00:00
///
/// ## 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
/// resource exhaustion through high memory consumption. The limit can be
/// increased by setting the `limits.json` configuration parameter. For
/// instance, to increase the JSON limit to 5MiB for all environments, you may
/// add the following to your `Rocket.toml`:
///
/// ```toml
/// [global.limits]
/// json = 5242880
/// ```
#[derive(Debug)]
pub struct JSON<T = Value>(pub T);
impl<T> JSON<T> {
/// Consumes the JSON wrapper and returns the wrapped item.
///
/// # Example
/// ```rust
/// # use rocket_contrib::JSON;
/// let string = "Hello".to_string();
/// let my_json = JSON(string);
/// assert_eq!(my_json.into_inner(), "Hello".to_string());
/// ```
pub fn into_inner(self) -> T {
self.0
}
}
/// Default limit for JSON is 1MB.
const LIMIT: u64 = 1 << 20;
impl<T: DeserializeOwned> FromData for JSON<T> {
type Error = SerdeError;
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, SerdeError> {
if !request.content_type().map_or(false, |ct| ct.is_json()) {
error_!("Content-Type is not JSON.");
return Outcome::Forward(data);
}
let size_limit = config::active()
.and_then(|c| c.limits.get("json"))
.unwrap_or(LIMIT);
serde_json::from_reader(data.open().take(size_limit))
.map(|val| JSON(val))
.map_err(|e| { error_!("Couldn't parse JSON body: {:?}", e); e })
.into_outcome(Status::BadRequest)
}
}
/// Serializes the wrapped value into JSON. Returns a response with Content-Type
/// JSON and a fixed-size body with the serialized value. If serialization
/// fails, an `Err` of `Status::InternalServerError` is returned.
impl<T: Serialize> Responder<'static> for JSON<T> {
fn respond(self) -> response::Result<'static> {
serde_json::to_string(&self.0).map(|string| {
content::JSON(string).respond().unwrap()
}).map_err(|e| {
error_!("JSON failed to serialize: {:?}", e);
Status::InternalServerError
})
}
}
impl<T> Deref for JSON<T> {
type Target = T;
fn deref<'a>(&'a self) -> &'a T {
&self.0
}
}
impl<T> DerefMut for JSON<T> {
fn deref_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.0
}
}
/// 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,ignore
/// #[macro_use] extern crate rocket_contrib;
/// ```
///
/// The return type of a macro invocation is
/// [Value](/rocket_contrib/enum.Value.html). This is the default value for the
/// type parameter of [JSON](/rocket_contrib/struct.JSON.html) and as such, you
/// can return `JSON` without specifying the type. A value created with this
/// macro can be returned from a handler as follows:
///
/// ```rust,ignore
/// use rocket_contrib::JSON;
///
/// #[get("/json")]
/// fn get_json() -> JSON {
/// JSON(json!({
/// "key": "value",
/// "array": [1, 2, 3, 4]
/// }))
/// }
/// ```
///
/// # Examples
///
/// Create a simple JSON object with two keys: `"username"` and `"id"`:
///
/// ```rust
/// # #![allow(unused_variables)]
/// # #[macro_use] extern crate rocket_contrib;
/// # fn main() {
/// 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() {
/// let value = json!({
/// "code": 200,
/// "success": true,
/// "payload": {
/// "features": ["serde", "json"],
/// "ids": [12, 121],
/// },
/// });
/// # }
/// ```
///
/// Variables or expressions can be interpolated into the JSON literal. Any type
/// interpolated into an array element or object value must implement Serde's
/// `Serialize` trait, while any type interpolated into a object key must
/// implement `Into<String>`.
///
/// ```rust
/// # #![allow(unused_variables)]
/// # #[macro_use] extern crate rocket_contrib;
/// # fn main() {
/// let code = 200;
/// let features = vec!["serde", "json"];
///
/// let value = json!({
/// "code": code,
/// "success": code == 200,
/// "payload": {
/// 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() {
/// let value = json!([
/// "notice",
/// "the",
/// "trailing",
/// "comma -->",
/// ]);
/// # }
/// ```
#[macro_export]
macro_rules! json {
($($json:tt)+) => {
json_internal!($($json)+)
};
}