use std::{
fmt,
time::Duration,
};
use super::{
batch_execution_result_build_error::BatchExecutionResultBuildError,
batch_task_error::BatchTaskError,
batch_task_failure::BatchTaskFailure,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BatchExecutionResult<E> {
task_count: usize,
completed_count: usize,
succeeded_count: usize,
failed_count: usize,
panicked_count: usize,
elapsed: Duration,
failures: Vec<BatchTaskFailure<E>>,
}
impl<E> BatchExecutionResult<E> {
#[inline]
pub fn try_new(
task_count: usize,
completed_count: usize,
succeeded_count: usize,
failed_count: usize,
panicked_count: usize,
elapsed: Duration,
failures: Vec<BatchTaskFailure<E>>,
) -> Result<Self, BatchExecutionResultBuildError> {
validate_result_invariants(
task_count,
completed_count,
succeeded_count,
failed_count,
panicked_count,
&failures,
)?;
let mut failures = failures;
failures.sort_by_key(|failure| failure.index());
Ok(Self {
task_count,
completed_count,
succeeded_count,
failed_count,
panicked_count,
elapsed,
failures,
})
}
#[inline]
#[track_caller]
pub(crate) fn from_validated_parts(
task_count: usize,
completed_count: usize,
succeeded_count: usize,
failed_count: usize,
panicked_count: usize,
elapsed: Duration,
failures: Vec<BatchTaskFailure<E>>,
) -> Self {
Self::try_new(
task_count,
completed_count,
succeeded_count,
failed_count,
panicked_count,
elapsed,
failures,
)
.expect("batch execution result invariants must hold")
}
#[inline]
pub const fn task_count(&self) -> usize {
self.task_count
}
#[inline]
pub const fn completed_count(&self) -> usize {
self.completed_count
}
#[inline]
pub const fn succeeded_count(&self) -> usize {
self.succeeded_count
}
#[inline]
pub const fn failed_count(&self) -> usize {
self.failed_count
}
#[inline]
pub const fn panicked_count(&self) -> usize {
self.panicked_count
}
#[inline]
pub const fn failure_count(&self) -> usize {
self.failed_count + self.panicked_count
}
#[inline]
pub const fn elapsed(&self) -> Duration {
self.elapsed
}
#[inline]
pub fn failures(&self) -> &[BatchTaskFailure<E>] {
self.failures.as_slice()
}
#[inline]
pub const fn is_success(&self) -> bool {
self.completed_count == self.task_count
&& self.failed_count == 0
&& self.panicked_count == 0
}
#[inline]
pub fn into_failures(self) -> Vec<BatchTaskFailure<E>> {
self.failures
}
}
impl<E> fmt::Display for BatchExecutionResult<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"BatchExecutionResult {{ task_count: {}, completed_count: {}, succeeded_count: {}, failed_count: {}, panicked_count: {}, elapsed: {:?} }}",
self.task_count(),
self.completed_count(),
self.succeeded_count(),
self.failed_count(),
self.panicked_count(),
self.elapsed(),
)
}
}
fn validate_result_invariants<E>(
task_count: usize,
completed_count: usize,
succeeded_count: usize,
failed_count: usize,
panicked_count: usize,
failures: &[BatchTaskFailure<E>],
) -> Result<(), BatchExecutionResultBuildError> {
let failure_count = failed_count.checked_add(panicked_count).ok_or(
BatchExecutionResultBuildError::FailureCountOverflow {
failed_count,
panicked_count,
},
)?;
let terminal_count = succeeded_count.checked_add(failure_count).ok_or(
BatchExecutionResultBuildError::TerminalCountOverflow {
succeeded_count,
failure_count,
},
)?;
if completed_count > task_count {
return Err(BatchExecutionResultBuildError::CompletedCountExceeded {
task_count,
completed_count,
});
}
if terminal_count != completed_count {
return Err(BatchExecutionResultBuildError::TerminalCountMismatch {
completed_count,
terminal_count,
succeeded_count,
failed_count,
panicked_count,
});
}
if failures.len() != failure_count {
return Err(BatchExecutionResultBuildError::FailureDetailCountMismatch {
expected: failure_count,
actual: failures.len(),
});
}
validate_failure_details(task_count, failed_count, panicked_count, failures)
}
fn validate_failure_details<E>(
task_count: usize,
failed_count: usize,
panicked_count: usize,
failures: &[BatchTaskFailure<E>],
) -> Result<(), BatchExecutionResultBuildError> {
let mut observed_failed_count = 0usize;
let mut observed_panicked_count = 0usize;
for failure in failures {
if failure.index() >= task_count {
return Err(BatchExecutionResultBuildError::FailureIndexOutOfRange {
index: failure.index(),
task_count,
});
}
match failure.error() {
BatchTaskError::Failed(_) => observed_failed_count += 1,
BatchTaskError::Panicked { .. } => observed_panicked_count += 1,
}
}
if observed_failed_count != failed_count || observed_panicked_count != panicked_count {
return Err(
BatchExecutionResultBuildError::FailureVariantCountMismatch {
expected_failed: failed_count,
actual_failed: observed_failed_count,
expected_panicked: panicked_count,
actual_panicked: observed_panicked_count,
},
);
}
Ok(())
}