Skip to main content

qubit_progress/error/
auto_reporter_error.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Errors returned when a scoped automatic reporter stops.
9// qubit-style: allow source-test-pair
10// qubit-style: allow multiple-public-types
11
12use std::{
13    any::Any,
14    error::Error,
15    fmt,
16    panic,
17};
18
19use crate::EmissionError;
20
21/// Failure returned by [`crate::AutoReporter::stop`].
22#[derive(Debug)]
23#[non_exhaustive]
24pub enum AutoReporterError {
25    /// The worker returned a normal emission failure.
26    Emission(EmissionError),
27    /// The worker panicked while reporting.
28    Panicked(WorkerPanic),
29}
30
31impl From<EmissionError> for AutoReporterError {
32    /// Wraps a normal worker emission failure.
33    fn from(error: EmissionError) -> Self {
34        Self::Emission(error)
35    }
36}
37
38impl fmt::Display for AutoReporterError {
39    /// Formats the structured worker failure.
40    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            Self::Emission(error) => error.fmt(formatter),
43            Self::Panicked(error) => error.fmt(formatter),
44        }
45    }
46}
47
48impl Error for AutoReporterError {
49    /// Returns the nested emission failure when present.
50    fn source(&self) -> Option<&(dyn Error + 'static)> {
51        match self {
52            Self::Emission(error) => Some(error),
53            Self::Panicked(_) => None,
54        }
55    }
56}
57
58/// An opaque panic payload captured from an automatic reporter worker.
59pub struct WorkerPanic {
60    /// Original payload retained for callers that need to resume unwinding.
61    payload: Box<dyn Any + Send + 'static>,
62}
63
64impl WorkerPanic {
65    /// Creates a structured panic from an unwound worker payload.
66    pub(crate) fn new(payload: Box<dyn Any + Send + 'static>) -> Self {
67        Self { payload }
68    }
69
70    /// Returns a borrowed message for standard string panic payloads.
71    #[must_use]
72    pub fn message(&self) -> Option<&str> {
73        self.payload
74            .downcast_ref::<String>()
75            .map(String::as_str)
76            .or_else(|| self.payload.downcast_ref::<&'static str>().copied())
77    }
78
79    /// Consumes the error and returns the original panic payload.
80    #[must_use]
81    pub fn into_payload(self) -> Box<dyn Any + Send + 'static> {
82        self.payload
83    }
84
85    /// Resumes unwinding with the original panic payload.
86    pub fn resume_unwind(self) -> ! {
87        panic::resume_unwind(self.into_payload())
88    }
89}
90
91impl fmt::Debug for WorkerPanic {
92    /// Formats the payload without requiring the payload itself to implement
93    /// `Debug`.
94    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
95        formatter
96            .debug_struct("WorkerPanic")
97            .field("message", &self.message())
98            .finish()
99    }
100}
101
102impl fmt::Display for WorkerPanic {
103    /// Formats the panic and includes its standard string message when known.
104    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self.message() {
106            Some(message) => write!(
107                formatter,
108                "background reporter worker panicked: {message}"
109            ),
110            None => formatter.write_str("background reporter worker panicked"),
111        }
112    }
113}
114
115impl Error for WorkerPanic {}