use crate::{
error::Error as ClientError,
http::{headers::HeaderMap, Method, StatusCode, Url},
};
use serde::{
de,
de::{DeserializeOwned, MapAccess, Visitor},
Deserialize, Deserializer, Serialize,
};
use serde_json::Value;
use std::{collections::BTreeMap, fmt, str::FromStr};
use void::Void;
pub struct Response(reqwest::Response, Method);
impl Response {
pub fn new(response: reqwest::Response, method: Method) -> Self {
Self(response, method)
}
pub fn content_length(&self) -> Option<u64> {
self.0.content_length()
}
pub fn content_type(&self) -> &str {
self.0
.headers()
.get(crate::http::headers::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.unwrap()
}
pub fn error_for_status_code(self) -> Result<Self, ClientError> {
match self.0.error_for_status_ref() {
Ok(_) => Ok(self),
Err(err) => Err(err.into()),
}
}
pub fn error_for_status_code_ref(&self) -> Result<&Self, ClientError> {
match self.0.error_for_status_ref() {
Ok(_) => Ok(self),
Err(err) => Err(err.into()),
}
}
pub async fn exception(self) -> Result<Option<Exception>, ClientError> {
if self.status_code().is_client_error() || self.status_code().is_server_error() {
let ex = self.json().await?;
Ok(Some(ex))
} else {
Ok(None)
}
}
pub async fn json<B>(self) -> Result<B, ClientError>
where
B: DeserializeOwned,
{
let body = self.0.json::<B>().await?;
Ok(body)
}
pub fn headers(&self) -> &HeaderMap {
self.0.headers()
}
pub fn method(&self) -> Method {
self.1
}
pub fn status_code(&self) -> StatusCode {
self.0.status()
}
pub async fn text(self) -> Result<String, ClientError> {
let body = self.0.text().await?;
Ok(body)
}
pub fn url(&self) -> &Url {
self.0.url()
}
pub fn warning_headers(&self) -> impl Iterator<Item = &str> {
self.0.headers().get_all("Warning").iter().map(|w| {
let s = w.to_str().unwrap();
let first_quote = s.find('"').unwrap();
let last_quote = s.len() - 1;
&s[first_quote + 1..last_quote]
})
}
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Response")
.field("method", &self.method())
.field("url", self.url())
.field("status_code", &self.status_code())
.field("headers", self.headers())
.finish()
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct Exception {
status: Option<u16>,
#[serde(deserialize_with = "crate::string_or_struct")]
error: Error,
}
impl Exception {
pub fn status(&self) -> Option<u16> {
self.status
}
pub fn error(&self) -> &Error {
&self.error
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct Error {
#[serde(deserialize_with = "option_box_cause", default)]
caused_by: Option<Box<Cause>>,
#[serde(default = "BTreeMap::new", deserialize_with = "header_map")]
header: BTreeMap<String, Vec<String>>,
#[serde(default = "Vec::new")]
root_cause: Vec<Cause>,
reason: Option<String>,
stack_trace: Option<String>,
#[serde(rename = "type")]
ty: Option<String>,
#[serde(default = "BTreeMap::new", flatten)]
additional_details: BTreeMap<String, Value>,
}
fn header_map<'de, D>(deserializer: D) -> Result<BTreeMap<String, Vec<String>>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wrapper(#[serde(deserialize_with = "crate::string_or_seq_string")] Vec<String>);
let v: BTreeMap<String, Wrapper> = BTreeMap::deserialize(deserializer)?;
Ok(v.into_iter().map(|(k, Wrapper(v))| (k, v)).collect())
}
impl Error {
pub fn caused_by(&self) -> Option<&Cause> {
self.caused_by.as_deref()
}
pub fn root_cause(&self) -> &Vec<Cause> {
&self.root_cause
}
pub fn header(&self) -> &BTreeMap<String, Vec<String>> {
&self.header
}
pub fn reason(&self) -> Option<&str> {
self.reason.as_deref()
}
pub fn stack_trace(&self) -> Option<&str> {
self.stack_trace.as_deref()
}
pub fn ty(&self) -> Option<&str> {
self.ty.as_deref()
}
pub fn additional_details(&self) -> &BTreeMap<String, Value> {
&self.additional_details
}
}
impl FromStr for Error {
type Err = Void;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Error {
caused_by: None,
header: Default::default(),
root_cause: Vec::new(),
reason: Some(s.to_string()),
stack_trace: None,
ty: None,
additional_details: Default::default(),
})
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct Cause {
#[serde(deserialize_with = "option_box_cause", default)]
caused_by: Option<Box<Cause>>,
reason: Option<String>,
stack_trace: Option<String>,
#[serde(rename = "type")]
ty: Option<String>,
#[serde(default = "BTreeMap::new", flatten)]
additional_details: BTreeMap<String, Value>,
}
fn option_box_cause<'de, D>(deserializer: D) -> Result<Option<Box<Cause>>, D::Error>
where
D: Deserializer<'de>,
{
struct CauseVisitor;
impl<'de> Visitor<'de> for CauseVisitor {
type Value = Cause;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or map")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Cause {
caused_by: None,
reason: Some(value.to_string()),
stack_trace: None,
ty: None,
additional_details: Default::default(),
})
}
fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
}
}
deserializer
.deserialize_any(CauseVisitor)
.map(|c| Some(Box::new(c)))
}
impl Cause {
pub fn caused_by(&self) -> Option<&Cause> {
self.caused_by.as_deref()
}
pub fn reason(&self) -> Option<&str> {
self.reason.as_deref()
}
pub fn stack_trace(&self) -> Option<&str> {
self.stack_trace.as_deref()
}
pub fn ty(&self) -> Option<&str> {
self.ty.as_deref()
}
pub fn additional_details(&self) -> &BTreeMap<String, Value> {
&self.additional_details
}
}
#[cfg(test)]
pub mod tests {
use crate::http::response::Exception;
use serde_json::json;
#[test]
fn deserialize_error_string() -> Result<(), failure::Error> {
let json = r#"{"error":"no handler found for uri [/test_1/test/1/_update?_source=foo%2Cbar] and method [POST]"}"#;
let ex: Exception = serde_json::from_str(json)?;
assert_eq!(ex.status(), None);
assert_eq!(ex.error().reason(), Some("no handler found for uri [/test_1/test/1/_update?_source=foo%2Cbar] and method [POST]"));
assert_eq!(ex.error().ty(), None);
Ok(())
}
#[test]
fn deserialize_illegal_argument_exception() -> Result<(), failure::Error> {
let json = r#"{
"error": {
"root_cause": [{
"type": "illegal_argument_exception",
"reason": "Missing mandatory contexts in context query"
}],
"type": "search_phase_execution_exception",
"reason": "all shards failed",
"phase": "query",
"grouped": true,
"header": {
"WWW-Authenticate": "Bearer: token",
"x": ["y", "z"]
},
"failed_shards": [{
"shard": 0,
"index": "test",
"node": "APOkVK-rQi2Ll6CcAdeR6Q",
"reason": {
"type": "illegal_argument_exception",
"reason": "Missing mandatory contexts in context query"
}
}],
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Missing mandatory contexts in context query",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Missing mandatory contexts in context query"
}
}
},
"status": 400
}"#;
let ex: Exception = serde_json::from_str(json)?;
assert_eq!(ex.status(), Some(400));
let error = ex.error();
assert_eq!(error.root_cause().len(), 1);
assert_eq!(
error.root_cause()[0].ty(),
Some("illegal_argument_exception")
);
assert_eq!(
error.root_cause()[0].reason(),
Some("Missing mandatory contexts in context query")
);
assert_eq!(error.header().len(), 2);
assert_eq!(
error.header().get("WWW-Authenticate"),
Some(&vec!["Bearer: token".to_string()])
);
assert_eq!(
error.header().get("x"),
Some(&vec!["y".to_string(), "z".to_string()])
);
assert!(error.caused_by().is_some());
let caused_by = error.caused_by().unwrap();
assert_eq!(caused_by.ty(), Some("illegal_argument_exception"));
assert_eq!(
caused_by.reason(),
Some("Missing mandatory contexts in context query")
);
assert!(caused_by.caused_by().is_some());
let caused_by_caused_by = caused_by.caused_by().unwrap();
assert_eq!(caused_by_caused_by.ty(), Some("illegal_argument_exception"));
assert_eq!(
caused_by_caused_by.reason(),
Some("Missing mandatory contexts in context query")
);
assert!(error.additional_details().len() > 0);
assert_eq!(
error.additional_details().get("phase"),
Some(&json!("query"))
);
assert_eq!(
error.additional_details().get("grouped"),
Some(&json!(true))
);
Ok(())
}
#[test]
fn deserialize_index_not_found_exception() -> Result<(), failure::Error> {
let json = r#"{
"error": {
"root_cause": [{
"type": "index_not_found_exception",
"reason": "no such index [test_index]",
"resource.type": "index_or_alias",
"resource.id": "test_index",
"index_uuid": "_na_",
"index": "test_index"
}],
"type": "index_not_found_exception",
"reason": "no such index [test_index]",
"resource.type": "index_or_alias",
"resource.id": "test_index",
"index_uuid": "_na_",
"index": "test_index"
},
"status": 404
}"#;
let ex: Exception = serde_json::from_str(json)?;
assert_eq!(ex.status(), Some(404));
let error = ex.error();
assert_eq!(error.ty(), Some("index_not_found_exception"));
assert_eq!(error.reason(), Some("no such index [test_index]"));
assert_eq!(
error.additional_details().get("index").unwrap(),
&json!("test_index")
);
assert_eq!(error.root_cause().len(), 1);
assert_eq!(
error.root_cause()[0].ty(),
Some("index_not_found_exception")
);
Ok(())
}
}