wenlan_types/
lint_check.rs1use super::*;
3use serde::{de::Error as _, Deserialize, Deserializer, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
6pub struct LintCheckResult {
7 check_id: String,
8 pub(crate) outcome: LintOutcome,
9 gate_effect: LintGateEffect,
10 severity: LintSeverity,
11 applicability: LintApplicability,
12 precondition: LintPrecondition,
13 coverage: LintCoverage,
14 metrics: Vec<LintMetric>,
15 summary_code: LintSummaryCode,
16 recommendation_code: Option<LintRecommendationCode>,
17 evidence: Vec<LintEvidenceRef>,
18 duration_ms: u64,
19}
20#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
21pub struct LintCheckResultInput {
22 pub check_id: String,
23 pub outcome: LintOutcome,
24 pub severity: LintSeverity,
25 pub applicability: LintApplicability,
26 pub precondition: LintPrecondition,
27 pub coverage: LintCoverage,
28 pub metrics: Vec<LintMetric>,
29 pub summary_code: LintSummaryCode,
30 pub recommendation_code: Option<LintRecommendationCode>,
31 pub evidence: Vec<LintEvidenceRef>,
32 pub duration_ms: u64,
33}
34#[derive(Deserialize)]
35struct LintCheckResultWire {
36 #[serde(flatten)]
37 input: LintCheckResultInput,
38 #[serde(default)]
39 gate_effect: LintGateEffect,
40}
41impl LintCheckResult {
42 pub fn try_new(input: LintCheckResultInput) -> Result<Self, LintContractError> {
43 Self::try_new_with_gate_effect(input, LintGateEffect::Actionable)
44 }
45
46 pub fn try_new_with_gate_effect(
47 input: LintCheckResultInput,
48 gate_effect: LintGateEffect,
49 ) -> Result<Self, LintContractError> {
50 let LintCheckResultInput {
51 check_id,
52 outcome,
53 severity,
54 applicability,
55 precondition,
56 coverage,
57 metrics,
58 summary_code,
59 recommendation_code,
60 evidence,
61 duration_ms,
62 } = input;
63 let legal_severity = match outcome {
64 LintOutcome::Pass => severity == LintSeverity::Info,
65 LintOutcome::Finding => match gate_effect {
66 LintGateEffect::Actionable => {
67 severity == LintSeverity::Warning || severity == LintSeverity::Error
68 }
69 LintGateEffect::Advisory => severity == LintSeverity::Warning,
70 },
71 LintOutcome::NotRunPrerequisite
72 | LintOutcome::InconsistentSnapshot
73 | LintOutcome::FailedToRun => severity == LintSeverity::Error,
74 };
75 if !legal_severity {
76 return Err(
77 if outcome == LintOutcome::Finding && gate_effect == LintGateEffect::Advisory {
78 LintContractError::InvalidGateEffect
79 } else {
80 LintContractError::InvalidOutcomeSeverity
81 },
82 );
83 }
84 let legal_context = match outcome {
85 LintOutcome::Pass => match applicability {
86 LintApplicability::Applicable | LintApplicability::Inventory => {
87 precondition == LintPrecondition::Ready
88 }
89 LintApplicability::ExpectedEmpty => {
90 precondition == LintPrecondition::ExpectedEmpty
91 || precondition == LintPrecondition::ConfiguredOff
92 }
93 LintApplicability::NotApplicable => false,
94 },
95 LintOutcome::Finding => {
96 applicability == LintApplicability::Applicable
97 && precondition == LintPrecondition::Ready
98 }
99 LintOutcome::NotRunPrerequisite => {
100 applicability == LintApplicability::NotApplicable
101 && precondition == LintPrecondition::MissingPrerequisite
102 }
103 LintOutcome::InconsistentSnapshot => {
104 applicability == LintApplicability::Applicable
105 && precondition == LintPrecondition::SnapshotUnstable
106 }
107 LintOutcome::FailedToRun => {
108 applicability == LintApplicability::Applicable
109 && precondition == LintPrecondition::Ready
110 }
111 };
112 if !legal_context {
113 return Err(LintContractError::InvalidApplicabilityPrecondition);
114 }
115 if evidence.len() > usize::from(LINT_MAX_EVIDENCE_PER_CHECK) {
116 return Err(LintContractError::EvidenceLimitExceeded);
117 }
118 coverage.validate(evidence.len())?;
119 if evidence
120 .iter()
121 .filter_map(LintEvidenceRef::opaque_id)
122 .any(|opaque_id| opaque_id.ordinal() > coverage.authorized_denominator())
123 {
124 return Err(LintContractError::EvidenceOutsideAuthorizedDenominator);
125 }
126 Ok(Self {
127 check_id,
128 outcome,
129 gate_effect,
130 severity,
131 applicability,
132 precondition,
133 coverage,
134 metrics,
135 summary_code,
136 recommendation_code,
137 evidence,
138 duration_ms,
139 })
140 }
141 pub fn check_id(&self) -> &str {
142 &self.check_id
143 }
144 pub const fn outcome(&self) -> LintOutcome {
145 self.outcome
146 }
147 pub const fn gate_effect(&self) -> LintGateEffect {
148 self.gate_effect
149 }
150 pub const fn severity(&self) -> LintSeverity {
151 self.severity
152 }
153 pub const fn applicability(&self) -> LintApplicability {
154 self.applicability
155 }
156 pub const fn precondition(&self) -> LintPrecondition {
157 self.precondition
158 }
159 pub fn coverage(&self) -> &LintCoverage {
160 &self.coverage
161 }
162 pub fn metrics(&self) -> &[LintMetric] {
163 &self.metrics
164 }
165 pub const fn summary_code(&self) -> LintSummaryCode {
166 self.summary_code
167 }
168 pub const fn recommendation_code(&self) -> Option<LintRecommendationCode> {
169 self.recommendation_code
170 }
171 pub fn evidence(&self) -> &[LintEvidenceRef] {
172 &self.evidence
173 }
174 pub const fn duration_ms(&self) -> u64 {
175 self.duration_ms
176 }
177}
178impl<'de> Deserialize<'de> for LintCheckResult {
179 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
180 where
181 D: Deserializer<'de>,
182 {
183 let wire = LintCheckResultWire::deserialize(deserializer)?;
184 Self::try_new_with_gate_effect(wire.input, wire.gate_effect).map_err(D::Error::custom)
185 }
186}