pub enum KopiaError {
OutputSink {
args: String,
source: Error,
},
Spawn {
binary: String,
source: Error,
},
NonZeroExit {
args: String,
code: Option<i32>,
class: KopiaErrorClass,
stderr_tail: String,
},
Json {
context: String,
source: Error,
},
StdinProducerFailed {
detail: String,
stderr_tail: String,
},
StdinProducerWroteNothing {
args: String,
stderr_tail: String,
},
EmptyOutput {
context: String,
stderr_tail: String,
},
Timeout {
args: String,
seconds: u64,
},
}Expand description
Errors produced while invoking kopia or parsing its --json output.
Each variant’s Display is actionable — it names the operation, the failure,
and (for non-zero exits) the error class plus the stderr tail — so it can be
dropped straight into a status.failure block (ADR §4.10):
use kopiur_kopia::{KopiaError, KopiaErrorClass};
let err = KopiaError::NonZeroExit {
args: "snapshot create".into(),
code: Some(1),
class: KopiaErrorClass::Locked,
stderr_tail: "repository is locked by another process".into(),
};
assert_eq!(
err.to_string(),
"kopia `snapshot create` failed (exit code 1, class Locked): \
repository is locked by another process",
);
// The class drives the retry decision; the stderr tail is recoverable.
assert_eq!(err.class(), KopiaErrorClass::Locked);
assert!(err.class().is_retryable());
assert_eq!(err.stderr_tail(), Some("repository is locked by another process"));
// A timeout names the args and elapsed seconds, and maps to a retryable class.
let to = KopiaError::Timeout { args: "maintenance run --full".into(), seconds: 3600 };
assert_eq!(to.to_string(), "kopia `maintenance run --full` timed out after 3600s");
assert_eq!(to.class(), KopiaErrorClass::RepositoryUnavailable);Variants§
OutputSink
Writing kopia’s OUTPUT to the consumer failed. kopia spawned and ran
fine; the sink — a streamExec restore’s exec stdin — broke.
Separate from KopiaError::Spawn because it used to BE Spawn, and
that misattribution actively cost debugging time: a streamExec restore
whose consumer pipe broke reported “failed to spawn kopia binary kopia:
broken pipe”, sending the reader after a missing binary that was in fact
running. The three fallible halves of the streaming copy — the sink write,
the stderr read, and the child wait — are not interchangeable and must not
share one error.
Fields
Spawn
The kopia binary could not be spawned at all (missing binary, not executable, fork failure). Carries the OS error.
NonZeroExit
kopia ran but exited with a non-zero status. Carries everything needed
to build a status.failure block.
Display renders a clean exit code (no Some(1) Debug leak) and a
humanized stderr extract (progress noise dropped, volatile temp-path
fragments stripped) — this is what reaches Warning Events,
status.failure.message, and logs. The full raw tail is preserved in the
stderr_tail field (surfaced via status.failure.stderrTail) for
debugging; read it with stderr_tail.
Fields
args: StringThe subcommand + args that were run (for diagnostics; secrets are passed via env, never argv).
class: KopiaErrorClassBest-effort error classification from stderr.
stderr_tail: StringThe last STDERR_TAIL_LINES lines of stderr, joined by newlines.
Json
kopia exited 0 (or produced output) but the JSON could not be parsed into the expected type — usually a kopia version skew.
Fields
StdinProducerFailed
The process feeding kopia’s stdin did not finish successfully, so the snapshot was deliberately aborted before kopia wrote any manifest.
Distinct from KopiaError::NonZeroExit: kopia itself was fine — it was
killed on purpose. Surfacing that difference is what tells an operator
“your dump command failed” rather than “the backup tool failed”.
Fields
StdinProducerWroteNothing
The stdin producer exited successfully having written ZERO bytes, so the snapshot was aborted before any manifest was written.
Deliberately a failure rather than an empty snapshot. A producer’s exit
status cannot be trusted to catch this on its own: pg_dump exits 0
against an instance it cannot see any databases in, mysqldump exits 0
when its credentials grant nothing, and a sh -c 'a | b' pipeline reports
only b’s status. Committing the result would put a zero-byte “restore
point” into the repository that retention keeps and a restore would
cheerfully write over a live database with.
Fields
EmptyOutput
We expected a JSON object/array on stdout but found none (kopia printed only progress / nothing) even though it exited 0.
stderr_tail is what makes this diagnosable. Without it the variant
carried neither a reason nor an exit code, so a kopia that exited
cleanly and explained itself on stderr was indistinguishable from a
kopia that said nothing at all — the whole of #351.
Fields
Timeout
The operation exceeded its configured timeout and was killed.
Implementations§
Source§impl KopiaError
impl KopiaError
Sourcepub fn class(&self) -> KopiaErrorClass
pub fn class(&self) -> KopiaErrorClass
The error class for this error, for retry decisions and metrics. Spawn, JSON-parse, empty-output, and timeout errors map to a fixed class; non-zero exits carry their own classification.
Sourcepub fn stderr_tail(&self) -> Option<&str>
pub fn stderr_tail(&self) -> Option<&str>
The trailing stderr lines, if this error captured any.
Trait Implementations§
Source§impl Debug for KopiaError
impl Debug for KopiaError
Source§impl Display for KopiaError
impl Display for KopiaError
Source§impl Error for KopiaError
impl Error for KopiaError
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()