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