use std::time::Duration;
use crate::task::TaskStatus;
pub type Result<T, E = Error> = std::result::Result<T, E>;
type Source = Box<dyn std::error::Error + Send + Sync>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("invalid request: {message}")]
InvalidRequest {
message: String,
#[source]
source: Option<Source>,
},
#[error("failed to decode response: {message}")]
Decode {
message: String,
#[source]
source: Option<Source>,
},
#[error("http transport error: {0}")]
Transport(#[from] reqwest_middleware::Error),
#[error("timed out after {timeout:?} waiting for the service")]
Timeout {
timeout: Duration,
},
#[error("service returned status {status}: {message}")]
Service {
status: u16,
message: String,
detail: Option<serde_json::Value>,
},
#[error("task {task_id} is not complete: status is {status:?}")]
TaskNotComplete {
task_id: String,
status: TaskStatus,
},
}
#[derive(serde::Deserialize)]
struct ErrorEnvelope {
error: String,
#[serde(default)]
detail: Option<serde_json::Value>,
}
impl Error {
pub fn status(&self) -> Option<u16> {
match self {
Self::Service { status, .. } => Some(*status),
Self::Transport(e) => e.status().map(|s| s.as_u16()),
_ => None,
}
}
pub(crate) fn invalid_request(message: impl Into<String>, source: impl Into<Source>) -> Self {
Self::InvalidRequest {
message: message.into(),
source: Some(source.into()),
}
}
pub(crate) fn invalid_message(message: impl Into<String>) -> Self {
Self::InvalidRequest {
message: message.into(),
source: None,
}
}
#[cfg(feature = "stream")]
pub(crate) fn decode(message: impl Into<String>, source: impl Into<Source>) -> Self {
Self::Decode {
message: message.into(),
source: Some(source.into()),
}
}
#[cfg(feature = "stream")]
pub(crate) fn decode_message(message: impl Into<String>) -> Self {
Self::Decode {
message: message.into(),
source: None,
}
}
pub(crate) fn service(status: u16, body: &str) -> Self {
match serde_json::from_str::<ErrorEnvelope>(body) {
Ok(env) => Self::Service {
status,
message: env.error,
detail: env.detail,
},
Err(_) => Self::Service {
status,
message: body.to_owned(),
detail: None,
},
}
}
}
impl From<url::ParseError> for Error {
fn from(e: url::ParseError) -> Self {
Self::invalid_request("invalid url", e)
}
}
impl From<reqwest::Error> for Error {
fn from(e: reqwest::Error) -> Self {
Self::Transport(e.into())
}
}