insta/content/
mod.rs

1//! This module implements a generic `Content` type that can hold
2//! runtime typed data.
3//!
4//! It's modelled after serde's data format but it's in fact possible to use
5//! this independently of serde.  The `yaml` and `json` support implemented
6//! here works without serde.  Only `yaml` has an implemented parser but since
7//! YAML is a superset of JSON insta instead currently parses JSON via the
8//! YAML implementation.
9
10pub mod json;
11#[cfg(feature = "serde")]
12mod serialization;
13pub mod yaml;
14
15#[cfg(feature = "serde")]
16pub use serialization::*;
17
18use std::fmt;
19
20/// An internal error type for content related errors.
21#[derive(Debug)]
22pub enum Error {
23    FailedParsingYaml(std::path::PathBuf),
24    UnexpectedDataType,
25    MissingField,
26    FileIo(std::io::Error, std::path::PathBuf),
27}
28
29impl fmt::Display for Error {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Error::FailedParsingYaml(p) => {
33                f.write_str(format!("Failed parsing the YAML from {:?}", p.display()).as_str())
34            }
35            Error::UnexpectedDataType => {
36                f.write_str("The present data type wasn't what was expected")
37            }
38            Error::MissingField => f.write_str("A required field was missing"),
39            Error::FileIo(e, p) => {
40                f.write_str(format!("File error for {:?}: {}", p.display(), e).as_str())
41            }
42        }
43    }
44}
45
46impl std::error::Error for Error {}
47
48/// Represents variable typed content.
49///
50/// This is used for the serialization system to represent values
51/// before the actual snapshots are written and is also exposed to
52/// dynamic redaction functions.
53///
54/// Some enum variants are intentionally not exposed to user code.
55/// It's generally recommended to construct content objects by
56/// using the [`From`] trait and by using the
57/// accessor methods to assert on it.
58///
59/// While matching on the content is possible in theory it is
60/// recommended against.  The reason for this is that the content
61/// enum holds variants that can "wrap" values where it's not
62/// expected.  For instance if a field holds an `Option<String>`
63/// you cannot use pattern matching to extract the string as it
64/// will be contained in an internal [`Some`] variant that is not
65/// exposed.  On the other hand the [`Content::as_str`] method will
66/// automatically resolve such internal wrappers.
67///
68/// If you do need to pattern match you should use the
69/// [`Content::resolve_inner`] method to resolve such internal wrappers.
70#[derive(Debug, Clone, PartialEq, PartialOrd)]
71pub enum Content {
72    Bool(bool),
73
74    U8(u8),
75    U16(u16),
76    U32(u32),
77    U64(u64),
78    U128(u128),
79
80    I8(i8),
81    I16(i16),
82    I32(i32),
83    I64(i64),
84    I128(i128),
85
86    F32(f32),
87    F64(f64),
88
89    Char(char),
90    String(String),
91    Bytes(Vec<u8>),
92
93    #[doc(hidden)]
94    None,
95    #[doc(hidden)]
96    Some(Box<Content>),
97
98    #[doc(hidden)]
99    Unit,
100    #[doc(hidden)]
101    UnitStruct(&'static str),
102    #[doc(hidden)]
103    UnitVariant(&'static str, u32, &'static str),
104    #[doc(hidden)]
105    NewtypeStruct(&'static str, Box<Content>),
106    #[doc(hidden)]
107    NewtypeVariant(&'static str, u32, &'static str, Box<Content>),
108
109    Seq(Vec<Content>),
110    #[doc(hidden)]
111    Tuple(Vec<Content>),
112    #[doc(hidden)]
113    TupleStruct(&'static str, Vec<Content>),
114    #[doc(hidden)]
115    TupleVariant(&'static str, u32, &'static str, Vec<Content>),
116    Map(Vec<(Content, Content)>),
117    #[doc(hidden)]
118    Struct(&'static str, Vec<(&'static str, Content)>),
119    #[doc(hidden)]
120    StructVariant(
121        &'static str,
122        u32,
123        &'static str,
124        Vec<(&'static str, Content)>,
125    ),
126}
127
128macro_rules! impl_from {
129    ($ty:ty, $newty:ident) => {
130        impl From<$ty> for Content {
131            fn from(value: $ty) -> Content {
132                Content::$newty(value)
133            }
134        }
135    };
136}
137
138impl_from!(bool, Bool);
139impl_from!(u8, U8);
140impl_from!(u16, U16);
141impl_from!(u32, U32);
142impl_from!(u64, U64);
143impl_from!(u128, U128);
144impl_from!(i8, I8);
145impl_from!(i16, I16);
146impl_from!(i32, I32);
147impl_from!(i64, I64);
148impl_from!(i128, I128);
149impl_from!(f32, F32);
150impl_from!(f64, F64);
151impl_from!(char, Char);
152impl_from!(String, String);
153impl_from!(Vec<u8>, Bytes);
154
155impl From<()> for Content {
156    fn from(_value: ()) -> Content {
157        Content::Unit
158    }
159}
160
161impl<'a> From<&'a str> for Content {
162    fn from(value: &'a str) -> Content {
163        Content::String(value.to_string())
164    }
165}
166
167impl<'a> From<&'a [u8]> for Content {
168    fn from(value: &'a [u8]) -> Content {
169        Content::Bytes(value.to_vec())
170    }
171}
172
173impl Content {
174    /// This resolves the innermost content in a chain of
175    /// wrapped content.
176    ///
177    /// For instance if you encounter an `Option<Option<String>>`
178    /// field the content will be wrapped twice in an internal
179    /// option wrapper.  If you need to pattern match you will
180    /// need in some situations to first resolve the inner value
181    /// before such matching can take place as there is no exposed
182    /// way to match on these wrappers.
183    ///
184    /// This method does not need to be called for the `as_`
185    /// methods which resolve automatically.
186    pub fn resolve_inner(&self) -> &Content {
187        match *self {
188            Content::Some(ref v)
189            | Content::NewtypeStruct(_, ref v)
190            | Content::NewtypeVariant(_, _, _, ref v) => v.resolve_inner(),
191            ref other => other,
192        }
193    }
194
195    /// Mutable version of [`Self::resolve_inner`].
196    pub fn resolve_inner_mut(&mut self) -> &mut Content {
197        match *self {
198            Content::Some(ref mut v)
199            | Content::NewtypeStruct(_, ref mut v)
200            | Content::NewtypeVariant(_, _, _, ref mut v) => v.resolve_inner_mut(),
201            ref mut other => other,
202        }
203    }
204
205    /// Returns the value as string
206    pub fn as_str(&self) -> Option<&str> {
207        match self.resolve_inner() {
208            Content::String(ref s) => Some(s.as_str()),
209            _ => None,
210        }
211    }
212
213    /// Returns the value as bytes
214    pub fn as_bytes(&self) -> Option<&[u8]> {
215        match self.resolve_inner() {
216            Content::Bytes(ref b) => Some(b),
217            _ => None,
218        }
219    }
220
221    /// Returns the value as slice of content values.
222    pub fn as_slice(&self) -> Option<&[Content]> {
223        match self.resolve_inner() {
224            Content::Seq(ref v) | Content::Tuple(ref v) | Content::TupleVariant(_, _, _, ref v) => {
225                Some(&v[..])
226            }
227            _ => None,
228        }
229    }
230
231    /// Returns true if the value is nil.
232    pub fn is_nil(&self) -> bool {
233        matches!(self.resolve_inner(), Content::None | Content::Unit)
234    }
235
236    /// Returns the value as bool
237    pub fn as_bool(&self) -> Option<bool> {
238        match *self.resolve_inner() {
239            Content::Bool(val) => Some(val),
240            _ => None,
241        }
242    }
243
244    /// Returns the value as u64
245    pub fn as_u64(&self) -> Option<u64> {
246        match *self.resolve_inner() {
247            Content::U8(v) => Some(u64::from(v)),
248            Content::U16(v) => Some(u64::from(v)),
249            Content::U32(v) => Some(u64::from(v)),
250            Content::U64(v) => Some(v),
251            Content::U128(v) => {
252                let rv = v as u64;
253                if rv as u128 == v {
254                    Some(rv)
255                } else {
256                    None
257                }
258            }
259            Content::I8(v) if v >= 0 => Some(v as u64),
260            Content::I16(v) if v >= 0 => Some(v as u64),
261            Content::I32(v) if v >= 0 => Some(v as u64),
262            Content::I64(v) if v >= 0 => Some(v as u64),
263            Content::I128(v) => {
264                let rv = v as u64;
265                if rv as i128 == v {
266                    Some(rv)
267                } else {
268                    None
269                }
270            }
271            _ => None,
272        }
273    }
274
275    /// Returns the value as u128
276    pub fn as_u128(&self) -> Option<u128> {
277        match *self.resolve_inner() {
278            Content::U128(v) => Some(v),
279            Content::I128(v) if v >= 0 => Some(v as u128),
280            _ => self.as_u64().map(u128::from),
281        }
282    }
283
284    /// Returns the value as i64
285    pub fn as_i64(&self) -> Option<i64> {
286        match *self.resolve_inner() {
287            Content::U8(v) => Some(i64::from(v)),
288            Content::U16(v) => Some(i64::from(v)),
289            Content::U32(v) => Some(i64::from(v)),
290            Content::U64(v) => {
291                let rv = v as i64;
292                if rv as u64 == v {
293                    Some(rv)
294                } else {
295                    None
296                }
297            }
298            Content::U128(v) => {
299                let rv = v as i64;
300                if rv as u128 == v {
301                    Some(rv)
302                } else {
303                    None
304                }
305            }
306            Content::I8(v) => Some(i64::from(v)),
307            Content::I16(v) => Some(i64::from(v)),
308            Content::I32(v) => Some(i64::from(v)),
309            Content::I64(v) => Some(v),
310            Content::I128(v) => {
311                let rv = v as i64;
312                if rv as i128 == v {
313                    Some(rv)
314                } else {
315                    None
316                }
317            }
318            _ => None,
319        }
320    }
321
322    /// Returns the value as i128
323    pub fn as_i128(&self) -> Option<i128> {
324        match *self.resolve_inner() {
325            Content::U128(v) => {
326                let rv = v as i128;
327                if rv as u128 == v {
328                    Some(rv)
329                } else {
330                    None
331                }
332            }
333            Content::I128(v) => Some(v),
334            _ => self.as_i64().map(i128::from),
335        }
336    }
337
338    /// Returns the value as f64
339    pub fn as_f64(&self) -> Option<f64> {
340        match *self.resolve_inner() {
341            Content::F32(v) => Some(f64::from(v)),
342            Content::F64(v) => Some(v),
343            _ => None,
344        }
345    }
346
347    /// Recursively walks the content structure mutably.
348    ///
349    /// The callback is invoked for every content in the tree.
350    pub fn walk<F: FnMut(&mut Content) -> bool>(&mut self, visit: &mut F) {
351        if !visit(self) {
352            return;
353        }
354
355        match *self {
356            Content::Some(ref mut inner) => {
357                Self::walk(&mut *inner, visit);
358            }
359            Content::NewtypeStruct(_, ref mut inner) => {
360                Self::walk(&mut *inner, visit);
361            }
362            Content::NewtypeVariant(_, _, _, ref mut inner) => {
363                Self::walk(&mut *inner, visit);
364            }
365            Content::Seq(ref mut vec) => {
366                for inner in vec.iter_mut() {
367                    Self::walk(inner, visit);
368                }
369            }
370            Content::Map(ref mut vec) => {
371                for inner in vec.iter_mut() {
372                    Self::walk(&mut inner.0, visit);
373                    Self::walk(&mut inner.1, visit);
374                }
375            }
376            Content::Struct(_, ref mut vec) => {
377                for inner in vec.iter_mut() {
378                    Self::walk(&mut inner.1, visit);
379                }
380            }
381            Content::StructVariant(_, _, _, ref mut vec) => {
382                for inner in vec.iter_mut() {
383                    Self::walk(&mut inner.1, visit);
384                }
385            }
386            Content::Tuple(ref mut vec) => {
387                for inner in vec.iter_mut() {
388                    Self::walk(inner, visit);
389                }
390            }
391            Content::TupleStruct(_, ref mut vec) => {
392                for inner in vec.iter_mut() {
393                    Self::walk(inner, visit);
394                }
395            }
396            Content::TupleVariant(_, _, _, ref mut vec) => {
397                for inner in vec.iter_mut() {
398                    Self::walk(inner, visit);
399                }
400            }
401            _ => {}
402        }
403    }
404}