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}