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(
68        "batch item count exceeded: expected {expected}, observed at least {observed_at_least}"
69    )]
70    CountExceeded {
71        /// Declared item count.
72        expected: usize,
73        /// Lower bound of observed items.
74        observed_at_least: usize,
75        /// Result accumulated before the excess item was observed.
76        result: BatchProcessResult,
77    },
78
79    /// The delegate processor failed while processing one chunk.
80    #[error("batch chunk {chunk_index} failed at item {start_index} with {chunk_len} items")]
81    ChunkFailed {
82        /// Zero-based chunk index.
83        chunk_index: usize,
84        /// Zero-based source item index where this chunk starts.
85        start_index: usize,
86        /// Number of items submitted in this chunk.
87        chunk_len: usize,
88        /// Error returned by the delegate processor.
89        source: E,
90        /// Result accumulated before this chunk failed.
91        result: BatchProcessResult,
92    },
93
94    /// The delegate returned `Ok` with counters that do not describe the
95    /// submitted chunk.
96    ///
97    /// A successful chunk delegate call must report both `item_count` and
98    /// `completed_count` equal to `chunk_len`. A lower `processed_count` is
99    /// allowed, but partial chunk completion should be represented by delegate
100    /// failure instead of an inconsistent success result.
101    #[error(
102        "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}"
103    )]
104    InvalidChunkResult {
105        /// Zero-based chunk index.
106        chunk_index: usize,
107        /// Zero-based source item index where this chunk starts.
108        start_index: usize,
109        /// Number of items submitted in this chunk.
110        chunk_len: usize,
111        /// Delegate-reported declared item count.
112        item_count: usize,
113        /// Delegate-reported completed item count.
114        completed_count: usize,
115        /// Result accumulated before this invalid chunk result was reported.
116        result: BatchProcessResult,
117    },
118}
119
120impl<E> ChunkedBatchProcessError<E> {
121    /// Returns the partial result attached to this error.
122    ///
123    /// # Returns
124    ///
125    /// A shared reference to the partial batch process result.
126    #[inline]
127    pub const fn result(&self) -> &BatchProcessResult {
128        match self {
129            Self::CountShortfall { result, .. }
130            | Self::CountExceeded { result, .. }
131            | Self::ChunkFailed { result, .. }
132            | Self::InvalidChunkResult { result, .. } => result,
133        }
134    }
135
136    /// Consumes this error and returns its partial result.
137    ///
138    /// # Returns
139    ///
140    /// The partial batch process result.
141    #[inline]
142    pub fn into_result(self) -> BatchProcessResult {
143        match self {
144            Self::CountShortfall { result, .. }
145            | Self::CountExceeded { result, .. }
146            | Self::ChunkFailed { result, .. }
147            | Self::InvalidChunkResult { result, .. } => result,
148        }
149    }
150}