Skip to main content

git_policy_cli/
scanner_failure_code.rs

1//! What:
2//!  Which engine failure code each failure of the candidate and scanner layers carries.
3//! Why:
4//!  An engine failure names its cause.
5//!  `content-unavailable` means candidate bytes or a
6//!      repository fact could not be read;
7//!  `policy-incomplete` means the policy's own
8//!      machinery failed.
9//!  Deciding the code here,
10//!  beside the failures,
11//!  makes every policy
12//!      that reads candidates or scans them report the same code for the same cause.
13//!
14//! In TS you'd write (pseudocode):
15//! ```ts
16//! // const code = scanRunFailureCode(error); // 'content-unavailable' | 'policy-incomplete'
17//! ```
18
19/// Import the candidate layer's closed list of causes.
20use super::candidate_error::CandidateFailure;
21/// Import the engine's failure codes;
22///  this module only chooses among them.
23use super::diagnostics::EngineFailureCode;
24/// Import the adapter's closed list of causes.
25use super::scanner_adapter::ScannerFailure;
26/// Import the failure of a whole scan pass.
27use super::scanner_run::ScanRunError;
28/// The scanner library's per-match result,
29///  which also carries its fail-closed notices.
30use forbidden_strings::ScanFinding;
31
32/// What:
33///  The code of one candidate-layer failure.
34///  `match` lists every cause by name and
35///       has no catch-all arm;
36///  `A | B` in an arm means "either of these".
37/// Why:
38///   Every cause but one is something Git could not list or hand over,
39///  or a private
40///       index a prediction needed could not be prepared,
41///  so the content was unavailable.
42///  A stale candidate is different:
43///  its content can be read through
44///       a fresh version,
45///  and what failed is the calling pass,
46///  which kept a candidate
47///       across an invalidation.
48///  Without a catch-all arm,
49///  a cause added later does not
50///       compile until someone chooses its code.
51///
52/// In TS you'd write (pseudocode):
53/// ```ts
54/// function candidateFailureCode(failure: CandidateFailure): EngineFailureCode;
55/// ```
56pub fn candidate_failure_code(failure: CandidateFailure) -> EngineFailureCode {
57    match failure {
58        CandidateFailure::GitNotStarted
59        | CandidateFailure::GitFailed
60        | CandidateFailure::ListingMalformed
61        | CandidateFailure::UnsupportedMode
62        | CandidateFailure::UnsupportedStatus
63        | CandidateFailure::UnmergedPath
64        | CandidateFailure::ReaderEnded
65        | CandidateFailure::ReplyMalformed
66        | CandidateFailure::ReplyTruncated
67        | CandidateFailure::ReplyMismatched
68        | CandidateFailure::ObjectMissing
69        | CandidateFailure::ObjectKindUnexpected
70        | CandidateFailure::PrivateIndexUnavailable => {
71            return EngineFailureCode::ContentUnavailable;
72        }
73        CandidateFailure::StaleCandidate => return EngineFailureCode::PolicyIncomplete,
74    }
75}
76
77/// What:
78///  The code of one adapter failure.
79/// Why:
80///   Rules that cannot be loaded,
81///  and a pathname that was read but cannot be handed
82///       to the scanner on this platform,
83///  are both failures of the policy's machinery;
84///       no repository content was unreadable.
85///  The `match` names both causes so a cause
86///       added later does not compile until someone chooses its code.
87///
88/// In TS you'd write (pseudocode):
89/// ```ts
90/// function scannerFailureCode(failure: ScannerFailure): EngineFailureCode;
91/// ```
92pub fn scanner_failure_code(failure: ScannerFailure) -> EngineFailureCode {
93    match failure {
94        ScannerFailure::RulesNotLoaded | ScannerFailure::PathnameUnrepresentable => {
95            return EngineFailureCode::PolicyIncomplete;
96        }
97    }
98}
99
100/// What:
101///  The code of a failed scan pass.
102///  `&ScanRunError` borrows the failure read-only.
103/// Why:
104///   A pass fails in the candidate layer or in the adapter,
105///  and each already has its
106///       code;
107///  the pass reports the code of the failure it wraps.
108///
109/// In TS you'd write (pseudocode):
110/// ```ts
111/// function scanRunFailureCode(error: ScanRunError): EngineFailureCode;
112/// ```
113pub fn scan_run_failure_code(error: &ScanRunError) -> EngineFailureCode {
114    match error {
115        ScanRunError::Candidate(candidate) => return candidate_failure_code(candidate.failure),
116        ScanRunError::Scanner(scanner) => return scanner_failure_code(scanner.failure),
117    }
118}
119
120/// What:
121///  The code of a finding that is not a violation,
122///  or nothing for a violation.
123///       `Option<EngineFailureCode>` is "a code or nothing";
124///  `{ .. }` ignores a variant's fields.
125/// Why:
126///   The scanner reports two things among its findings that are not matches:
127///  a matcher
128///       that failed,
129///  and a pathname with a line break that it could not inspect.
130///  Neither
131///       says the candidate is clean and neither names a rule,
132///  so each must end the pass
133///       as an engine failure instead of being printed as a rule violation.
134///  Both arrive
135///       after the candidate was read,
136///  so the cause is the policy's machinery.
137///
138/// In TS you'd write (pseudocode):
139/// ```ts
140/// function findingFailureCode(finding: ScanFinding): EngineFailureCode | undefined;
141/// ```
142pub fn finding_failure_code(finding: &ScanFinding) -> Option<EngineFailureCode> {
143    match finding {
144        // `None` is the "absent" variant: a content or name match is a violation, not a failure.
145        ScanFinding::Content { .. } | ScanFinding::Name { .. } => return None,
146        // `Some(...)` is the "present" variant.
147        ScanFinding::EngineError | ScanFinding::PathnameLineBreak => {
148            return Some(EngineFailureCode::PolicyIncomplete);
149        }
150    }
151}
152
153/// One control per mapping,
154///  over every variant.
155#[cfg(test)]
156#[path = "scanner_failure_code_tests.rs"]
157mod tests;