From a402f39633cf85283c959b475ccba2d3cb4b3818 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Fri, 19 May 2017 14:35:49 -0700 Subject: [PATCH] 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. --- api/Cargo.toml | 1 - api/src/endpoints.rs | 7 ++--- api/src/lib.rs | 2 -- api/src/rest.rs | 70 +++++++++++++++++++++++++++++++------------- 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/api/Cargo.toml b/api/Cargo.toml index 271af69fa..5805bc16b 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -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" diff --git a/api/src/endpoints.rs b/api/src/endpoints.rs index 8854e5b62..a6493cddb 100644 --- a/api/src/endpoints.rs +++ b/api/src/endpoints.rs @@ -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 { - vec![Method::Get] + fn operations(&self) -> Vec { + vec![Operation::Get] } fn get(&self, id: String) -> ApiResult { diff --git a/api/src/lib.rs b/api/src/lib.rs index c56e43e98..0549d94cb 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -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; diff --git a/api/src/rest.rs b/api/src/rest.rs index db05bc695..7fdcb39d0 100644 --- a/api/src/rest.rs +++ b/api/src/rest.rs @@ -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 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 = ::std::result::Result; /// 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; + fn operations(&self) -> Vec; #[allow(unused_variables)] fn create(&self, o: Self::T) -> ApiResult { @@ -111,6 +132,14 @@ pub trait ApiEndpoint: Clone + Send + Sync + 'static { fn get(&self, id: Self::ID) -> ApiResult { unimplemented!() } + + #[allow(unused_variables)] + fn operation(&self, op: String, input: IN) -> ApiResult + 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,