Skip to content

Zart is in active development — breaking API changes may occur despite our best efforts to keep contracts stable.

Error Types

Zart uses a typed error hierarchy that separates business decisions from framework failures. This document is a reference — for the conceptual walkthrough see Error Handling.

The three-way result of zart::step().

pub enum StepOutcome<T, E> {
Ok(T),
BusinessErr(E),
ZartErr(ZartStepError),
}
VariantMeaning
Ok(T)The step succeeded; T is the result.
BusinessErr(E)The step failed with its own error type. This is a business decision (e.g., PaymentError::InsufficientFunds).
ZartErr(ZartStepError)The framework could not complete the step (retry exhausted, timeout). This is not a business decision.

Used in zart::wait results and for control-flow signalling.

pub enum StepError {
Scheduled { step: String, next_execution: Option<DateTime<Utc>> },
StepExecuted { step: String },
Failed { step: String, reason: String },
RetryExhausted { step: String, attempts: usize },
Timeout { step: String, duration: Duration },
DeadlineExceeded { step: String },
Other(Box<dyn std::error::Error + Send + Sync>),
}
VariantWhen it appears
ScheduledControl-flow: the step has been registered but not yet executed. Propagated internally, never matched by users.
StepExecutedControl-flow: the step was executed and its result was persisted. Propagated internally, never matched by users.
FailedThe step’s run() returned Err(E). The reason is the error message.
RetryExhaustedAll retry attempts failed. attempts is the total count.
TimeoutThe step exceeded its configured timeout.
DeadlineExceededThe execution-level timeout was exceeded.
OtherAn opaque framework error.

The top-level error type returned by run() and by zart::require().

pub enum TaskError {
StepFailed { step: String, source: StepError },
MaxRetriesExhausted { max_retries: usize },
Timeout { duration: Duration },
Cancelled,
HandlerPanic(String),
}
VariantWhen it appears
StepFailedA step returned a non-Ok outcome that propagated to the top.
MaxRetriesExhaustedThe execution-level retry policy exhausted all attempts.
TimeoutThe execution-level timeout was exceeded.
CancelledThe execution was cancelled via cancel().
HandlerPanicThe handler body panicked. The String contains the panic message.

The framework-level error type inside StepOutcome::ZartErr.

pub enum ZartStepError {
RetryExhausted { step: String, attempts: usize, last_error: serde_json::Value },
TimedOut { step: String, duration: Duration },
DeadlineExceeded { step: String },
}
VariantWhen it fires
RetryExhaustedAll retry attempts failed with business errors. last_error holds the error from the final attempt.
TimedOutThe step’s deadline was exceeded. Under global scope, this means the total budget across all attempts ran out. Under per_attempt scope, this single attempt exceeded its budget. Timeout is always terminal — it never triggers a retry.
DeadlineExceededA wait_for_event call exceeded its event-wait deadline. Independent of step/execution deadlines.

When a step exhausts retries, the last error is preserved as JSON. You can deserialize it back into the step’s error type:

let err: ZartStepError = // ...
if let Some(Ok(last)) = err.last_error::<PaymentError>() {
match last {
PaymentError::InsufficientFunds { balance, needed } => { /* ... */ }
PaymentError::CardDeclined { reason } => { /* ... */ }
}
}

Passed to the on_failure handler.

pub enum ExecutionFailure {
StepFailed { step: String, raw: serde_json::Value },
ExecutionDeadlineExceeded,
RetriesExhausted { attempts: usize },
}
VariantWhen it appears
StepFailedA step returned a non-Ok outcome. raw is the JSON-serialized error — deserialize it into the step’s typed error for precise matching.
ExecutionDeadlineExceededThe execution-level timeout (set via #[zart_durable(..., timeout = "...")]) was exceeded. The deadline is calculated from when the execution was first scheduled.
RetriesExhaustedThe execution-level retry policy exhausted all attempts.

Errors from DurableScheduler operations (start, wait, cancel, offer_event).

pub enum SchedulerError {
// ... database and scheduling errors ...
ExecutionAlreadyExists(String, ExecutionStatus),
WaitTimedOut(String),
}
VariantWhen it appears
ExecutionAlreadyExists(id, status)start or start_for was called with an ID that already exists. status is the current status of the existing execution.
WaitTimedOut(id)wait or wait_with_timeout exceeded its timeout without the execution completing.