use core::fmt;
use std::marker::PhantomData;
use crate::types::Outcome;
use crate::types::cancel::CancelReason;
use crate::types::outcome::PanicPayload;
#[derive(Debug)]
pub struct FirstOk<T, E> {
_t: PhantomData<T>,
_e: PhantomData<E>,
}
impl<T, E> FirstOk<T, E> {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
_t: PhantomData,
_e: PhantomData,
}
}
}
impl<T, E> Default for FirstOk<T, E> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum FirstOkError<E> {
AllFailed {
errors: Vec<E>,
attempted: usize,
},
Cancelled {
reason: CancelReason,
errors_before_cancel: Vec<E>,
attempted_before_cancel: usize,
},
Panicked(PanicPayload),
Empty,
}
impl<E: fmt::Display> fmt::Display for FirstOkError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AllFailed { errors, attempted } => {
write!(
f,
"all {} operations failed: {} errors",
attempted,
errors.len()
)
}
Self::Cancelled {
reason,
errors_before_cancel,
attempted_before_cancel,
} => {
write!(
f,
"cancelled after {} attempts ({} errors): {}",
attempted_before_cancel,
errors_before_cancel.len(),
reason
)
}
Self::Panicked(p) => write!(f, "operation panicked: {p}"),
Self::Empty => write!(f, "no operations provided"),
}
}
}
impl<E: fmt::Debug + fmt::Display> std::error::Error for FirstOkError<E> {}
#[derive(Debug, Clone)]
pub enum FirstOkFailure<E> {
Error(E),
Cancelled(CancelReason),
Panicked(PanicPayload),
}
impl<E: fmt::Display> fmt::Display for FirstOkFailure<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Error(e) => write!(f, "error: {e}"),
Self::Cancelled(r) => write!(f, "cancelled: {r}"),
Self::Panicked(p) => write!(f, "panicked: {p}"),
}
}
}
#[derive(Debug)]
pub struct FirstOkResult<T, E> {
pub success: Option<FirstOkSuccess<T>>,
pub failures: Vec<(usize, FirstOkFailure<E>)>,
pub total: usize,
pub was_cancelled: bool,
pub had_panic: bool,
}
#[derive(Debug)]
pub struct FirstOkSuccess<T> {
pub index: usize,
pub value: T,
}
impl<T, E> FirstOkResult<T, E> {
#[must_use]
pub fn success(
index: usize,
value: T,
failures: Vec<(usize, FirstOkFailure<E>)>,
total: usize,
) -> Self {
Self {
success: Some(FirstOkSuccess { index, value }),
failures,
total,
was_cancelled: false,
had_panic: false,
}
}
#[must_use]
pub fn failure(failures: Vec<(usize, FirstOkFailure<E>)>, total: usize) -> Self {
let was_cancelled = failures
.iter()
.any(|(_, f)| matches!(f, FirstOkFailure::Cancelled(_)));
let had_panic = failures
.iter()
.any(|(_, f)| matches!(f, FirstOkFailure::Panicked(_)));
Self {
success: None,
failures,
total,
was_cancelled,
had_panic,
}
}
#[must_use]
pub const fn is_success(&self) -> bool {
self.success.is_some()
}
#[must_use]
pub fn attempts(&self) -> usize {
self.success
.as_ref()
.map_or(self.failures.len(), |s| s.index + 1)
}
}
#[must_use]
pub fn first_ok_outcomes<T, E>(outcomes: Vec<Outcome<T, E>>) -> FirstOkResult<T, E> {
let total = outcomes.len();
if total == 0 {
return FirstOkResult::failure(Vec::new(), 0);
}
let mut failures = Vec::with_capacity(total);
for (i, outcome) in outcomes.into_iter().enumerate() {
match outcome {
Outcome::Ok(v) => {
return FirstOkResult::success(i, v, failures, total);
}
Outcome::Err(e) => {
failures.push((i, FirstOkFailure::Error(e)));
}
Outcome::Cancelled(r) => {
failures.push((i, FirstOkFailure::Cancelled(r)));
return FirstOkResult::failure(failures, total);
}
Outcome::Panicked(p) => {
failures.push((i, FirstOkFailure::Panicked(p)));
return FirstOkResult::failure(failures, total);
}
}
}
FirstOkResult::failure(failures, total)
}
pub fn first_ok_to_result<T, E>(result: FirstOkResult<T, E>) -> Result<T, FirstOkError<E>> {
if let Some(success) = result.success {
return Ok(success.value);
}
if result.total == 0 {
return Err(FirstOkError::Empty);
}
for (_, failure) in &result.failures {
if let FirstOkFailure::Panicked(p) = failure {
return Err(FirstOkError::Panicked(p.clone()));
}
}
if let Some(cancel_idx) = result
.failures
.iter()
.position(|(_, f)| matches!(f, FirstOkFailure::Cancelled(_)))
{
let _attempted = result.failures.len();
let mut failures = result.failures;
let cancel_failure = failures.remove(cancel_idx);
let FirstOkFailure::Cancelled(reason) = cancel_failure.1 else {
unreachable!()
};
let errors_before: Vec<E> = failures
.into_iter()
.take(cancel_idx)
.filter_map(|(_, f)| match f {
FirstOkFailure::Error(e) => Some(e),
_ => None,
})
.collect();
return Err(FirstOkError::Cancelled {
reason,
errors_before_cancel: errors_before,
attempted_before_cancel: cancel_idx, });
}
let attempted = result.failures.len();
let errors: Vec<E> = result
.failures
.into_iter()
.filter_map(|(_, f)| match f {
FirstOkFailure::Error(e) => Some(e),
_ => None,
})
.collect();
Err(FirstOkError::AllFailed { errors, attempted })
}
#[must_use]
pub const fn first_ok_still_possible(
was_cancelled: bool,
had_panic: bool,
remaining: usize,
) -> bool {
!was_cancelled && !had_panic && remaining > 0
}
#[macro_export]
macro_rules! first_ok {
(@count $head:expr $(, $tail:expr)*) => {
1usize $(+ $crate::first_ok!(@count $tail))*
};
($($operation:expr),+ $(,)?) => {{
async move {
let __first_ok_total = $crate::first_ok!(@count $($operation),+);
let mut __first_ok_failures = ::std::vec::Vec::with_capacity(__first_ok_total);
let mut __first_ok_index = 0usize;
$(
match ($operation).await {
$crate::types::Outcome::Ok(__first_ok_value) => {
return $crate::combinator::first_ok::FirstOkResult::success(
__first_ok_index,
__first_ok_value,
__first_ok_failures,
__first_ok_total,
);
}
$crate::types::Outcome::Err(__first_ok_error) => {
__first_ok_failures.push((
__first_ok_index,
$crate::combinator::first_ok::FirstOkFailure::Error(__first_ok_error),
));
}
$crate::types::Outcome::Cancelled(__first_ok_reason) => {
__first_ok_failures.push((
__first_ok_index,
$crate::combinator::first_ok::FirstOkFailure::Cancelled(__first_ok_reason),
));
return $crate::combinator::first_ok::FirstOkResult::failure(
__first_ok_failures,
__first_ok_total,
);
}
$crate::types::Outcome::Panicked(__first_ok_payload) => {
__first_ok_failures.push((
__first_ok_index,
$crate::combinator::first_ok::FirstOkFailure::Panicked(__first_ok_payload),
));
return $crate::combinator::first_ok::FirstOkResult::failure(
__first_ok_failures,
__first_ok_total,
);
}
}
__first_ok_index += 1;
)+
$crate::combinator::first_ok::FirstOkResult::failure(
__first_ok_failures,
__first_ok_total,
)
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn first_ok_first_succeeds() {
let outcomes: Vec<Outcome<i32, &str>> =
vec![Outcome::Ok(1), Outcome::Ok(2), Outcome::Ok(3)];
let result = first_ok_outcomes(outcomes);
assert!(result.is_success());
let success = result.success.unwrap();
assert_eq!(success.index, 0);
assert_eq!(success.value, 1);
assert!(result.failures.is_empty());
}
#[test]
fn first_ok_middle_succeeds() {
let outcomes: Vec<Outcome<i32, &str>> =
vec![Outcome::Err("e1"), Outcome::Ok(42), Outcome::Err("e3")];
let result = first_ok_outcomes(outcomes);
assert!(result.is_success());
let success = result.success.unwrap();
assert_eq!(success.index, 1);
assert_eq!(success.value, 42);
assert_eq!(result.failures.len(), 1); }
#[test]
fn first_ok_last_succeeds() {
let outcomes: Vec<Outcome<i32, &str>> =
vec![Outcome::Err("e1"), Outcome::Err("e2"), Outcome::Ok(99)];
let result = first_ok_outcomes(outcomes);
assert!(result.is_success());
let success = result.success.unwrap();
assert_eq!(success.index, 2);
assert_eq!(success.value, 99);
assert_eq!(result.failures.len(), 2);
}
#[test]
fn first_ok_all_fail() {
let outcomes: Vec<Outcome<i32, &str>> =
vec![Outcome::Err("e1"), Outcome::Err("e2"), Outcome::Err("e3")];
let result = first_ok_outcomes(outcomes);
assert!(!result.is_success());
assert!(result.success.is_none());
assert_eq!(result.failures.len(), 3);
}
#[test]
fn first_ok_empty() {
let outcomes: Vec<Outcome<i32, &str>> = vec![];
let result = first_ok_outcomes(outcomes);
assert!(!result.is_success());
assert_eq!(result.total, 0);
}
#[test]
fn first_ok_single_success() {
let outcomes: Vec<Outcome<i32, &str>> = vec![Outcome::Ok(42)];
let result = first_ok_outcomes(outcomes);
assert!(result.is_success());
assert_eq!(result.success.unwrap().value, 42);
}
#[test]
fn first_ok_single_failure() {
let outcomes: Vec<Outcome<i32, &str>> = vec![Outcome::Err("fail")];
let result = first_ok_outcomes(outcomes);
assert!(!result.is_success());
assert_eq!(result.failures.len(), 1);
}
#[test]
fn first_ok_cancellation_stops() {
let outcomes: Vec<Outcome<i32, &str>> = vec![
Outcome::Err("e1"),
Outcome::Cancelled(CancelReason::timeout()),
Outcome::Ok(42), ];
let result = first_ok_outcomes(outcomes);
assert!(!result.is_success());
assert!(result.was_cancelled);
assert_eq!(result.failures.len(), 2);
}
#[test]
fn first_ok_panic_stops() {
let outcomes: Vec<Outcome<i32, &str>> = vec![
Outcome::Err("e1"),
Outcome::Panicked(PanicPayload::new("boom")),
Outcome::Ok(42), ];
let result = first_ok_outcomes(outcomes);
assert!(!result.is_success());
assert!(result.had_panic);
assert_eq!(result.failures.len(), 2);
}
#[test]
fn first_ok_to_result_success() {
let outcomes: Vec<Outcome<i32, &str>> = vec![Outcome::Err("e1"), Outcome::Ok(42)];
let result = first_ok_outcomes(outcomes);
let value = first_ok_to_result(result);
assert!(value.is_ok());
assert_eq!(value.unwrap(), 42);
}
#[test]
fn first_ok_to_result_all_failed() {
let outcomes: Vec<Outcome<i32, &str>> = vec![Outcome::Err("e1"), Outcome::Err("e2")];
let result = first_ok_outcomes(outcomes);
let value = first_ok_to_result(result);
assert!(value.is_err());
match value.unwrap_err() {
FirstOkError::AllFailed { errors, attempted } => {
assert_eq!(errors.len(), 2);
assert_eq!(attempted, 2);
}
_ => panic!("Expected AllFailed"),
}
}
#[test]
fn first_ok_to_result_cancelled() {
let outcomes: Vec<Outcome<i32, &str>> = vec![
Outcome::Err("e1"),
Outcome::Cancelled(CancelReason::timeout()),
];
let result = first_ok_outcomes(outcomes);
let value = first_ok_to_result(result);
assert!(value.is_err());
match value.unwrap_err() {
FirstOkError::Cancelled {
errors_before_cancel,
attempted_before_cancel,
..
} => {
assert_eq!(errors_before_cancel.len(), 1);
assert_eq!(attempted_before_cancel, 1);
}
_ => panic!("Expected Cancelled"),
}
}
#[test]
fn first_ok_to_result_panicked() {
let outcomes: Vec<Outcome<i32, &str>> = vec![Outcome::Panicked(PanicPayload::new("boom"))];
let result = first_ok_outcomes(outcomes);
let value = first_ok_to_result(result);
assert!(value.is_err());
assert!(matches!(value.unwrap_err(), FirstOkError::Panicked(_)));
}
#[test]
fn first_ok_to_result_empty() {
let outcomes: Vec<Outcome<i32, &str>> = vec![];
let result = first_ok_outcomes(outcomes);
let value = first_ok_to_result(result);
assert!(value.is_err());
assert!(matches!(value.unwrap_err(), FirstOkError::Empty));
}
#[test]
fn first_ok_error_display() {
let err: FirstOkError<&str> = FirstOkError::AllFailed {
errors: vec!["e1", "e2"],
attempted: 2,
};
assert!(err.to_string().contains("2 operations failed"));
let err: FirstOkError<&str> = FirstOkError::Cancelled {
reason: CancelReason::timeout(),
errors_before_cancel: vec!["e1"],
attempted_before_cancel: 2,
};
assert!(err.to_string().contains("cancelled"));
let err: FirstOkError<&str> = FirstOkError::Panicked(PanicPayload::new("boom"));
assert!(err.to_string().contains("panicked"));
let err: FirstOkError<&str> = FirstOkError::Empty;
assert!(err.to_string().contains("no operations"));
}
#[test]
fn first_ok_still_possible_test() {
assert!(first_ok_still_possible(false, false, 3));
assert!(!first_ok_still_possible(false, false, 0));
assert!(!first_ok_still_possible(true, false, 3));
assert!(!first_ok_still_possible(false, true, 3));
assert!(!first_ok_still_possible(true, true, 3));
}
#[test]
fn first_ok_attempts_count() {
let outcomes: Vec<Outcome<i32, &str>> =
vec![Outcome::Err("e1"), Outcome::Ok(42), Outcome::Err("e3")];
let result = first_ok_outcomes(outcomes);
assert_eq!(result.attempts(), 2);
let outcomes: Vec<Outcome<i32, &str>> = vec![Outcome::Err("e1"), Outcome::Err("e2")];
let result = first_ok_outcomes(outcomes);
assert_eq!(result.attempts(), 2);
}
#[test]
fn first_ok_preserves_failure_indices() {
let outcomes: Vec<Outcome<i32, &str>> =
vec![Outcome::Err("e0"), Outcome::Err("e1"), Outcome::Ok(2)];
let result = first_ok_outcomes(outcomes);
assert!(result.is_success());
assert_eq!(result.failures.len(), 2);
assert_eq!(result.failures[0].0, 0);
assert_eq!(result.failures[1].0, 1);
}
#[test]
fn first_ok_short_circuits() {
let outcomes1: Vec<Outcome<i32, &str>> =
vec![Outcome::Err("e"), Outcome::Ok(1), Outcome::Ok(2)];
let outcomes2: Vec<Outcome<i32, &str>> =
vec![Outcome::Err("e"), Outcome::Ok(1), Outcome::Err("ignored")];
let r1 = first_ok_outcomes(outcomes1);
let r2 = first_ok_outcomes(outcomes2);
assert_eq!(
r1.success.as_ref().unwrap().value,
r2.success.as_ref().unwrap().value
);
assert_eq!(
r1.success.as_ref().unwrap().index,
r2.success.as_ref().unwrap().index
);
}
#[test]
fn first_ok_macro_returns_first_success() {
let result = futures_lite::future::block_on(first_ok!(
async { Outcome::<i32, &str>::Err("e1") },
async { Outcome::<i32, &str>::Ok(42) },
async { Outcome::<i32, &str>::Err("never reached") },
));
assert!(result.is_success());
let success = result.success.expect("success expected");
assert_eq!(success.index, 1);
assert_eq!(success.value, 42);
assert_eq!(result.failures.len(), 1);
}
#[test]
fn first_ok_macro_short_circuits_late_operations() {
let touched = Arc::new(AtomicUsize::new(0));
let touched_late = Arc::clone(&touched);
let result = futures_lite::future::block_on(first_ok!(
async { Outcome::<i32, &str>::Err("e1") },
async { Outcome::<i32, &str>::Ok(7) },
async move {
touched_late.fetch_add(1, Ordering::Relaxed);
Outcome::<i32, &str>::Err("late")
},
));
assert!(result.is_success());
assert_eq!(touched.load(Ordering::Relaxed), 0);
}
#[test]
fn first_ok_macro_stops_on_cancelled() {
let result = futures_lite::future::block_on(first_ok!(
async { Outcome::<i32, &str>::Err("e1") },
async { Outcome::<i32, &str>::Cancelled(CancelReason::timeout()) },
async { Outcome::<i32, &str>::Ok(99) },
));
assert!(!result.is_success());
assert!(result.was_cancelled);
assert_eq!(result.failures.len(), 2);
}
#[test]
fn first_ok_err_after_success_not_collected() {
let outcomes: Vec<Outcome<i32, &str>> =
vec![Outcome::Ok(1), Outcome::Err("e2"), Outcome::Err("e3")];
let result = first_ok_outcomes(outcomes);
assert!(result.is_success());
assert!(result.failures.is_empty());
}
#[test]
fn first_ok_vs_race_semantic_difference() {
let outcomes: Vec<Outcome<i32, &str>> = vec![Outcome::Err("fast_error"), Outcome::Ok(42)];
let result = first_ok_outcomes(outcomes);
assert!(result.is_success());
assert_eq!(result.success.unwrap().value, 42);
}
#[test]
fn first_ok_error_debug_clone() {
let e: FirstOkError<&str> = FirstOkError::AllFailed {
errors: vec!["e1", "e2"],
attempted: 2,
};
let e2 = e.clone();
let dbg = format!("{e:?}");
assert!(dbg.contains("AllFailed"));
let dbg2 = format!("{e2:?}");
assert!(dbg2.contains("AllFailed"));
}
#[test]
fn first_ok_failure_debug_clone() {
let f: FirstOkFailure<&str> = FirstOkFailure::Error("msg");
let f2 = f.clone();
let dbg = format!("{f:?}");
assert!(dbg.contains("Error"));
let dbg2 = format!("{f2:?}");
assert!(dbg2.contains("Error"));
}
}