1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
5#[serde(rename_all = "kebab-case")]
6pub enum CaptureSemanticRole {
7 #[default]
9 Unknown,
10 AssignmentValue,
12 Token,
14 CredentialEnvelope,
16 PrivateKeyBlock,
18 ConnectionString,
20 UrlUserinfo,
22 HeaderValue,
24 CommandArgumentValue,
26}
27
28impl CaptureSemanticRole {
29 pub const fn as_str(self) -> &'static str {
31 match self {
32 Self::Unknown => "unknown",
33 Self::AssignmentValue => "assignment-value",
34 Self::Token => "token",
35 Self::CredentialEnvelope => "credential-envelope",
36 Self::PrivateKeyBlock => "private-key-block",
37 Self::ConnectionString => "connection-string",
38 Self::UrlUserinfo => "url-userinfo",
39 Self::HeaderValue => "header-value",
40 Self::CommandArgumentValue => "command-argument-value",
41 }
42 }
43
44 pub const fn is_unknown(&self) -> bool {
46 matches!(self, Self::Unknown)
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
52#[serde(rename_all = "kebab-case")]
53pub enum AnchorSemanticRole {
54 #[default]
56 Unknown,
57 ExactKey,
59 DistinctivePrefix,
61 StructuredEnvelope,
63 CompanionBound,
65 WeakContext,
67 Unanchored,
69}
70
71impl AnchorSemanticRole {
72 pub const fn as_str(self) -> &'static str {
74 match self {
75 Self::Unknown => "unknown",
76 Self::ExactKey => "exact-key",
77 Self::DistinctivePrefix => "distinctive-prefix",
78 Self::StructuredEnvelope => "structured-envelope",
79 Self::CompanionBound => "companion-bound",
80 Self::WeakContext => "weak-context",
81 Self::Unanchored => "unanchored",
82 }
83 }
84
85 pub const fn is_unknown(&self) -> bool {
87 matches!(self, Self::Unknown)
88 }
89}
90
91#[repr(u8)]
93#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
94#[serde(rename_all = "kebab-case")]
95pub enum SemanticSourceRole {
96 StructuredAssignmentValue,
98 EnvironmentAssignmentValue,
100 StringLiteral,
102 CommandArgumentValue,
104 CommandOptionDeclaration,
106 HeaderValue,
108 UrlAuthorityUserinfo,
110 ConnectionString,
112 StandaloneToken,
114 PemBlock,
116 RegexRuleDefinition,
118 IdentifierTypeMemberName,
120 ProseDocumentation,
122 TestFixture,
124 GeneratedVendorMaterial,
126 Unknown,
128}
129
130impl SemanticSourceRole {
131 pub const fn as_str(self) -> &'static str {
133 match self {
134 Self::StructuredAssignmentValue => "structured-assignment-value",
135 Self::EnvironmentAssignmentValue => "environment-assignment-value",
136 Self::StringLiteral => "string-literal",
137 Self::CommandArgumentValue => "command-argument-value",
138 Self::CommandOptionDeclaration => "command-option-declaration",
139 Self::HeaderValue => "header-value",
140 Self::UrlAuthorityUserinfo => "url-authority-userinfo",
141 Self::ConnectionString => "connection-string",
142 Self::StandaloneToken => "standalone-token",
143 Self::PemBlock => "pem-block",
144 Self::RegexRuleDefinition => "regex-rule-definition",
145 Self::IdentifierTypeMemberName => "identifier-type-member-name",
146 Self::ProseDocumentation => "prose-documentation",
147 Self::TestFixture => "test-fixture",
148 Self::GeneratedVendorMaterial => "generated-vendor-material",
149 Self::Unknown => "unknown",
150 }
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
156#[serde(rename_all = "kebab-case")]
157pub enum RequiredSemanticEvidence {
158 Checksum,
160 RequiredCompanion,
162 PrivateKeyCompanion,
164 StructuralGrammar,
166 LiveVerification,
168}
169
170impl RequiredSemanticEvidence {
171 pub const fn as_str(self) -> &'static str {
173 match self {
174 Self::Checksum => "checksum",
175 Self::RequiredCompanion => "required-companion",
176 Self::PrivateKeyCompanion => "private-key-companion",
177 Self::StructuralGrammar => "structural-grammar",
178 Self::LiveVerification => "live-verification",
179 }
180 }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
185#[serde(rename_all = "kebab-case")]
186pub enum DetectorHardNegativeClass {
187 Boundary,
189 Identifier,
191 Prose,
193 RegexLiteral,
195 SiblingPrefix,
197}
198
199impl DetectorHardNegativeClass {
200 pub const ALL: &'static [Self] = &[
202 Self::Boundary,
203 Self::Identifier,
204 Self::Prose,
205 Self::RegexLiteral,
206 Self::SiblingPrefix,
207 ];
208
209 pub const fn as_str(self) -> &'static str {
211 match self {
212 Self::Boundary => "boundary",
213 Self::Identifier => "identifier",
214 Self::Prose => "prose",
215 Self::RegexLiteral => "regex-literal",
216 Self::SiblingPrefix => "sibling-prefix",
217 }
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
226#[serde(deny_unknown_fields)]
227pub struct DetectorSemanticPolicySpec {
228 #[serde(default)]
230 pub capture_role: CaptureSemanticRole,
231 #[serde(default)]
233 pub anchor_role: AnchorSemanticRole,
234 #[serde(default, skip_serializing_if = "Vec::is_empty")]
236 pub allowed_source_roles: Vec<SemanticSourceRole>,
237 #[serde(default, skip_serializing_if = "Vec::is_empty")]
239 pub required_evidence: Vec<RequiredSemanticEvidence>,
240}
241
242impl DetectorSemanticPolicySpec {
243 pub fn is_enforcement_capable(&self) -> bool {
246 self.capture_role != CaptureSemanticRole::Unknown
247 && self.anchor_role != AnchorSemanticRole::Unknown
248 && !self.allowed_source_roles.is_empty()
249 && self
250 .allowed_source_roles
251 .iter()
252 .all(|role| *role != SemanticSourceRole::Unknown)
253 }
254}