mirror of https://github.com/rwf2/Rocket.git
Add contrib crate. Add JSON to contrib. Add JSON example.
This commit is contained in:
parent
1c00793d0d
commit
a3218192dd
|
@ -16,3 +16,6 @@ db.sql
|
|||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||
Cargo.lock
|
||||
|
||||
# Scratch list of items that need to get done.
|
||||
_TODO
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"lib/",
|
||||
"codegen/",
|
||||
"contrib/",
|
||||
"examples/cookies",
|
||||
"examples/errors",
|
||||
"examples/extended_validation",
|
||||
|
@ -18,4 +21,8 @@ members = [
|
|||
"examples/testing",
|
||||
"examples/from_request",
|
||||
"examples/stream",
|
||||
"examples/json",
|
||||
]
|
||||
|
||||
[replace]
|
||||
"aster:0.26.1" = { git = "https://github.com/jchlapinski/aster" }
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "rocket_contrib"
|
||||
version = "0.0.8"
|
||||
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||
|
||||
[features]
|
||||
default = ["json"]
|
||||
json = ["serde", "serde_json"]
|
||||
|
||||
[dependencies]
|
||||
rocket = { path = "../lib/" }
|
||||
log = "*"
|
||||
|
||||
# JSON module dependencies
|
||||
serde = { version = "*", optional = true }
|
||||
serde_json = { version = "*", optional = true }
|
|
@ -0,0 +1,131 @@
|
|||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use rocket::request::{Request, FromRequest};
|
||||
use rocket::response::{header, Responder, Outcome, FreshHyperResponse};
|
||||
use rocket::response::mime::{Mime, TopLevel, SubLevel};
|
||||
|
||||
use self::serde::{Serialize, Deserialize};
|
||||
use self::serde_json::Error as JSONError;
|
||||
|
||||
/// The JSON datatype, which implements both `FromRequest` and `Responder`. This
|
||||
/// type allows you to trivially consume and respond with JSON in your Rocket
|
||||
/// application.
|
||||
///
|
||||
/// If you're receiving JSON data, simple add a `JSON<T>` type to your function
|
||||
/// signature where `T` is some type you'd like to parse from JSON. `T` must
|
||||
/// implement `Deserialize` from [Serde](https://github.com/serde-rs/json). The
|
||||
/// data is parsed from the HTTP request body.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[post("/users/", format = "application/json")]
|
||||
/// 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
|
||||
/// doesn't specify "application/json" as its first `Accept:` header parameter
|
||||
/// will not be routed to this handler.
|
||||
///
|
||||
/// If you're responding with JSON data, return a `JSON<T>` type, where `T`
|
||||
/// implements implements `Serialize` from
|
||||
/// [Serde](https://github.com/serde-rs/json). The content type 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)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
pub struct JSON<T>(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.unwrap(), "Hello".to_string());
|
||||
/// ```
|
||||
pub fn unwrap(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 'c, T: Deserialize> FromRequest<'r, 'c> for JSON<T> {
|
||||
type Error = JSONError;
|
||||
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
|
||||
Ok(JSON(serde_json::from_slice(request.data.as_slice())?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> Responder for JSON<T> {
|
||||
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> {
|
||||
let mime = Mime(TopLevel::Application, SubLevel::Json, vec![]);
|
||||
res.headers_mut().set(header::ContentType(mime));
|
||||
match serde_json::to_string(&self.0) {
|
||||
Ok(mut json_string) => json_string.respond(res),
|
||||
Err(e) => {
|
||||
error_!("JSON failed to serialize: {:?}", e);
|
||||
Outcome::FailStop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 nice little macro to create simple HashMaps. Really convenient for
|
||||
/// returning ad-hoc JSON messages.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate rocket_contrib;
|
||||
/// use std::collections::HashMap;
|
||||
/// # fn main() {
|
||||
/// let map: HashMap<&str, usize> = map! {
|
||||
/// "status" => 0,
|
||||
/// "count" => 100
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(map.len(), 2);
|
||||
/// assert_eq!(map.get("status"), Some(&0));
|
||||
/// assert_eq!(map.get("count"), Some(&100));
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! map {
|
||||
($($key:expr => $value:expr),+) => ({
|
||||
use std::collections::HashMap;
|
||||
let mut map = HashMap::new();
|
||||
$(map.insert($key, $value);)+
|
||||
map
|
||||
});
|
||||
|
||||
($($key:expr => $value:expr),+,) => {
|
||||
map!($($key => $value),+)
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#![feature(question_mark)]
|
||||
|
||||
//! This crate contains officially sanctioned contributor libraries that provide
|
||||
//! functionality commonly used by Rocket applications.
|
||||
//!
|
||||
//! These libraries are always kept in-sync with the core Rocket library. They
|
||||
//! provide common, but not fundamental, abstractions to be used by Rocket
|
||||
//! applications. In particular, contributor libraries typically export types
|
||||
//! that implement the `FromRequest` trait, `Responder` trait, or both.
|
||||
//!
|
||||
//! Each module in this library is held behind a feature flag, with the most
|
||||
//! common modules exposed by default. The present feature list is below, with
|
||||
//! an asterisk next to the features that are enabled by default:
|
||||
//!
|
||||
//! * json*
|
||||
//!
|
||||
//! The recommend way to include features from this crate via Cargo 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:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! [dependencies.rocket_contrib]
|
||||
//! version = "*"
|
||||
//! default-features = false
|
||||
//! features = ["json"]
|
||||
//! ```
|
||||
//!
|
||||
//! This crate is expected to grow with time, bringing in outside crates to be
|
||||
//! officially supported by Rocket.
|
||||
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[cfg_attr(feature = "json", macro_use)]
|
||||
#[cfg(feature = "json")]
|
||||
mod json;
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
pub use json::JSON;
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "json"
|
||||
version = "0.0.1"
|
||||
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||
workspace = "../../"
|
||||
|
||||
[dependencies]
|
||||
rocket = { path = "../../lib" }
|
||||
rocket_codegen = { path = "../../codegen" }
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
serde_macros = "*"
|
||||
lazy_static = "*"
|
||||
|
||||
[dependencies.rocket_contrib]
|
||||
path = "../../contrib"
|
||||
default-features = false
|
||||
features = ["json"]
|
|
@ -0,0 +1,92 @@
|
|||
#![feature(plugin, custom_derive)]
|
||||
#![plugin(rocket_codegen, serde_macros)]
|
||||
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use] extern crate rocket_contrib;
|
||||
extern crate rocket;
|
||||
extern crate serde_json;
|
||||
|
||||
use rocket::{Rocket, Request, Error};
|
||||
use rocket_contrib::JSON;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
// The type to represent the ID of a message.
|
||||
type ID = usize;
|
||||
type SimpleMap = HashMap<&'static str, &'static str>;
|
||||
|
||||
// We're going to store all of the messages here. No need for a DB.
|
||||
lazy_static! {
|
||||
static ref MAP: Mutex<HashMap<ID, String>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Message {
|
||||
id: Option<ID>,
|
||||
contents: String
|
||||
}
|
||||
|
||||
// TODO: This example can be improved by using `route` with muliple HTTP verbs.
|
||||
// To be precise, put/post could/should look like:
|
||||
// #[route(PUT, POST, path = "/<id>", format = "application/json")]
|
||||
// fn f(method: Method, id: ID, message: JSON<Message>) -> Option<JSON<SimpleMap>> {
|
||||
// let mut hashmap = MAP.lock().unwrap();
|
||||
// let exists = hashmap.contains_key(&id);
|
||||
// if method == Method::Put && exists || method == Method::Post && !exists {
|
||||
// hashmap.insert(id, message.0.contents);
|
||||
// return Ok(JSON(map!{ "status" => "ok" }))
|
||||
// }
|
||||
//
|
||||
// None
|
||||
// }
|
||||
|
||||
#[post("/<id>", format = "application/json")]
|
||||
fn new(id: ID, message: JSON<Message>) -> JSON<SimpleMap> {
|
||||
let mut hashmap = MAP.lock().unwrap();
|
||||
if hashmap.contains_key(&id) {
|
||||
JSON(map!{
|
||||
"status" => "error",
|
||||
"reason" => "ID exists. Try put."
|
||||
})
|
||||
} else {
|
||||
hashmap.insert(id, message.0.contents);
|
||||
JSON(map!{ "status" => "ok" })
|
||||
}
|
||||
}
|
||||
|
||||
#[put("/<id>", format = "application/json")]
|
||||
fn update(id: ID, message: JSON<Message>) -> Option<JSON<SimpleMap>> {
|
||||
let mut hashmap = MAP.lock().unwrap();
|
||||
if hashmap.contains_key(&id) {
|
||||
hashmap.insert(id, message.0.contents);
|
||||
Some(JSON(map!{ "status" => "ok" }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/<id>", format = "application/json")]
|
||||
fn get(id: ID) -> Option<JSON<Message>> {
|
||||
let hashmap = MAP.lock().unwrap();
|
||||
hashmap.get(&id).map(|contents| {
|
||||
JSON(Message {
|
||||
id: Some(id),
|
||||
contents: contents.clone()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[error(404)]
|
||||
fn not_found<'r>(_: Error, _: &'r Request<'r>) -> JSON<SimpleMap> {
|
||||
JSON(map! {
|
||||
"status" => "error",
|
||||
"reason" => "Resource was not found."
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rocket = Rocket::new("localhost", 8000);
|
||||
rocket.mount("/message", routes![new, update, get]);
|
||||
rocket.catch(errors![not_found]);
|
||||
rocket.launch();
|
||||
}
|
|
@ -13,7 +13,7 @@ impl<T: Read> Stream<T> {
|
|||
}
|
||||
|
||||
impl<T: Read> Responder for Stream<T> {
|
||||
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> {
|
||||
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
|
||||
let mut stream = res.start().unwrap();
|
||||
let mut buffer = [0; CHUNK_SIZE];
|
||||
let mut complete = false;
|
||||
|
|
|
@ -4,6 +4,7 @@ set -e
|
|||
EXAMPLES_DIR="examples"
|
||||
LIB_DIR="lib"
|
||||
CODEGEN_DIR="codegen"
|
||||
CONTRIB_DIR="contrib"
|
||||
|
||||
# Add Cargo to PATH.
|
||||
export PATH=${HOME}/.cargo/bin:${PATH}
|
||||
|
@ -26,6 +27,7 @@ function build_and_test() {
|
|||
|
||||
build_and_test $LIB_DIR
|
||||
build_and_test $CODEGEN_DIR
|
||||
build_and_test $CONTRIB_DIR
|
||||
|
||||
for file in ${EXAMPLES_DIR}/*; do
|
||||
if [ -d "${file}" ]; then
|
||||
|
|
Loading…
Reference in New Issue