git-ents.gitmain
⌘K
foforge
error.rs216 lines · 8.2 KB · rusthistorycomment on this file
1//! `ents-effect`'s error type: everything that can prevent a run from
2//! reaching a recorded outcome.
3//!
4//! Mirrors `ents-receive`'s split: an [`Error`] means the run never reached
5//! a judgment at all (a store or object read failed, a sandbox never
6//! started, `curl`/`tar`/`docker`/`sprite` was not on `PATH`) — as opposed
7//! to a completed run reporting `pass` or `fail`
8//! (`effect.result-taxonomy`), which is a reached judgment, not an `Err`.
9//! Per `effect.result-taxonomy`, an [`Error`] here is exactly the "queue
10//! concern with bounded retry" case: this crate never turns one into a
11//! `Status::Error` result itself — only a caller that has exhausted its own
12//! retry bound does that, by calling [`crate::write_result`] with
13//! `Status::Error` explicitly.
14
15use std::path::PathBuf;
16
17use gix_hash::ObjectId;
18
19/// Everything that can prevent an `ents-effect` operation from reaching a
20/// result.
21#[derive(Debug, thiserror::Error)]
22pub enum Error {
23 /// The ref store's read or write half failed.
24 #[error("ref store operation failed: {0}")]
25 Refs(#[from] gix_ref_store::Error),
26
27 /// `receive` (the write-back path, `effect.results-writeback`) could
28 /// not reach an outcome.
29 ///
30 /// Boxed rather than `#[from]`-derived inline (see [`From`] below):
31 /// `ents_receive::Error` embeds `ents_gate::Error`, which is large
32 /// enough on its own to trip `clippy::result_large_err` for every
33 /// fallible function in this crate if stored inline.
34 #[error("receive failed: {0}")]
35 Receive(Box<ents_receive::Error>),
36
37 /// The query evaluator could not compute an effect's work set.
38 #[error("query evaluation failed: {0}")]
39 Eval(#[from] ents_query::EvalError),
40
41 /// An effect's `trigger` failed to parse as a `CommitQuery`
42 /// (`effect.validation`).
43 #[error("trigger does not parse as a CommitQuery: {0}")]
44 Trigger(#[from] ents_query::ParseError),
45
46 /// A typed-tree entity (a [`ents_model::Status`]) could not be
47 /// (de)serialized.
48 #[error("typed-tree operation failed: {0}")]
49 Facet(#[from] facet_git_tree::Error),
50
51 /// An object could not be read or decoded.
52 #[error("object {oid} could not be read: {detail}")]
53 Decode {
54 /// The undecodable object.
55 oid: ObjectId,
56 /// What failed, human-readable.
57 detail: String,
58 },
59
60 /// An object referenced by a tree or commit is missing from the object
61 /// store.
62 #[error("object {oid} is missing")]
63 Missing {
64 /// The missing object.
65 oid: ObjectId,
66 },
67
68 /// `refs/meta/toolchains/<name>` does not exist.
69 #[error("no toolchain named {0:?}")]
70 UnknownToolchain(String),
71
72 /// The named entity (a toolchain, looked up by something other than
73 /// its recipe — e.g. `ents-kiln`'s `toolchain::log`) does not exist.
74 /// Distinct from [`Error::UnknownToolchain`], which names the
75 /// resolve-a-recipe failure specifically; this variant is the general
76 /// "nothing at that name" case a caller renders as a not-found error.
77 #[error("not found: {what}")]
78 NotFound {
79 /// What was being looked up.
80 what: String,
81 },
82
83 /// A toolchain's `recipe` field did not parse as a `Recipe`
84 /// (`ents-kiln`'s own type; `effect.toolchains`: "a manifest's declared
85 /// components MUST be resolved during effect execution").
86 #[error("toolchain {name:?} has an unreadable recipe: {detail}")]
87 InvalidRecipe {
88 /// The toolchain's name.
89 name: String,
90 /// What failed, human-readable.
91 detail: String,
92 },
93
94 /// An effect's `toolchains` list named something that is not a valid
95 /// ref-path segment (`effect.validation`).
96 #[error("{0:?} is not a valid toolchain name")]
97 InvalidToolchainName(String),
98
99 /// A materialized tree entry was a git submodule (a commit entry).
100 /// Gitlinks retain nothing in this design (no embedded submodule
101 /// content), so materializing one is refused rather than silently
102 /// skipped.
103 #[error("cannot materialize {path:?}: it is a git submodule (gitlink)")]
104 Submodule {
105 /// The offending path, relative to the materialization root.
106 path: String,
107 },
108
109 /// A tree entry's filename, or a downloaded component's extracted file
110 /// name, was not valid UTF-8.
111 #[error("{0:?} is not valid UTF-8")]
112 NotUtf8(PathBuf),
113
114 /// A tree entry carried a name that could escape or collide inside the
115 /// materialization destination — `.`, `..`, a path separator, or a
116 /// duplicate of an earlier entry in the same tree. Checkout runs on
117 /// the *host*, before any sandbox exists, so a crafted (fsck-invalid
118 /// but storable) tree is refused before anything is written.
119 #[error("refusing to materialize tree entry {name:?}: {detail}")]
120 UnsafeEntry {
121 /// The offending entry name.
122 name: String,
123 /// Why it was refused, human-readable.
124 detail: String,
125 },
126
127 /// A path under a materialization destination could not be read or
128 /// written.
129 #[error("could not access {path}: {source}")]
130 Io {
131 /// The path being accessed.
132 path: PathBuf,
133 /// The underlying I/O error.
134 #[source]
135 source: std::io::Error,
136 },
137
138 /// A `Component` (`ents-kiln`'s own type) carried a `dest` that is not
139 /// empty or a single safe path segment, a `url` that is empty or
140 /// carries whitespace or a quote, or a `sha256` that is not 64 hex
141 /// characters — all of which feed filesystem paths, `curl`, or an
142 /// in-sandbox shell string downstream. Checked when a recipe is parsed
143 /// and re-checked before any fetch.
144 #[error("invalid toolchain component: {0}")]
145 InvalidComponent(String),
146
147 /// Running an external program (`docker`, `sprite`, `curl`, `tar`,
148 /// `sha256sum`/`shasum`) failed to start at all — the readiness probes
149 /// this phase ports from `pre-redo` exist precisely to turn this into
150 /// an actionable message instead of a raw "os error 2".
151 #[error("could not run `{program}`: {detail}")]
152 Spawn {
153 /// The program that could not be started.
154 program: String,
155 /// What failed, human-readable.
156 detail: String,
157 },
158
159 /// An external program ran but reported failure (nonzero exit, or
160 /// output this crate could not parse).
161 #[error("{program} failed: {detail}")]
162 Process {
163 /// The program that failed.
164 program: String,
165 /// What failed, human-readable.
166 detail: String,
167 },
168
169 /// A downloaded component's content did not match its recorded
170 /// sha256 — refused rather than extracted anyway.
171 #[error("{url}: expected sha256 {expected}, got {actual}")]
172 HashMismatch {
173 /// The component's source URL.
174 url: String,
175 /// The recorded sha256.
176 expected: String,
177 /// The sha256 actually computed.
178 actual: String,
179 },
180
181 /// The sandbox reported an infrastructure failure rather than a
182 /// completed run — never itself a `Status::Error` result
183 /// (`effect.result-taxonomy`); see this type's own doc.
184 #[error("the sandbox did not complete a run: {0}")]
185 Sandbox(String),
186
187 /// Writing a new object to the object store failed (building a
188 /// toolchain import's tree, `ents-kiln`'s `toolchain::import`).
189 #[error(transparent)]
190 ObjectWrite(#[from] gix_object::write::Error),
191
192 /// One commit's run stopped a [`crate::run_effect`] batch. Earlier
193 /// commits' outcomes were already durably recorded through `receive`
194 /// before this error, so a caller applying its own retry policy
195 /// (`effect.deployment-property`) knows exactly which commit to resume
196 /// from — and a plain retry of the whole batch re-runs only what is
197 /// still owed, since the work set subtracts recorded results
198 /// (`query.workset`).
199 #[error("run for {oid} stopped the batch: {source}")]
200 Run {
201 /// The commit whose run failed.
202 oid: ObjectId,
203 /// The underlying failure.
204 #[source]
205 source: Box<Error>,
206 },
207}
208
209impl From<ents_receive::Error> for Error {
210 fn from(source: ents_receive::Error) -> Self {
211 Self::Receive(Box::new(source))
212 }
213}
214
215/// The `Result` alias every fallible `ents-effect` operation returns.
216pub type Result<T> = std::result::Result<T, Error>;