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