Skip to main content

wenlan_types/
lint_check.rs

1// SPDX-License-Identifier: Apache-2.0
2use 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}