qubit_redact/policy/field_classification.rs
1// =============================================================================
2// Copyright (c) 2025 - 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Explainable borrowed results from field-policy classification.
9
10use super::{
11 AllowRule,
12 FieldMatchKind,
13 SensitiveFieldRule,
14 Sensitivity,
15};
16
17/// Explains why a field is sensitive, allowed, or unknown to a policy.
18///
19/// Matched rules borrow canonical field names from the immutable policy, so
20/// callers can inspect classification without cloning rule metadata.
21///
22/// # Type Parameters
23///
24/// * `'a` - Lifetime of canonical field names borrowed from the policy.
25#[must_use]
26#[non_exhaustive]
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum FieldClassification<'a> {
29 /// A sensitive rule classified the field.
30 Sensitive {
31 /// Borrowed configured rule that supplied the sensitivity.
32 rule: SensitiveFieldRule<'a>,
33 /// The canonical input candidate that matched this rule.
34 match_kind: FieldMatchKind,
35 },
36 /// An allow rule took precedence over sensitivity at the same candidate.
37 Allowed {
38 /// Borrowed configured rule that allowed the field.
39 rule: AllowRule<'a>,
40 /// The canonical input candidate that matched this rule.
41 match_kind: FieldMatchKind,
42 },
43 /// No configured rule classified the field.
44 Unknown,
45}
46
47impl<'a> FieldClassification<'a> {
48 /// Returns the configured sensitivity when the field is sensitive.
49 ///
50 /// # Returns
51 ///
52 /// `Some(level)` for [`Self::Sensitive`], or `None` for allowed and unknown
53 /// fields.
54 #[must_use]
55 pub const fn sensitivity(self) -> Option<Sensitivity> {
56 match self {
57 Self::Sensitive { rule, .. } => Some(rule.sensitivity()),
58 Self::Allowed { .. } | Self::Unknown => None,
59 }
60 }
61
62 /// Returns the canonical configured field that matched.
63 ///
64 /// # Returns
65 ///
66 /// A field name borrowed from the policy for sensitive and allowed
67 /// classifications, or `None` for [`Self::Unknown`].
68 #[must_use]
69 pub const fn matched_field(self) -> Option<&'a str> {
70 match self {
71 Self::Sensitive { rule, .. } => Some(rule.field()),
72 Self::Allowed { rule, .. } => Some(rule.field()),
73 Self::Unknown => None,
74 }
75 }
76
77 /// Returns the canonical input candidate that matched the configured rule.
78 ///
79 /// # Returns
80 ///
81 /// The exact input or a semantic token suffix for sensitive and allowed
82 /// classifications, or `None` for [`Self::Unknown`].
83 #[must_use]
84 pub const fn match_kind(self) -> Option<FieldMatchKind> {
85 match self {
86 Self::Sensitive { match_kind, .. }
87 | Self::Allowed { match_kind, .. } => Some(match_kind),
88 Self::Unknown => None,
89 }
90 }
91
92 /// Reports whether an allow rule classified the field.
93 ///
94 /// # Returns
95 ///
96 /// `true` only for [`Self::Allowed`].
97 #[must_use]
98 pub const fn is_allowed(self) -> bool {
99 matches!(self, Self::Allowed { .. })
100 }
101
102 /// Reports whether no configured rule classified the field.
103 ///
104 /// # Returns
105 ///
106 /// `true` only for [`Self::Unknown`].
107 #[must_use]
108 pub const fn is_unknown(self) -> bool {
109 matches!(self, Self::Unknown)
110 }
111}