diff --git a/contrib/src/json.rs b/contrib/src/json.rs index a461127a..2eb36680 100644 --- a/contrib/src/json.rs +++ b/contrib/src/json.rs @@ -12,7 +12,6 @@ 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 @@ -67,7 +66,7 @@ pub use serde_json::error::Error as SerdeError; /// json = 5242880 /// ``` #[derive(Debug)] -pub struct Json(pub T); +pub struct Json(pub T); impl Json { /// Consumes the JSON wrapper and returns the wrapped item. @@ -135,6 +134,88 @@ impl DerefMut for Json { } } +/// An arbitrary JSON value. +/// +/// This structure wraps `serde`'s [`Value`] type. Importantly, unlike `Value`, +/// this type implements [`Responder`], allowing a value of this type to be +/// returned directly from a handler. +/// +/// [`Value`]: https://docs.rs/serde_json/1.0.2/serde_json/value/enum.Value.html +/// [`Responder`]: /rocket/response/trait.Responder.html +/// +/// # `Responder` +/// +/// The `Responder` implementation for `JsonValue` 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`. +/// +/// # Usage +/// +/// A value of this type is constructed via the +/// [`json!`](/rocket_contrib/macro.json.html) macro. The macro and this type +/// are typically used to construct JSON values in an ad-hoc fashion during +/// request handling. This looks something like: +/// +/// ```rust,ignore +/// use rocket_contrib::JsonValue; +/// +/// #[get("/item")] +/// fn get_item() -> JsonValue { +/// json!({ +/// "id": 83, +/// "values": [1, 2, 3, 4] +/// }) +/// } +/// ``` +#[derive(Debug, Clone, PartialEq, Default)] +pub struct JsonValue(pub serde_json::Value); + +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<'a>(&'a self) -> &'a Self::Target { + &self.0 + } +} + +impl DerefMut for JsonValue { + #[inline(always)] + fn deref_mut<'a>(&'a mut self) -> &'a mut Self::Target { + &mut self.0 + } +} + +impl Into for JsonValue { + #[inline(always)] + fn into(self) -> serde_json::Value { + self.into_inner() + } +} + +impl From for JsonValue { + #[inline(always)] + fn from(value: serde_json::Value) -> JsonValue { + JsonValue(value) + } +} + +/// Serializes the value into JSON. Returns a response with Content-Type JSON +/// and a fixed-size body with the serialized value. +impl<'a> Responder<'a> for JsonValue { + #[inline] + fn respond_to(self, req: &Request) -> response::Result<'a> { + content::Json(self.0.to_string()).respond_to(req) + } +} + /// A macro to create ad-hoc JSON serializable values using JSON syntax. /// /// # Usage @@ -146,25 +227,26 @@ impl DerefMut for Json { /// #[macro_use] extern crate rocket_contrib; /// ``` /// -/// The return type of a macro invocation is -/// [`Value`](/rocket_contrib/enum.Value.html). This is the default type for the -/// type parameter of [`Json`](/rocket_contrib/struct.Json.html) and as such, -/// you can return `Json` without specifying the type using a `json!` value for -/// `Json`. A value created with this macro can be returned from a handler as -/// follows: +/// The return type of a `json!` invocation is +/// [`JsonValue`](/rocket_contrib/struct.JsonValue.html). A value created with +/// this macro can be returned from a handler as follows: /// /// ```rust,ignore -/// use rocket_contrib::Json; +/// use rocket_contrib::JsonValue; /// /// #[get("/json")] -/// fn get_json() -> Json { -/// Json(json!({ +/// fn get_json() -> JsonValue { +/// json!({ /// "key": "value", /// "array": [1, 2, 3, 4] -/// })) +/// }) /// } /// ``` /// +/// The `Responder` implementation for `JsonValue` serializes the value into a +/// JSON string and sets it as the body of the response with a `Content-Type` of +/// `application/json`. +/// /// # Examples /// /// Create a simple JSON object with two keys: `"username"` and `"id"`: @@ -236,6 +318,6 @@ impl DerefMut for Json { #[macro_export] macro_rules! json { ($($json:tt)+) => { - json_internal!($($json)+) + $crate::JsonValue(json_internal!($($json)+)) }; } diff --git a/contrib/src/lib.rs b/contrib/src/lib.rs index 74237adf..a142fac1 100644 --- a/contrib/src/lib.rs +++ b/contrib/src/lib.rs @@ -54,7 +54,7 @@ extern crate serde_json; pub mod json; #[cfg(feature = "json")] -pub use json::{Json, SerdeError, Value}; +pub use json::{Json, SerdeError, JsonValue}; #[cfg(feature = "msgpack")] #[doc(hidden)] diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index a05c5185..c5d04774 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -8,7 +8,7 @@ extern crate serde_json; #[cfg(test)] mod tests; -use rocket_contrib::{Json, Value}; +use rocket_contrib::{Json, JsonValue}; use rocket::State; use std::collections::HashMap; use std::sync::Mutex; @@ -27,25 +27,25 @@ struct Message { // TODO: This example can be improved by using `route` with multiple HTTP verbs. #[post("/", format = "application/json", data = "")] -fn new(id: ID, message: Json, map: State) -> Json { +fn new(id: ID, message: Json, map: State) -> JsonValue { let mut hashmap = map.lock().expect("map lock."); if hashmap.contains_key(&id) { - Json(json!({ + json!({ "status": "error", "reason": "ID exists. Try put." - })) + }) } else { hashmap.insert(id, message.0.contents); - Json(json!({ "status": "ok" })) + json!({ "status": "ok" }) } } #[put("/", format = "application/json", data = "")] -fn update(id: ID, message: Json, map: State) -> Option> { +fn update(id: ID, message: Json, map: State) -> Option { let mut hashmap = map.lock().unwrap(); if hashmap.contains_key(&id) { hashmap.insert(id, message.0.contents); - Some(Json(json!({ "status": "ok" }))) + Some(json!({ "status": "ok" })) } else { None } @@ -63,11 +63,11 @@ fn get(id: ID, map: State) -> Option> { } #[error(404)] -fn not_found() -> Json { - Json(json!({ +fn not_found() -> JsonValue { + json!({ "status": "error", "reason": "Resource was not found." - })) + }) } fn rocket() -> rocket::Rocket { diff --git a/site/index.toml b/site/index.toml index be5ae25b..0f3351a6 100644 --- a/site/index.toml +++ b/site/index.toml @@ -107,12 +107,12 @@ code = ''' } #[put("/", data = "")] - fn update(id: ID, message: Json) -> Json { + fn update(id: ID, message: Json) -> JsonValue { if DB.contains_key(&id) { DB.insert(id, &message.contents); - Json(json!{ "status": "ok" }) + json!({ "status": "ok" }) } else { - Json(json!{ "status": "error" }) + json!({ "status": "error" }) } } '''