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:
+20
-12
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user