Skip to main content

vanta_core/
error.rs

1//! The error taxonomy: stable `VTA-<AREA>-<NNNN>` codes and process exit codes.
2//!
3//! See `docs/25-error-and-exit-code-catalog.md`. Every library function returns
4//! [`VtaResult`]; every error carries an [`Area`], a number, and a message.
5
6use std::error::Error;
7use std::fmt;
8
9/// Subsystem area of an error code. The string form (`CFG`, `RES`, …) is stable.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[non_exhaustive]
12pub enum Area {
13    /// Config / manifest (`vanta-config`).
14    Cfg,
15    /// Resolution / versioning (`vanta-resolve`).
16    Res,
17    /// Registry (`vanta-registry`).
18    Reg,
19    /// Provider / sandbox (`vanta-provider`).
20    Prov,
21    /// Network / download (`vanta-net`).
22    Net,
23    /// Verification / security (`vanta-security`).
24    Vrf,
25    /// Store / state / IO (`vanta-store`, `vanta-state`).
26    Store,
27    /// Install engine (`vanta-install`).
28    Inst,
29    /// Environment / activation (`vanta-env`).
30    Env,
31    /// Lockfile (`vanta-lock`).
32    Lock,
33    /// Platform (`vanta-platform`).
34    Sys,
35    /// Internal (a bug).
36    Int,
37}
38
39impl Area {
40    /// The stable string token used in a code (e.g. `"CFG"`).
41    pub fn as_str(self) -> &'static str {
42        match self {
43            Area::Cfg => "CFG",
44            Area::Res => "RES",
45            Area::Reg => "REG",
46            Area::Prov => "PROV",
47            Area::Net => "NET",
48            Area::Vrf => "VRF",
49            Area::Store => "STORE",
50            Area::Inst => "INST",
51            Area::Env => "ENV",
52            Area::Lock => "LOCK",
53            Area::Sys => "SYS",
54            Area::Int => "INT",
55        }
56    }
57
58    /// The default process exit code for this area (see `docs/25`).
59    pub fn exit(self) -> ExitCode {
60        match self {
61            Area::Cfg => ExitCode::Config,
62            Area::Res => ExitCode::Resolve,
63            Area::Reg | Area::Net => ExitCode::Network,
64            Area::Vrf => ExitCode::Verify,
65            Area::Store | Area::Inst | Area::Lock => ExitCode::Store,
66            Area::Prov | Area::Env | Area::Sys | Area::Int => ExitCode::Failure,
67        }
68    }
69}
70
71impl fmt::Display for Area {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        f.write_str(self.as_str())
74    }
75}
76
77/// Stable process exit codes (`docs/25-error-and-exit-code-catalog.md`).
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[non_exhaustive]
80pub enum ExitCode {
81    Ok = 0,
82    Failure = 1,
83    Usage = 2,
84    Config = 3,
85    Resolve = 4,
86    Network = 5,
87    Verify = 6,
88    Store = 7,
89    NotFound = 8,
90    Trust = 9,
91}
92
93impl ExitCode {
94    /// The numeric code, for `std::process::exit` / `process::ExitCode`.
95    pub fn as_i32(self) -> i32 {
96        self as i32
97    }
98}
99
100/// The structured error type returned across the workspace.
101#[derive(Debug)]
102pub struct VtaError {
103    /// The subsystem area.
104    pub area: Area,
105    /// The stable per-area number (rendered zero-padded to four digits).
106    pub number: u16,
107    /// A human-readable, actionable message.
108    pub message: String,
109    /// An optional underlying cause.
110    pub source: Option<Box<dyn Error + Send + Sync>>,
111}
112
113impl VtaError {
114    /// Construct an error with an area, a stable number, and a message.
115    pub fn new(area: Area, number: u16, message: impl Into<String>) -> Self {
116        VtaError {
117            area,
118            number,
119            message: message.into(),
120            source: None,
121        }
122    }
123
124    /// Attach an underlying cause.
125    pub fn with_source(mut self, source: impl Error + Send + Sync + 'static) -> Self {
126        self.source = Some(Box::new(source));
127        self
128    }
129
130    /// The stable code string, e.g. `"VTA-CFG-0007"`.
131    pub fn code(&self) -> String {
132        format!("VTA-{}-{:04}", self.area.as_str(), self.number)
133    }
134
135    /// The process exit code this error maps to.
136    pub fn exit(&self) -> ExitCode {
137        self.area.exit()
138    }
139}
140
141impl fmt::Display for VtaError {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        write!(f, "error[{}]: {}", self.code(), self.message)
144    }
145}
146
147impl Error for VtaError {
148    fn source(&self) -> Option<&(dyn Error + 'static)> {
149        self.source
150            .as_ref()
151            .map(|s| s.as_ref() as &(dyn Error + 'static))
152    }
153}
154
155/// The workspace result alias.
156pub type VtaResult<T> = Result<T, VtaError>;