Error handling using failure in API (#949)

This PR adresses #166
Error handling in wallet was ported to failure in https://github.com/mimblewimble/grin/pull/713
Using the same error model makes wallet code simpler and may simplify migration to Hyper.
This commit is contained in:
hashmap
2018-04-16 11:00:32 +02:00
committed by Yeastplume
parent ffa5bfe16f
commit b28de95da4
10 changed files with 171 additions and 117 deletions
+20 -12
View File
@@ -14,6 +14,7 @@
//! High level JSON/HTTP client API
use failure::{Fail, ResultExt};
use hyper;
use hyper::client::Response;
use hyper::status::{StatusClass, StatusCode};
@@ -21,7 +22,7 @@ use serde::{Deserialize, Serialize};
use serde_json;
use std::io::Read;
use rest::Error;
use rest::{Error, ErrorKind};
/// Helper function to easily issue a HTTP GET request against a given URL that
/// returns a JSON object. Handles request building, JSON deserialization and
@@ -32,8 +33,11 @@ where
{
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| {
e.context(ErrorKind::Internal(
"Server returned invalid JSON".to_owned(),
)).into()
})
}
/// Helper function to easily issue a HTTP POST request with the provided JSON
@@ -44,8 +48,9 @@ pub fn post<'a, IN>(url: &'a str, input: &IN) -> Result<(), Error>
where
IN: Serialize,
{
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).context(ErrorKind::Internal(
"Could not serialize data to JSON".to_owned(),
))?;
let client = hyper::Client::new();
let _res = check_error(client.post(url).body(&mut in_json.as_bytes()).send())?;
Ok(())
@@ -54,24 +59,27 @@ where
// convert hyper error and check for non success response codes
fn check_error(res: hyper::Result<Response>) -> Result<Response, Error> {
if let Err(e) = res {
return Err(Error::Internal(format!("Error during request: {}", e)));
return Err(
e.context(ErrorKind::Internal("Error during request".to_owned()))
.into(),
);
}
let mut response = res.unwrap();
match response.status.class() {
StatusClass::Success => Ok(response),
StatusClass::ServerError => Err(Error::Internal(format!(
StatusClass::ServerError => Err(ErrorKind::Internal(format!(
"Server error: {}",
err_msg(&mut response)
))),
)))?,
StatusClass::ClientError => if response.status == StatusCode::NotFound {
Err(Error::NotFound)
Err(ErrorKind::NotFound)?
} else {
Err(Error::Argument(format!(
Err(ErrorKind::Argument(format!(
"Argument error: {}",
err_msg(&mut response)
)))
)))?
},
_ => Err(Error::Internal(format!("Unrecognized error."))),
_ => Err(ErrorKind::Internal(format!("Unrecognized error.")))?,
}
}
+28 -22
View File
@@ -16,25 +16,26 @@ use std::io::Read;
use std::sync::{Arc, RwLock, Weak};
use std::thread;
use iron::prelude::*;
use failure::{Fail, ResultExt};
use iron::Handler;
use iron::prelude::*;
use iron::status;
use urlencoded::UrlEncodedQuery;
use serde::Serialize;
use serde_json;
use urlencoded::UrlEncodedQuery;
use chain;
use core::core::{OutputFeatures, OutputIdentifier, Transaction};
use core::core::hash::{Hash, Hashed};
use core::core::{OutputFeatures, OutputIdentifier, Transaction};
use core::ser;
use pool;
use p2p;
use pool;
use regex::Regex;
use rest::*;
use util::secp::pedersen::Commitment;
use types::*;
use util;
use util::LOGGER;
use util::secp::pedersen::Commitment;
// All handlers use `Weak` references instead of `Arc` to avoid cycles that
// can never be destroyed. These 2 functions are simple helpers to reduce the
@@ -67,8 +68,10 @@ struct OutputHandler {
impl OutputHandler {
fn get_output(&self, id: &str) -> Result<Output, Error> {
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)).context(ErrorKind::Argument(format!(
"Not a valid commitment: {}",
id
)))?;
let commit = Commitment::from_vec(c);
// We need the features here to be able to generate the necessary hash
@@ -85,7 +88,7 @@ impl OutputHandler {
return Ok(Output::new(&commit));
}
}
Err(Error::NotFound)
Err(ErrorKind::NotFound)?
}
fn outputs_by_ids(&self, req: &mut Request) -> Vec<Output> {
@@ -499,19 +502,18 @@ impl Handler for ChainCompactHandler {
///
/// Optionally return results as "compact blocks" by passing "?compact" query param
/// GET /v1/blocks/<hash>?compact
///
pub struct BlockHandler {
pub chain: Weak<chain::Chain>,
}
impl BlockHandler {
fn get_block(&self, h: &Hash) -> Result<BlockPrintable, Error> {
let block = w(&self.chain).get_block(h).map_err(|_| Error::NotFound)?;
let block = w(&self.chain).get_block(h).context(ErrorKind::NotFound)?;
Ok(BlockPrintable::from_block(&block, w(&self.chain), false))
}
fn get_compact_block(&self, h: &Hash) -> Result<CompactBlockPrintable, Error> {
let block = w(&self.chain).get_block(h).map_err(|_| Error::NotFound)?;
let block = w(&self.chain).get_block(h).context(ErrorKind::NotFound)?;
Ok(CompactBlockPrintable::from_compact_block(
&block.as_compact_block(),
w(&self.chain),
@@ -523,14 +525,16 @@ impl BlockHandler {
if let Ok(height) = input.parse() {
match w(&self.chain).get_header_by_height(height) {
Ok(header) => return Ok(header.hash()),
Err(_) => return Err(Error::NotFound),
Err(_) => return Err(ErrorKind::NotFound)?,
}
}
lazy_static! {
static ref RE: Regex = Regex::new(r"[0-9a-fA-F]{64}").unwrap();
}
if !RE.is_match(&input) {
return Err(Error::Argument(String::from("Not a valid hash or height.")));
return Err(ErrorKind::Argument(
"Not a valid hash or height.".to_owned(),
))?;
}
let vec = util::from_hex(input).unwrap();
Ok(Hash::from_vec(vec))
@@ -545,7 +549,8 @@ impl Handler for BlockHandler {
path_elems.pop();
}
let el = *path_elems.last().unwrap();
let h = try!(self.parse_input(el.to_string()));
let h = self.parse_input(el.to_string())
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
let mut compact = false;
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
@@ -555,10 +560,12 @@ impl Handler for BlockHandler {
}
if compact {
let b = try!(self.get_compact_block(&h));
let b = self.get_compact_block(&h)
.map_err(|e| IronError::new(Fail::compat(e), status::InternalServerError))?;
json_response(&b)
} else {
let b = try!(self.get_block(&h));
let b = self.get_block(&h)
.map_err(|e| IronError::new(Fail::compat(e), status::InternalServerError))?;
json_response(&b)
}
}
@@ -603,14 +610,13 @@ where
{
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))?;
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
let tx_bin = util::from_hex(wrapper.tx_hex)
.map_err(|_| Error::Argument(format!("Invalid hex in transaction wrapper.")))?;
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
let tx: Transaction = ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
Error::Argument("Could not deserialize transaction, invalid format.".to_string())
})?;
let tx: Transaction = ser::deserialize(&mut &tx_bin[..])
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
let source = pool::TxSource {
debug_name: "push-api".to_string(),
@@ -650,7 +656,7 @@ where
Ok(()) => Ok(Response::with(status::Ok)),
Err(e) => {
debug!(LOGGER, "error - {:?}", e);
Err(IronError::from(Error::Argument(format!("{:?}", e))))
Err(IronError::new(Fail::compat(e), status::BadRequest))
}
}
}
+4
View File
@@ -19,6 +19,10 @@ extern crate grin_pool as pool;
extern crate grin_store as store;
extern crate grin_util as util;
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate hyper;
extern crate iron;
#[macro_use]
+42 -36
View File
@@ -18,64 +18,70 @@
//! To use it, just have your service(s) implement the ApiEndpoint trait and
//! register them on a ApiServer.
use std::error;
use std::fmt::{self, Display, Formatter};
use std::mem;
use std::net::ToSocketAddrs;
use std::string::ToString;
use std::mem;
use failure::{Backtrace, Context, Fail, ResultExt};
use iron::middleware::Handler;
use iron::prelude::*;
use iron::{status, Listening};
use iron::middleware::Handler;
use router::Router;
use mount::Mount;
use router::Router;
use store;
/// Errors that can be returned by an ApiEndpoint implementation.
#[derive(Debug)]
pub enum Error {
pub struct Error {
inner: Context<ErrorKind>,
}
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
pub enum ErrorKind {
#[fail(display = "Internal error: {}", _0)]
Internal(String),
#[fail(display = "Bad arguments: {}", _0)]
Argument(String),
#[fail(display = "Not found.")]
NotFound,
}
impl Fail for Error {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
Error::Argument(ref s) => write!(f, "Bad arguments: {}", s),
Error::Internal(ref s) => write!(f, "Internal error: {}", s),
Error::NotFound => write!(f, "Not found."),
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl Error {
pub fn kind(&self) -> &ErrorKind {
self.inner.get_context()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error {
inner: Context::new(kind),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::Argument(_) => "Bad arguments.",
Error::Internal(_) => "Internal error.",
Error::NotFound => "Not found.",
}
}
}
impl From<Error> for IronError {
fn from(e: Error) -> IronError {
match e {
Error::Argument(_) => IronError::new(e, status::Status::BadRequest),
Error::Internal(_) => IronError::new(e, status::Status::InternalServerError),
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()),
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Error {
Error { inner: inner }
}
}