Skip to main content

qubit_value/value/
redaction.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//! Policy-aware redaction for structured [`super::Value`] instances.
9
10use std::fmt;
11
12#[cfg(feature = "json")]
13use qubit_redact::RedactedJsonSession;
14use qubit_redact::{
15    Redact,
16    RedactMapValue,
17    RedactValue,
18    RedactedKeyedValueSession,
19    RedactedMapSession,
20    RedactedValue,
21    RedactionSession,
22};
23
24use super::{
25    Value,
26    ValueRepr,
27};
28use crate::multi_values::MultiValuesRepr;
29use crate::{
30    MultiValues,
31    NamedMultiValues,
32    NamedValue,
33    ValueContainer,
34};
35
36impl RedactValue for Value {
37    /// Redacts string contents while replacing every other variant opaquely.
38    fn redact_value<'a>(
39        &'a self,
40        level: qubit_redact::Sensitivity,
41        masking: &qubit_redact::MaskingPolicy,
42    ) -> RedactedValue<'a> {
43        match &self.repr {
44            ValueRepr::String(value) => value.redact_value(level, masking),
45            _ => RedactedValue::opaque(level, masking),
46        }
47    }
48}
49
50impl RedactValue for MultiValues {
51    /// Replaces a sensitive collection without formatting its contents.
52    #[inline(always)]
53    fn redact_value<'a>(
54        &'a self,
55        level: qubit_redact::Sensitivity,
56        masking: &qubit_redact::MaskingPolicy,
57    ) -> RedactedValue<'a> {
58        RedactedValue::opaque(level, masking)
59    }
60}
61
62impl Redact for Value {
63    /// Writes this value through `policy` without altering its ordinary debug
64    /// representation when no key context is available.
65    ///
66    /// String maps classify every entry by its key. Other variants retain their
67    /// ordinary debug representation until a structured redaction rule applies.
68    fn fmt_redacted(
69        &self,
70        session: &RedactionSession<'_>,
71        formatter: &mut fmt::Formatter<'_>,
72    ) -> fmt::Result {
73        match &self.repr {
74            ValueRepr::StringMap(values) => {
75                values.fmt_redacted_map(session, formatter)
76            }
77            #[cfg(feature = "json")]
78            ValueRepr::Json(value) => fmt::Debug::fmt(
79                &RedactedJsonSession::new(value, session),
80                formatter,
81            ),
82            _ => fmt::Debug::fmt(self, formatter),
83        }
84    }
85}
86
87impl Redact for MultiValues {
88    /// Writes collection entries through the policy where their structure has
89    /// key-bearing values; other typed collections retain normal debug output.
90    fn fmt_redacted(
91        &self,
92        session: &RedactionSession<'_>,
93        formatter: &mut fmt::Formatter<'_>,
94    ) -> fmt::Result {
95        match &self.repr {
96            MultiValuesRepr::StringMap(values) => {
97                let mut output = formatter.debug_list();
98                for value in values {
99                    output.entry(&RedactedMapSession::new(value, session));
100                }
101                output.finish()
102            }
103            #[cfg(feature = "json")]
104            MultiValuesRepr::Json(values) => {
105                let mut output = formatter.debug_list();
106                for value in values {
107                    output.entry(&RedactedJsonSession::new(value, session));
108                }
109                output.finish()
110            }
111            _ => fmt::Debug::fmt(self, formatter),
112        }
113    }
114}
115
116impl Redact for ValueContainer {
117    /// Delegates policy-aware rendering to the explicit scalar or collection.
118    fn fmt_redacted(
119        &self,
120        session: &RedactionSession<'_>,
121        formatter: &mut fmt::Formatter<'_>,
122    ) -> fmt::Result {
123        match self {
124            Self::Scalar(value) => value.fmt_redacted(session, formatter),
125            Self::Collection(values) => values.fmt_redacted(session, formatter),
126        }
127    }
128}
129
130/// Formats a named value while applying its name as the policy lookup key.
131fn fmt_named_value<T: Redact + RedactValue>(
132    name: &str,
133    value: &T,
134    type_name: &str,
135    value_name: &str,
136    session: &RedactionSession<'_>,
137    formatter: &mut fmt::Formatter<'_>,
138) -> fmt::Result {
139    let mut output = formatter.debug_struct(type_name);
140    output.field("name", &name);
141    output.field(
142        value_name,
143        &RedactedKeyedValueSession::new(name, value, session),
144    );
145    output.finish()
146}
147
148impl Redact for NamedValue {
149    /// Uses the wrapper name to determine whether its complete value is masked.
150    fn fmt_redacted(
151        &self,
152        session: &RedactionSession<'_>,
153        formatter: &mut fmt::Formatter<'_>,
154    ) -> fmt::Result {
155        fmt_named_value(
156            self.name(),
157            self.value(),
158            "NamedValue",
159            "value",
160            session,
161            formatter,
162        )
163    }
164}
165
166impl Redact for NamedMultiValues {
167    /// Uses the wrapper name to determine whether its complete collection is
168    /// masked.
169    fn fmt_redacted(
170        &self,
171        session: &RedactionSession<'_>,
172        formatter: &mut fmt::Formatter<'_>,
173    ) -> fmt::Result {
174        fmt_named_value(
175            self.name(),
176            self.values(),
177            "NamedMultiValues",
178            "value",
179            session,
180            formatter,
181        )
182    }
183}