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}