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 qubit_redact::Redact;
11use qubit_redact::RedactionWriter;
12
13use super::Value;
14use super::ValueRepr;
15use super::internal::RedactedStringMap;
16use super::internal::write_unredacted_values;
17use crate::MultiValues;
18use crate::MultiValuesRef;
19use crate::NamedMultiValues;
20use crate::NamedValue;
21use crate::ValueContainer;
22
23/// Selects policy-aware writing for string, map, and JSON collection payloads.
24/// Other value families share the unredacted collection writer.
25macro_rules! redact_collection_payload {
26    (String, $values:expr, $writer:expr) => {
27        $writer.sequence(|items| {
28            items.for_each($values, |items, value| {
29                items.unredacted_item(|| value);
30            });
31        })
32    };
33    (StringMap, $values:expr, $writer:expr) => {
34        $writer.sequence(|items| {
35            items.for_each($values, |items, values| {
36                items.nested_item(&RedactedStringMap { values });
37            });
38        })
39    };
40    (Json, $values:expr, $writer:expr) => {
41        $writer.sequence(|items| {
42            items.for_each($values, |items, value| {
43                items.json_value_item(value);
44            });
45        })
46    };
47    ($variant:ident, $values:expr, $writer:expr) => {
48        write_unredacted_values($writer, stringify!($variant), $values)
49    };
50}
51
52/// Builds borrowed collection dispatch from the canonical value type table.
53/// The caller must handle unset collections before invoking this macro.
54macro_rules! redact_multi_values_match {
55    ($values:expr, $writer:expr; $(([$($cfg:meta),*], $variant:ident, $type:ty, $data_type:expr, $materialization:ident, $json_class:ident, $number_projection:ident, $value_doc:literal, $multi_doc:literal $(, $_wire:tt)*)),+ $(,)?) => {
56        match $values {
57            MultiValuesRef::Unset(_) => unreachable!("unset values are handled before dispatch"),
58            $($(#[$cfg])* MultiValuesRef::$variant(values) => {
59                redact_collection_payload!($variant, values, $writer)
60            },)+
61        }
62    };
63}
64
65#[cfg_attr(docsrs, doc(cfg(feature = "redact")))]
66impl Redact for Value {
67    /// Writes one value through the shared structured writer.
68    ///
69    /// # Parameters
70    ///
71    /// - `writer`: Borrowed writer for the active redaction session; receives
72    ///   the classified representation without modifying the source value.
73    fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
74        match &self.repr {
75            ValueRepr::StringMap(values) => {
76                writer.record("Value", |fields| {
77                    fields.map("StringMap", values);
78                });
79            }
80            #[cfg(feature = "json")]
81            ValueRepr::Json(value) => {
82                writer.record("Value", |fields| {
83                    fields.json_value("Json", value);
84                });
85            }
86            _ => {
87                writer.unredacted(self);
88            }
89        }
90    }
91}
92
93#[cfg_attr(docsrs, doc(cfg(feature = "redact")))]
94impl Redact for MultiValues {
95    /// Writes a typed collection through the shared structured writer.
96    ///
97    /// # Parameters
98    ///
99    /// - `writer`: Borrowed writer for the active redaction session; receives
100    ///   the classified representation without modifying the source value.
101    fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
102        let values = self.view();
103        if matches!(values, MultiValuesRef::Unset(_)) {
104            writer.unredacted(self);
105            return;
106        }
107        for_each_value_type!(redact_multi_values_match, values, writer);
108    }
109}
110
111#[cfg_attr(docsrs, doc(cfg(feature = "redact")))]
112impl Redact for ValueContainer {
113    /// Writes the selected container payload through the shared session.
114    ///
115    /// # Parameters
116    ///
117    /// - `writer`: Borrowed writer for the active redaction session; receives
118    ///   the classified representation without modifying the source value.
119    fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
120        match self {
121            Self::Scalar(value) => {
122                writer.tuple("ValueContainer", |fields| {
123                    fields.nested("Scalar", value);
124                });
125            }
126            Self::Collection(values) => {
127                writer.tuple("ValueContainer", |fields| {
128                    fields.nested("Collection", values);
129                });
130            }
131        }
132    }
133}
134
135#[cfg_attr(docsrs, doc(cfg(feature = "redact")))]
136impl Redact for NamedValue {
137    /// Writes the name and policy-selected value through one session.
138    ///
139    /// # Parameters
140    ///
141    /// - `writer`: Borrowed writer for the active redaction session; receives
142    ///   the classified representation without modifying the source value.
143    fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
144        writer.record("NamedValue", |fields| {
145            fields.unredacted("name", || self.name());
146            fields.keyed_nested("value", self.name(), self.value());
147        });
148    }
149}
150
151#[cfg_attr(docsrs, doc(cfg(feature = "redact")))]
152impl Redact for NamedMultiValues {
153    /// Writes the name and policy-selected collection through one session.
154    ///
155    /// # Parameters
156    ///
157    /// - `writer`: Borrowed writer for the active redaction session; receives
158    ///   the classified representation without modifying the source value.
159    fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
160        writer.record("NamedMultiValues", |fields| {
161            fields.unredacted("name", || self.name());
162            fields.keyed_nested("value", self.name(), self.values());
163        });
164    }
165}