Skip to main content

qubit_batch/execute/
batch_execution_error.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use thiserror::Error;
11
12use crate::BatchOutcome;
13
14/// Batch-level error returned when the batch contract is violated.
15///
16/// Task failures are reported through [`BatchOutcome`], not through
17/// this enum. This error is reserved for situations such as declared task-count
18/// mismatches.
19///
20/// ```rust
21/// use qubit_batch::{
22///     BatchExecutionError,
23///     BatchExecutor,
24///     SequentialBatchExecutor,
25/// };
26///
27/// let error = SequentialBatchExecutor::new()
28///     .for_each_with_count([10, 20], 3, |_value| Ok::<(), &'static str>(()))
29///     .expect_err("iterator should yield fewer items than declared");
30///
31/// assert!(error.is_count_shortfall());
32/// assert_eq!(error.outcome().completed_count(), 2);
33/// match error {
34///     BatchExecutionError::CountShortfall { expected, actual, .. } => {
35///         assert_eq!(expected, 3);
36///         assert_eq!(actual, 2);
37///     }
38///     BatchExecutionError::CountExceeded { .. } => unreachable!(),
39/// }
40/// ```
41///
42/// # Type Parameters
43///
44/// * `E` - The task-specific error type stored inside the attached outcome.
45///
46#[derive(Debug, Clone, Error, PartialEq, Eq)]
47pub enum BatchExecutionError<E> {
48    /// The task source ended before the declared task count was reached.
49    #[error("batch task count shortfall: expected {expected}, actual {actual}")]
50    CountShortfall {
51        /// Declared task count.
52        expected: usize,
53        /// Actual number of tasks observed from the source.
54        actual: usize,
55        /// Outcome accumulated from the tasks that did run.
56        outcome: BatchOutcome<E>,
57    },
58
59    /// The task source yielded more tasks than the declared task count.
60    #[error("batch task count exceeded: expected {expected}, observed at least {observed_at_least}")]
61    CountExceeded {
62        /// Declared task count.
63        expected: usize,
64        /// Lower bound of observed tasks. This is typically `expected + 1`
65        /// because the executor stops reading once it confirms the overflow.
66        observed_at_least: usize,
67        /// Outcome accumulated from the tasks that did run.
68        outcome: BatchOutcome<E>,
69    },
70}
71
72impl<E> BatchExecutionError<E> {
73    /// Returns the batch outcome attached to this error.
74    ///
75    /// # Returns
76    ///
77    /// A shared reference to the attached batch outcome.
78    #[inline]
79    pub const fn outcome(&self) -> &BatchOutcome<E> {
80        match self {
81            Self::CountShortfall { outcome, .. } | Self::CountExceeded { outcome, .. } => outcome,
82        }
83    }
84
85    /// Consumes this error and returns the attached batch outcome.
86    ///
87    /// # Returns
88    ///
89    /// The batch outcome accumulated before this error was reported.
90    #[inline]
91    pub fn into_outcome(self) -> BatchOutcome<E> {
92        match self {
93            Self::CountShortfall { outcome, .. } | Self::CountExceeded { outcome, .. } => outcome,
94        }
95    }
96
97    /// Returns whether this error represents a task-count shortfall.
98    ///
99    /// # Returns
100    ///
101    /// `true` if this error is [`Self::CountShortfall`].
102    #[inline]
103    pub const fn is_count_shortfall(&self) -> bool {
104        matches!(self, Self::CountShortfall { .. })
105    }
106
107    /// Returns whether this error represents an oversized task source.
108    ///
109    /// # Returns
110    ///
111    /// `true` if this error is [`Self::CountExceeded`].
112    #[inline]
113    pub const fn is_count_exceeded(&self) -> bool {
114        matches!(self, Self::CountExceeded { .. })
115    }
116}