// Copyright 2016 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! RESTful API server to easily expose services as RESTful JSON/HTTP endpoints. //! Fairly constrained on what the service API must look like by design. //! //! To use it, just have your service(s) implement the ApiEndpoint trait and //! register them on a ApiServer. use std::error::Error; use std::fmt::{self, Display, Debug, Formatter}; use std::io::Read; use std::net::ToSocketAddrs; use std::ops::Index; use std::string::ToString; use std::str::FromStr; use iron::{Iron, Request, Response, IronResult, IronError, status, headers}; use iron::method::Method; use iron::modifiers::Header; use iron::middleware::Handler; use router::Router; use serde::{Serialize, Deserialize}; use serde_json; /// Errors that can be returned by an ApiEndpoint implementation. #[derive(Debug)] pub enum ApiError { Internal(String), Argument(String), } impl Display for ApiError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match *self { ApiError::Argument(ref s) => write!(f, "Bad arguments: {}", s), ApiError::Internal(ref s) => write!(f, "Internal error: {}", s), } } } impl Error for ApiError { fn description(&self) -> &str { match *self { ApiError::Argument(_) => "Bad arguments.", ApiError::Internal(_) => "Internal error.", } } } impl From for IronError { fn from(e: ApiError) -> IronError { match e { ApiError::Argument(_) => IronError::new(e, status::Status::BadRequest), ApiError::Internal(_) => IronError::new(e, status::Status::InternalServerError), } } } pub type ApiResult = ::std::result::Result; /// Trait to implement to expose a service as a RESTful HTTP endpoint. Each /// method corresponds to a specific relative URL and HTTP method following /// basic REST principles: /// /// * create: POST / /// * get: GET /:id /// * update: PUT /:id /// * delete: DELETE /:id /// /// The methods method defines which operation the endpoint implements, they're /// all optional by default. It also allows the framework to automatically /// define the OPTIONS HTTP method. /// /// The type accepted by create and update, and returned by get, must implement /// the serde Serialize and Deserialize traits. The identifier type returned by /// create and accepted by all other methods must have a string representation. pub trait ApiEndpoint: Clone + Send + Sync + 'static { type ID: ToString + FromStr; type T: Serialize + Deserialize; fn methods(&self) -> Vec; #[allow(unused_variables)] fn create(&self, o: Self::T) -> ApiResult { unimplemented!() } #[allow(unused_variables)] fn delete(&self, id: Self::ID) -> ApiResult<()> { unimplemented!() } #[allow(unused_variables)] fn update(&self, id: Self::ID, o: Self::T) -> ApiResult<()> { unimplemented!() } #[allow(unused_variables)] fn get(&self, id: Self::ID) -> ApiResult { unimplemented!() } } // Wrapper required to define the implementation below, Rust doesn't let us // define the parametric implementation for trait from another crate. struct ApiWrapper(E); impl Handler for ApiWrapper where E: ApiEndpoint, <::ID as FromStr>::Err: Debug + Send + Error { fn handle(&self, req: &mut Request) -> IronResult { match req.method { Method::Get => { let res = self.0.get(extract_param(req, "id")?)?; let res_json = serde_json::to_string(&res) .map_err(|e| IronError::new(e, status::InternalServerError))?; Ok(Response::with((status::Ok, res_json))) } Method::Put => { let id = extract_param(req, "id")?; let t: E::T = serde_json::from_reader(req.body.by_ref()) .map_err(|e| IronError::new(e, status::BadRequest))?; self.0.update(id, t)?; Ok(Response::with(status::NoContent)) } Method::Delete => { let id = extract_param(req, "id")?; self.0.delete(id)?; Ok(Response::with(status::NoContent)) } Method::Post => { let t: E::T = serde_json::from_reader(req.body.by_ref()) .map_err(|e| IronError::new(e, status::BadRequest))?; let id = self.0.create(t)?; Ok(Response::with((status::Created, id.to_string()))) } _ => Ok(Response::with(status::MethodNotAllowed)), } } } fn extract_param(req: &mut Request, param: &'static str) -> IronResult where ID: ToString + FromStr, ::Err: Debug + Send + Error + 'static { let id = req.extensions.get::().unwrap().find(param).unwrap_or(""); id.parse::().map_err(|e| IronError::new(e, status::BadRequest)) } /// HTTP server allowing the registration of ApiEndpoint implementations. pub struct ApiServer { root: String, router: Router, } impl ApiServer { /// Creates a new ApiServer that will serve ApiEndpoint implementations /// under the root URL. pub fn new(root: String) -> ApiServer { ApiServer { root: root, router: Router::new(), } } /// Starts the ApiServer at the provided address. pub fn start(self, addr: A) -> Result<(), String> { Iron::new(self.router).http(addr).map(|_| ()).map_err(|e| e.to_string()) } /// Register a new API endpoint, providing a relative URL for the new /// endpoint. pub fn register_endpoint(&mut self, subpath: String, endpoint: E) where E: ApiEndpoint, <::ID as FromStr>::Err: Debug + Send + Error { assert_eq!(subpath.chars().nth(0).unwrap(), '/'); // declare a route for each method actually implemented by the endpoint let route_postfix = &subpath[1..]; let root = self.root.clone() + &subpath; for m in endpoint.methods() { let full_path = match m { Method::Get => root.clone() + "/:id", Method::Put => root.clone() + "/:id", Method::Delete => root.clone() + "/:id", Method::Post => root.clone(), _ => panic!(format!("Unsupported method: {}.", m)), }; self.router.route(m.clone(), full_path, ApiWrapper(endpoint.clone()), m.to_string() + "_" + route_postfix); } // support for the HTTP Options method by differentiating what's on the // root resource vs the id resource let (root_opts, sub_opts) = endpoint.methods().iter().fold((vec![], vec![]), |mut acc, m| { if *m == Method::Post { acc.0.push(m.clone()); } else { acc.1.push(m.clone()); } acc }); self.router.options(root.clone(), move |_: &mut Request| { Ok(Response::with((status::Ok, Header(headers::Allow(root_opts.clone()))))) }, "option_".to_string() + route_postfix); self.router.options(root.clone() + "/:id", move |_: &mut Request| { Ok(Response::with((status::Ok, Header(headers::Allow(sub_opts.clone()))))) }, "option_id_".to_string() + route_postfix); } } #[cfg(test)] mod test { use super::*; use rest::*; #[derive(Serialize, Deserialize)] pub struct Animal { name: String, legs: u32, lethal: bool, } #[derive(Clone)] pub struct TestApi; impl ApiEndpoint for TestApi { type ID = String; type T = Animal; fn methods(&self) -> Vec { vec![Method::Get] } fn get(&self, name: String) -> Result { Ok(Animal { name: name, legs: 4, lethal: false, }) } } #[test] fn req_chain_json() { let mut apis = ApiServer::new("/v1".to_string()); apis.register_endpoint("/animal".to_string(), TestApi); } }