1use serde::{Deserialize, Serialize};
11
12use crate::event::EventKind;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Carrier {
18 Event(EventKind),
19 Loadtime,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
25pub enum AxCode {
26 PathNotFound,
28 ToolUnknown,
29 ToolUnavailable,
30 InvalidArgs,
31 OutsideWriteDomain,
32 VersionConflict,
33 GateDenied,
34 BudgetExhausted,
35 Timeout,
36 Provider,
37 EvidenceMissing,
38 LoopSuspected,
39 LocatorInvalid,
40 SandboxDenied,
41 Busy,
48 DraftStale,
54 GoalConflict,
55 TaintedAction,
56 RepairBusy,
57 DelegationDepth,
58 ApprovalPending,
60 ApprovalDenied,
61 CrossBuildingDenied,
62 DigestSuspect,
63 CredentialMissing,
64 ModelUnchosen,
70 ConfigInvalid,
71 CasCorrupt,
72 StorageFatal,
73 WorktreeBusy,
74 BrowserUnavailable,
75 EndpointDialectUnsupported,
76 WireMismatch,
77 LogVersionUnsupported,
78 LedgerHeld,
81 HistoryUnproven,
86 SecretEgress,
88 DiscardIrreversible,
89 BackpressureShed,
93 ToolOutcomeUnknown,
95 PlanMissing,
99 ProviderAccountsExhausted,
104}
105
106impl AxCode {
107 pub const ALL: [AxCode; 42] = [
110 AxCode::PathNotFound,
111 AxCode::ToolUnknown,
112 AxCode::ToolUnavailable,
113 AxCode::InvalidArgs,
114 AxCode::OutsideWriteDomain,
115 AxCode::VersionConflict,
116 AxCode::GateDenied,
117 AxCode::BudgetExhausted,
118 AxCode::Timeout,
119 AxCode::Provider,
120 AxCode::EvidenceMissing,
121 AxCode::LoopSuspected,
122 AxCode::LocatorInvalid,
123 AxCode::SandboxDenied,
124 AxCode::Busy,
125 AxCode::DraftStale,
126 AxCode::GoalConflict,
127 AxCode::TaintedAction,
128 AxCode::RepairBusy,
129 AxCode::DelegationDepth,
130 AxCode::ApprovalPending,
131 AxCode::ApprovalDenied,
132 AxCode::CrossBuildingDenied,
133 AxCode::DigestSuspect,
134 AxCode::CredentialMissing,
135 AxCode::ModelUnchosen,
136 AxCode::ConfigInvalid,
137 AxCode::CasCorrupt,
138 AxCode::StorageFatal,
139 AxCode::WorktreeBusy,
140 AxCode::BrowserUnavailable,
141 AxCode::EndpointDialectUnsupported,
142 AxCode::WireMismatch,
143 AxCode::LogVersionUnsupported,
144 AxCode::LedgerHeld,
145 AxCode::HistoryUnproven,
146 AxCode::SecretEgress,
147 AxCode::DiscardIrreversible,
148 AxCode::BackpressureShed,
149 AxCode::ToolOutcomeUnknown,
150 AxCode::PlanMissing,
151 AxCode::ProviderAccountsExhausted,
152 ];
153
154 pub fn as_str(&self) -> &'static str {
156 match self {
157 AxCode::PathNotFound => "E_PATH_NOT_FOUND",
158 AxCode::ToolUnknown => "E_TOOL_UNKNOWN",
159 AxCode::ToolUnavailable => "E_TOOL_UNAVAILABLE",
160 AxCode::InvalidArgs => "E_INVALID_ARGS",
161 AxCode::OutsideWriteDomain => "E_OUTSIDE_WRITE_DOMAIN",
162 AxCode::VersionConflict => "E_VERSION_CONFLICT",
163 AxCode::GateDenied => "E_GATE_DENIED",
164 AxCode::BudgetExhausted => "E_BUDGET_EXHAUSTED",
165 AxCode::Timeout => "E_TIMEOUT",
166 AxCode::Provider => "E_PROVIDER",
167 AxCode::EvidenceMissing => "E_EVIDENCE_MISSING",
168 AxCode::LoopSuspected => "E_LOOP_SUSPECTED",
169 AxCode::LocatorInvalid => "E_LOCATOR_INVALID",
170 AxCode::SandboxDenied => "E_SANDBOX_DENIED",
171 AxCode::Busy => "E_BUSY",
172 AxCode::DraftStale => "E_DRAFT_STALE",
173 AxCode::GoalConflict => "E_GOAL_CONFLICT",
174 AxCode::TaintedAction => "E_TAINTED_ACTION",
175 AxCode::RepairBusy => "E_REPAIR_BUSY",
176 AxCode::DelegationDepth => "E_DELEGATION_DEPTH",
177 AxCode::ApprovalPending => "E_APPROVAL_PENDING",
178 AxCode::ApprovalDenied => "E_APPROVAL_DENIED",
179 AxCode::CrossBuildingDenied => "E_CROSS_BUILDING_DENIED",
180 AxCode::DigestSuspect => "E_DIGEST_SUSPECT",
181 AxCode::CredentialMissing => "E_CREDENTIAL_MISSING",
182 AxCode::ModelUnchosen => "E_MODEL_UNCHOSEN",
183 AxCode::ConfigInvalid => "E_CONFIG_INVALID",
184 AxCode::CasCorrupt => "E_CAS_CORRUPT",
185 AxCode::StorageFatal => "E_STORAGE_FATAL",
186 AxCode::WorktreeBusy => "E_WORKTREE_BUSY",
187 AxCode::BrowserUnavailable => "E_BROWSER_UNAVAILABLE",
188 AxCode::EndpointDialectUnsupported => "E_ENDPOINT_DIALECT_UNSUPPORTED",
189 AxCode::WireMismatch => "E_WIRE_MISMATCH",
190 AxCode::LogVersionUnsupported => "E_LOG_VERSION_UNSUPPORTED",
191 AxCode::LedgerHeld => "E_LEDGER_HELD",
192 AxCode::HistoryUnproven => "E_HISTORY_UNPROVEN",
193 AxCode::SecretEgress => "E_SECRET_EGRESS",
194 AxCode::BackpressureShed => "E_BACKPRESSURE_SHED",
195 AxCode::DiscardIrreversible => "E_DISCARD_IRREVERSIBLE",
196 AxCode::ToolOutcomeUnknown => "E_TOOL_OUTCOME_UNKNOWN",
197 AxCode::PlanMissing => "E_PLAN_MISSING",
198 AxCode::ProviderAccountsExhausted => "E_PROVIDER_ACCOUNTS_EXHAUSTED",
199 }
200 }
201
202 pub fn parse(raw: &str) -> Option<AxCode> {
204 AxCode::ALL.into_iter().find(|code| code.as_str() == raw)
205 }
206
207 pub fn carrier(&self) -> Carrier {
214 match self {
215 AxCode::GateDenied
216 | AxCode::OutsideWriteDomain
217 | AxCode::TaintedAction
218 | AxCode::CrossBuildingDenied
219 | AxCode::DiscardIrreversible
220 | AxCode::SecretEgress
221 | AxCode::DelegationDepth => Carrier::Event(EventKind::GateDenied),
222 AxCode::ApprovalPending => Carrier::Event(EventKind::ApprovalRequested),
223 AxCode::ApprovalDenied => Carrier::Event(EventKind::ApprovalResolved),
224 AxCode::BudgetExhausted => Carrier::Event(EventKind::BudgetLimit),
225 AxCode::LoopSuspected => Carrier::Event(EventKind::WatchdogFired),
226 AxCode::Provider | AxCode::ProviderAccountsExhausted => {
227 Carrier::Event(EventKind::ProviderDegraded)
228 }
229 AxCode::EndpointDialectUnsupported => Carrier::Event(EventKind::EndpointLost),
230 AxCode::ConfigInvalid
231 | AxCode::CasCorrupt
232 | AxCode::StorageFatal
233 | AxCode::WireMismatch
234 | AxCode::LogVersionUnsupported
235 | AxCode::LedgerHeld
236 | AxCode::HistoryUnproven => Carrier::Loadtime,
237 AxCode::PathNotFound
238 | AxCode::ToolUnknown
239 | AxCode::ToolUnavailable
240 | AxCode::InvalidArgs
241 | AxCode::VersionConflict
242 | AxCode::Timeout
243 | AxCode::EvidenceMissing
244 | AxCode::LocatorInvalid
245 | AxCode::SandboxDenied
246 | AxCode::Busy
247 | AxCode::DraftStale
248 | AxCode::GoalConflict
249 | AxCode::RepairBusy
250 | AxCode::DigestSuspect
251 | AxCode::CredentialMissing
252 | AxCode::ModelUnchosen
253 | AxCode::WorktreeBusy
254 | AxCode::BrowserUnavailable
255 | AxCode::ToolOutcomeUnknown
256 | AxCode::BackpressureShed
257 | AxCode::PlanMissing => Carrier::Event(EventKind::ToolResult),
258 }
259 }
260}
261
262impl std::fmt::Display for AxCode {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 f.write_str(self.as_str())
265 }
266}
267
268impl Serialize for AxCode {
269 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
270 serializer.serialize_str(self.as_str())
271 }
272}
273
274impl<'de> Deserialize<'de> for AxCode {
275 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
276 let raw = String::deserialize(deserializer)?;
277 AxCode::parse(&raw)
278 .ok_or_else(|| serde::de::Error::custom(format_args!("unknown AxCode `{raw}`")))
279 }
280}
281
282#[cfg(test)]
283#[allow(
284 clippy::unwrap_used,
285 clippy::expect_used,
286 clippy::panic,
287 clippy::indexing_slicing,
288 reason = "test code"
289)]
290mod tests {
291 use super::*;
292 use std::collections::BTreeSet;
293
294 #[test]
295 fn axcode_is_the_declared_length_and_spelling_is_bijective() {
296 assert_eq!(AxCode::ALL.len(), 42);
300 let spellings: BTreeSet<&str> = AxCode::ALL.iter().map(AxCode::as_str).collect();
301 assert_eq!(spellings.len(), 42);
302 for s in &spellings {
303 assert!(s.starts_with("E_"));
304 }
305 }
306
307 #[test]
308 fn axcode_parse_roundtrips_every_variant() {
309 for code in AxCode::ALL {
310 assert_eq!(AxCode::parse(code.as_str()), Some(code));
311 }
312 assert_eq!(AxCode::parse("E_NO_SUCH_CODE"), None);
313 }
314
315 #[test]
316 fn axcode_serde_uses_the_wire_spelling() {
317 let json = serde_json::to_string(&AxCode::PathNotFound).unwrap();
318 assert_eq!(json, "\"E_PATH_NOT_FOUND\"");
319 let back: AxCode = serde_json::from_str("\"E_LOCATOR_INVALID\"").unwrap();
320 assert_eq!(back, AxCode::LocatorInvalid);
321 assert!(serde_json::from_str::<AxCode>("\"E_BOGUS\"").is_err());
322 }
323}