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(
61 "batch task count exceeded: expected {expected}, observed at least {observed_at_least}"
62 )]
63 CountExceeded {
64 /// Declared task count.
65 expected: usize,
66 /// Lower bound of observed tasks. This is typically `expected + 1`
67 /// because the executor stops reading once it confirms the overflow.
68 observed_at_least: usize,
69 /// Outcome accumulated from the tasks that did run.
70 outcome: BatchOutcome<E>,
71 },
72}
73
74impl<E> BatchExecutionError<E> {
75 /// Returns the batch outcome attached to this error.
76 ///
77 /// # Returns
78 ///
79 /// A shared reference to the attached batch outcome.
80 #[inline]
81 pub const fn outcome(&self) -> &BatchOutcome<E> {
82 match self {
83 Self::CountShortfall { outcome, .. } | Self::CountExceeded { outcome, .. } => outcome,
84 }
85 }
86
87 /// Consumes this error and returns the attached batch outcome.
88 ///
89 /// # Returns
90 ///
91 /// The batch outcome accumulated before this error was reported.
92 #[inline]
93 pub fn into_outcome(self) -> BatchOutcome<E> {
94 match self {
95 Self::CountShortfall { outcome, .. } | Self::CountExceeded { outcome, .. } => outcome,
96 }
97 }
98
99 /// Returns whether this error represents a task-count shortfall.
100 ///
101 /// # Returns
102 ///
103 /// `true` if this error is [`Self::CountShortfall`].
104 #[inline]
105 pub const fn is_count_shortfall(&self) -> bool {
106 matches!(self, Self::CountShortfall { .. })
107 }
108
109 /// Returns whether this error represents an oversized task source.
110 ///
111 /// # Returns
112 ///
113 /// `true` if this error is [`Self::CountExceeded`].
114 #[inline]
115 pub const fn is_count_exceeded(&self) -> bool {
116 matches!(self, Self::CountExceeded { .. })
117 }
118}