Skip to main content

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}