Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor API errors #282

Merged
merged 4 commits into from
Dec 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/elastic/examples/custom_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct Hit {

// Implement `IsOk` for our custom `SearchResponse` so it can be used in the call to `into_response`.
impl IsOk for SearchResponse {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError> {
match head.status() {
200...299 => Ok(MaybeOkResponse::ok(body)),
_ => Ok(MaybeOkResponse::err(body)),
Expand Down
5 changes: 1 addition & 4 deletions src/elastic/src/client/responses/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,5 @@ pub use elastic_responses::bulk;
pub mod prelude {
/*! A glob import for convenience. */

pub use super::{BulkErrorsResponse, BulkResponse, CommandResponse, DeleteResponse, GetResponse, IndexResponse, PingResponse, SearchResponse, Shards, UpdateResponse};

pub use super::async::AsyncResponseBuilder;
pub use super::sync::SyncResponseBuilder;
pub use super::{AsyncResponseBuilder, SyncResponseBuilder, BulkErrorsResponse, BulkResponse, CommandResponse, DeleteResponse, GetResponse, IndexResponse, PingResponse, SearchResponse, Shards, UpdateResponse};
}
6 changes: 3 additions & 3 deletions src/elastic/src/client/responses/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct MyResponse {
}

impl IsOk for MyResponse {
fn is_ok<B>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError>
fn is_ok<B>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError>
where B: ResponseBody
{
match head.status() {
Expand Down Expand Up @@ -48,7 +48,7 @@ The `MyResponse` type can then be used for deserialising a concrete response:
# took: u64
# }
# impl IsOk for MyResponse {
# fn is_ok<B>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError>
# fn is_ok<B>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError>
# where B: ResponseBody
# {
# match head.status() {
Expand Down Expand Up @@ -90,4 +90,4 @@ See the [`IsOk`][IsOk] trait for more details.
pub(crate) use elastic_responses::parse;

pub use elastic_responses::parsing::{Buffered, HttpResponseHead, IsOk, MaybeBufferedResponse, MaybeOkResponse, ResponseBody, Unbuffered};
pub use elastic_responses::error::ParseResponseError;
pub use elastic_responses::error::ParseError;
4 changes: 2 additions & 2 deletions src/elastic/src/client/sender/sniffed_nodes/nodes_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::fmt;
use serde::de::{Deserialize, Deserializer, MapAccess, Visitor};
use client::responses::parse::{HttpResponseHead, IsOk, MaybeOkResponse, ParseResponseError, ResponseBody, Unbuffered};
use client::responses::parse::{HttpResponseHead, IsOk, MaybeOkResponse, ParseError, ResponseBody, Unbuffered};

use std::iter::IntoIterator;
use std::vec::IntoIter;
Expand Down Expand Up @@ -32,7 +32,7 @@ impl IntoIterator for NodesInfoResponse {
}

impl IsOk for NodesInfoResponse {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, unbuffered: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, unbuffered: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError> {
match head.status() {
200...299 => Ok(MaybeOkResponse::ok(unbuffered)),
_ => Ok(MaybeOkResponse::err(unbuffered)),
Expand Down
4 changes: 2 additions & 2 deletions src/responses/src/bulk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ pub enum Action {
}

impl<TIndex, TType, TId> IsOk for BulkResponse<TIndex, TType, TId> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError> {
match head.status() {
200...299 => Ok(MaybeOkResponse::ok(body)),
_ => Ok(MaybeOkResponse::err(body)),
Expand All @@ -530,7 +530,7 @@ impl<TIndex, TType, TId> IsOk for BulkResponse<TIndex, TType, TId> {
}

impl<TIndex, TType, TId> IsOk for BulkErrorsResponse<TIndex, TType, TId> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError> {
match head.status() {
200...299 => Ok(MaybeOkResponse::ok(body)),
_ => Ok(MaybeOkResponse::err(body)),
Expand Down
2 changes: 1 addition & 1 deletion src/responses/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl CommandResponse {
}

impl IsOk for CommandResponse {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError> {
match head.status() {
200...299 => Ok(MaybeOkResponse::ok(body)),
_ => Ok(MaybeOkResponse::err(body)),
Expand Down
2 changes: 1 addition & 1 deletion src/responses/src/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl DeleteResponse {
}

impl IsOk for DeleteResponse {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError> {
match head.status() {
200...299 | 404 => Ok(MaybeOkResponse::ok(body)),
_ => Ok(MaybeOkResponse::err(body)),
Expand Down
162 changes: 90 additions & 72 deletions src/responses/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,89 @@
/*!
Error types from Elasticsearch
Error types from Elasticsearch.
*/

use serde::{Deserialize, Deserializer};
use serde_json::{Error as JsonError, Map, Value};
use std::error::Error as StdError;
use std::io::Error as IoError;
use std::fmt;

mod inner {
use std::fmt;
use std::error::Error as StdError;
use serde_json::{Map, Value};

use super::ApiError;

quick_error! {
/** An error parsing a response stream. */
#[derive(Debug)]
pub enum ParseResponseError {
/** The response contains invalid json. */
Json(err: JsonError) {
from()
pub struct UnknownApiError(pub Map<String, Value>);

impl fmt::Display for UnknownApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(&self.0 as &fmt::Debug).fmt(f)
}
/** The response caused an io error while buffering. */
Io(err: IoError) {
from()
}

impl StdError for UnknownApiError {
fn description(&self) -> &str {
"an unknown API error"
}

fn cause(&self) -> Option<&StdError> {
None
}
}

pub enum ParsedApiError {
Known(ApiError),
Unknown(Map<String, Value>)
}
}

pub(crate) use self::inner::*;

/**
A generic error parsing an API response.
*/
#[derive(Debug)]
pub struct ParseError {
inner: Box<StdError + Send + Sync>,
}

impl ParseError {
pub fn new<E>(err: E) -> Self where E: StdError + Send + Sync + 'static {
ParseError {
inner: Box::new(err)
}
}
}

impl From<IoError> for ParseError {
fn from(err: IoError) -> Self {
ParseError::new(err)
}
}

impl From<JsonError> for ParseError {
fn from(err: JsonError) -> Self {
ParseError::new(err)
}
}

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}

impl StdError for ParseError {
fn description(&self) -> &str {
self.inner.description()
}

fn cause(&self) -> Option<&StdError> {
Some(&*self.inner)
}
}

quick_error! {
Expand All @@ -30,7 +95,7 @@ quick_error! {
from()
}
/** An error parsing a response body. */
Parse(err: ParseResponseError) {
Parse(err: ParseError) {
from()
}
}
Expand Down Expand Up @@ -69,24 +134,6 @@ quick_error! {
display("index already exists: '{}'", index)
}
/**
The request body contains invalid data.

If a Query DSL query contains invalid JSON or unrecognised properties then Elasticsearch will return a `Parsing` error.
*/
Parsing { line: u64, col: u64, reason: String } {
description("request parse error")
display("request parse error: '{}' on line: {}, col: {}", reason, line, col)
}
/**
The document mapping contains invalid data.

If a put mapping request contains invalid JSON or unrecognised properties then Elasticsearch will return a `MapperParsing` error.
*/
MapperParsing { reason: String } {
description("mapper parse error")
display("mapper parse error: '{}'", reason)
}
/**
The request body can't be processed.

Some endpoints that expect certain constraints of a request to hold will return an `ActionRequestValidation` error if those constraints aren't met.
Expand All @@ -95,17 +142,6 @@ quick_error! {
description("action request failed validation")
display("action request failed validation: '{}'", reason)
}
/**
A currently unrecognised error occurred.

**WARNING:** Don't rely on this variant to capture an error.
As new variants are introduced they will no longer be matched by `ApiError::Other`.
For this reason, this variant will disappear in the future.
*/
Other(v: Map<String, Value>) {
description("error response from Elasticsearch")
display("error response from Elasticsearch: {:?}", v)
}
#[doc(hidden)]
__NonExhaustive {}
}
Expand All @@ -119,13 +155,13 @@ macro_rules! error_key {

match key {
Some(v) => v,
_ => return ApiError::Other($obj).into()
_ => return ParsedApiError::Unknown($obj)
}
}
)
}

impl<'de> Deserialize<'de> for ApiError {
impl<'de> Deserialize<'de> for ParsedApiError {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
Expand All @@ -136,12 +172,12 @@ impl<'de> Deserialize<'de> for ApiError {
}
}

impl From<Map<String, Value>> for ApiError {
impl From<Map<String, Value>> for ParsedApiError {
fn from(mut value: Map<String, Value>) -> Self {
let obj = {
match value.remove("error") {
Some(Value::Object(value)) => value,
_ => return ApiError::Other(value),
_ => return ParsedApiError::Unknown(value),
}
};

Expand All @@ -152,58 +188,40 @@ impl From<Map<String, Value>> for ApiError {

match ty {
Some(ty) => ty,
_ => return ApiError::Other(obj),
_ => return ParsedApiError::Unknown(obj),
}
};

match ty.as_ref() {
"index_not_found_exception" => {
let index = error_key!(obj[index]: |v| v.as_str());

ApiError::IndexNotFound {
ParsedApiError::Known(ApiError::IndexNotFound {
index: index.into(),
}
})
}
"index_already_exists_exception" => {
let index = error_key!(obj[index]: |v| v.as_str());

ApiError::IndexAlreadyExists {
ParsedApiError::Known(ApiError::IndexAlreadyExists {
index: index.into(),
}
})
}
"document_missing_exception" => {
let index = error_key!(obj[index]: |v| v.as_str());

ApiError::DocumentMissing {
ParsedApiError::Known(ApiError::DocumentMissing {
index: index.into(),
}
}
"parsing_exception" => {
let line = error_key!(obj[line]: |v| v.as_u64());
let col = error_key!(obj[col]: |v| v.as_u64());
let reason = error_key!(obj[reason]: |v| v.as_str());

ApiError::Parsing {
line: line,
col: col,
reason: reason.into(),
}
}
"mapper_parsing_exception" => {
let reason = error_key!(obj[reason]: |v| v.as_str());

ApiError::MapperParsing {
reason: reason.into(),
}
})
}
"action_request_validation_exception" => {
let reason = error_key!(obj[reason]: |v| v.as_str());

ApiError::ActionRequestValidation {
ParsedApiError::Known(ApiError::ActionRequestValidation {
reason: reason.into(),
}
})
}
_ => ApiError::Other(obj),
_ => ParsedApiError::Unknown(obj),
}
}
}
2 changes: 1 addition & 1 deletion src/responses/src/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl<T> GetResponse<T> {
}

impl<T: DeserializeOwned> IsOk for GetResponse<T> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError> {
match head.status() {
200...299 => Ok(MaybeOkResponse::ok(body)),
404 => {
Expand Down
2 changes: 1 addition & 1 deletion src/responses/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl IndexResponse {
}

impl IsOk for IndexResponse {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseResponseError> {
fn is_ok<B: ResponseBody>(head: HttpResponseHead, body: Unbuffered<B>) -> Result<MaybeOkResponse<B>, ParseError> {
match head.status() {
200...299 => Ok(MaybeOkResponse::ok(body)),
_ => Ok(MaybeOkResponse::err(body)),
Expand Down
Loading