qubit_batch/execute/batch_outcome.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 std::{
11 fmt,
12 time::Duration,
13};
14
15use qubit_progress::model::ProgressCounter;
16
17use crate::{
18 BatchOutcomeBuilder,
19 BatchTaskFailure,
20};
21
22use super::EXECUTION_PROGRESS_METRIC_ID;
23
24/// Final or partial outcome produced by one batch execution.
25///
26/// Create outcomes through [`BatchOutcomeBuilder::build`] so counters and
27/// failure details are validated before the outcome exists.
28///
29/// ```rust
30/// use qubit_batch::{
31/// BatchOutcome,
32/// BatchOutcomeBuilder,
33/// };
34///
35/// let outcome: BatchOutcome<&'static str> = BatchOutcomeBuilder::builder(2)
36/// .completed_count(2)
37/// .succeeded_count(2)
38/// .build()
39/// .expect("outcome counters should be consistent");
40///
41/// assert!(outcome.is_success());
42/// assert_eq!(outcome.failure_count(), 0);
43/// ```
44///
45/// ```compile_fail
46/// use qubit_batch::{
47/// BatchOutcome,
48/// BatchOutcomeBuilder,
49/// };
50///
51/// let builder = BatchOutcomeBuilder::<&'static str>::builder(1);
52/// let _outcome = BatchOutcome::new(builder);
53/// ```
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct BatchOutcome<E> {
56 /// Declared task count for this batch.
57 task_count: usize,
58 /// Number of tasks that reached a terminal outcome.
59 completed_count: usize,
60 /// Number of tasks that completed successfully.
61 succeeded_count: usize,
62 /// Number of tasks that returned their own error.
63 failed_count: usize,
64 /// Number of tasks that panicked.
65 panicked_count: usize,
66 /// Total monotonic elapsed duration for the batch.
67 elapsed: Duration,
68 /// Detailed failure records sorted by task index.
69 failures: Vec<BatchTaskFailure<E>>,
70}
71
72impl<E> BatchOutcome<E> {
73 /// Creates a new batch outcome from a validated builder.
74 ///
75 /// # Parameters
76 ///
77 /// * `builder` - Validated outcome builder carrying all outcome fields.
78 ///
79 /// # Returns
80 ///
81 /// A fully populated batch outcome.
82 #[inline]
83 pub(crate) fn new(builder: BatchOutcomeBuilder<E>) -> Self {
84 Self {
85 task_count: builder.task_count,
86 completed_count: builder.completed_count,
87 succeeded_count: builder.succeeded_count,
88 failed_count: builder.failed_count,
89 panicked_count: builder.panicked_count,
90 elapsed: builder.elapsed,
91 failures: builder.failures,
92 }
93 }
94
95 /// Returns the declared task count for this batch.
96 ///
97 /// # Returns
98 ///
99 /// The expected number of tasks supplied by the caller.
100 #[inline]
101 pub const fn task_count(&self) -> usize {
102 self.task_count
103 }
104
105 /// Returns how many tasks reached a terminal outcome.
106 ///
107 /// # Returns
108 ///
109 /// The number of completed tasks.
110 #[inline]
111 pub const fn completed_count(&self) -> usize {
112 self.completed_count
113 }
114
115 /// Returns how many tasks completed successfully.
116 ///
117 /// # Returns
118 ///
119 /// The number of successful tasks.
120 #[inline]
121 pub const fn succeeded_count(&self) -> usize {
122 self.succeeded_count
123 }
124
125 /// Returns how many tasks returned their own error.
126 ///
127 /// # Returns
128 ///
129 /// The number of failed tasks.
130 #[inline]
131 pub const fn failed_count(&self) -> usize {
132 self.failed_count
133 }
134
135 /// Returns how many tasks panicked.
136 ///
137 /// # Returns
138 ///
139 /// The number of panicked tasks.
140 #[inline]
141 pub const fn panicked_count(&self) -> usize {
142 self.panicked_count
143 }
144
145 /// Returns the total number of task failures.
146 ///
147 /// # Returns
148 ///
149 /// Failed plus panicked task count.
150 #[inline]
151 pub const fn failure_count(&self) -> usize {
152 self.failed_count + self.panicked_count
153 }
154
155 /// Builds progress counters from this outcome for terminal progress reporting.
156 ///
157 /// # Returns
158 ///
159 /// A single task counter with total set to [`Self::task_count`], completed to
160 /// [`Self::completed_count`], succeeded to [`Self::succeeded_count`], and
161 /// failed to [`Self::failure_count`] (errors plus panics). Active count
162 /// stays zero because the batch has finished.
163 #[inline]
164 pub fn progress_counters(&self) -> Vec<ProgressCounter> {
165 vec![
166 ProgressCounter::new(EXECUTION_PROGRESS_METRIC_ID)
167 .total(self.task_count() as u64)
168 .completed(self.completed_count() as u64)
169 .succeeded(self.succeeded_count() as u64)
170 .failed(self.failure_count() as u64),
171 ]
172 }
173
174 /// Returns the total monotonic elapsed duration.
175 ///
176 /// # Returns
177 ///
178 /// The elapsed duration for this batch execution.
179 #[inline]
180 pub const fn elapsed(&self) -> Duration {
181 self.elapsed
182 }
183
184 /// Returns the detailed failure records collected during execution.
185 ///
186 /// # Returns
187 ///
188 /// A shared slice of task failure records.
189 #[inline]
190 pub fn failures(&self) -> &[BatchTaskFailure<E>] {
191 self.failures.as_slice()
192 }
193
194 /// Returns whether every task completed successfully.
195 ///
196 /// # Returns
197 ///
198 /// `true` if the batch has no failures and every declared task completed.
199 #[inline]
200 pub const fn is_success(&self) -> bool {
201 self.completed_count == self.task_count && self.failed_count == 0 && self.panicked_count == 0
202 }
203
204 /// Consumes this outcome and returns its failure list.
205 ///
206 /// # Returns
207 ///
208 /// The detailed failure records collected during execution.
209 #[inline]
210 pub fn into_failures(self) -> Vec<BatchTaskFailure<E>> {
211 self.failures
212 }
213}
214
215impl<E> fmt::Display for BatchOutcome<E> {
216 /// Formats a concise summary of this batch outcome.
217 ///
218 /// # Parameters
219 ///
220 /// * `formatter` - Formatter receiving the summary text.
221 ///
222 /// # Returns
223 ///
224 /// The formatting result.
225 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
226 write!(
227 formatter,
228 "BatchOutcome {{ task_count: {}, completed_count: {}, succeeded_count: {}, failed_count: {}, panicked_count: {}, elapsed: {:?} }}",
229 self.task_count(),
230 self.completed_count(),
231 self.succeeded_count(),
232 self.failed_count(),
233 self.panicked_count(),
234 self.elapsed(),
235 )
236 }
237}