use std::error::Error as StdError;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, BosonError>;
#[derive(Debug, Error)]
pub enum BosonError {
#[error("task not found: {0}")]
TaskNotFound(String),
#[error("job not found: {0}")]
JobNotFound(String),
#[error("run not found: {0}")]
RunNotFound(String),
#[error("task config not found: {0}")]
TaskConfigNotFound(String),
#[error("parameter error: {0}")]
ParamError(String),
#[error("signature mismatch: job expects {expected}, task has {actual}")]
SignatureMismatch {
expected: String,
actual: String,
},
#[error("invalid config: {0}")]
InvalidConfig(String),
#[error("backend error: {message}")]
Backend {
message: String,
#[source]
source: Option<Box<dyn StdError + Send + Sync>>,
},
#[error("internal error: {message}")]
Internal {
message: String,
#[source]
source: Option<Box<dyn StdError + Send + Sync>>,
},
#[error("enqueue rate limited for task: {0}")]
RateLimited(String),
#[error("unknown queue backend: {0}")]
UnknownBackend(String),
}
impl BosonError {
#[must_use]
pub fn backend(message: impl Into<String>) -> Self {
Self::Backend {
message: message.into(),
source: None,
}
}
#[must_use]
pub fn backend_source(
message: impl Into<String>,
source: impl StdError + Send + Sync + 'static,
) -> Self {
Self::Backend {
message: message.into(),
source: Some(Box::new(source)),
}
}
#[must_use]
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
source: None,
}
}
#[must_use]
pub fn internal_source(
message: impl Into<String>,
source: impl StdError + Send + Sync + 'static,
) -> Self {
Self::Internal {
message: message.into(),
source: Some(Box::new(source)),
}
}
#[must_use]
pub fn backend_message(&self) -> Option<&str> {
match self {
Self::Backend { message, .. } => Some(message.as_str()),
_ => None,
}
}
#[must_use]
pub fn is_backend_unique_violation(&self) -> bool {
self.backend_message().is_some_and(|msg| {
msg.contains("UNIQUE") || msg.contains("unique") || msg.contains("Duplicate")
})
}
}
#[derive(Debug, Error)]
pub enum IdentityError {
#[error("invalid actor: {0}")]
InvalidActor(String),
}
impl From<serde_json::Error> for BosonError {
fn from(err: serde_json::Error) -> Self {
Self::ParamError(err.to_string())
}
}
#[cfg(test)]
mod tests {
use std::error::Error as StdError;
use super::BosonError;
#[derive(Debug, thiserror::Error)]
#[error("dial failed")]
struct DialFailed;
#[test]
fn backend_source_is_reachable_via_std_error() {
let err = BosonError::backend_source("nats connect", DialFailed);
assert!(err
.backend_message()
.is_some_and(|m| m.contains("nats connect")));
assert!(err
.source()
.is_some_and(|s| s.to_string().contains("dial failed")));
}
#[test]
fn unique_violation_helper_matches_message() {
let err = BosonError::backend("sql backend: UNIQUE constraint failed");
assert!(err.is_backend_unique_violation());
assert!(!BosonError::backend("timeout").is_backend_unique_violation());
}
}