Skip to main content

musli/context/
default_context.rs

1use core::error::Error;
2use core::fmt;
3
4#[cfg(feature = "alloc")]
5use crate::alloc::Global;
6use crate::{Allocator, Context};
7
8use super::{
9    Capture, ContextError, Emit, ErrorMode, Errors, Ignore, NoTrace, Report, Trace, TraceImpl,
10    TraceMode,
11};
12
13/// The default context which uses an allocator to track the location of errors.
14///
15/// This is typically constructed using [`new`] and by default uses the
16/// [`Global`] allocator to allocate memory. To customized the allocator to use
17/// [`new_in`] can be used during construction.
18///
19/// The default constructor is only available when the `alloc` feature is
20/// enabled, and will use the [`Global`] allocator.
21///
22/// [`new`]: super::new
23/// [`new_in`]: super::new_in
24pub struct DefaultContext<A, T, C>
25where
26    A: Allocator,
27    T: TraceMode,
28{
29    alloc: A,
30    trace: T::Impl<A>,
31    capture: C,
32}
33
34#[cfg(feature = "alloc")]
35impl DefaultContext<Global, NoTrace, Ignore> {
36    /// Construct the default context which uses the [`Global`] allocator for
37    /// memory.
38    #[inline]
39    pub(super) fn new() -> Self {
40        Self::new_in(Global::new())
41    }
42}
43
44impl<A> DefaultContext<A, NoTrace, Ignore>
45where
46    A: Allocator,
47{
48    /// Construct a new context which uses allocations to a fixed but
49    /// configurable number of diagnostics.
50    #[inline]
51    pub(super) fn new_in(alloc: A) -> Self {
52        let trace = NoTrace::new_in(alloc);
53        Self {
54            alloc,
55            trace,
56            capture: Ignore,
57        }
58    }
59}
60
61#[cfg(feature = "alloc")]
62impl Default for DefaultContext<Global, NoTrace, Ignore> {
63    #[inline]
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl<A, T, C> DefaultContext<A, T, C>
70where
71    A: Allocator,
72    T: TraceMode,
73    C: ErrorMode<A>,
74{
75    /// Enable tracing through the current allocator `A`.
76    ///
77    /// Note that this makes diagnostics methods such as [`report`] and
78    /// [`errors`] available on the type.
79    ///
80    /// Tracing requires the configured allocator to work, if for example the
81    /// [`Disabled`] allocator was in use, no diagnostics would be collected.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use musli::context;
87    ///
88    /// let cx = context::new().with_trace();
89    /// // Use cx for encoding/decoding to get detailed error information
90    /// ```
91    ///
92    /// [`report`]: DefaultContext::report
93    /// [`errors`]: DefaultContext::errors
94    /// [`Disabled`]: crate::alloc::Disabled
95    #[inline]
96    pub fn with_trace(self) -> DefaultContext<A, Trace, C> {
97        let trace = Trace::new_in(self.alloc);
98
99        DefaultContext {
100            alloc: self.alloc,
101            trace,
102            capture: self.capture,
103        }
104    }
105
106    /// Capture the specified error type.
107    ///
108    /// This gives access to the last captured error through
109    /// [`DefaultContext::unwrap`] and [`DefaultContext::result`].
110    ///
111    /// Capturing instead of forwarding the error might be beneficial if the
112    /// error type is large.
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use musli::{Decode, Encode};
118    /// use musli::alloc::Global;
119    /// use musli::context;
120    /// use musli::json::{Encoding, Error};
121    ///
122    /// const ENCODING: Encoding = Encoding::new();
123    ///
124    /// #[derive(Decode, Encode)]
125    /// struct Person {
126    ///     name: String,
127    ///     age: u32,
128    /// }
129    ///
130    /// let cx = context::new().with_capture::<Error>();
131    ///
132    /// let mut data = Vec::new();
133    ///
134    /// ENCODING.encode_with(&cx, &mut data, &Person {
135    ///     name: "Aristotle".to_string(),
136    ///     age: 61,
137    /// })?;
138    ///
139    /// assert!(cx.result().is_ok());
140    ///
141    /// let _: Result<Person, _> = ENCODING.from_slice_with(&cx, &data[..data.len() - 2]);
142    /// assert!(cx.result().is_err());
143    /// Ok::<_, musli::context::ErrorMarker>(())
144    /// ```
145    #[inline]
146    pub fn with_capture<E>(self) -> DefaultContext<A, T, Capture<E>>
147    where
148        E: ContextError<A>,
149    {
150        DefaultContext {
151            alloc: self.alloc,
152            trace: self.trace,
153            capture: Capture::new(),
154        }
155    }
156
157    /// Emit the specified error type `E`.
158    ///
159    /// This causes the method receiving the context to return the specified
160    /// error type directly instead through [`Context::Error`].
161    ///
162    /// # Examples
163    ///
164    /// ```
165    /// use musli::{Decode, Encode};
166    /// use musli::alloc::Global;
167    /// use musli::context;
168    /// use musli::json::{Encoding, Error};
169    ///
170    /// const ENCODING: Encoding = Encoding::new();
171    ///
172    /// #[derive(Decode, Encode)]
173    /// struct Person {
174    ///     name: String,
175    ///     age: u32,
176    /// }
177    ///
178    /// let cx = context::new().with_error();
179    ///
180    /// let mut data = Vec::new();
181    ///
182    /// ENCODING.encode_with(&cx, &mut data, &Person {
183    ///     name: "Aristotle".to_string(),
184    ///     age: 61,
185    /// })?;
186    ///
187    /// let person: Person = ENCODING.from_slice_with(&cx, &data[..])?;
188    /// assert_eq!(person.name, "Aristotle");
189    /// assert_eq!(person.age, 61);
190    /// Ok::<_, Error>(())
191    /// ```
192    #[inline]
193    pub fn with_error<E>(self) -> DefaultContext<A, T, Emit<E>>
194    where
195        E: ContextError<A>,
196    {
197        DefaultContext {
198            alloc: self.alloc,
199            trace: self.trace,
200            capture: Emit::new(),
201        }
202    }
203}
204
205impl<A, C> DefaultContext<A, Trace, C>
206where
207    A: Allocator,
208{
209    /// If tracing is enabled through [`DefaultContext::with_trace`], this
210    /// configured the context to visualize type information, and not just
211    /// variant and fields.
212    ///
213    /// # Examples
214    ///
215    /// ```
216    /// use musli::context;
217    ///
218    /// let cx = context::new().with_trace().with_type();
219    /// ```
220    #[inline]
221    pub fn with_type(mut self) -> Self {
222        self.trace.include_type();
223        self
224    }
225
226    /// Generate a line-separated report of all reported errors.
227    ///
228    /// This can be useful if you want a quick human-readable overview of
229    /// errors. The line separator used will be platform dependent.
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use musli::context::{self, ErrorMarker};
235    /// use musli::value::Value;
236    /// use musli::json::Encoding;
237    ///
238    /// const ENCODING: Encoding = Encoding::new();
239    ///
240    /// let cx = context::new().with_trace();
241    ///
242    /// let ErrorMarker = ENCODING.from_str_with::<_, Value<_>>(&cx, "not json").unwrap_err();
243    /// let report = cx.report();
244    /// println!("{report}");
245    /// ```
246    #[inline]
247    pub fn report(&self) -> Report<'_, A> {
248        self.trace.report()
249    }
250
251    /// Iterate over all reported errors.
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// use musli::context::{self, ErrorMarker};
257    /// use musli::value::Value;
258    /// use musli::json::Encoding;
259    ///
260    /// const ENCODING: Encoding = Encoding::new();
261    ///
262    /// let cx = context::new().with_trace();
263    ///
264    /// let ErrorMarker = ENCODING.from_str_with::<_, Value<_>>(&cx, "not json").unwrap_err();
265    /// assert!(cx.errors().count() > 0);
266    /// ```
267    #[inline]
268    pub fn errors(&self) -> Errors<'_, A> {
269        self.trace.errors()
270    }
271}
272
273impl<A, T, E> DefaultContext<A, T, Capture<E>>
274where
275    A: Allocator,
276    T: TraceMode,
277    E: ContextError<A>,
278{
279    /// Unwrap the error marker or panic if there is no error.
280    ///
281    /// # Examples
282    ///
283    /// ```should_panic
284    /// use musli::context;
285    ///
286    /// let cx = context::new().with_capture::<String>();
287    /// // This will panic since no error has been captured
288    /// let error = cx.unwrap();
289    /// ```
290    #[inline]
291    pub fn unwrap(&self) -> E {
292        self.capture.unwrap()
293    }
294
295    /// Coerce a captured error into a result.
296    ///
297    /// # Examples
298    ///
299    /// ```
300    /// use musli::context;
301    ///
302    /// let cx = context::new().with_capture::<String>();
303    /// let result = cx.result();
304    /// assert!(result.is_ok());
305    /// ```
306    #[inline]
307    pub fn result(&self) -> Result<(), E> {
308        self.capture.result()
309    }
310}
311
312impl<A, T, C> Context for &DefaultContext<A, T, C>
313where
314    A: Allocator,
315    T: TraceMode,
316    C: ErrorMode<A>,
317{
318    type Error = C::Error;
319    type Mark = <<T as TraceMode>::Impl<A> as TraceImpl<A>>::Mark;
320    type Allocator = A;
321
322    #[inline]
323    fn clear(self) {
324        self.trace.clear();
325        self.capture.clear();
326    }
327
328    #[inline]
329    fn alloc(self) -> Self::Allocator {
330        self.alloc
331    }
332
333    #[inline]
334    fn custom<E>(self, message: E) -> Self::Error
335    where
336        E: 'static + Send + Sync + Error,
337    {
338        self.trace.custom(self.alloc, &message);
339        self.capture.custom(self.alloc, message)
340    }
341
342    #[inline]
343    fn message<M>(self, message: M) -> Self::Error
344    where
345        M: fmt::Display,
346    {
347        self.trace.message(self.alloc, &message);
348        self.capture.message(self.alloc, message)
349    }
350
351    #[inline]
352    fn message_at<M>(self, mark: &Self::Mark, message: M) -> Self::Error
353    where
354        M: fmt::Display,
355    {
356        self.trace.message_at(self.alloc, mark, &message);
357        self.capture.message(self.alloc, message)
358    }
359
360    #[inline]
361    fn custom_at<E>(self, mark: &Self::Mark, message: E) -> Self::Error
362    where
363        E: 'static + Send + Sync + Error,
364    {
365        self.trace.custom_at(self.alloc, mark, &message);
366        self.capture.custom(self.alloc, message)
367    }
368
369    #[inline]
370    fn mark(self) -> Self::Mark {
371        self.trace.mark()
372    }
373
374    #[inline]
375    fn restore(self, mark: &Self::Mark) {
376        self.trace.restore(mark);
377    }
378
379    #[inline]
380    fn advance(self, n: usize) {
381        self.trace.advance(n);
382    }
383
384    #[inline]
385    fn enter_named_field<F>(self, name: &'static str, field: F)
386    where
387        F: fmt::Display,
388    {
389        self.trace.enter_named_field(name, &field);
390    }
391
392    #[inline]
393    fn enter_unnamed_field<F>(self, index: u32, name: F)
394    where
395        F: fmt::Display,
396    {
397        self.trace.enter_unnamed_field(index, &name);
398    }
399
400    #[inline]
401    fn leave_field(self) {
402        self.trace.leave_field();
403    }
404
405    #[inline]
406    fn enter_struct(self, name: &'static str) {
407        self.trace.enter_struct(name);
408    }
409
410    #[inline]
411    fn leave_struct(self) {
412        self.trace.leave_struct();
413    }
414
415    #[inline]
416    fn enter_enum(self, name: &'static str) {
417        self.trace.enter_enum(name);
418    }
419
420    #[inline]
421    fn leave_enum(self) {
422        self.trace.leave_enum();
423    }
424
425    #[inline]
426    fn enter_variant<V>(self, name: &'static str, tag: V)
427    where
428        V: fmt::Display,
429    {
430        self.trace.enter_variant(name, &tag);
431    }
432
433    #[inline]
434    fn leave_variant(self) {
435        self.trace.leave_variant();
436    }
437
438    #[inline]
439    fn enter_sequence_index(self, index: usize) {
440        self.trace.enter_sequence_index(index);
441    }
442
443    #[inline]
444    fn leave_sequence_index(self) {
445        self.trace.leave_sequence_index();
446    }
447
448    #[inline]
449    fn enter_map_key<F>(self, field: F)
450    where
451        F: fmt::Display,
452    {
453        self.trace.enter_map_key(self.alloc, &field);
454    }
455
456    #[inline]
457    fn leave_map_key(self) {
458        self.trace.leave_map_key();
459    }
460}