use std::{fmt, sync::Arc, time::Duration};
use http::{HeaderMap, StatusCode, header::RETRY_AFTER};
use serde::{Deserialize, Serialize};
use serde_json::error::Category;
use serde_json::{Map, Value};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct ImportFailure {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rule_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub list_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item_id: Option<String>,
pub error: ImportError,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct ImportError {
pub status_code: u16,
pub message: String,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("invalid client configuration: {0}")]
Configuration(String),
#[error("invalid request: {0}")]
InvalidRequest(String),
#[error("HTTP transport failed: {0}")]
Transport(#[source] TransportError),
#[error("Kibana returned HTTP {status}")]
#[non_exhaustive]
Api {
status: StatusCode,
headers: Box<HeaderMap>,
body: String,
truncated: bool,
body_error: Option<TransportError>,
},
#[error("reading the body of a Kibana response with HTTP {status} failed: {source}")]
#[non_exhaustive]
Body {
status: StatusCode,
headers: Box<HeaderMap>,
received: usize,
#[source]
source: TransportError,
},
#[error("could not decode Kibana response with HTTP {status}: {source}")]
#[non_exhaustive]
Decode {
status: StatusCode,
headers: Box<HeaderMap>,
body: String,
#[source]
source: DecodeError,
},
#[error("response with HTTP {status} exceeded the configured limit of {limit} bytes")]
#[non_exhaustive]
ResponseTooLarge {
limit: usize,
status: StatusCode,
headers: Box<HeaderMap>,
},
#[error("requested page {requested} but Kibana returned page {returned}")]
UnexpectedPage { requested: u32, returned: u32 },
#[error("stopped after {max_pages} pages before the end of the collection")]
PageLimit { max_pages: u32 },
#[error("request serialization failed: {0}")]
Serialize(#[source] Arc<dyn std::error::Error + Send + Sync>),
}
impl Error {
pub fn status(&self) -> Option<StatusCode> {
match self {
Self::Api { status, .. }
| Self::Body { status, .. }
| Self::Decode { status, .. }
| Self::ResponseTooLarge { status, .. } => Some(*status),
_ => None,
}
}
pub fn headers(&self) -> Option<&HeaderMap> {
match self {
Self::Api { headers, .. }
| Self::Body { headers, .. }
| Self::Decode { headers, .. }
| Self::ResponseTooLarge { headers, .. } => Some(headers),
_ => None,
}
}
pub fn body(&self) -> Option<&str> {
match self {
Self::Api { body, .. } | Self::Decode { body, .. } => Some(body),
_ => None,
}
}
pub fn transport(&self) -> Option<&TransportError> {
match self {
Self::Transport(error) | Self::Body { source: error, .. } => Some(error),
Self::Api { body_error, .. } => body_error.as_ref(),
_ => None,
}
}
pub fn message(&self) -> Option<String> {
let Self::Api { body, .. } = self else {
return None;
};
let value: serde_json::Value = serde_json::from_str(body).ok()?;
value.get("message")?.as_str().map(str::to_owned)
}
pub fn retry_after(&self) -> Option<Duration> {
let Self::Api { headers, .. } = self else {
return None;
};
let seconds = headers
.get(RETRY_AFTER)?
.to_str()
.ok()?
.trim()
.parse()
.ok()?;
Some(Duration::from_secs(seconds))
}
pub(crate) fn serialize(error: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::Serialize(Arc::new(error))
}
}
impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self {
Self::Transport(TransportError::from(error))
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names = |headers: &HeaderMap| headers.keys().cloned().collect::<Vec<_>>();
match self {
Self::Configuration(message) => f.debug_tuple("Configuration").field(message).finish(),
Self::InvalidRequest(message) => {
f.debug_tuple("InvalidRequest").field(message).finish()
}
Self::Transport(error) => f.debug_tuple("Transport").field(error).finish(),
Self::Api {
status,
headers,
body,
truncated,
body_error,
} => f
.debug_struct("Api")
.field("status", status)
.field("headers", &names(headers))
.field("body_bytes", &body.len())
.field("truncated", truncated)
.field("body_error", body_error)
.finish(),
Self::Body {
status,
headers,
received,
source,
} => f
.debug_struct("Body")
.field("status", status)
.field("headers", &names(headers))
.field("received", received)
.field("source", source)
.finish(),
Self::Decode {
status,
headers,
body,
source,
} => f
.debug_struct("Decode")
.field("status", status)
.field("headers", &names(headers))
.field("body_bytes", &body.len())
.field("source", source)
.finish(),
Self::ResponseTooLarge {
limit,
status,
headers,
} => f
.debug_struct("ResponseTooLarge")
.field("limit", limit)
.field("status", status)
.field("headers", &names(headers))
.finish(),
Self::UnexpectedPage {
requested,
returned,
} => f
.debug_struct("UnexpectedPage")
.field("requested", requested)
.field("returned", returned)
.finish(),
Self::PageLimit { max_pages } => f
.debug_struct("PageLimit")
.field("max_pages", max_pages)
.finish(),
Self::Serialize(error) => f.debug_tuple("Serialize").field(error).finish(),
}
}
}
pub struct DecodeError(serde_json::Error);
impl DecodeError {
pub(crate) fn new(error: serde_json::Error) -> Self {
Self(error)
}
pub fn category(&self) -> Category {
self.0.classify()
}
pub fn classify(&self) -> Category {
self.category()
}
pub fn line(&self) -> usize {
self.0.line()
}
pub fn column(&self) -> usize {
self.0.column()
}
pub fn inner(&self) -> &serde_json::Error {
&self.0
}
pub fn as_serde_error(&self) -> &serde_json::Error {
self.inner()
}
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let category = match self.category() {
Category::Io => "I/O error",
Category::Syntax => "invalid JSON",
Category::Data => "unexpected JSON content",
Category::Eof => "unexpected end of JSON",
};
write!(
f,
"{category} at line {} column {}",
self.line(),
self.column()
)
}
}
impl fmt::Debug for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DecodeError")
.field("category", &self.category())
.field("line", &self.line())
.field("column", &self.column())
.finish()
}
}
impl std::error::Error for DecodeError {}
impl From<reqwest::Error> for TransportError {
fn from(mut error: reqwest::Error) -> Self {
if let Some(url) = error.url_mut() {
url.set_query(None);
}
Self(error)
}
}
pub struct TransportError(reqwest::Error);
impl TransportError {
pub fn is_timeout(&self) -> bool {
self.0.is_timeout()
}
pub fn is_connect(&self) -> bool {
self.0.is_connect()
}
}
impl fmt::Debug for TransportError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for TransportError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl std::error::Error for TransportError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.source()
}
}
#[derive(Clone)]
pub(crate) enum Deferred {
InvalidRequest(String),
Serialize(Arc<dyn std::error::Error + Send + Sync>),
}
impl From<Error> for Deferred {
fn from(error: Error) -> Self {
match error {
Error::Serialize(source) => Self::Serialize(source),
Error::InvalidRequest(message) => Self::InvalidRequest(message),
other => Self::InvalidRequest(other.to_string()),
}
}
}
impl From<Deferred> for Error {
fn from(error: Deferred) -> Self {
match error {
Deferred::InvalidRequest(message) => Self::InvalidRequest(message),
Deferred::Serialize(source) => Self::Serialize(source),
}
}
}