Skip to main content

qubit_redact/
field_redaction.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Typed results of field-sensitive scalar redaction.
9
10use std::borrow::Cow;
11
12pub use crate::pass_through_reason::PassThroughReason;
13use crate::{
14    LogSafeText,
15    RedactedText,
16    Sensitivity,
17};
18
19/// Explains whether a field value was masked or intentionally passed through.
20///
21/// [`std::fmt::Debug`] remains available for inspecting the policy result
22/// during debugging. Plain-text log sinks should consume
23/// [`Self::escape_for_log`] instead of formatting this enum directly.
24#[must_use]
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum FieldRedaction<'a> {
27    /// The policy masked the value at the reported sensitivity.
28    Masked {
29        /// The typed masked value.
30        value: RedactedText<'a>,
31        /// Sensitivity used to select the mask.
32        sensitivity: Sensitivity,
33    },
34    /// The policy intentionally retained the original value.
35    PassedThrough {
36        /// The original value borrowed from the caller.
37        value: &'a str,
38        /// Why the policy retained the value.
39        reason: PassThroughReason,
40    },
41}
42
43impl<'a> FieldRedaction<'a> {
44    /// Returns the processed value as a string slice.
45    #[inline]
46    pub fn as_str(&self) -> &str {
47        match self {
48            Self::Masked { value, .. } => value.as_str(),
49            Self::PassedThrough { value, .. } => value,
50        }
51    }
52
53    /// Returns `true` when the value was masked.
54    #[inline]
55    pub const fn is_masked(&self) -> bool {
56        matches!(self, Self::Masked { .. })
57    }
58
59    /// Returns the masking sensitivity, or `None` for pass-through values.
60    #[inline]
61    pub const fn sensitivity(&self) -> Option<Sensitivity> {
62        match self {
63            Self::Masked { sensitivity, .. } => Some(*sensitivity),
64            Self::PassedThrough { .. } => None,
65        }
66    }
67
68    /// Returns the pass-through reason, or `None` for masked values.
69    #[inline]
70    pub const fn pass_through_reason(&self) -> Option<PassThroughReason> {
71        match self {
72            Self::Masked { .. } => None,
73            Self::PassedThrough { reason, .. } => Some(*reason),
74        }
75    }
76
77    /// Converts the processed value into an owned string.
78    #[inline]
79    pub fn into_owned(self) -> String {
80        match self {
81            Self::Masked { value, .. } => value.into_owned(),
82            Self::PassedThrough { value, .. } => value.to_owned(),
83        }
84    }
85
86    /// Escapes the processed value for a plain-text log boundary.
87    #[inline]
88    pub fn escape_for_log(self) -> LogSafeText<'a> {
89        match self {
90            Self::Masked { value, .. } => value.escape_for_log(),
91            Self::PassedThrough { value, .. } => {
92                RedactedText::new(Cow::Borrowed(value)).escape_for_log()
93            }
94        }
95    }
96}