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}