Thanks to visit codestin.com
Credit goes to docs.rs

error_stack/
sink.rs

1#[cfg(any(all(not(target_arch = "wasm32"), feature = "std"), feature = "tracing"))]
2use core::panic::Location;
3use core::{
4    convert::Infallible,
5    ops::{FromResidual, Try},
6};
7
8use crate::Report;
9
10/// The `Bomb` type is used to enforce proper usage of `ReportSink` at runtime.
11///
12/// It addresses a limitation of the `#[must_use]` attribute, which becomes ineffective
13/// when methods like `&mut self` are called, marking the value as used prematurely.
14///
15/// By moving this check to runtime, `Bomb` ensures that `ReportSink` is properly
16/// consumed.
17///
18/// This runtime check complements the compile-time `#[must_use]` attribute,
19/// providing a more robust mechanism to prevent `ReportSink` not being consumed.
20#[derive(Debug)]
21enum BombState {
22    /// Panic if the `ReportSink` is dropped without being used.
23    Panic,
24    /// Emit a warning to stderr if the `ReportSink` is dropped without being used.
25    Warn(
26        // We capture the location if either `tracing` is enabled or `std` is enabled on non-WASM
27        // targets.
28        #[cfg(any(all(not(target_arch = "wasm32"), feature = "std"), feature = "tracing"))]
29        &'static Location<'static>,
30    ),
31    /// Do nothing if the `ReportSink` is properly consumed.
32    Defused,
33}
34
35impl Default for BombState {
36    #[track_caller]
37    fn default() -> Self {
38        Self::Warn(
39            #[cfg(any(all(not(target_arch = "wasm32"), feature = "std"), feature = "tracing"))]
40            Location::caller(),
41        )
42    }
43}
44
45#[derive(Debug, Default)]
46struct Bomb(BombState);
47
48impl Bomb {
49    const fn panic() -> Self {
50        Self(BombState::Panic)
51    }
52
53    #[track_caller]
54    const fn warn() -> Self {
55        Self(BombState::Warn(
56            #[cfg(any(all(not(target_arch = "wasm32"), feature = "std"), feature = "tracing"))]
57            Location::caller(),
58        ))
59    }
60
61    const fn defuse(&mut self) {
62        self.0 = BombState::Defused;
63    }
64}
65
66impl Drop for Bomb {
67    fn drop(&mut self) {
68        // If we're in release mode, we don't need to do anything
69        if !cfg!(debug_assertions) {
70            return;
71        }
72
73        match self.0 {
74            BombState::Panic => panic!("ReportSink was dropped without being consumed"),
75            #[cfg_attr(not(feature = "tracing"), expect(clippy::print_stderr))]
76            #[cfg(any(all(not(target_arch = "wasm32"), feature = "std"), feature = "tracing"))]
77            BombState::Warn(location) => {
78                #[cfg(feature = "tracing")]
79                tracing::warn!(
80                    target: "error_stack",
81                    %location,
82                    "`ReportSink` was dropped without being consumed"
83                );
84                #[cfg(not(feature = "tracing"))]
85                eprintln!("`ReportSink` was dropped without being consumed at {location}");
86            }
87            _ => {}
88        }
89    }
90}
91/// A sink for collecting multiple [`Report`]s into a single [`Result`].
92///
93/// [`ReportSink`] allows you to accumulate multiple errors or reports and then
94/// finalize them into a single `Result`. This is particularly useful when you
95/// need to collect errors from multiple operations before deciding whether to
96/// proceed or fail.
97///
98/// The sink is equipped with a "bomb" mechanism to ensure proper usage,
99/// if the sink hasn't been finished when dropped, it will emit a warning or panic,
100/// depending on the constructor used.
101///
102/// # Examples
103///
104/// ```
105/// use error_stack::{Report, ReportSink};
106///
107/// #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
108/// struct InternalError;
109///
110/// impl core::fmt::Display for InternalError {
111///     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112///         f.write_str("Internal error")
113///     }
114/// }
115///
116/// impl core::error::Error for InternalError {}
117///
118/// fn operation1() -> Result<u32, Report<InternalError>> {
119///     // ...
120///     # Ok(42)
121/// }
122///
123/// fn operation2() -> Result<(), Report<InternalError>> {
124///     // ...
125///     # Ok(())
126/// }
127///
128/// fn process_data() -> Result<(), Report<[InternalError]>> {
129///     let mut sink = ReportSink::new();
130///
131///     if let Some(value) = sink.attempt(operation1()) {
132///         // process value
133///         # let _value = value;
134///     }
135///
136///     if let Err(e) = operation2() {
137///         sink.append(e);
138///     }
139///
140///     sink.finish()
141/// }
142/// # let _result = process_data();
143/// ```
144#[must_use]
145pub struct ReportSink<C> {
146    report: Option<Report<[C]>>,
147    bomb: Bomb,
148}
149
150impl<C> ReportSink<C> {
151    /// Creates a new [`ReportSink`].
152    ///
153    /// If the sink hasn't been finished when dropped, it will emit a warning.
154    #[track_caller]
155    pub const fn new() -> Self {
156        Self {
157            report: None,
158            bomb: Bomb::warn(),
159        }
160    }
161
162    /// Creates a new [`ReportSink`].
163    ///
164    /// If the sink hasn't been finished when dropped, it will panic.
165    pub const fn new_armed() -> Self {
166        Self {
167            report: None,
168            bomb: Bomb::panic(),
169        }
170    }
171
172    /// Adds a [`Report`] to the sink.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// # use error_stack::{ReportSink, Report};
178    /// # use std::io;
179    /// let mut sink = ReportSink::new();
180    /// sink.append(Report::new(io::Error::new(
181    ///     io::ErrorKind::Other,
182    ///     "I/O error",
183    /// )));
184    /// ```
185    #[track_caller]
186    pub fn append(&mut self, report: impl Into<Report<[C]>>) {
187        let report = report.into();
188
189        match self.report.as_mut() {
190            Some(existing) => existing.append(report),
191            None => self.report = Some(report),
192        }
193    }
194
195    /// Captures a single error or report in the sink.
196    ///
197    /// This method is similar to [`append`], but allows for bare errors without prior [`Report`]
198    /// creation.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// # use error_stack::ReportSink;
204    /// # use std::io;
205    /// let mut sink = ReportSink::new();
206    /// sink.capture(io::Error::new(io::ErrorKind::Other, "I/O error"));
207    /// ```
208    ///
209    /// [`append`]: ReportSink::append
210    #[track_caller]
211    pub fn capture(&mut self, error: impl Into<Report<C>>) {
212        let report = error.into();
213
214        match self.report.as_mut() {
215            Some(existing) => existing.push(report),
216            None => self.report = Some(report.into()),
217        }
218    }
219
220    /// Attempts to execute a fallible operation and collect any errors.
221    ///
222    /// This method takes a [`Result`] and returns an [`Option`]:
223    /// - If the [`Result`] is [`Ok`], it returns <code>[Some]\(T)</code> with the successful value.
224    /// - If the [`Result`] is [`Err`], it captures the error in the sink and returns [`None`].
225    ///
226    /// This is useful for concisely handling operations that may fail, allowing you to
227    /// collect errors while continuing execution.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// # use error_stack::ReportSink;
233    /// # use std::io;
234    /// fn fallible_operation() -> Result<u32, io::Error> {
235    ///     // ...
236    ///     # Ok(42)
237    /// }
238    ///
239    /// let mut sink = ReportSink::new();
240    /// let value = sink.attempt(fallible_operation());
241    /// if let Some(v) = value {
242    ///     // Use the successful value
243    ///     # let _v = v;
244    /// }
245    /// // Any errors are now collected in the sink
246    /// # let _result = sink.finish();
247    /// ```
248    #[track_caller]
249    pub fn attempt<T, R>(&mut self, result: Result<T, R>) -> Option<T>
250    where
251        R: Into<Report<C>>,
252    {
253        match result {
254            Ok(value) => Some(value),
255            Err(error) => {
256                self.capture(error);
257                None
258            }
259        }
260    }
261
262    /// Finishes the sink and returns a [`Result`].
263    ///
264    /// This method consumes the sink, and returns `Ok(())` if no errors
265    /// were collected, or `Err(Report<[C]>)` containing all collected errors otherwise.
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// # use error_stack::ReportSink;
271    /// # use std::io;
272    /// let mut sink = ReportSink::new();
273    /// # // needed for type inference
274    /// # sink.capture(io::Error::new(io::ErrorKind::Other, "I/O error"));
275    /// // ... add errors ...
276    /// let result = sink.finish();
277    /// # let _result = result;
278    /// ```
279    pub fn finish(mut self) -> Result<(), Report<[C]>> {
280        self.bomb.defuse();
281        self.report.map_or(Ok(()), Err)
282    }
283
284    /// Finishes the sink and returns a [`Result`] with a custom success value.
285    ///
286    /// Similar to [`finish`], but allows specifying a function to generate the success value.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// # use error_stack::ReportSink;
292    /// # use std::io;
293    /// let mut sink = ReportSink::new();
294    /// # // needed for type inference
295    /// # sink.capture(io::Error::new(io::ErrorKind::Other, "I/O error"));
296    /// // ... add errors ...
297    /// let result = sink.finish_with(|| "Operation completed");
298    /// # let _result = result;
299    /// ```
300    ///
301    /// [`finish`]: ReportSink::finish
302    pub fn finish_with<T>(mut self, ok: impl FnOnce() -> T) -> Result<T, Report<[C]>> {
303        self.bomb.defuse();
304        self.report.map_or_else(|| Ok(ok()), Err)
305    }
306
307    /// Finishes the sink and returns a [`Result`] with a default success value.
308    ///
309    /// Similar to [`finish`], but uses `T::default()` as the success value.
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// # use error_stack::ReportSink;
315    /// # use std::io;
316    /// let mut sink = ReportSink::new();
317    /// # // needed for type inference
318    /// # sink.capture(io::Error::new(io::ErrorKind::Other, "I/O error"));
319    /// // ... add errors ...
320    /// let result: Result<Vec<String>, _> = sink.finish_default();
321    /// # let _result = result;
322    /// ```
323    ///
324    /// [`finish`]: ReportSink::finish
325    pub fn finish_default<T: Default>(mut self) -> Result<T, Report<[C]>> {
326        self.bomb.defuse();
327        self.report.map_or_else(|| Ok(T::default()), Err)
328    }
329
330    /// Finishes the sink and returns a [`Result`] with a provided success value.
331    ///
332    /// Similar to [`finish`], but allows specifying a concrete value for the success case.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// # use error_stack::ReportSink;
338    /// # use std::io;
339    /// let mut sink = ReportSink::new();
340    /// # // needed for type inference
341    /// # sink.capture(io::Error::new(io::ErrorKind::Other, "I/O error"));
342    /// // ... add errors ...
343    /// let result = sink.finish_ok(42);
344    /// # let _result = result;
345    /// ```
346    ///
347    /// [`finish`]: ReportSink::finish
348    pub fn finish_ok<T>(mut self, ok: T) -> Result<T, Report<[C]>> {
349        self.bomb.defuse();
350        self.report.map_or(Ok(ok), Err)
351    }
352}
353
354impl<C> Default for ReportSink<C> {
355    fn default() -> Self {
356        Self::new()
357    }
358}
359
360#[cfg(nightly)]
361impl<C> FromResidual for ReportSink<C> {
362    fn from_residual(residual: <Self as Try>::Residual) -> Self {
363        match residual {
364            Err(report) => Self {
365                report: Some(report),
366                bomb: Bomb::default(),
367            },
368        }
369    }
370}
371
372#[cfg(nightly)]
373impl<C> Try for ReportSink<C> {
374    type Output = ();
375    // needs to be infallible, not `!` because of the `Try` of `Result`
376    type Residual = Result<Infallible, Report<[C]>>;
377
378    fn from_output((): ()) -> Self {
379        Self {
380            report: None,
381            bomb: Bomb::default(),
382        }
383    }
384
385    fn branch(mut self) -> core::ops::ControlFlow<Self::Residual, Self::Output> {
386        self.bomb.defuse();
387        self.report.map_or(
388            core::ops::ControlFlow::Continue(()), //
389            |report| core::ops::ControlFlow::Break(Err(report)),
390        )
391    }
392}
393
394#[cfg(test)]
395mod test {
396    use alloc::collections::BTreeSet;
397    use core::fmt::Display;
398
399    use crate::{Report, sink::ReportSink};
400
401    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
402    struct TestError(u8);
403
404    impl Display for TestError {
405        fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
406            fmt.write_str("TestError(")?;
407            core::fmt::Display::fmt(&self.0, fmt)?;
408            fmt.write_str(")")
409        }
410    }
411
412    impl core::error::Error for TestError {}
413
414    #[test]
415    fn add_single() {
416        let mut sink = ReportSink::new();
417
418        sink.append(Report::new(TestError(0)));
419
420        let report = sink.finish().expect_err("should have failed");
421
422        let contexts: BTreeSet<_> = report.current_contexts().collect();
423        assert_eq!(contexts.len(), 1);
424        assert!(contexts.contains(&TestError(0)));
425    }
426
427    #[test]
428    fn add_multiple() {
429        let mut sink = ReportSink::new();
430
431        sink.append(Report::new(TestError(0)));
432        sink.append(Report::new(TestError(1)));
433
434        let report = sink.finish().expect_err("should have failed");
435
436        let contexts: BTreeSet<_> = report.current_contexts().collect();
437        assert_eq!(contexts.len(), 2);
438        assert!(contexts.contains(&TestError(0)));
439        assert!(contexts.contains(&TestError(1)));
440    }
441
442    #[test]
443    fn capture_single() {
444        let mut sink = ReportSink::new();
445
446        sink.capture(TestError(0));
447
448        let report = sink.finish().expect_err("should have failed");
449
450        let contexts: BTreeSet<_> = report.current_contexts().collect();
451        assert_eq!(contexts.len(), 1);
452        assert!(contexts.contains(&TestError(0)));
453    }
454
455    #[test]
456    fn capture_multiple() {
457        let mut sink = ReportSink::new();
458
459        sink.capture(TestError(0));
460        sink.capture(TestError(1));
461
462        let report = sink.finish().expect_err("should have failed");
463
464        let contexts: BTreeSet<_> = report.current_contexts().collect();
465        assert_eq!(contexts.len(), 2);
466        assert!(contexts.contains(&TestError(0)));
467        assert!(contexts.contains(&TestError(1)));
468    }
469
470    #[test]
471    fn new_does_not_panic() {
472        let _sink: ReportSink<TestError> = ReportSink::new();
473    }
474
475    #[cfg(nightly)]
476    #[test]
477    fn try_none() {
478        fn sink() -> Result<(), Report<[TestError]>> {
479            let sink = ReportSink::new();
480
481            sink?;
482
483            Ok(())
484        }
485
486        sink().expect("should not have failed");
487    }
488
489    #[cfg(nightly)]
490    #[test]
491    fn try_single() {
492        fn sink() -> Result<(), Report<[TestError]>> {
493            let mut sink = ReportSink::new();
494
495            sink.append(Report::new(TestError(0)));
496
497            sink?;
498            Ok(())
499        }
500
501        let report = sink().expect_err("should have failed");
502
503        let contexts: BTreeSet<_> = report.current_contexts().collect();
504        assert_eq!(contexts.len(), 1);
505        assert!(contexts.contains(&TestError(0)));
506    }
507
508    #[cfg(nightly)]
509    #[test]
510    fn try_multiple() {
511        fn sink() -> Result<(), Report<[TestError]>> {
512            let mut sink = ReportSink::new();
513
514            sink.append(Report::new(TestError(0)));
515            sink.append(Report::new(TestError(1)));
516
517            sink?;
518            Ok(())
519        }
520
521        let report = sink().expect_err("should have failed");
522
523        let contexts: BTreeSet<_> = report.current_contexts().collect();
524        assert_eq!(contexts.len(), 2);
525        assert!(contexts.contains(&TestError(0)));
526        assert!(contexts.contains(&TestError(1)));
527    }
528
529    #[cfg(nightly)]
530    #[test]
531    fn try_arbitrary_return() {
532        fn sink() -> Result<u8, Report<[TestError]>> {
533            let mut sink = ReportSink::new();
534
535            sink.append(Report::new(TestError(0)));
536
537            sink?;
538            Ok(8)
539        }
540
541        let report = sink().expect_err("should have failed");
542
543        let contexts: BTreeSet<_> = report.current_contexts().collect();
544        assert_eq!(contexts.len(), 1);
545        assert!(contexts.contains(&TestError(0)));
546    }
547
548    #[test]
549    #[should_panic(expected = "without being consumed")]
550    fn panic_on_unused() {
551        #[expect(clippy::unnecessary_wraps)]
552        fn sink() -> Result<(), Report<[TestError]>> {
553            let mut sink = ReportSink::new_armed();
554
555            sink.append(Report::new(TestError(0)));
556
557            Ok(())
558        }
559
560        let _result = sink();
561    }
562
563    #[test]
564    fn panic_on_unused_with_defuse() {
565        fn sink() -> Result<(), Report<[TestError]>> {
566            let mut sink = ReportSink::new_armed();
567
568            sink.append(Report::new(TestError(0)));
569
570            sink?;
571            Ok(())
572        }
573
574        let report = sink().expect_err("should have failed");
575
576        let contexts: BTreeSet<_> = report.current_contexts().collect();
577        assert_eq!(contexts.len(), 1);
578        assert!(contexts.contains(&TestError(0)));
579    }
580
581    #[test]
582    fn finish() {
583        let mut sink = ReportSink::new();
584
585        sink.append(Report::new(TestError(0)));
586        sink.append(Report::new(TestError(1)));
587
588        let report = sink.finish().expect_err("should have failed");
589
590        let contexts: BTreeSet<_> = report.current_contexts().collect();
591        assert_eq!(contexts.len(), 2);
592        assert!(contexts.contains(&TestError(0)));
593        assert!(contexts.contains(&TestError(1)));
594    }
595
596    #[test]
597    fn finish_ok() {
598        let sink: ReportSink<TestError> = ReportSink::new();
599
600        sink.finish().expect("should have succeeded");
601    }
602
603    #[test]
604    fn finish_with() {
605        let mut sink = ReportSink::new();
606
607        sink.append(Report::new(TestError(0)));
608        sink.append(Report::new(TestError(1)));
609
610        let report = sink.finish_with(|| 8).expect_err("should have failed");
611
612        let contexts: BTreeSet<_> = report.current_contexts().collect();
613        assert_eq!(contexts.len(), 2);
614        assert!(contexts.contains(&TestError(0)));
615        assert!(contexts.contains(&TestError(1)));
616    }
617
618    #[test]
619    fn finish_with_ok() {
620        let sink: ReportSink<TestError> = ReportSink::new();
621
622        let value = sink.finish_with(|| 8).expect("should have succeeded");
623        assert_eq!(value, 8);
624    }
625
626    #[test]
627    fn finish_default() {
628        let mut sink = ReportSink::new();
629
630        sink.append(Report::new(TestError(0)));
631        sink.append(Report::new(TestError(1)));
632
633        let report = sink.finish_default::<u8>().expect_err("should have failed");
634
635        let contexts: BTreeSet<_> = report.current_contexts().collect();
636        assert_eq!(contexts.len(), 2);
637        assert!(contexts.contains(&TestError(0)));
638        assert!(contexts.contains(&TestError(1)));
639    }
640
641    #[test]
642    fn finish_default_ok() {
643        let sink: ReportSink<TestError> = ReportSink::new();
644
645        let value = sink.finish_default::<u8>().expect("should have succeeded");
646        assert_eq!(value, 0);
647    }
648
649    #[test]
650    fn finish_with_value() {
651        let mut sink = ReportSink::new();
652
653        sink.append(Report::new(TestError(0)));
654        sink.append(Report::new(TestError(1)));
655
656        let report = sink.finish_ok(8).expect_err("should have failed");
657
658        let contexts: BTreeSet<_> = report.current_contexts().collect();
659        assert_eq!(contexts.len(), 2);
660        assert!(contexts.contains(&TestError(0)));
661        assert!(contexts.contains(&TestError(1)));
662    }
663
664    #[test]
665    fn finish_with_value_ok() {
666        let sink: ReportSink<TestError> = ReportSink::new();
667
668        let value = sink.finish_ok(8).expect("should have succeeded");
669        assert_eq!(value, 8);
670    }
671}