Cleanup HTTP APIs, update ports to avoid gap, rustfmt

Moved the HTTP APIs away from the REST endpoint abstraction and
to simpler Hyper handlers. Re-established all routes as v1.
Changed wallet receiver port to 13415 to avoid a gap in port
numbers.

Finally, rustfmt seems to have ignored specific files arguments,
running on everything.
This commit is contained in:
Ignotus Peverell
2017-10-31 19:32:33 -04:00
parent 05d22cb632
commit e4ebb7c7cb
78 changed files with 1705 additions and 1928 deletions
+20 -17
View File
@@ -17,7 +17,7 @@
use hyper;
use hyper::client::Response;
use hyper::status::{StatusClass, StatusCode};
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use serde_json;
use rest::Error;
@@ -26,12 +26,14 @@ use rest::Error;
/// returns a JSON object. Handles request building, JSON deserialization and
/// response code checking.
pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
where for<'de> T: Deserialize<'de>
where
for<'de> T: Deserialize<'de>,
{
let client = hyper::Client::new();
let res = check_error(client.get(url).send())?;
serde_json::from_reader(res)
.map_err(|e| Error::Internal(format!("Server returned invalid JSON: {}", e)))
serde_json::from_reader(res).map_err(|e| {
Error::Internal(format!("Server returned invalid JSON: {}", e))
})
}
/// Helper function to easily issue a HTTP POST request with the provided JSON
@@ -39,15 +41,18 @@ pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
/// building, JSON serialization and deserialization, and response code
/// checking.
pub fn post<'a, IN, OUT>(url: &'a str, input: &IN) -> Result<OUT, Error>
where IN: Serialize,
for<'de> OUT: Deserialize<'de>
where
IN: Serialize,
for<'de> OUT: Deserialize<'de>,
{
let in_json = serde_json::to_string(input)
.map_err(|e| Error::Internal(format!("Could not serialize data to JSON: {}", e)))?;
let in_json = serde_json::to_string(input).map_err(|e| {
Error::Internal(format!("Could not serialize data to JSON: {}", e))
})?;
let client = hyper::Client::new();
let res = check_error(client.post(url).body(&mut in_json.as_bytes()).send())?;
serde_json::from_reader(res)
.map_err(|e| Error::Internal(format!("Server returned invalid JSON: {}", e)))
serde_json::from_reader(res).map_err(|e| {
Error::Internal(format!("Server returned invalid JSON: {}", e))
})
}
// convert hyper error and check for non success response codes
@@ -59,13 +64,11 @@ fn check_error(res: hyper::Result<Response>) -> Result<Response, Error> {
match response.status.class() {
StatusClass::Success => Ok(response),
StatusClass::ServerError => Err(Error::Internal(format!("Server error."))),
StatusClass::ClientError => {
if response.status == StatusCode::NotFound {
Err(Error::NotFound)
} else {
Err(Error::Argument(format!("Argument error")))
}
}
StatusClass::ClientError => if response.status == StatusCode::NotFound {
Err(Error::NotFound)
} else {
Err(Error::Argument(format!("Argument error")))
},
_ => Err(Error::Internal(format!("Unrecognized error."))),
}
}
-127
View File
@@ -1,127 +0,0 @@
// 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.
use std::sync::{Arc, RwLock};
use std::thread;
use chain;
use core::core::Transaction;
use core::ser;
use pool;
use handlers::{UtxoHandler, ChainHandler, SumTreeHandler};
use rest::*;
use types::*;
use util;
use util::LOGGER;
/// ApiEndpoint implementation for the transaction pool, to check its status
/// and size as well as push new transactions.
#[derive(Clone)]
pub struct PoolApi<T> {
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
}
impl<T> ApiEndpoint for PoolApi<T>
where
T: pool::BlockChain + Clone + Send + Sync + 'static,
{
type ID = String;
type T = PoolInfo;
type OP_IN = TxWrapper;
type OP_OUT = ();
fn operations(&self) -> Vec<Operation> {
vec![Operation::Get, Operation::Custom("push".to_string())]
}
fn get(&self, _: String) -> ApiResult<PoolInfo> {
let pool = self.tx_pool.read().unwrap();
Ok(PoolInfo {
pool_size: pool.pool_size(),
orphans_size: pool.orphans_size(),
total_size: pool.total_size(),
})
}
fn operation(&self, _: String, input: TxWrapper) -> ApiResult<()> {
let tx_bin = util::from_hex(input.tx_hex).map_err(|_| {
Error::Argument(format!("Invalid hex in transaction wrapper."))
})?;
let tx: Transaction = ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
Error::Argument(
"Could not deserialize transaction, invalid format.".to_string(),
)
})?;
let source = pool::TxSource {
debug_name: "push-api".to_string(),
identifier: "?.?.?.?".to_string(),
};
info!(
LOGGER,
"Pushing transaction with {} inputs and {} outputs to pool.",
tx.inputs.len(),
tx.outputs.len()
);
self.tx_pool
.write()
.unwrap()
.add_to_memory_pool(source, tx)
.map_err(|e| {
Error::Internal(format!("Addition to transaction pool failed: {:?}", e))
})?;
Ok(())
}
}
/// Dummy wrapper for the hex-encoded serialized transaction.
#[derive(Serialize, Deserialize)]
pub struct TxWrapper {
tx_hex: String,
}
/// Start all server REST APIs. Just register all of them on a ApiServer
/// instance and runs the corresponding HTTP server.
pub fn start_rest_apis<T>(
addr: String,
chain: Arc<chain::Chain>,
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
) where
T: pool::BlockChain + Clone + Send + Sync + 'static,
{
thread::spawn(move || {
let mut apis = ApiServer::new("/v1".to_string());
apis.register_endpoint("/pool".to_string(), PoolApi {tx_pool: tx_pool});
// register a nested router at "/v2" for flexibility
// so we can experiment with raw iron handlers
let utxo_handler = UtxoHandler {chain: chain.clone()};
let chain_tip_handler = ChainHandler {chain: chain.clone()};
let sumtree_handler = SumTreeHandler {chain: chain.clone()};
let router = router!(
chain_tip: get "/chain" => chain_tip_handler,
chain_utxos: get "/chain/utxos" => utxo_handler,
sumtree_roots: get "/sumtrees/*" => sumtree_handler,
);
apis.register_handler("/v2", router);
apis.start(&addr[..]).unwrap_or_else(|e| {
error!(LOGGER, "Failed to start API HTTP server: {}.", e);
});
});
}
+171 -72
View File
@@ -12,37 +12,45 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use std::io::Read;
use std::sync::{Arc, RwLock};
use std::thread;
use iron::prelude::*;
use iron::Handler;
use iron::status;
use urlencoded::UrlEncodedQuery;
use serde::Serialize;
use serde_json;
use chain;
use core::core::Transaction;
use core::ser;
use pool;
use rest::*;
use types::*;
use util::secp::pedersen::Commitment;
use types::*;
use util;
use util::LOGGER;
pub struct UtxoHandler {
pub chain: Arc<chain::Chain>,
// Supports retrieval of multiple outputs in a single request -
// GET /v1/chain/utxos?id=xxx,yyy,zzz
// GET /v1/chain/utxos?id=xxx&id=yyy&id=zzz
struct UtxoHandler {
chain: Arc<chain::Chain>,
}
impl UtxoHandler {
fn get_utxo(&self, id: &str) -> Result<Output, Error> {
debug!(LOGGER, "getting utxo: {}", id);
let c = util::from_hex(String::from(id))
.map_err(|_| {
Error::Argument(format!("Not a valid commitment: {}", id))
})?;
let c = util::from_hex(String::from(id)).map_err(|_| {
Error::Argument(format!("Not a valid commitment: {}", id))
})?;
let commit = Commitment::from_vec(c);
let out = self.chain.get_unspent(&commit)
let out = self.chain
.get_unspent(&commit)
.map_err(|_| Error::NotFound)?;
let header = self.chain
@@ -53,11 +61,6 @@ impl UtxoHandler {
}
}
//
// Supports retrieval of multiple outputs in a single request -
// GET /v2/chain/utxos?id=xxx,yyy,zzz
// GET /v2/chain/utxos?id=xxx&id=yyy&id=zzz
//
impl Handler for UtxoHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let mut commitments: Vec<&str> = vec![];
@@ -72,60 +75,49 @@ impl Handler for UtxoHandler {
}
let mut utxos: Vec<Output> = vec![];
for commit in commitments {
if let Ok(out) = self.get_utxo(commit) {
utxos.push(out);
}
}
match serde_json::to_string(&utxos) {
Ok(json) => Ok(Response::with((status::Ok, json))),
Err(_) => Ok(Response::with((status::BadRequest, ""))),
}
json_response(&utxos)
}
}
// Sum tree handler
pub struct SumTreeHandler {
pub chain: Arc<chain::Chain>,
// Sum tree handler. Retrieve the roots:
// GET /v1/sumtrees/roots
//
// Last inserted nodes::
// GET /v1/sumtrees/lastutxos (gets last 10)
// GET /v1/sumtrees/lastutxos?n=5
// GET /v1/sumtrees/lastrangeproofs
// GET /v1/sumtrees/lastkernels
struct SumTreeHandler {
chain: Arc<chain::Chain>,
}
impl SumTreeHandler {
//gets roots
// gets roots
fn get_roots(&self) -> SumTrees {
SumTrees::from_head(self.chain.clone())
}
// gets last n utxos inserted in to the tree
fn get_last_n_utxo(&self, distance:u64) -> Vec<SumTreeNode> {
fn get_last_n_utxo(&self, distance: u64) -> Vec<SumTreeNode> {
SumTreeNode::get_last_n_utxo(self.chain.clone(), distance)
}
// gets last n utxos inserted in to the tree
fn get_last_n_rangeproof(&self, distance:u64) -> Vec<SumTreeNode> {
fn get_last_n_rangeproof(&self, distance: u64) -> Vec<SumTreeNode> {
SumTreeNode::get_last_n_rangeproof(self.chain.clone(), distance)
}
// gets last n utxos inserted in to the tree
fn get_last_n_kernel(&self, distance:u64) -> Vec<SumTreeNode> {
fn get_last_n_kernel(&self, distance: u64) -> Vec<SumTreeNode> {
SumTreeNode::get_last_n_kernel(self.chain.clone(), distance)
}
}
//
// Retrieve the roots:
// GET /v2/sumtrees/roots
//
// Last inserted nodes::
// GET /v2/sumtrees/lastutxos (gets last 10)
// GET /v2/sumtrees/lastutxos?n=5
// GET /v2/sumtrees/lastrangeproofs
// GET /v2/sumtrees/lastkernels
//
impl Handler for SumTreeHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let url = req.url.clone();
@@ -133,40 +125,29 @@ impl Handler for SumTreeHandler {
if *path_elems.last().unwrap() == "" {
path_elems.pop();
}
//TODO: probably need to set a reasonable max limit here
let mut last_n=10;
// TODO: probably need to set a reasonable max limit here
let mut last_n = 10;
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
if let Some(nums) = params.get("n") {
for num in nums {
if let Ok(n) = str::parse(num) {
last_n=n;
last_n = n;
}
}
}
}
match *path_elems.last().unwrap(){
"roots" => match serde_json::to_string_pretty(&self.get_roots()) {
Ok(json) => Ok(Response::with((status::Ok, json))),
Err(_) => Ok(Response::with((status::BadRequest, ""))),
},
"lastutxos" => match serde_json::to_string_pretty(&self.get_last_n_utxo(last_n)) {
Ok(json) => Ok(Response::with((status::Ok, json))),
Err(_) => Ok(Response::with((status::BadRequest, ""))),
},
"lastrangeproofs" => match serde_json::to_string_pretty(&self.get_last_n_rangeproof(last_n)) {
Ok(json) => Ok(Response::with((status::Ok, json))),
Err(_) => Ok(Response::with((status::BadRequest, ""))),
},
"lastkernels" => match serde_json::to_string_pretty(&self.get_last_n_kernel(last_n)) {
Ok(json) => Ok(Response::with((status::Ok, json))),
Err(_) => Ok(Response::with((status::BadRequest, ""))),
},_ => Ok(Response::with((status::BadRequest, "")))
match *path_elems.last().unwrap() {
"roots" => json_response(&self.get_roots()),
"lastutxos" => json_response(&self.get_last_n_utxo(last_n)),
"lastrangeproofs" => json_response(&self.get_last_n_rangeproof(last_n)),
"lastkernels" => json_response(&self.get_last_n_kernel(last_n)),
_ => Ok(Response::with((status::BadRequest, ""))),
}
}
}
// Chain Handler
// Chain handler. Get the head details.
// GET /v1/chain
pub struct ChainHandler {
pub chain: Arc<chain::Chain>,
}
@@ -177,16 +158,134 @@ impl ChainHandler {
}
}
//
// Get the head details
// GET /v2/chain
//
impl Handler for ChainHandler {
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
match serde_json::to_string_pretty(&self.get_tip()) {
Ok(json) => Ok(Response::with((status::Ok, json))),
Err(_) => Ok(Response::with((status::BadRequest, ""))),
}
json_response(&self.get_tip())
}
}
// Get basic information about the transaction pool.
struct PoolInfoHandler<T> {
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
}
impl<T> Handler for PoolInfoHandler<T>
where
T: pool::BlockChain + Send + Sync + 'static,
{
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
let pool = self.tx_pool.read().unwrap();
json_response(&PoolInfo {
pool_size: pool.pool_size(),
orphans_size: pool.orphans_size(),
total_size: pool.total_size(),
})
}
}
/// Dummy wrapper for the hex-encoded serialized transaction.
#[derive(Serialize, Deserialize)]
struct TxWrapper {
tx_hex: String,
}
// Push new transactions to our transaction pool, that should broadcast it
// to the network if valid.
struct PoolPushHandler<T> {
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
}
impl<T> Handler for PoolPushHandler<T>
where
T: pool::BlockChain + Send + Sync + 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let wrapper: TxWrapper = serde_json::from_reader(req.body.by_ref())
.map_err(|e| IronError::new(e, status::BadRequest))?;
let tx_bin = util::from_hex(wrapper.tx_hex).map_err(|_| {
Error::Argument(format!("Invalid hex in transaction wrapper."))
})?;
let tx: Transaction = ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
Error::Argument("Could not deserialize transaction, invalid format.".to_string())
})?;
let source = pool::TxSource {
debug_name: "push-api".to_string(),
identifier: "?.?.?.?".to_string(),
};
info!(
LOGGER,
"Pushing transaction with {} inputs and {} outputs to pool.",
tx.inputs.len(),
tx.outputs.len()
);
self.tx_pool
.write()
.unwrap()
.add_to_memory_pool(source, tx)
.map_err(|e| {
Error::Internal(format!("Addition to transaction pool failed: {:?}", e))
})?;
Ok(Response::with(status::Ok))
}
}
// Utility to serialize a struct into JSON and produce a sensible IronResult
// out of it.
fn json_response<T>(s: &T) -> IronResult<Response>
where
T: Serialize,
{
match serde_json::to_string_pretty(s) {
Ok(json) => Ok(Response::with((status::Ok, json))),
Err(_) => Ok(Response::with((status::InternalServerError, ""))),
}
}
/// Start all server HTTP handlers. Register all of them with Iron
/// and runs the corresponding HTTP server.
pub fn start_rest_apis<T>(
addr: String,
chain: Arc<chain::Chain>,
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
) where
T: pool::BlockChain + Send + Sync + 'static,
{
thread::spawn(move || {
// build handlers and register them under the appropriate endpoint
let utxo_handler = UtxoHandler {
chain: chain.clone(),
};
let chain_tip_handler = ChainHandler {
chain: chain.clone(),
};
let sumtree_handler = SumTreeHandler {
chain: chain.clone(),
};
let pool_info_handler = PoolInfoHandler {
tx_pool: tx_pool.clone(),
};
let pool_push_handler = PoolPushHandler {
tx_pool: tx_pool.clone(),
};
let router = router!(
chain_tip: get "/chain" => chain_tip_handler,
chain_utxos: get "/chain/utxos" => utxo_handler,
sumtree_roots: get "/sumtrees/*" => sumtree_handler,
pool_info: get "/pool" => pool_info_handler,
pool_push: post "/pool/push" => pool_push_handler,
);
let mut apis = ApiServer::new("/v1".to_string());
apis.register_handler(router);
info!(LOGGER, "Starting HTTP API server at {}.", addr);
apis.start(&addr[..]).unwrap_or_else(|e| {
error!(LOGGER, "Failed to start API HTTP server: {}.", e);
});
});
}
+6 -7
View File
@@ -12,31 +12,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.
extern crate grin_core as core;
extern crate grin_chain as chain;
extern crate grin_core as core;
extern crate grin_pool as pool;
extern crate grin_store as store;
extern crate grin_util as util;
extern crate hyper;
#[macro_use]
extern crate slog;
extern crate iron;
extern crate urlencoded;
extern crate mount;
#[macro_use]
extern crate router;
extern crate mount;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[macro_use]
extern crate slog;
extern crate urlencoded;
pub mod client;
mod endpoints;
mod handlers;
mod rest;
mod types;
pub use endpoints::start_rest_apis;
pub use handlers::start_rest_apis;
pub use types::*;
pub use rest::*;
+4 -279
View File
@@ -19,26 +19,18 @@
//! register them on a ApiServer.
use std::error;
use std::fmt::{self, Display, Debug, Formatter};
use std::io::Read;
use std::fmt::{self, Display, Formatter};
use std::net::ToSocketAddrs;
use std::string::ToString;
use std::str::FromStr;
use std::mem;
use iron::prelude::*;
use iron::{status, headers, Listening};
use iron::method::Method;
use iron::modifiers::Header;
use iron::{status, Listening};
use iron::middleware::Handler;
use router::Router;
use mount::Mount;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json;
use store;
use util::LOGGER;
/// Errors that can be returned by an ApiEndpoint implementation.
#[derive(Debug)]
@@ -87,161 +79,6 @@ impl From<store::Error> for Error {
}
}
#[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, Error>;
/// 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 + DeserializeOwned;
type OP_IN: Serialize + DeserializeOwned;
type OP_OUT: Serialize + DeserializeOwned;
fn operations(&self) -> Vec<Operation>;
#[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!()
}
#[allow(unused_variables)]
fn operation(&self, op: String, input: Self::OP_IN) -> ApiResult<Self::OP_OUT> {
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>(E);
impl<E> Handler for ApiWrapper<E>
where E: ApiEndpoint,
<<E as ApiEndpoint>::ID as FromStr>::Err: Debug + Send + error::Error
{
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)),
}
}
}
struct OpWrapper<E> {
operation: String,
endpoint: E,
}
impl<E> Handler for OpWrapper<E>
where E: ApiEndpoint
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let t: E::OP_IN = serde_json::from_reader(req.body.by_ref()).map_err(|e| {
IronError::new(e, status::BadRequest)
})?;
let res = self.endpoint.operation(self.operation.clone(), t);
match res {
Ok(resp) => {
let res_json = serde_json::to_string(&resp).map_err(|e| {
IronError::new(e, status::InternalServerError)
})?;
Ok(Response::with((status::Ok, res_json)))
}
Err(e) => {
error!(LOGGER, "API operation: {:?}", e);
Err(IronError::from(e))
}
}
}
}
fn extract_param<ID>(req: &mut Request, param: &'static str) -> IronResult<ID>
where ID: ToString + FromStr,
<ID as FromStr>::Err: Debug + Send + error::Error + 'static
{
let id = req.extensions
.get::<Router>()
.unwrap()
.find(param)
.unwrap_or("");
id.parse::<ID>()
.map_err(|e| IronError::new(e, status::BadRequest))
}
/// HTTP server allowing the registration of ApiEndpoint implementations.
pub struct ApiServer {
root: String,
@@ -281,119 +118,7 @@ impl ApiServer {
}
/// Registers an iron handler (via mount)
pub fn register_handler<H: Handler>(&mut self, route: &str, handler: H) -> &mut Mount {
self.mount.mount(route, handler)
}
/// Register a new API endpoint, providing a relative URL for the new
/// endpoint.
pub fn register_endpoint<E>(&mut self, subpath: String, endpoint: E)
where E: ApiEndpoint,
<<E as ApiEndpoint>::ID as FromStr>::Err: Debug + Send + error::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 op in endpoint.operations() {
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(),
};
let full_path = format!("{}/{}", root.clone(), op_s.clone());
self.router
.route(op.to_method(), full_path.clone(), wrapper, route_name);
info!(LOGGER, "route: POST {}", full_path);
} 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());
self.router
.route(op.to_method(), full_path.clone(), wrapper, route_name);
info!(LOGGER, "route: {} {}", op.to_method(), full_path);
}
}
// support for the HTTP Options method by differentiating what's on the
// root resource vs the id resource
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, 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::*;
#[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;
type OP_IN = ();
type OP_OUT = ();
fn operations(&self) -> Vec<Operation> {
vec![Operation::Get]
}
fn get(&self, name: String) -> ApiResult<Animal> {
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);
pub fn register_handler<H: Handler>(&mut self, handler: H) -> &mut Mount {
self.mount.mount(&self.root, handler)
}
}
+18 -16
View File
@@ -44,7 +44,7 @@ impl Tip {
}
}
/// Sumtrees
/// Sumtrees
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SumTrees {
/// UTXO Root Hash
@@ -59,7 +59,7 @@ pub struct SumTrees {
impl SumTrees {
pub fn from_head(head: Arc<chain::Chain>) -> SumTrees {
let roots=head.get_sumtree_roots();
let roots = head.get_sumtree_roots();
SumTrees {
utxo_root_hash: util::to_hex(roots.0.hash.to_vec()),
utxo_root_sum: util::to_hex(roots.0.sum.commit.0.to_vec()),
@@ -80,14 +80,13 @@ pub struct SumTreeNode {
}
impl SumTreeNode {
pub fn get_last_n_utxo(chain: Arc<chain::Chain>, distance:u64) -> Vec<SumTreeNode> {
pub fn get_last_n_utxo(chain: Arc<chain::Chain>, distance: u64) -> Vec<SumTreeNode> {
let mut return_vec = Vec::new();
let last_n = chain.get_last_n_utxo(distance);
for elem_output in last_n {
let header = chain
.get_block_header_by_output_commit(&elem_output.1.commit)
.map_err(|_| Error::NotFound);
.get_block_header_by_output_commit(&elem_output.1.commit)
.map_err(|_| Error::NotFound);
// Need to call further method to check if output is spent
let mut output = OutputPrintable::from_output(&elem_output.1, &header.unwrap());
if let Ok(_) = chain.get_unspent(&elem_output.1.commit) {
@@ -101,7 +100,7 @@ impl SumTreeNode {
return_vec
}
pub fn get_last_n_rangeproof(head: Arc<chain::Chain>, distance:u64) -> Vec<SumTreeNode> {
pub fn get_last_n_rangeproof(head: Arc<chain::Chain>, distance: u64) -> Vec<SumTreeNode> {
let mut return_vec = Vec::new();
let last_n = head.get_last_n_rangeproof(distance);
for elem in last_n {
@@ -113,7 +112,7 @@ impl SumTreeNode {
return_vec
}
pub fn get_last_n_kernel(head: Arc<chain::Chain>, distance:u64) -> Vec<SumTreeNode> {
pub fn get_last_n_kernel(head: Arc<chain::Chain>, distance: u64) -> Vec<SumTreeNode> {
let mut return_vec = Vec::new();
let last_n = head.get_last_n_kernel(distance);
for elem in last_n {
@@ -149,9 +148,10 @@ pub struct Output {
impl Output {
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader) -> Output {
let (output_type, lock_height) = match output.features {
x if x.contains(core::transaction::COINBASE_OUTPUT) => {
(OutputType::Coinbase, block_header.height + global::coinbase_maturity())
}
x if x.contains(core::transaction::COINBASE_OUTPUT) => (
OutputType::Coinbase,
block_header.height + global::coinbase_maturity(),
),
_ => (OutputType::Transaction, 0),
};
@@ -165,12 +165,13 @@ impl Output {
}
}
//As above, except formatted a bit better for human viewing
// As above, except formatted a bit better for human viewing
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OutputPrintable {
/// The type of output Coinbase|Transaction
pub output_type: OutputType,
/// The homomorphic commitment representing the output's amount (as hex string)
/// The homomorphic commitment representing the output's amount (as hex
/// string)
pub commit: String,
/// The height of the block creating this output
pub height: u64,
@@ -185,9 +186,10 @@ pub struct OutputPrintable {
impl OutputPrintable {
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader) -> OutputPrintable {
let (output_type, lock_height) = match output.features {
x if x.contains(core::transaction::COINBASE_OUTPUT) => {
(OutputType::Coinbase, block_header.height + global::coinbase_maturity())
}
x if x.contains(core::transaction::COINBASE_OUTPUT) => (
OutputType::Coinbase,
block_header.height + global::coinbase_maturity(),
),
_ => (OutputType::Transaction, 0),
};
OutputPrintable {