Skip to main content

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}