use std::borrow::Cow;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Error {
Backend(BackendError),
Malformed(String),
ConflictingWorkflow {
workflow_id: String,
detail: String,
},
ConcurrentRecv {
workflow_id: String,
topic: Option<String>,
},
InvalidInput {
field: Cow<'static, str>,
detail: String,
},
QueueDeduplicated {
workflow_id: String,
queue_name: String,
deduplication_id: String,
},
WorkflowCancelled {
workflow_id: String,
},
UnexpectedStep {
workflow_id: String,
step_id: i32,
expected: String,
recorded: String,
},
StepAlreadyRecorded {
workflow_id: String,
step_id: i32,
},
NoForkPoint {
workflow_ids: Vec<String>,
step_name: Option<String>,
},
NonExistentWorkflow {
workflow_ids: Vec<String>,
},
MaxRecoveryAttemptsExceeded {
workflow_id: String,
limit: i64,
},
AlreadyRegistered {
kind: Cow<'static, str>,
name: String,
},
NotRegistered {
kind: Cow<'static, str>,
name: String,
},
RegisteredByAnother {
kind: Cow<'static, str>,
name: String,
holder: String,
claimant: Option<String>,
},
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Backend(e) => write!(f, "system database error: {e}"),
Error::Malformed(m) => write!(f, "unexpected value in the system database: {m}"),
Error::ConflictingWorkflow {
workflow_id,
detail,
} => write!(f, "workflow {workflow_id} already exists: {detail}"),
Error::ConcurrentRecv { workflow_id, topic } => match topic {
Some(topic) => write!(
f,
"workflow {workflow_id} is already receiving on topic {topic}"
),
None => write!(f, "workflow {workflow_id} is already receiving"),
},
Error::InvalidInput { field, detail } => write!(f, "invalid {field}: {detail}"),
Error::QueueDeduplicated {
workflow_id,
queue_name,
deduplication_id,
} => write!(
f,
"workflow {workflow_id} (queue: {queue_name}, \
deduplication id: {deduplication_id}) is already enqueued"
),
Error::WorkflowCancelled { workflow_id } => {
write!(f, "workflow {workflow_id} is cancelled")
}
Error::UnexpectedStep {
workflow_id,
step_id,
expected,
recorded,
} => write!(
f,
"workflow {workflow_id} step {step_id} was recorded as {recorded:?}, \
but {expected:?} was expected"
),
Error::StepAlreadyRecorded {
workflow_id,
step_id,
} => write!(
f,
"workflow {workflow_id} step {step_id} was already recorded by another execution"
),
Error::NoForkPoint {
workflow_ids,
step_name,
} => match step_name {
Some(name) => write!(
f,
"no step named {name} in workflows {}",
workflow_ids.join(", ")
),
None => write!(f, "no steps in workflows {}", workflow_ids.join(", ")),
},
Error::NonExistentWorkflow { workflow_ids } => {
write!(f, "no such workflow: {}", workflow_ids.join(", "))
}
Error::MaxRecoveryAttemptsExceeded { workflow_id, limit } => write!(
f,
"workflow {workflow_id} exceeded {limit} recovery attempts"
),
Error::AlreadyRegistered { kind, name } => {
write!(f, "{kind} {name:?} is already registered")
}
Error::NotRegistered { kind, name } => {
write!(f, "{kind} {name:?} is not registered")
}
Error::RegisteredByAnother {
kind,
name,
holder,
claimant,
} => {
let lower = kind.to_lowercase();
write!(
f,
"{kind} {name:?} is already registered by application {holder:?} in this \
system database, and {lower} names must be unique across the applications \
sharing one"
)?;
if let Some(claimant) = claimant {
write!(
f,
": either give {claimant:?} a different {lower} name, or, if \
{holder:?} was renamed to {claimant:?}, move its rows first"
)?;
}
Ok(())
}
}
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct BackendError {
pub message: String,
pub sqlstate: Option<String>,
pub kind: BackendErrorKind,
}
impl std::fmt::Display for BackendError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.sqlstate {
Some(code) => write!(f, "{} ({code})", self.message),
None => f.write_str(&self.message),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum BackendErrorKind {
Connection,
Transient,
Permanent,
}