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