Skip to main content

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