Skip to main content

qubit_redact/domain/
redacted_keyed_value.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//! Lazy redaction view selected by an external field key.
9
10use std::{
11    fmt::Write as _,
12    fmt::{
13        self,
14        Debug,
15        Display,
16        Formatter,
17    },
18};
19
20use crate::{
21    Redact,
22    RedactValue,
23    RedactionPolicy,
24    text::internal::LogEscapeWriter,
25};
26
27/// A borrowed value rendered according to a separate field key.
28///
29/// The key is used only to select a policy rule. The view itself renders or
30/// serializes the value, and borrows an immutable policy snapshot.
31///
32/// # Type Parameters
33///
34/// * `'value` - Lifetime of the borrowed key and value.
35/// * `'policy` - Lifetime of the borrowed policy snapshot.
36/// * `T` - Value type rendered or serialized through redaction.
37#[must_use = "format or serialize the keyed redaction view"]
38pub struct RedactedKeyedValue<'value, 'policy, T: ?Sized> {
39    /// Field name used for policy classification.
40    key: &'value str,
41    /// Value represented by this view.
42    value: &'value T,
43    /// Immutable policy snapshot borrowed by every output protocol.
44    policy: &'policy RedactionPolicy,
45}
46
47impl<'value, 'policy, T: ?Sized> RedactedKeyedValue<'value, 'policy, T> {
48    /// Creates a keyed view from borrowed inputs and a borrowed policy
49    /// snapshot.
50    ///
51    /// # Parameters
52    ///
53    /// * `key` - Field name used only for policy classification.
54    /// * `value` - Value to render or serialize lazily.
55    /// * `policy` - Complete policy snapshot borrowed by this view.
56    ///
57    /// # Returns
58    ///
59    /// A view that never modifies the original value.
60    #[must_use = "format or serialize the keyed redaction view"]
61    #[inline(always)]
62    pub const fn new(
63        key: &'value str,
64        value: &'value T,
65        policy: &'policy RedactionPolicy,
66    ) -> Self {
67        Self { key, value, policy }
68    }
69
70    /// Returns the external field key used by this view.
71    ///
72    /// # Returns
73    ///
74    /// The unchanged policy lookup key.
75    #[must_use]
76    #[inline(always)]
77    pub const fn key(&self) -> &'value str {
78        self.key
79    }
80}
81
82impl<T: Redact + RedactValue + ?Sized> Debug for RedactedKeyedValue<'_, '_, T> {
83    /// Formats the value through its selected field classification.
84    ///
85    /// # Parameters
86    ///
87    /// * `formatter` - Destination formatter whose flags are preserved.
88    ///
89    /// # Returns
90    ///
91    /// The complete redacted debug result.
92    ///
93    /// # Errors
94    ///
95    /// Returns [`fmt::Error`] when the destination formatter cannot accept the
96    /// complete representation.
97    #[inline]
98    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
99        match self.policy.sensitivity_for(self.key) {
100            Some(level) => Debug::fmt(
101                &self.value.redact_value(level, self.policy.masking()),
102                formatter,
103            ),
104            None => self.value.fmt_redacted(self.policy, formatter),
105        }
106    }
107}
108
109impl<T: Redact + RedactValue + ?Sized> Display
110    for RedactedKeyedValue<'_, '_, T>
111{
112    /// Formats the selected redacted representation for a plain-text log.
113    ///
114    /// # Parameters
115    ///
116    /// * `formatter` - Destination plain-text log boundary.
117    ///
118    /// # Returns
119    ///
120    /// The complete escaped redacted representation.
121    ///
122    /// # Errors
123    ///
124    /// Returns [`fmt::Error`] when the destination formatter cannot accept the
125    /// complete escaped representation.
126    #[inline]
127    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
128        let mut writer = LogEscapeWriter::new(formatter);
129        write!(&mut writer, "{self:?}")
130    }
131}
132
133#[cfg(feature = "serde")]
134impl<T: RedactValue + crate::domain::RedactSerialize + ?Sized> serde::Serialize
135    for RedactedKeyedValue<'_, '_, T>
136{
137    /// Serializes the value through its selected field classification.
138    ///
139    /// # Type Parameters
140    ///
141    /// * `S` - Destination Serde serializer type.
142    ///
143    /// # Parameters
144    ///
145    /// * `serializer` - Destination serde serializer.
146    ///
147    /// # Returns
148    ///
149    /// The serializer's successful redacted output.
150    ///
151    /// # Errors
152    ///
153    /// Returns the destination serializer's error unchanged.
154    #[inline]
155    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156    where
157        S: serde::Serializer,
158    {
159        match self.policy.sensitivity_for(self.key) {
160            Some(level) => serde::Serialize::serialize(
161                &self.value.redact_value(level, self.policy.masking()),
162                serializer,
163            ),
164            None => crate::domain::RedactSerialize::serialize_redacted(
165                self.value,
166                self.policy,
167                serializer,
168            ),
169        }
170    }
171}