strop_containers/error.rs
1//! Typed failures for every container conversation. A caller must always be
2//! able to tell "engine gone" from "container gone" from "this build of
3//! strop refuses that operation" — those have different UI and retry rules.
4
5/// Why a container operation did not produce a result.
6#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
7pub enum ContainerError {
8 /// The local engine cannot be reached: the CLI is missing, the daemon
9 /// is down, or the probe timed out. Retrying later is meaningful.
10 #[error("container engine unavailable: {detail}")]
11 EngineUnavailable { detail: String },
12
13 /// The name or id resolved to nothing on the engine.
14 #[error("no such container: {name}")]
15 NoSuchContainer { name: String },
16
17 /// The container exists but is not running; attach semantics require a
18 /// running container even though the engine could read stopped ones.
19 #[error("container is not running: {id}")]
20 NotRunning { id: String },
21
22 /// A previously resolved identity no longer matches the engine: the
23 /// name now maps to a different container, or the same container id
24 /// restarted (a new `StartedAt` incarnation). Old reads, caches and
25 /// completions must not attach to the new incarnation.
26 #[error(
27 "stale container identity for {name}: held incarnation {expected}, engine reports {found}"
28 )]
29 StaleIdentity {
30 name: String,
31 expected: String,
32 found: String,
33 },
34
35 /// A path inside the container does not exist.
36 #[error("no such path in container {id}: {path}")]
37 NoSuchPath { id: String, path: String },
38
39 /// The operation is outside this crate's read-only capability
40 /// boundary (writes, arbitrary exec, reading a directory as a file).
41 /// This is a policy answer, not a failure to be retried.
42 #[error("capability refused: {what}")]
43 CapabilityRefused { what: String },
44
45 /// A user-supplied name/id could inject CLI options or otherwise
46 /// cannot be a Docker name or id prefix. Refused before the engine
47 /// ever sees it.
48 #[error("unsafe container name refused: {name:?}")]
49 PoisonedName { name: String },
50
51 /// Bounded capture overflowed: the engine had more to say than the
52 /// retention limit. A partial listing is never presented as complete.
53 #[error("bounded output exceeded: {what}")]
54 OutputTooLarge { what: String },
55
56 /// The engine answered but not in the shape its CLI contract
57 /// promises (non-canonical id, malformed JSON, malformed tar).
58 #[error("malformed engine response: {detail}")]
59 Protocol { detail: String },
60
61 /// The caller's cancellation token fired.
62 #[error("operation cancelled")]
63 Cancelled,
64
65 /// Anything else the supervised subprocess reported: non-zero exit,
66 /// deadline, pipe failure. The detail carries a bounded stderr tail.
67 #[error("engine I/O: {detail}")]
68 Io { detail: String },
69}