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}