no_debug/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::fmt::Debug;
4use std::ops::{Deref, DerefMut};
5
6/// [Msg] is a trait for defining custom formatters for [NoDebug] values.
7pub trait Msg<T> {
8    /// Prints a custom message to the given formatter without necessarily revealing the values
9    /// information.
10    ///
11    /// Takes a reference to the value being debugged to allow some introspection.
12    fn fmt(value: &T, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error>;
13}
14
15#[derive(Debug, Clone)]
16pub struct WithTypeInfo;
17
18impl<T> Msg<T> for WithTypeInfo {
19    fn fmt(_value: &T, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
20        write!(f, "<no debug: {}>", std::any::type_name::<T>())
21    }
22}
23
24#[derive(Debug, Clone)]
25pub struct Ellipses;
26
27impl<T> Msg<T> for Ellipses {
28    fn fmt(_value: &T, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
29        write!(f, "...")
30    }
31}
32
33/// Wraps a type `T` and provides a [Debug] impl that does not rely on `T` being [Debug].
34#[derive(Eq, Ord, Clone)]
35pub struct NoDebug<T, M: Msg<T> = WithTypeInfo>(T, std::marker::PhantomData<M>);
36
37impl<T: std::hash::Hash, M: Msg<T>> std::hash::Hash for NoDebug<T, M> {
38    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
39        self.0.hash::<H>(state)
40    }
41}
42
43impl<T: PartialOrd, M: Msg<T>> std::cmp::PartialOrd<T> for NoDebug<T, M> {
44    fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
45        self.0.partial_cmp(other)
46    }
47}
48
49impl<T: PartialOrd, M: Msg<T>, N: Msg<T>> std::cmp::PartialOrd<NoDebug<T, N>> for NoDebug<T, M> {
50    fn partial_cmp(&self, other: &NoDebug<T, N>) -> Option<std::cmp::Ordering> {
51        self.0.partial_cmp(&**other)
52    }
53}
54
55impl<T: PartialEq, M: Msg<T>> std::cmp::PartialEq<T> for NoDebug<T, M> {
56    fn eq(&self, other: &T) -> bool {
57        &self.0 == other
58    }
59}
60
61impl<T: PartialEq, M: Msg<T>, N: Msg<T>> std::cmp::PartialEq<NoDebug<T, N>> for NoDebug<T, M> {
62    fn eq(&self, other: &NoDebug<T, N>) -> bool {
63        **self == **other
64    }
65}
66
67impl<T, M: Msg<T>> NoDebug<T, M> {
68    pub fn take(self) -> T {
69        self.0
70    }
71}
72
73impl<T> NoDebug<T, WithTypeInfo> {
74    pub fn new(value: T) -> Self {
75        value.into()
76    }
77}
78
79impl<T, M: Msg<T>> From<T> for NoDebug<T, M> {
80    fn from(value: T) -> Self {
81        Self(value, std::marker::PhantomData::default())
82    }
83}
84
85impl<T, M: Msg<T>> Debug for NoDebug<T, M> {
86    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
87        M::fmt(&self.0, f)
88    }
89}
90
91impl<T, M: Msg<T>> Deref for NoDebug<T, M> {
92    type Target = T;
93    fn deref(&self) -> &Self::Target {
94        &self.0
95    }
96}
97
98impl<T, M: Msg<T>> DerefMut for NoDebug<T, M> {
99    fn deref_mut(&mut self) -> &mut Self::Target {
100        &mut self.0
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn cannot_debug_nodebug() {
110        let value = NoDebug::new(3);
111        assert_eq!(format!("{:?}", value), "<no debug: i32>")
112    }
113
114    #[test]
115    fn cannot_debug_nodebug_via_into() {
116        let value: NoDebug<i32> = 3.into();
117        assert_eq!(format!("{:?}", value), "<no debug: i32>")
118    }
119
120    #[test]
121    fn can_show_type_info() {
122        let value: NoDebug<i32, WithTypeInfo> = 3.into();
123        assert_eq!(format!("{:?}", value), "<no debug: i32>")
124    }
125
126    #[test]
127    fn can_show_custom_message() {
128        let value: NoDebug<i32, Ellipses> = 3.into();
129        assert_eq!(format!("{:?}", value), "...")
130    }
131
132    #[test]
133    fn dereferences_nodebug() {
134        let value = NoDebug::new(3);
135        assert_eq!(format!("{:?}", value), "<no debug: i32>");
136        assert_eq!(format!("{:?}", *value), "3");
137    }
138
139    #[test]
140    fn mut_dereferences_nodebug() {
141        let mut value = NoDebug::new(3);
142        *value = 4;
143        assert_eq!(format!("{:?}", value), "<no debug: i32>");
144        assert_eq!(format!("{:?}", *value), "4");
145    }
146
147    #[test]
148    fn take_gets_value_from_nodebug() {
149        let value = NoDebug::new(3);
150        assert_eq!(value.take(), 3);
151    }
152
153    #[test]
154    fn has_eq_with_inner() {
155        let value = NoDebug::new(3);
156        assert_eq!(*value, 3);
157    }
158
159    #[test]
160    fn has_eq_with_raw_value() {
161        let value = NoDebug::new(3);
162        assert_eq!(value, 3);
163    }
164
165    #[test]
166    fn has_eq_with_another_no_debug() {
167        let value = NoDebug::new(3);
168        let other = NoDebug::new(3);
169        assert_eq!(value, other);
170    }
171
172    #[test]
173    fn has_eq_with_another_no_debug_with_different_msg() {
174        let value: NoDebug<i32, Ellipses> = 3.into();
175        let other: NoDebug<i32, WithTypeInfo> = 3.into();
176        assert_eq!(value, other);
177    }
178
179    #[test]
180    fn has_ord_with_raw_value() {
181        let value = NoDebug::new(2);
182        assert!(value < 3);
183    }
184
185    #[test]
186    fn has_ord_with_another_no_debug() {
187        let value = NoDebug::new(2);
188        let other = NoDebug::new(3);
189        assert!(value < other);
190    }
191
192    #[test]
193    fn has_ord_with_another_no_debug_with_different_msg() {
194        let value: NoDebug<i32, Ellipses> = 2.into();
195        let other: NoDebug<i32, WithTypeInfo> = 3.into();
196        assert!(value < other);
197    }
198
199    fn get_hash<T>(obj: T) -> u64
200    where
201        T: std::hash::Hash,
202    {
203        use std::collections::hash_map::DefaultHasher;
204        use std::hash::Hasher;
205        let mut hasher = DefaultHasher::new();
206        obj.hash(&mut hasher);
207        hasher.finish()
208    }
209
210    #[test]
211    fn has_hash_with_raw_value() {
212        let value: NoDebug<i32> = 3.into();
213        assert_eq!(get_hash(value), get_hash(3));
214    }
215
216    #[test]
217    fn has_hash_with_another_no_debug() {
218        let value: NoDebug<i32> = 3.into();
219        let other: NoDebug<i32> = 3.into();
220        assert_eq!(get_hash(value), get_hash(other));
221    }
222
223    #[test]
224    fn has_hash_with_another_no_debug_with_different_msg() {
225        let value: NoDebug<i32, Ellipses> = 3.into();
226        let other: NoDebug<i32, WithTypeInfo> = 3.into();
227        assert_eq!(get_hash(value), get_hash(other));
228    }
229}