2017-03-08 04:00:34 +03:00
|
|
|
// 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.
|
|
|
|
|
2017-05-26 03:21:56 +03:00
|
|
|
use std::error;
|
2017-03-08 04:00:34 +03:00
|
|
|
use std::fmt::{self, Display, Debug, Formatter};
|
|
|
|
use std::io::Read;
|
|
|
|
use std::net::ToSocketAddrs;
|
|
|
|
use std::string::ToString;
|
|
|
|
use std::str::FromStr;
|
2017-06-16 19:47:29 +03:00
|
|
|
use std::mem;
|
2017-03-08 04:00:34 +03:00
|
|
|
|
2017-06-16 19:47:29 +03:00
|
|
|
use iron::{Iron, Request, Response, IronResult, IronError, status, headers, Listening};
|
2017-03-08 04:00:34 +03:00
|
|
|
use iron::method::Method;
|
|
|
|
use iron::modifiers::Header;
|
|
|
|
use iron::middleware::Handler;
|
|
|
|
use router::Router;
|
2017-08-10 03:54:10 +03:00
|
|
|
use serde::Serialize;
|
2017-05-29 06:21:29 +03:00
|
|
|
use serde::de::DeserializeOwned;
|
2017-03-08 04:00:34 +03:00
|
|
|
use serde_json;
|
|
|
|
|
2017-06-13 02:41:27 +03:00
|
|
|
use store;
|
|
|
|
|
2017-03-08 04:00:34 +03:00
|
|
|
/// Errors that can be returned by an ApiEndpoint implementation.
|
|
|
|
#[derive(Debug)]
|
2017-05-26 03:21:56 +03:00
|
|
|
pub enum Error {
|
2017-03-08 04:00:34 +03:00
|
|
|
Internal(String),
|
|
|
|
Argument(String),
|
2017-06-13 02:41:27 +03:00
|
|
|
NotFound,
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
|
2017-05-26 03:21:56 +03:00
|
|
|
impl Display for Error {
|
2017-03-08 04:00:34 +03:00
|
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
|
|
match *self {
|
2017-05-26 03:21:56 +03:00
|
|
|
Error::Argument(ref s) => write!(f, "Bad arguments: {}", s),
|
|
|
|
Error::Internal(ref s) => write!(f, "Internal error: {}", s),
|
2017-06-13 02:41:27 +03:00
|
|
|
Error::NotFound => write!(f, "Not found."),
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-26 03:21:56 +03:00
|
|
|
impl error::Error for Error {
|
2017-03-08 04:00:34 +03:00
|
|
|
fn description(&self) -> &str {
|
|
|
|
match *self {
|
2017-05-26 03:21:56 +03:00
|
|
|
Error::Argument(_) => "Bad arguments.",
|
|
|
|
Error::Internal(_) => "Internal error.",
|
2017-06-13 02:41:27 +03:00
|
|
|
Error::NotFound => "Not found.",
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-26 03:21:56 +03:00
|
|
|
impl From<Error> for IronError {
|
|
|
|
fn from(e: Error) -> IronError {
|
2017-03-08 04:00:34 +03:00
|
|
|
match e {
|
2017-05-26 03:21:56 +03:00
|
|
|
Error::Argument(_) => IronError::new(e, status::Status::BadRequest),
|
|
|
|
Error::Internal(_) => IronError::new(e, status::Status::InternalServerError),
|
2017-06-13 02:41:27 +03:00
|
|
|
Error::NotFound => IronError::new(e, status::Status::NotFound),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<store::Error> for Error {
|
|
|
|
fn from(e: store::Error) -> Error {
|
|
|
|
match e {
|
|
|
|
store::Error::NotFoundErr => Error::NotFound,
|
|
|
|
_ => Error::Internal(e.to_string()),
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-20 00:35:49 +03:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub enum Operation {
|
|
|
|
Create,
|
|
|
|
Delete,
|
|
|
|
Update,
|
|
|
|
Get,
|
|
|
|
Custom(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Operation {
|
|
|
|
fn to_method(&self) -> Method {
|
|
|
|
match *self {
|
|
|
|
Operation::Create => Method::Post,
|
|
|
|
Operation::Delete => Method::Delete,
|
|
|
|
Operation::Update => Method::Put,
|
|
|
|
Operation::Get => Method::Get,
|
|
|
|
Operation::Custom(_) => Method::Post,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-26 03:21:56 +03:00
|
|
|
pub type ApiResult<T> = ::std::result::Result<T, Error>;
|
2017-03-08 04:00:34 +03:00
|
|
|
|
|
|
|
/// 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;
|
2017-05-29 06:21:29 +03:00
|
|
|
type T: Serialize + DeserializeOwned;
|
|
|
|
type OP_IN: Serialize + DeserializeOwned;
|
|
|
|
type OP_OUT: Serialize + DeserializeOwned;
|
2017-03-08 04:00:34 +03:00
|
|
|
|
2017-05-20 00:35:49 +03:00
|
|
|
fn operations(&self) -> Vec<Operation>;
|
2017-03-08 04:00:34 +03:00
|
|
|
|
|
|
|
#[allow(unused_variables)]
|
|
|
|
fn create(&self, o: Self::T) -> ApiResult<Self::ID> {
|
|
|
|
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<Self::T> {
|
|
|
|
unimplemented!()
|
|
|
|
}
|
2017-05-20 00:35:49 +03:00
|
|
|
|
|
|
|
#[allow(unused_variables)]
|
2017-05-25 02:08:39 +03:00
|
|
|
fn operation(&self, op: String, input: Self::OP_IN) -> ApiResult<Self::OP_OUT> {
|
2017-05-20 00:35:49 +03:00
|
|
|
unimplemented!()
|
|
|
|
}
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Wrapper required to define the implementation below, Rust doesn't let us
|
|
|
|
// define the parametric implementation for trait from another crate.
|
|
|
|
struct ApiWrapper<E>(E);
|
|
|
|
|
|
|
|
impl<E> Handler for ApiWrapper<E>
|
|
|
|
where E: ApiEndpoint,
|
2017-05-26 03:21:56 +03:00
|
|
|
<<E as ApiEndpoint>::ID as FromStr>::Err: Debug + Send + error::Error
|
2017-03-08 04:00:34 +03:00
|
|
|
{
|
|
|
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
|
|
|
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)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-25 02:08:39 +03:00
|
|
|
struct OpWrapper<E> {
|
|
|
|
operation: String,
|
|
|
|
endpoint: E,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<E> Handler for OpWrapper<E>
|
2017-09-29 21:44:25 +03:00
|
|
|
where
|
|
|
|
E: ApiEndpoint,
|
2017-05-25 02:08:39 +03:00
|
|
|
{
|
|
|
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
2017-09-29 21:44:25 +03:00
|
|
|
let t: E::OP_IN = serde_json::from_reader(req.body.by_ref()).map_err(|e| {
|
|
|
|
IronError::new(e, status::BadRequest)
|
|
|
|
})?;
|
2017-05-25 02:08:39 +03:00
|
|
|
let res = self.endpoint.operation(self.operation.clone(), t)?;
|
2017-09-29 21:44:25 +03:00
|
|
|
let res_json = serde_json::to_string(&res).map_err(|e| {
|
|
|
|
IronError::new(e, status::InternalServerError)
|
|
|
|
})?;
|
2017-05-25 02:08:39 +03:00
|
|
|
Ok(Response::with((status::Ok, res_json)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-08 04:00:34 +03:00
|
|
|
fn extract_param<ID>(req: &mut Request, param: &'static str) -> IronResult<ID>
|
2017-09-29 21:44:25 +03:00
|
|
|
where
|
|
|
|
ID: ToString + FromStr,
|
|
|
|
<ID as FromStr>::Err: Debug + Send + error::Error + 'static,
|
2017-03-08 04:00:34 +03:00
|
|
|
{
|
|
|
|
|
2017-09-29 21:44:25 +03:00
|
|
|
let id = req.extensions
|
|
|
|
.get::<Router>()
|
|
|
|
.unwrap()
|
|
|
|
.find(param)
|
|
|
|
.unwrap_or("");
|
|
|
|
id.parse::<ID>().map_err(
|
|
|
|
|e| IronError::new(e, status::BadRequest),
|
|
|
|
)
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// HTTP server allowing the registration of ApiEndpoint implementations.
|
|
|
|
pub struct ApiServer {
|
|
|
|
root: String,
|
|
|
|
router: Router,
|
2017-06-16 19:47:29 +03:00
|
|
|
server_listener: Option<Listening>,
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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(),
|
2017-06-16 19:47:29 +03:00
|
|
|
server_listener: None,
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Starts the ApiServer at the provided address.
|
2017-06-16 19:47:29 +03:00
|
|
|
pub fn start<A: ToSocketAddrs>(&mut self, addr: A) -> Result<(), String> {
|
2017-09-29 21:44:25 +03:00
|
|
|
// replace this value to satisfy borrow checker
|
2017-06-16 19:47:29 +03:00
|
|
|
let r = mem::replace(&mut self.router, Router::new());
|
|
|
|
let result = Iron::new(r).http(addr);
|
|
|
|
let return_value = result.as_ref().map(|_| ()).map_err(|e| e.to_string());
|
|
|
|
self.server_listener = Some(result.unwrap());
|
|
|
|
return_value
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Stops the API server
|
2017-09-29 21:44:25 +03:00
|
|
|
pub fn stop(&mut self) {
|
2017-06-16 19:47:29 +03:00
|
|
|
let r = mem::replace(&mut self.server_listener, None);
|
|
|
|
r.unwrap().close().unwrap();
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Register a new API endpoint, providing a relative URL for the new
|
|
|
|
/// endpoint.
|
|
|
|
pub fn register_endpoint<E>(&mut self, subpath: String, endpoint: E)
|
2017-09-29 21:44:25 +03:00
|
|
|
where
|
|
|
|
E: ApiEndpoint,
|
|
|
|
<<E as ApiEndpoint>::ID as FromStr>::Err: Debug + Send + error::Error,
|
2017-03-08 04:00:34 +03:00
|
|
|
{
|
|
|
|
|
|
|
|
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;
|
2017-05-20 00:35:49 +03:00
|
|
|
for op in endpoint.operations() {
|
2017-05-25 02:08:39 +03:00
|
|
|
let route_name = format!("{:?}_{}", op, route_postfix);
|
|
|
|
|
|
|
|
// special case of custom operations
|
|
|
|
if let Operation::Custom(op_s) = op.clone() {
|
|
|
|
let wrapper = OpWrapper {
|
|
|
|
operation: op_s.clone(),
|
|
|
|
endpoint: endpoint.clone(),
|
|
|
|
};
|
2017-06-13 02:41:27 +03:00
|
|
|
let full_path = format!("{}/{}", root.clone(), op_s.clone());
|
2017-09-29 21:44:25 +03:00
|
|
|
self.router.route(
|
|
|
|
op.to_method(),
|
|
|
|
full_path.clone(),
|
|
|
|
wrapper,
|
|
|
|
route_name,
|
|
|
|
);
|
2017-05-29 06:21:29 +03:00
|
|
|
info!("route: POST {}", full_path);
|
2017-05-25 02:08:39 +03:00
|
|
|
} else {
|
|
|
|
|
|
|
|
// regular REST operations
|
|
|
|
let full_path = match op.clone() {
|
|
|
|
Operation::Get => root.clone() + "/:id",
|
|
|
|
Operation::Update => root.clone() + "/:id",
|
|
|
|
Operation::Delete => root.clone() + "/:id",
|
|
|
|
Operation::Create => root.clone(),
|
|
|
|
_ => panic!("unreachable"),
|
|
|
|
};
|
|
|
|
let wrapper = ApiWrapper(endpoint.clone());
|
2017-09-29 21:44:25 +03:00
|
|
|
self.router.route(
|
|
|
|
op.to_method(),
|
|
|
|
full_path.clone(),
|
|
|
|
wrapper,
|
|
|
|
route_name,
|
|
|
|
);
|
2017-05-29 06:21:29 +03:00
|
|
|
info!("route: {} {}", op.to_method(), full_path);
|
2017-05-25 02:08:39 +03:00
|
|
|
}
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// support for the HTTP Options method by differentiating what's on the
|
|
|
|
// root resource vs the id resource
|
2017-09-29 21:44:25 +03:00
|
|
|
let (root_opts, sub_opts) = endpoint.operations().iter().fold(
|
|
|
|
(vec![], vec![]),
|
|
|
|
|mut acc, op| {
|
2017-05-20 00:35:49 +03:00
|
|
|
let m = op.to_method();
|
|
|
|
if m == Method::Post {
|
|
|
|
acc.0.push(m);
|
|
|
|
} else {
|
|
|
|
acc.1.push(m);
|
|
|
|
}
|
|
|
|
acc
|
2017-09-29 21:44:25 +03:00
|
|
|
},
|
|
|
|
);
|
|
|
|
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,
|
|
|
|
);
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[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;
|
2017-09-29 21:44:25 +03:00
|
|
|
type OP_IN = ();
|
|
|
|
type OP_OUT = ();
|
2017-03-08 04:00:34 +03:00
|
|
|
|
2017-07-18 20:33:49 +03:00
|
|
|
fn operations(&self) -> Vec<Operation> {
|
|
|
|
vec![Operation::Get]
|
2017-03-08 04:00:34 +03:00
|
|
|
}
|
|
|
|
|
2017-04-16 02:36:42 +03:00
|
|
|
fn get(&self, name: String) -> ApiResult<Animal> {
|
2017-03-08 04:00:34 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|