Skip to main content

foundry_cli/
exit_code.rs

1//! Canonical process exit codes for the Foundry agent contract.
2//!
3//! See [`docs/agents/exit-codes.md`](../../../docs/agents/exit-codes.md) for the
4//! contract these codes implement.
5
6use std::fmt;
7
8/// Canonical exit codes emitted by Foundry binaries.
9///
10/// Only the variants below are guaranteed by the agent contract. Commands MAY
11/// document additional, command-specific codes via
12/// [`ExitCodeInfo`](crate::introspect::ExitCodeInfo); those codes MUST NOT
13/// collide with this global table.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15#[repr(i32)]
16pub enum ExitCode {
17    /// Command completed successfully.
18    Success = 0,
19    /// Unclassified failure.
20    GenericError = 1,
21    /// Argument parse error, missing subcommand, or invalid flag combination.
22    Usage = 2,
23    /// Foundry config invalid or missing required value.
24    Config = 3,
25    /// Compilation, linking, or artifact generation failed.
26    Build = 4,
27    /// Tests ran but at least one failed (distinct from a build/setup failure).
28    TestFailure = 5,
29    /// RPC, HTTP, or chain-connectivity failure.
30    Network = 6,
31    /// Authentication, authorization, or wallet/key-related failure.
32    User = 7,
33    /// Command terminated by `SIGINT` / `SIGTERM`.
34    Interrupted = 8,
35}
36
37impl ExitCode {
38    /// Returns the numeric process exit code.
39    pub const fn to_i32(self) -> i32 {
40        self as i32
41    }
42
43    /// Returns the stable, human-readable variant name.
44    pub const fn name(self) -> &'static str {
45        match self {
46            Self::Success => "Success",
47            Self::GenericError => "GenericError",
48            Self::Usage => "Usage",
49            Self::Config => "Config",
50            Self::Build => "Build",
51            Self::TestFailure => "TestFailure",
52            Self::Network => "Network",
53            Self::User => "User",
54            Self::Interrupted => "Interrupted",
55        }
56    }
57}
58
59impl fmt::Display for ExitCode {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(f, "{} ({})", self.name(), self.to_i32())
62    }
63}
64
65impl From<ExitCode> for i32 {
66    fn from(code: ExitCode) -> Self {
67        code.to_i32()
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn numeric_codes_match_spec() {
77        assert_eq!(ExitCode::Success.to_i32(), 0);
78        assert_eq!(ExitCode::GenericError.to_i32(), 1);
79        assert_eq!(ExitCode::Usage.to_i32(), 2);
80        assert_eq!(ExitCode::Config.to_i32(), 3);
81        assert_eq!(ExitCode::Build.to_i32(), 4);
82        assert_eq!(ExitCode::TestFailure.to_i32(), 5);
83        assert_eq!(ExitCode::Network.to_i32(), 6);
84        assert_eq!(ExitCode::User.to_i32(), 7);
85        assert_eq!(ExitCode::Interrupted.to_i32(), 8);
86    }
87}