Skip to main content

qubit_redact/domain/
redacted.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//! Borrowed, policy-snapshot view of a domain object.
9
10use std::fmt::{
11    self,
12    Debug,
13    Display,
14    Formatter,
15    Write as _,
16};
17
18use crate::{
19    BoundedRedactedDisplay,
20    LogOutputLimit,
21    Redact,
22    RedactionPolicy,
23    text::internal::LogEscapeWriter,
24};
25
26/// A lazy non-destructive redacted view of a domain object.
27///
28/// The view borrows the original object and owns a cheap clone of the complete
29/// policy. Creating it does not inspect, clone, or modify object fields.
30///
31/// # Type Parameters
32///
33/// * `'a` - Lifetime of the borrowed domain object.
34/// * `T` - Domain-object type rendered or serialized through redaction.
35#[must_use = "format or serialize the redacted view"]
36pub struct Redacted<'a, T: ?Sized> {
37    /// Domain object rendered through this view.
38    value: &'a T,
39    /// Immutable policy snapshot used for every formatting operation.
40    policy: RedactionPolicy,
41}
42
43impl<'a, T: ?Sized> Redacted<'a, T> {
44    /// Creates a redacted view from a borrowed object and an owned policy.
45    ///
46    /// # Parameters
47    ///
48    /// * `value` - Domain object to borrow without inspecting its fields.
49    /// * `policy` - Complete policy snapshot owned by the view.
50    ///
51    /// # Returns
52    ///
53    /// A lazy redacted view.
54    #[inline(always)]
55    pub(crate) const fn new(value: &'a T, policy: RedactionPolicy) -> Self {
56        Self { value, policy }
57    }
58
59    /// Converts this view into a byte-bounded, log-safe display adapter.
60    ///
61    /// # Parameters
62    ///
63    /// * `limit` - Maximum rendered bytes including any truncation marker.
64    ///
65    /// # Returns
66    ///
67    /// A display-only adapter that owns this redacted view.
68    #[inline(always)]
69    pub const fn with_output_limit(
70        self,
71        limit: LogOutputLimit,
72    ) -> BoundedRedactedDisplay<Self> {
73        BoundedRedactedDisplay::new(self, limit)
74    }
75
76    /// Converts this view into a byte-bounded display adapter using its policy.
77    ///
78    /// # Returns
79    ///
80    /// A display-only adapter bounded by this view's diagnostic output budget.
81    #[must_use = "format the bounded redacted display adapter"]
82    #[inline]
83    pub fn with_policy_output_limit(self) -> BoundedRedactedDisplay<Self> {
84        let limit = LogOutputLimit::from(self.policy.diagnostic_budget());
85        BoundedRedactedDisplay::new(self, limit)
86    }
87
88    /// Returns the borrowed domain value to crate-internal adapters.
89    ///
90    /// # Returns
91    ///
92    /// The original domain value borrowed for the view's lifetime.
93    #[cfg(feature = "serde")]
94    #[inline(always)]
95    pub(crate) const fn value(&self) -> &'a T {
96        self.value
97    }
98
99    /// Returns the policy snapshot to crate-internal adapters.
100    ///
101    /// # Returns
102    ///
103    /// The immutable policy snapshot owned by this view.
104    #[cfg(feature = "serde")]
105    #[inline(always)]
106    pub(crate) const fn policy(&self) -> &RedactionPolicy {
107        &self.policy
108    }
109}
110
111#[cfg(feature = "serde")]
112impl<T: crate::domain::RedactSerialize + ?Sized> serde::Serialize
113    for Redacted<'_, T>
114{
115    /// Delegates serialization to the derived redaction hook.
116    ///
117    /// # Type Parameters
118    ///
119    /// * `S` - Destination Serde serializer type.
120    ///
121    /// # Parameters
122    ///
123    /// * `serializer` - Destination Serde serializer.
124    ///
125    /// # Returns
126    ///
127    /// The derived hook's successful output.
128    ///
129    /// # Errors
130    ///
131    /// Returns the derived hook's serialization error unchanged.
132    #[inline(always)]
133    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134    where
135        S: serde::Serializer,
136    {
137        self.value().serialize_redacted(self.policy(), serializer)
138    }
139}
140
141impl<T: Redact + ?Sized> Debug for Redacted<'_, T> {
142    /// Writes the object's redacted representation while preserving formatter
143    /// flags such as alternate pretty formatting.
144    ///
145    /// # Parameters
146    ///
147    /// * `formatter` - Destination formatting context whose flags are passed to
148    ///   the object's redaction hook.
149    ///
150    /// # Returns
151    ///
152    /// The formatter result for the complete redacted representation.
153    ///
154    /// # Errors
155    ///
156    /// Returns [`fmt::Error`] when the object cannot write its complete
157    /// redacted representation.
158    #[inline(always)]
159    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
160        self.value.fmt_redacted(&self.policy, formatter)
161    }
162}
163
164impl<T: Redact + ?Sized> Display for Redacted<'_, T> {
165    /// Writes a compact redacted debug representation escaped for logs.
166    ///
167    /// Redacted debug output is escaped directly into the destination without
168    /// constructing an intermediate [`String`]. This implementation never
169    /// calls the original object's `Display`.
170    ///
171    /// # Parameters
172    ///
173    /// * `formatter` - Destination formatting context.
174    ///
175    /// # Returns
176    ///
177    /// The formatter result for the escaped redacted representation.
178    ///
179    /// # Errors
180    ///
181    /// Returns [`fmt::Error`] when the destination cannot accept the complete
182    /// log-safe representation.
183    #[inline]
184    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
185        let mut writer = LogEscapeWriter::new(formatter);
186        write!(&mut writer, "{self:?}")
187    }
188}