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}