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    #[inline(always)]
53    pub const fn sensitivity(self) -> Option<Sensitivity> {
54        match self {
55            Self::Sensitive { rule, .. } => Some(rule.sensitivity()),
56            Self::Allowed { .. } | Self::Unknown => None,
57        }
58    }
59
60    /// Returns the canonical configured field that matched.
61    ///
62    /// # Returns
63    ///
64    /// A field name borrowed from the policy for sensitive and allowed
65    /// classifications, or `None` for [`Self::Unknown`].
66    #[must_use]
67    #[inline(always)]
68    pub const fn matched_field(self) -> Option<&'a str> {
69        match self {
70            Self::Sensitive { rule, .. } => Some(rule.field()),
71            Self::Allowed { rule, .. } => Some(rule.field()),
72            Self::Unknown => None,
73        }
74    }
75
76    /// Returns the canonical input candidate that matched the configured rule.
77    ///
78    /// # Returns
79    ///
80    /// The exact input or a semantic token suffix for sensitive and allowed
81    /// classifications, or `None` for [`Self::Unknown`].
82    #[must_use]
83    #[inline(always)]
84    pub const fn match_kind(self) -> Option<FieldMatchKind> {
85        match self {
86            Self::Sensitive { match_kind, .. } | Self::Allowed { match_kind, .. } => Some(match_kind),
87            Self::Unknown => None,
88        }
89    }
90
91    /// Reports whether an allow rule classified the field.
92    ///
93    /// # Returns
94    ///
95    /// `true` only for [`Self::Allowed`].
96    #[inline(always)]
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    #[inline(always)]
108    #[must_use]
109    pub const fn is_unknown(self) -> bool {
110        matches!(self, Self::Unknown)
111    }
112}