Custom REST API operations in addition to CRUD

Allows for custom operations associated with POST requests under
the main resource path. For example, in addition to POST on /user
to create a user, allow easy support for /user/login and
/user/logout.
This commit is contained in:
Ignotus Peverell 2017-05-19 14:35:49 -07:00
parent d8deba15ee
commit a402f39633
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
4 changed files with 52 additions and 28 deletions

View file

@ -11,5 +11,4 @@ iron = "~0.5.1"
log = "~0.3"
router = "~0.5.1"
serde = "~0.9.10"
serde_derive = "~0.9.10"
serde_json = "~0.9.8"

View file

@ -21,12 +21,9 @@
// }
// }
use std::net::ToSocketAddrs;
use std::sync::Arc;
use std::thread;
use iron::method::Method;
use chain::{self, Tip};
use rest::*;
@ -42,8 +39,8 @@ impl ApiEndpoint for ChainApi {
type ID = String;
type T = Tip;
fn methods(&self) -> Vec<Method> {
vec![Method::Get]
fn operations(&self) -> Vec<Operation> {
vec![Operation::Get]
}
fn get(&self, id: String) -> ApiResult<Tip> {

View file

@ -19,8 +19,6 @@ extern crate log;
extern crate iron;
extern crate router;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
mod endpoints;

View file

@ -22,7 +22,6 @@ 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;
@ -68,6 +67,28 @@ impl From<ApiError> for IronError {
}
}
#[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,
}
}
}
pub type ApiResult<T> = ::std::result::Result<T, ApiError>;
/// Trait to implement to expose a service as a RESTful HTTP endpoint. Each
@ -90,7 +111,7 @@ pub trait ApiEndpoint: Clone + Send + Sync + 'static {
type ID: ToString + FromStr;
type T: Serialize + Deserialize;
fn methods(&self) -> Vec<Method>;
fn operations(&self) -> Vec<Operation>;
#[allow(unused_variables)]
fn create(&self, o: Self::T) -> ApiResult<Self::ID> {
@ -111,6 +132,14 @@ pub trait ApiEndpoint: Clone + Send + Sync + 'static {
fn get(&self, id: Self::ID) -> ApiResult<Self::T> {
unimplemented!()
}
#[allow(unused_variables)]
fn operation<IN, OUT>(&self, op: String, input: IN) -> ApiResult<OUT>
where IN: Serialize + Deserialize,
OUT: Serialize + Deserialize
{
unimplemented!()
}
}
// Wrapper required to define the implementation below, Rust doesn't let us
@ -194,31 +223,32 @@ impl ApiServer {
// 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)),
for op in endpoint.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(),
Operation::Custom(op_s) => format!("{}/:{}", root.clone(), op_s),
};
self.router.route(m.clone(),
self.router.route(op.to_method(),
full_path,
ApiWrapper(endpoint.clone()),
m.to_string() + "_" + route_postfix);
format!("{:?}_{}", op, 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
});
let (root_opts, sub_opts) =
endpoint.operations().iter().fold((vec![], vec![]), |mut acc, op| {
let m = op.to_method();
if m == Method::Post {
acc.0.push(m);
} else {
acc.1.push(m);
}
acc
});
self.router.options(root.clone(),
move |_: &mut Request| {
Ok(Response::with((status::Ok,