Skip to main content

qubit_batch/process/
chunked_batch_process_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 super::BatchProcessResult;
13
14/// Error returned by [`crate::ChunkedBatchProcessor`].
15///
16/// Count-mismatch variants carry the aggregate result accumulated before the
17/// mismatch was detected. `ChunkFailed` carries the delegate error plus the
18/// aggregate result collected before the failing chunk. `InvalidChunkResult`
19/// means the delegate returned `Ok`, but the returned `item_count` or
20/// `completed_count` did not match the submitted chunk length.
21///
22/// ```rust
23/// use std::time::Duration;
24///
25/// use qubit_batch::{
26///     BatchProcessResult,
27///     ChunkedBatchProcessError,
28/// };
29///
30/// let result = BatchProcessResult::builder(4)
31///     .completed_count(2)
32///     .processed_count(2)
33///     .chunk_count(1)
34///     .elapsed(Duration::ZERO)
35///     .build()
36///     .expect("process result counters should be valid");
37/// let error: ChunkedBatchProcessError<&'static str> =
38///     ChunkedBatchProcessError::ChunkFailed {
39///         chunk_index: 1,
40///         start_index: 2,
41///         chunk_len: 2,
42///         source: "insert failed",
43///         result,
44///     };
45///
46/// assert_eq!(error.result().processed_count(), 2);
47/// ```
48///
49/// # Type Parameters
50///
51/// * `E` - Error type returned by the delegate processor.
52///
53#[derive(Debug, Clone, Error, PartialEq, Eq)]
54pub enum ChunkedBatchProcessError<E> {
55    /// The input source ended before the declared item count was reached.
56    #[error("batch item count shortfall: expected {expected}, actual {actual}")]
57    CountShortfall {
58        /// Declared item count.
59        expected: usize,
60        /// Actual number of items observed from the source.
61        actual: usize,
62        /// Result accumulated before the shortfall was reported.
63        result: BatchProcessResult,
64    },
65
66    /// The input source yielded more items than the declared item count.
67    #[error("batch item count exceeded: expected {expected}, observed at least {observed_at_least}")]
68    CountExceeded {
69        /// Declared item count.
70        expected: usize,
71        /// Lower bound of observed items.
72        observed_at_least: usize,
73        /// Result accumulated before the excess item was observed.
74        result: BatchProcessResult,
75    },
76
77    /// The delegate processor failed while processing one chunk.
78    #[error("batch chunk {chunk_index} failed at item {start_index} with {chunk_len} items")]
79    ChunkFailed {
80        /// Zero-based chunk index.
81        chunk_index: usize,
82        /// Zero-based source item index where this chunk starts.
83        start_index: usize,
84        /// Number of items submitted in this chunk.
85        chunk_len: usize,
86        /// Error returned by the delegate processor.
87        source: E,
88        /// Result accumulated before this chunk failed.
89        result: BatchProcessResult,
90    },
91
92    /// The delegate returned `Ok` with counters that do not describe the
93    /// submitted chunk.
94    ///
95    /// A successful chunk delegate call must report both `item_count` and
96    /// `completed_count` equal to `chunk_len`. A lower `processed_count` is
97    /// allowed, but partial chunk completion should be represented by delegate
98    /// failure instead of an inconsistent success result.
99    #[error(
100        "batch chunk {chunk_index} returned invalid result at item {start_index}: expected {chunk_len} completed items, got item_count {item_count}, completed_count {completed_count}"
101    )]
102    InvalidChunkResult {
103        /// Zero-based chunk index.
104        chunk_index: usize,
105        /// Zero-based source item index where this chunk starts.
106        start_index: usize,
107        /// Number of items submitted in this chunk.
108        chunk_len: usize,
109        /// Delegate-reported declared item count.
110        item_count: usize,
111        /// Delegate-reported completed item count.
112        completed_count: usize,
113        /// Result accumulated before this invalid chunk result was reported.
114        result: BatchProcessResult,
115    },
116}
117
118impl<E> ChunkedBatchProcessError<E> {
119    /// Returns the partial result attached to this error.
120    ///
121    /// # Returns
122    ///
123    /// A shared reference to the partial batch process result.
124    #[inline]
125    pub const fn result(&self) -> &BatchProcessResult {
126        match self {
127            Self::CountShortfall { result, .. }
128            | Self::CountExceeded { result, .. }
129            | Self::ChunkFailed { result, .. }
130            | Self::InvalidChunkResult { result, .. } => result,
131        }
132    }
133
134    /// Consumes this error and returns its partial result.
135    ///
136    /// # Returns
137    ///
138    /// The partial batch process result.
139    #[inline]
140    pub fn into_result(self) -> BatchProcessResult {
141        match self {
142            Self::CountShortfall { result, .. }
143            | Self::CountExceeded { result, .. }
144            | Self::ChunkFailed { result, .. }
145            | Self::InvalidChunkResult { result, .. } => result,
146        }
147    }
148}