Skip to main content

git_policy_cli/
candidate_error.rs

1//! What:
2//!  The one failure type of the candidate layer,
3//!  with a closed list of causes.
4//! Why:
5//!  Every candidate failure stops policy evaluation (a file that could not be read
6//!      was not checked),
7//!  and callers branch on the cause without parsing message text.
8//!
9//! In TS you'd write (pseudocode):
10//! ```ts
11//! class CandidateError extends Error { readonly failure: CandidateFailure }
12//! ```
13
14/// What:
15///  Why candidate listing or reading failed.
16///       `#[derive(...)]` generates copying,
17///  debug printing and `==`.
18/// Why:
19///   The policy engine maps causes to engine-failure codes,
20///  and tests name the
21///       exact cause instead of matching diagnostic wording.
22///
23/// In TS you'd write (pseudocode):
24/// ```ts
25/// type CandidateFailure = 'git-not-started' | 'git-failed' | 'listing-malformed' | ...;
26/// ```
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum CandidateFailure {
29    /// The real Git program could not be started at all.
30    GitNotStarted,
31    /// A listing command ran and exited unsuccessfully.
32    GitFailed,
33    /// Listing output is not the NUL-delimited record format Git 2.56.0 prints.
34    ListingMalformed,
35    /// A listed entry has a file mode no candidate can carry.
36    UnsupportedMode,
37    /// A listed entry has a change status this layer does not interpret.
38    UnsupportedStatus,
39    /// The index holds a conflicted (unmerged) entry.
40    UnmergedPath,
41    /// The object reader ended,
42    ///  or was closed after an earlier failure.
43    ReaderEnded,
44    /// An object reply does not follow the `cat-file --batch` format.
45    ReplyMalformed,
46    /// An object reply stopped before its declared size or terminator.
47    ReplyTruncated,
48    /// An object reply names a different object than the one requested.
49    ReplyMismatched,
50    /// Git reported the requested object as missing.
51    ObjectMissing,
52    /// The requested object exists with a type the caller cannot use.
53    ObjectKindUnexpected,
54    /// A candidate belongs to a version that was invalidated.
55    StaleCandidate,
56    /// The private index copy a prediction stages into could not be created or copied.
57    PrivateIndexUnavailable,
58}
59
60/// What:
61///  One candidate failure:
62///  its cause and a complete explanation.
63///       `String` is a heap-allocated,
64///  growable UTF-8 buffer this struct owns.
65///       Sibling the reader might expect:
66///  `&str`,
67///  a borrowed view that owns nothing.
68/// Why:
69///   The error outlives the formatted text built at the failure site,
70///  so a
71///       borrowed `&str` would dangle.
72///  Messages never contain file content or
73///       pathnames:
74///  a pathname can itself be a forbidden string.
75///
76/// In TS you'd write (pseudocode):
77/// ```ts
78/// class CandidateError extends Error { failure: CandidateFailure; message: string }
79/// ```
80#[derive(Clone, Debug, Eq, PartialEq)]
81pub struct CandidateError {
82    /// Closed cause callers branch on.
83    pub failure: CandidateFailure,
84    /// Complete diagnostic naming the Git operation and what to check.
85    pub message: String,
86}
87
88/// What:
89///  `impl CandidateError { ... }` attaches functions to the struct,
90///  like class statics.
91/// Why:
92///   Failure sites keep their condition next to its user-facing explanation.
93///
94/// In TS you'd write (pseudocode):
95/// ```ts
96/// new CandidateError(failure, message);
97/// ```
98impl CandidateError {
99    /// What:
100    ///  Build an error from a cause and borrowed text.
101    ///  `&str` lends the caller's
102    ///       bytes;
103    ///  `String::from` copies them into storage the error owns.
104    /// Why:
105    ///   Messages are usually `format!` temporaries that end with the caller's statement.
106    ///
107    /// In TS you'd write (pseudocode):
108    /// ```ts
109    /// constructor(failure: CandidateFailure, message: string) { super(message); }
110    /// ```
111    pub fn new(failure: CandidateFailure, message: &str) -> CandidateError {
112        // What: `return CandidateError { ... };` builds the struct and hands it back.
113        // Why:  Rust never copies a borrowed string into an owning field implicitly.
114        //
115        // In TS you'd write (pseudocode):
116        // ```ts
117        // return { failure, message };
118        // ```
119        return CandidateError {
120            failure,
121            message: String::from(message),
122        };
123    }
124}
125
126/// What:
127///  `impl std::fmt::Display for CandidateError` supplies Rust's "print me" interface.
128///       `::` walks module paths,
129///  like `std.fmt.Display` would in TS.
130/// Why:
131///   The executable writes the message with ordinary formatting,
132///  not field access.
133///
134/// In TS you'd write (pseudocode):
135/// ```ts
136/// toString(): string { return this.message; }
137/// ```
138impl std::fmt::Display for CandidateError {
139    /// What:
140    ///  `&self` borrows this error read-only;
141    ///  `&mut` lends the formatter for writing;
142    ///       `'_` is an unnamed lifetime the compiler fills in.
143    /// Why:
144    ///   Formatting writes into the caller's buffer without allocating another string.
145    ///
146    /// In TS you'd write (pseudocode):
147    /// ```ts
148    /// format(out: Writer): void { out.write(this.message); }
149    /// ```
150    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        // `.as_str()` lends the owned message as a borrowed view for the writer.
152        return formatter.write_str(self.message.as_str());
153    }
154}
155
156/// What:
157///  An empty `impl` marks the type as a standard error value.
158/// Why:
159///   Generic error handling can carry it without converting it to a bare string.
160///
161/// In TS you'd write (pseudocode):
162/// ```ts
163/// // `extends Error` already provides this in TypeScript.
164/// ```
165impl std::error::Error for CandidateError {}
166
167/// Message controls stay out of the release executable.
168#[cfg(test)]
169#[path = "candidate_error_tests.rs"]
170mod tests;