redactable/redaction/wrappers.rs
1//! Wrapper types for sensitive and non-sensitive values.
2//!
3//! This module provides wrapper types for handling foreign types:
4//!
5//! - [`SensitiveValue<T, P>`]: Wraps a value and applies a redaction policy
6//! - [`NotSensitiveValue<T>`]: Wraps a value that should pass through unchanged
7
8use std::marker::PhantomData;
9
10#[cfg(feature = "json")]
11use serde::{Deserialize, Serialize};
12
13use super::{
14 redact::RedactableMapper,
15 traits::{Redactable, RedactableWithMapper, SensitiveWithPolicy},
16};
17use crate::policy::RedactionPolicy;
18
19// =============================================================================
20// SensitiveValue - Wrapper for leaf values with a policy
21// =============================================================================
22
23/// Wrapper for leaf values to apply a redaction policy.
24///
25/// For external types, implement `SensitiveWithPolicy<P>` in your crate and
26/// wrap the value in `SensitiveValue<T, P>` to apply the policy.
27///
28/// # Serialization warning
29///
30/// When the `json` feature is enabled, `serde::Serialize` emits the raw inner
31/// value unchanged. This is intentional because application storage and wire
32/// formats usually need the real value. Logging integrations redact before
33/// serializing the log value; direct `serde` serialization does not. If you
34/// need redacted JSON, use `.redacted()`, `.to_redacted_output()`, or
35/// `redacted_json()` (from `RedactedJsonExt`) at the logging/serialization
36/// boundary instead of serializing the wrapper directly.
37///
38/// Leaf values are **atomic**: `SensitiveValue` treats `T` as an opaque unit
39/// and does not traverse its fields.
40#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub struct SensitiveValue<T, P>(T, PhantomData<P>);
42
43impl<T, P> SensitiveValue<T, P>
44where
45 T: SensitiveWithPolicy<P>,
46 P: RedactionPolicy,
47{
48 /// Returns the redacted string representation using the policy `P`.
49 #[must_use]
50 pub fn redacted(&self) -> String {
51 let policy = P::policy();
52 self.0.redacted_string(&policy)
53 }
54}
55
56impl<T, P> RedactableWithMapper for SensitiveValue<T, P>
57where
58 T: SensitiveWithPolicy<P>,
59 P: RedactionPolicy,
60{
61 fn redact_with<M: RedactableMapper>(self, mapper: &M) -> Self {
62 let redacted = mapper.map_sensitive::<T, P>(self.0);
63 Self(redacted, PhantomData)
64 }
65}
66
67impl<T, P> Redactable for SensitiveValue<T, P>
68where
69 T: SensitiveWithPolicy<P>,
70 P: RedactionPolicy,
71{
72}
73
74impl<T, P> From<T> for SensitiveValue<T, P> {
75 fn from(value: T) -> Self {
76 Self(value, PhantomData)
77 }
78}
79
80impl<T, P> SensitiveValue<T, P> {
81 /// Explicitly access the inner value.
82 ///
83 /// This method makes it clear in your code that you are intentionally
84 /// accessing the raw sensitive value. Use with care.
85 #[must_use]
86 pub fn expose(&self) -> &T {
87 &self.0
88 }
89
90 /// Explicitly access the inner value mutably.
91 ///
92 /// This method makes it clear in your code that you are intentionally
93 /// accessing the raw sensitive value. Use with care.
94 pub fn expose_mut(&mut self) -> &mut T {
95 &mut self.0
96 }
97
98 /// Consume the wrapper and return the inner value.
99 #[must_use]
100 pub fn into_inner(self) -> T {
101 self.0
102 }
103}
104
105impl<T, P> std::fmt::Debug for SensitiveValue<T, P>
106where
107 T: SensitiveWithPolicy<P>,
108 P: RedactionPolicy,
109{
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_tuple("SensitiveValue")
112 .field(&self.redacted())
113 .finish()
114 }
115}
116
117#[cfg(feature = "json")]
118impl<T, P> Serialize for SensitiveValue<T, P>
119where
120 T: Serialize,
121{
122 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123 where
124 S: serde::Serializer,
125 {
126 self.0.serialize(serializer)
127 }
128}
129
130#[cfg(feature = "json")]
131impl<'de, T, P> Deserialize<'de> for SensitiveValue<T, P>
132where
133 T: Deserialize<'de>,
134{
135 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
136 where
137 D: serde::Deserializer<'de>,
138 {
139 T::deserialize(deserializer).map(Self::from)
140 }
141}
142
143// =============================================================================
144// NotSensitiveValue - Wrapper for foreign types that should not be redacted
145// =============================================================================
146
147/// Wrapper for foreign types that should pass through unchanged.
148///
149/// Use this when a field's type comes from another crate and doesn't implement
150/// `RedactableWithMapper`. The wrapper provides a passthrough implementation
151/// that simply returns the value without any redaction.
152///
153/// **Serialization:** when the `json` feature is enabled, `serde::Serialize`
154/// emits the raw inner value unchanged. This wrapper is intentionally a
155/// passthrough for both redaction and serialization.
156///
157/// This is the mirror of [`SensitiveValue<T, P>`]: where `SensitiveValue` applies a policy,
158/// `NotSensitiveValue` explicitly opts out of redaction.
159///
160/// Note: This type coexists with the `#[derive(NotSensitive)]` macro. The derive
161/// macro is for types you own; this wrapper is for foreign types you don't own.
162///
163/// ```ignore
164/// use redactable::{NotSensitiveValue, Sensitive};
165///
166/// #[derive(Clone, Sensitive)]
167/// struct Config {
168/// // ForeignConfig doesn't implement RedactableWithMapper
169/// foreign: NotSensitiveValue<other_crate::ForeignConfig>,
170/// }
171/// ```
172#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
173pub struct NotSensitiveValue<T>(pub T);
174
175impl<T> RedactableWithMapper for NotSensitiveValue<T> {
176 fn redact_with<M: RedactableMapper>(self, _mapper: &M) -> Self {
177 self
178 }
179}
180
181// The wrapper itself is the declaration: wrapping a value in
182// `NotSensitiveValue` is an explicit opt-out, unlike a bare passthrough leaf.
183impl<T> Redactable for NotSensitiveValue<T> {}
184
185impl<T> From<T> for NotSensitiveValue<T> {
186 fn from(value: T) -> Self {
187 Self(value)
188 }
189}
190
191impl<T> std::ops::Deref for NotSensitiveValue<T> {
192 type Target = T;
193
194 fn deref(&self) -> &Self::Target {
195 &self.0
196 }
197}
198
199impl<T> std::ops::DerefMut for NotSensitiveValue<T> {
200 fn deref_mut(&mut self) -> &mut Self::Target {
201 &mut self.0
202 }
203}
204
205impl<T: std::fmt::Debug> std::fmt::Debug for NotSensitiveValue<T> {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 f.debug_tuple("NotSensitiveValue").field(&self.0).finish()
208 }
209}
210
211#[cfg(feature = "json")]
212impl<T: Serialize> Serialize for NotSensitiveValue<T> {
213 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
214 where
215 S: serde::Serializer,
216 {
217 self.0.serialize(serializer)
218 }
219}
220
221#[cfg(feature = "json")]
222impl<'de, T> Deserialize<'de> for NotSensitiveValue<T>
223where
224 T: Deserialize<'de>,
225{
226 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
227 where
228 D: serde::Deserializer<'de>,
229 {
230 T::deserialize(deserializer).map(Self::from)
231 }
232}