Skip to main content

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}