Skip to main content

allow_core/
policy.rs

1use crate::{FindingKind, normalize_path, source_tree_path::normalize_source_tree_scope};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct LastSeen {
6    pub line: u32,
7    pub column: u32,
8}
9
10#[derive(Debug, Clone, Default, PartialEq, Eq)]
11pub struct Selector {
12    pub ast_kind: Option<String>,
13    pub container: Option<String>,
14    pub callee: Option<String>,
15    pub macro_name: Option<String>,
16    pub lint: Option<String>,
17    pub symbol: Option<String>,
18    pub receiver_fingerprint: Option<String>,
19    pub target_fingerprint: Option<String>,
20    pub normalized_snippet_hash: Option<String>,
21    pub line_hint: Option<u32>,
22    pub glob: Option<String>,
23}
24
25impl Selector {
26    pub fn has_structural_identity(&self) -> bool {
27        [
28            self.ast_kind.as_deref(),
29            self.container.as_deref(),
30            self.callee.as_deref(),
31            self.macro_name.as_deref(),
32            self.lint.as_deref(),
33            self.symbol.as_deref(),
34            self.receiver_fingerprint.as_deref(),
35            self.target_fingerprint.as_deref(),
36            self.normalized_snippet_hash.as_deref(),
37        ]
38        .into_iter()
39        .any(|value| value.is_some_and(|text| !text.trim().is_empty()))
40    }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct Lifecycle {
45    pub created: Option<String>,
46    pub review_after: Option<String>,
47    pub expires: Option<String>,
48}
49
50impl Lifecycle {
51    pub fn empty() -> Self {
52        Self {
53            created: None,
54            review_after: None,
55            expires: None,
56        }
57    }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct AllowEntry {
62    pub id: String,
63    pub kind: FindingKind,
64    pub family: Option<String>,
65    pub path: Option<PathBuf>,
66    pub glob: Option<String>,
67    pub owner: String,
68    pub classification: String,
69    pub reason: String,
70    pub evidence: Vec<String>,
71    pub links: Vec<String>,
72    pub occurrence_limit: Option<u32>,
73    pub lifecycle: Lifecycle,
74    pub selector: Selector,
75    pub last_seen: Option<LastSeen>,
76}
77
78impl AllowEntry {
79    pub fn path_or_glob(&self) -> String {
80        if let Some(path) = &self.path {
81            normalize_path(path)
82        } else if let Some(glob) = &self.glob {
83            normalize_source_tree_scope(glob)
84        } else if let Some(glob) = &self.selector.glob {
85            normalize_source_tree_scope(glob)
86        } else {
87            String::new()
88        }
89    }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct Requirements {
94    pub owner_required: bool,
95    pub reason_required: bool,
96    pub classification_required: bool,
97    pub evidence_required: bool,
98    pub expires_or_review_after_required: bool,
99    pub allow_bare_allow_attributes: bool,
100    pub lint_policy_id_required: bool,
101    pub stale_entries_fail: bool,
102    pub unsafe_evidence_required: bool,
103    pub unsafe_safety_comment_required: bool,
104}
105
106impl Default for Requirements {
107    fn default() -> Self {
108        Self {
109            owner_required: true,
110            reason_required: true,
111            classification_required: true,
112            evidence_required: false,
113            expires_or_review_after_required: true,
114            allow_bare_allow_attributes: false,
115            lint_policy_id_required: false,
116            stale_entries_fail: false,
117            unsafe_evidence_required: true,
118            unsafe_safety_comment_required: false,
119        }
120    }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct WorkspaceConfig {
125    pub root: String,
126    pub inventory: String,
127    pub ignored: Vec<String>,
128    pub generated: Vec<String>,
129    pub default_mode: String,
130}
131
132impl Default for WorkspaceConfig {
133    fn default() -> Self {
134        Self {
135            root: ".".to_string(),
136            inventory: "git-tracked".to_string(),
137            ignored: vec![".git/**".to_string(), "target/**".to_string()],
138            generated: vec!["target/**".to_string(), "vendor/**".to_string()],
139            default_mode: "no-new".to_string(),
140        }
141    }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct AllowConfig {
146    pub schema_version: String,
147    pub policy: String,
148    pub owner: Option<String>,
149    pub status: Option<String>,
150    pub workspace: WorkspaceConfig,
151    pub requirements: Requirements,
152    pub allow: Vec<AllowEntry>,
153}
154
155impl AllowConfig {
156    pub fn empty() -> Self {
157        Self {
158            schema_version: "0.1".to_string(),
159            policy: "cargo-allow".to_string(),
160            owner: None,
161            status: Some("active".to_string()),
162            workspace: WorkspaceConfig::default(),
163            requirements: Requirements::default(),
164            allow: Vec::new(),
165        }
166    }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
170pub enum MatchStatus {
171    Matched,
172    New,
173    Stale,
174    Expired,
175    ReviewDue,
176    Ambiguous,
177    InvalidSelector,
178    MissingRequiredField,
179    EvidenceMissing,
180    BaselineDebt,
181}
182
183impl MatchStatus {
184    pub const ALL: &[Self] = &[
185        Self::Matched,
186        Self::New,
187        Self::Stale,
188        Self::Expired,
189        Self::ReviewDue,
190        Self::Ambiguous,
191        Self::InvalidSelector,
192        Self::MissingRequiredField,
193        Self::EvidenceMissing,
194        Self::BaselineDebt,
195    ];
196
197    pub fn as_str(self) -> &'static str {
198        match self {
199            Self::Matched => "matched",
200            Self::New => "new",
201            Self::Stale => "stale",
202            Self::Expired => "expired",
203            Self::ReviewDue => "review_due",
204            Self::Ambiguous => "ambiguous",
205            Self::InvalidSelector => "invalid_selector",
206            Self::MissingRequiredField => "missing_required_field",
207            Self::EvidenceMissing => "evidence_missing",
208            Self::BaselineDebt => "baseline_debt",
209        }
210    }
211
212    pub fn is_failure_in_strict(self) -> bool {
213        !matches!(self, Self::Matched)
214    }
215
216    pub fn is_failure_in_no_new(self) -> bool {
217        matches!(
218            self,
219            Self::New
220                | Self::Expired
221                | Self::Ambiguous
222                | Self::InvalidSelector
223                | Self::MissingRequiredField
224                | Self::EvidenceMissing
225        )
226    }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct MatchOutcome {
231    pub status: MatchStatus,
232    pub allow_id: Option<String>,
233    pub finding_index: Option<usize>,
234    pub message: String,
235    pub score: u32,
236}