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::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}