Skip to main content

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}