Skip to main content

durable_execution_sdk/
macros.rs

1//! Ergonomic macros for promise combinators.
2//!
3//! This module provides declarative macros (`all!`, `any!`, `race!`, `all_settled!`)
4//! that enable combining heterogeneous futures without manual type erasure.
5//!
6//! # Problem
7//!
8//! The method-based API (`ctx.all()`, etc.) requires all futures in the vector to have
9//! the exact same concrete type. In Rust, each closure has a unique anonymous type,
10//! which means combining different step operations doesn't compile:
11//!
12//! ```rust,ignore
13//! // This doesn't compile - each closure has a unique type!
14//! let futures = vec![
15//!     ctx.step(|_| Ok(1), None),  // Type A
16//!     ctx.step(|_| Ok(2), None),  // Type B (different!)
17//! ];
18//! let results = ctx.all(futures).await?;
19//! ```
20//!
21//! # Solution
22//!
23//! These macros automatically box each future to erase their concrete types,
24//! enabling the common use case of combining different step operations:
25//!
26//! ```rust,ignore
27//! use durable_execution_sdk::all;
28//!
29//! // Clone contexts for each future
30//! let ctx1 = ctx.clone();
31//! let ctx2 = ctx.clone();
32//! let ctx3 = ctx.clone();
33//!
34//! // This works with macros!
35//! let results = all!(ctx,
36//!     async move { ctx1.step(|_| Ok(1), None).await },
37//!     async move { ctx2.step(|_| Ok(2), None).await },
38//!     async move { ctx3.step(|_| Ok(3), None).await },
39//! ).await?;
40//! // results: Vec<i32> = [1, 2, 3]
41//! ```
42//!
43//! # When to Use Macros vs Methods
44//!
45//! ## Use Macros When:
46//!
47//! - **Combining different step operations** - Each closure has a unique type
48//! - **Mixing operation types** - Combining step + wait + callback operations
49//! - **Writing inline futures** - Ad-hoc combinations of different operations
50//! - **Small, fixed number of futures** - Known at compile time
51//!
52//! ```rust,ignore
53//! use durable_execution_sdk::{all, any};
54//!
55//! // Different closures = different types - use macros!
56//! let ctx1 = ctx.clone();
57//! let ctx2 = ctx.clone();
58//! let ctx3 = ctx.clone();
59//!
60//! let results = all!(ctx,
61//!     async move { ctx1.step(|_| fetch_user_data(), None).await },
62//!     async move { ctx2.step(|_| fetch_preferences(), None).await },
63//!     async move { ctx3.step(|_| fetch_notifications(), None).await },
64//! ).await?;
65//!
66//! // Fallback pattern with any!
67//! let ctx1 = ctx.clone();
68//! let ctx2 = ctx.clone();
69//! let ctx3 = ctx.clone();
70//!
71//! let data = any!(ctx,
72//!     async move { ctx1.step(|_| fetch_from_primary(), None).await },
73//!     async move { ctx2.step(|_| fetch_from_secondary(), None).await },
74//!     async move { ctx3.step(|_| fetch_from_cache(), None).await },
75//! ).await?;
76//! ```
77//!
78//! ## Use Methods When:
79//!
80//! - **Processing homogeneous futures from iterators/loops** - Same closure applied to different data
81//! - **Working with pre-boxed futures** - Already type-erased
82//! - **Programmatically generating futures** - Dynamic number of futures from a single source
83//! - **Using `ctx.map()`** - Preferred for iterating over collections
84//!
85//! ```rust,ignore
86//! // Same closure applied to different data - use methods!
87//! let user_ids = vec![1, 2, 3, 4, 5];
88//! let futures: Vec<_> = user_ids
89//!     .into_iter()
90//!     .map(|id| {
91//!         let ctx = ctx.clone();
92//!         async move { ctx.step(move |_| fetch_user(id), None).await }
93//!     })
94//!     .collect();
95//! let users = ctx.all(futures).await?;
96//!
97//! // Or even better, use ctx.map():
98//! let batch = ctx.map(user_ids, |child_ctx, id, _| {
99//!     Box::pin(async move { child_ctx.step(|_| fetch_user(id), None).await })
100//! }, None).await?;
101//! ```
102//!
103//! # Quick Reference
104//!
105//! | Macro | Behavior | Returns | Use Case |
106//! |-------|----------|---------|----------|
107//! | `all!` | Wait for all to succeed | `Vec<T>` or first error | Parallel fetch, batch processing |
108//! | `any!` | First success wins | `T` or combined error | Fallback patterns, redundancy |
109//! | `race!` | First to settle wins | `T` (success or error) | Timeouts, competitive operations |
110//! | `all_settled!` | Wait for all to settle | `BatchResult<T>` | Collect all outcomes, partial success |
111//!
112//! # Available Macros
113//!
114//! - `all!` - Wait for all futures to succeed, return error on first failure
115//! - `any!` - Return first successful result, error only if all fail
116//! - `race!` - Return first result to settle (success or failure)
117//! - `all_settled!` - Wait for all futures to settle, return all outcomes
118
119/// Waits for all futures to complete successfully.
120///
121/// This macro boxes each future to enable combining heterogeneous future types,
122/// then delegates to `ctx.all()`. All futures run concurrently, and the macro
123/// returns when all have completed successfully or when the first error occurs.
124///
125/// # Arguments
126///
127/// * `$ctx` - The [`DurableContext`](crate::DurableContext) instance
128/// * `$fut` - One or more future expressions (comma-separated)
129///
130/// # Returns
131///
132/// * `Ok(Vec<T>)` - All results in the same order as the input futures
133/// * `Err(DurableError)` - The first error encountered (remaining futures are cancelled)
134///
135/// # Examples
136///
137/// ## Basic Usage
138///
139/// ```rust,ignore
140/// use durable_execution_sdk::all;
141///
142/// // Clone contexts for each future to satisfy lifetime requirements
143/// let ctx1 = ctx.clone();
144/// let ctx2 = ctx.clone();
145/// let ctx3 = ctx.clone();
146///
147/// let results = all!(ctx,
148///     async move { ctx1.step(|_| Ok(1), None).await },
149///     async move { ctx2.step(|_| Ok(2), None).await },
150///     async move { ctx3.step(|_| Ok(3), None).await },
151/// ).await?;
152/// assert_eq!(results, vec![1, 2, 3]);
153/// ```
154///
155/// ## Parallel Data Fetching
156///
157/// ```rust,ignore
158/// use durable_execution_sdk::all;
159///
160/// // Clone contexts for parallel operations
161/// let ctx1 = ctx.clone();
162/// let ctx2 = ctx.clone();
163/// let ctx3 = ctx.clone();
164///
165/// // Fetch multiple pieces of data in parallel
166/// let results = all!(ctx,
167///     async move { ctx1.step(|_| fetch_user(user_id), None).await },
168///     async move { ctx2.step(|_| fetch_preferences(user_id), None).await },
169///     async move { ctx3.step(|_| fetch_notifications(user_id), None).await },
170/// ).await?;
171/// let (user, preferences, notifications) = (
172///     results[0].clone(),
173///     results[1].clone(),
174///     results[2].clone()
175/// );
176/// ```
177///
178/// ## Error Handling
179///
180/// ```rust,ignore
181/// use durable_execution_sdk::all;
182///
183/// let ctx1 = ctx.clone();
184/// let ctx2 = ctx.clone();
185/// let ctx3 = ctx.clone();
186///
187/// // If any step fails, the entire operation fails
188/// let result = all!(ctx,
189///     async move { ctx1.step(|_| Ok(1), None).await },
190///     async move { ctx2.step(|_| Err::<i32, _>("failed".into()), None).await },
191///     async move { ctx3.step(|_| Ok(3), None).await },  // This may not execute
192/// ).await;
193///
194/// assert!(result.is_err());
195/// ```
196///
197/// # When to Use
198///
199/// Use `all!` when you need all operations to succeed and want to fail fast
200/// on the first error. For collecting all outcomes (including failures),
201/// use `all_settled!` instead.
202///
203/// # See Also
204///
205/// * `any!` - Return first success
206/// * `race!` - Return first to settle
207/// * `all_settled!` - Collect all outcomes
208#[macro_export]
209macro_rules! all {
210    ($ctx:expr, $($fut:expr),+ $(,)?) => {{
211        let futures: ::std::vec::Vec<
212            ::std::pin::Pin<
213                ::std::boxed::Box<
214                    dyn ::std::future::Future<
215                        Output = $crate::error::DurableResult<_>
216                    > + ::std::marker::Send
217                >
218            >
219        > = ::std::vec![
220            $(::std::boxed::Box::pin($fut)),+
221        ];
222        $ctx.all(futures)
223    }};
224}
225
226/// Returns the first successful result from multiple futures.
227///
228/// This macro boxes each future to enable combining heterogeneous future types,
229/// then delegates to `ctx.any()`. All futures run concurrently, and the macro
230/// returns as soon as any future succeeds. If all futures fail, returns a
231/// combined error.
232///
233/// # Arguments
234///
235/// * `$ctx` - The [`DurableContext`](crate::DurableContext) instance
236/// * `$fut` - One or more future expressions (comma-separated)
237///
238/// # Returns
239///
240/// * `Ok(T)` - The first successful result (remaining futures are cancelled)
241/// * `Err(DurableError)` - Combined error if all futures fail
242///
243/// # Examples
244///
245/// ## Basic Fallback Pattern
246///
247/// ```rust,ignore
248/// use durable_execution_sdk::any;
249///
250/// // Clone contexts for each future
251/// let ctx1 = ctx.clone();
252/// let ctx2 = ctx.clone();
253/// let ctx3 = ctx.clone();
254///
255/// // Try multiple sources, return first success
256/// let data = any!(ctx,
257///     async move { ctx1.step(|_| fetch_from_primary(), None).await },
258///     async move { ctx2.step(|_| fetch_from_secondary(), None).await },
259///     async move { ctx3.step(|_| fetch_from_cache(), None).await },
260/// ).await?;
261/// ```
262///
263/// ## Redundant Service Calls
264///
265/// ```rust,ignore
266/// use durable_execution_sdk::any;
267///
268/// let ctx1 = ctx.clone();
269/// let ctx2 = ctx.clone();
270///
271/// // Call multiple redundant services, use first response
272/// let price = any!(ctx,
273///     async move { ctx1.step(|_| get_price_from_service_a(item_id), None).await },
274///     async move { ctx2.step(|_| get_price_from_service_b(item_id), None).await },
275/// ).await?;
276/// ```
277///
278/// ## Handling All Failures
279///
280/// ```rust,ignore
281/// use durable_execution_sdk::any;
282///
283/// let ctx1 = ctx.clone();
284/// let ctx2 = ctx.clone();
285///
286/// // If all sources fail, get combined error
287/// let result = any!(ctx,
288///     async move { ctx1.step(|_| Err::<String, _>("primary failed".into()), None).await },
289///     async move { ctx2.step(|_| Err::<String, _>("secondary failed".into()), None).await },
290/// ).await;
291///
292/// // Error contains information about all failures
293/// assert!(result.is_err());
294/// ```
295///
296/// # When to Use
297///
298/// Use `any!` for fallback patterns where you want the first successful result
299/// and don't care which source provides it. Unlike `race!`, `any!` ignores
300/// failures and only returns an error if ALL futures fail.
301///
302/// # See Also
303///
304/// * `all!` - Wait for all to succeed
305/// * `race!` - Return first to settle (success or failure)
306/// * `all_settled!` - Collect all outcomes
307#[macro_export]
308macro_rules! any {
309    ($ctx:expr, $($fut:expr),+ $(,)?) => {{
310        let futures: ::std::vec::Vec<
311            ::std::pin::Pin<
312                ::std::boxed::Box<
313                    dyn ::std::future::Future<
314                        Output = $crate::error::DurableResult<_>
315                    > + ::std::marker::Send
316                >
317            >
318        > = ::std::vec![
319            $(::std::boxed::Box::pin($fut)),+
320        ];
321        $ctx.any(futures)
322    }};
323}
324
325/// Returns the result of the first future to settle (success or failure).
326///
327/// This macro boxes each future to enable combining heterogeneous future types,
328/// then delegates to `ctx.race()`. All futures run concurrently, and the macro
329/// returns as soon as any future completes, regardless of whether it succeeded
330/// or failed.
331///
332/// # Arguments
333///
334/// * `$ctx` - The [`DurableContext`](crate::DurableContext) instance
335/// * `$fut` - One or more future expressions (comma-separated)
336///
337/// # Returns
338///
339/// * `Ok(T)` - If the first future to settle succeeded
340/// * `Err(DurableError)` - If the first future to settle failed
341///
342/// # Examples
343///
344/// ## Timeout Pattern
345///
346/// ```rust,ignore
347/// use durable_execution_sdk::race;
348///
349/// let ctx1 = ctx.clone();
350/// let ctx2 = ctx.clone();
351///
352/// // Race between operation and timeout
353/// let result = race!(ctx,
354///     async move { ctx1.step(|_| slow_operation(), None).await },
355///     async move {
356///         ctx2.step(|_| {
357///             std::thread::sleep(std::time::Duration::from_secs(5));
358///             Err::<String, _>("timeout".into())
359///         }, None).await
360///     },
361/// ).await;
362///
363/// match result {
364///     Ok(data) => println!("Operation completed: {}", data),
365///     Err(e) => println!("Timed out or failed: {}", e),
366/// }
367/// ```
368///
369/// ## Competitive Operations
370///
371/// ```rust,ignore
372/// use durable_execution_sdk::race;
373///
374/// let ctx1 = ctx.clone();
375/// let ctx2 = ctx.clone();
376///
377/// // Use whichever service responds first
378/// let result = race!(ctx,
379///     async move { ctx1.step(|_| call_service_a(), None).await },
380///     async move { ctx2.step(|_| call_service_b(), None).await },
381/// ).await?;
382/// ```
383///
384/// ## First Failure Wins
385///
386/// ```rust,ignore
387/// use durable_execution_sdk::race;
388///
389/// let ctx1 = ctx.clone();
390/// let ctx2 = ctx.clone();
391///
392/// // If the first to settle is an error, that error is returned
393/// let result = race!(ctx,
394///     async move { ctx1.step(|_| Err::<i32, _>("fast error".into()), None).await },
395///     async move {
396///         ctx2.step(|_| {
397///             std::thread::sleep(std::time::Duration::from_millis(100));
398///             Ok(42)
399///         }, None).await
400///     },
401/// ).await;
402///
403/// assert!(result.is_err()); // Error settled first
404/// ```
405///
406/// # When to Use
407///
408/// Use `race!` when you want the first result regardless of success or failure.
409/// This is useful for timeout patterns or when competing operations should
410/// cancel each other. Unlike `any!`, `race!` returns immediately on the
411/// first settlement, even if it's a failure.
412///
413/// # See Also
414///
415/// * `all!` - Wait for all to succeed
416/// * `any!` - Return first success (ignores failures)
417/// * `all_settled!` - Collect all outcomes
418#[macro_export]
419macro_rules! race {
420    ($ctx:expr, $($fut:expr),+ $(,)?) => {{
421        let futures: ::std::vec::Vec<
422            ::std::pin::Pin<
423                ::std::boxed::Box<
424                    dyn ::std::future::Future<
425                        Output = $crate::error::DurableResult<_>
426                    > + ::std::marker::Send
427                >
428            >
429        > = ::std::vec![
430            $(::std::boxed::Box::pin($fut)),+
431        ];
432        $ctx.race(futures)
433    }};
434}
435
436/// Waits for all futures to settle, returning all outcomes.
437///
438/// This macro boxes each future to enable combining heterogeneous future types,
439/// then delegates to `ctx.all_settled()`. Unlike `all!`, this macro does not
440/// short-circuit on failure - it waits for all futures to complete and returns
441/// a [`BatchResult`](crate::concurrency::BatchResult) containing all outcomes.
442///
443/// # Arguments
444///
445/// * `$ctx` - The [`DurableContext`](crate::DurableContext) instance
446/// * `$fut` - One or more future expressions (comma-separated)
447///
448/// # Returns
449///
450/// * `Ok(BatchResult<T>)` - Contains outcomes for all futures (never fails)
451///
452/// The [`BatchResult`](crate::concurrency::BatchResult) provides methods to:
453/// - `successes()` / `succeeded()` - Get successful results
454/// - `failures()` / `failed()` - Get failed results
455/// - `success_count()` / `failure_count()` - Count outcomes
456/// - `all_succeeded()` / `has_failures()` - Check overall status
457/// - `get_results()` - Get all successful results or error if any failed
458///
459/// # Examples
460///
461/// ## Collecting All Outcomes
462///
463/// ```rust,ignore
464/// use durable_execution_sdk::all_settled;
465///
466/// let ctx1 = ctx.clone();
467/// let ctx2 = ctx.clone();
468/// let ctx3 = ctx.clone();
469///
470/// let batch = all_settled!(ctx,
471///     async move { ctx1.step(|_| Ok(1), None).await },
472///     async move { ctx2.step(|_| Err::<i32, _>("failed".into()), None).await },
473///     async move { ctx3.step(|_| Ok(3), None).await },
474/// ).await?;
475///
476/// println!("Total: {}", batch.items.len());        // 3
477/// println!("Succeeded: {}", batch.success_count()); // 2
478/// println!("Failed: {}", batch.failure_count());    // 1
479/// ```
480///
481/// ## Processing Successes and Failures Separately
482///
483/// ```rust,ignore
484/// use durable_execution_sdk::all_settled;
485///
486/// let ctx1 = ctx.clone();
487/// let ctx2 = ctx.clone();
488/// let ctx3 = ctx.clone();
489///
490/// let batch = all_settled!(ctx,
491///     async move { ctx1.step(|_| process_item_a(), None).await },
492///     async move { ctx2.step(|_| process_item_b(), None).await },
493///     async move { ctx3.step(|_| process_item_c(), None).await },
494/// ).await?;
495///
496/// // Process successful results
497/// for item in batch.succeeded() {
498///     if let Some(result) = item.get_result() {
499///         println!("Success: {:?}", result);
500///     }
501/// }
502///
503/// // Log failures for retry or investigation
504/// for item in batch.failed() {
505///     if let Some(error) = item.get_error() {
506///         eprintln!("Failed at index {}: {:?}", item.index, error);
507///     }
508/// }
509/// ```
510///
511/// ## Partial Success Handling
512///
513/// ```rust,ignore
514/// use durable_execution_sdk::all_settled;
515///
516/// let ctx1 = ctx.clone();
517/// let ctx2 = ctx.clone();
518/// let ctx3 = ctx.clone();
519///
520/// let batch = all_settled!(ctx,
521///     async move { ctx1.step(|_| send_notification_email(), None).await },
522///     async move { ctx2.step(|_| send_notification_sms(), None).await },
523///     async move { ctx3.step(|_| send_notification_push(), None).await },
524/// ).await?;
525///
526/// if batch.all_succeeded() {
527///     println!("All notifications sent!");
528/// } else {
529///     println!("Sent {} of {} notifications",
530///         batch.success_count(),
531///         batch.items.len()
532///     );
533/// }
534/// ```
535///
536/// ## Order Preservation
537///
538/// Results are returned in the same order as input futures:
539///
540/// ```rust,ignore
541/// use durable_execution_sdk::all_settled;
542///
543/// let ctx1 = ctx.clone();
544/// let ctx2 = ctx.clone();
545/// let ctx3 = ctx.clone();
546///
547/// let batch = all_settled!(ctx,
548///     async move { ctx1.step(|_| Ok("first"), None).await },
549///     async move { ctx2.step(|_| Ok("second"), None).await },
550///     async move { ctx3.step(|_| Ok("third"), None).await },
551/// ).await?;
552///
553/// assert_eq!(batch.items[0].index, 0);
554/// assert_eq!(batch.items[1].index, 1);
555/// assert_eq!(batch.items[2].index, 2);
556/// ```
557///
558/// # When to Use
559///
560/// Use `all_settled!` when you need to:
561/// - Collect all outcomes regardless of individual success/failure
562/// - Implement partial success handling
563/// - Gather errors for logging or retry logic
564/// - Process results even when some operations fail
565///
566/// # See Also
567///
568/// * `all!` - Fail fast on first error
569/// * `any!` - Return first success
570/// * `race!` - Return first to settle
571#[macro_export]
572macro_rules! all_settled {
573    ($ctx:expr, $($fut:expr),+ $(,)?) => {{
574        let futures: ::std::vec::Vec<
575            ::std::pin::Pin<
576                ::std::boxed::Box<
577                    dyn ::std::future::Future<
578                        Output = $crate::error::DurableResult<_>
579                    > + ::std::marker::Send
580                >
581            >
582        > = ::std::vec![
583            $(::std::boxed::Box::pin($fut)),+
584        ];
585        $ctx.all_settled(futures)
586    }};
587}
588
589#[cfg(test)]
590mod tests {
591    use std::future::Future;
592    use std::pin::Pin;
593    use std::sync::Arc;
594
595    use crate::client::{CheckpointResponse, MockDurableServiceClient, SharedDurableServiceClient};
596    use crate::context::TracingLogger;
597    use crate::error::DurableError;
598    use crate::lambda::InitialExecutionState;
599    use crate::state::ExecutionState;
600
601    fn create_mock_client() -> SharedDurableServiceClient {
602        Arc::new(
603            MockDurableServiceClient::new()
604                .with_checkpoint_response(Ok(CheckpointResponse::new("token-1")))
605                .with_checkpoint_response(Ok(CheckpointResponse::new("token-2")))
606                .with_checkpoint_response(Ok(CheckpointResponse::new("token-3")))
607                .with_checkpoint_response(Ok(CheckpointResponse::new("token-4")))
608                .with_checkpoint_response(Ok(CheckpointResponse::new("token-5"))),
609        )
610    }
611
612    fn create_test_state(client: SharedDurableServiceClient) -> Arc<ExecutionState> {
613        Arc::new(ExecutionState::new(
614            "arn:aws:lambda:us-east-1:123456789012:function:test:durable:abc123",
615            "initial-token",
616            InitialExecutionState::new(),
617            client,
618        ))
619    }
620
621    /// A mock context that provides the `all`, `any`, `race`, and `all_settled` methods for testing macros.
622    /// This simulates the DurableContext interface without requiring the full context.
623    struct MockContext {
624        state: Arc<ExecutionState>,
625        logger: Arc<dyn crate::context::Logger>,
626        id_counter: std::sync::atomic::AtomicU64,
627    }
628
629    impl MockContext {
630        fn new(state: Arc<ExecutionState>) -> Self {
631            Self {
632                state,
633                logger: Arc::new(TracingLogger),
634                id_counter: std::sync::atomic::AtomicU64::new(0),
635            }
636        }
637
638        fn next_op_id(&self) -> crate::context::OperationIdentifier {
639            let counter = self
640                .id_counter
641                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
642            crate::context::OperationIdentifier::new(format!("test-op-{}", counter), None, None)
643        }
644
645        /// Simulates the `all` method from DurableContext.
646        /// Returns all results if all futures succeed, or returns the first error.
647        pub async fn all<T, Fut>(&self, futures: Vec<Fut>) -> Result<Vec<T>, DurableError>
648        where
649            T: serde::Serialize + serde::de::DeserializeOwned + Send + Clone + 'static,
650            Fut: Future<Output = Result<T, DurableError>> + Send + 'static,
651        {
652            let op_id = self.next_op_id();
653            crate::handlers::promise::all_handler(futures, &self.state, &op_id, &self.logger).await
654        }
655
656        /// Simulates the `any` method from DurableContext.
657        /// Returns the first successful result, or an error if all fail.
658        pub async fn any<T, Fut>(&self, futures: Vec<Fut>) -> Result<T, DurableError>
659        where
660            T: serde::Serialize + serde::de::DeserializeOwned + Send + Clone + 'static,
661            Fut: Future<Output = Result<T, DurableError>> + Send + 'static,
662        {
663            let op_id = self.next_op_id();
664            crate::handlers::promise::any_handler(futures, &self.state, &op_id, &self.logger).await
665        }
666
667        /// Simulates the `race` method from DurableContext.
668        /// Returns the result of the first future to settle (success or failure).
669        pub async fn race<T, Fut>(&self, futures: Vec<Fut>) -> Result<T, DurableError>
670        where
671            T: serde::Serialize + serde::de::DeserializeOwned + Send + Clone + 'static,
672            Fut: Future<Output = Result<T, DurableError>> + Send + 'static,
673        {
674            let op_id = self.next_op_id();
675            crate::handlers::promise::race_handler(futures, &self.state, &op_id, &self.logger).await
676        }
677
678        /// Simulates the `all_settled` method from DurableContext.
679        /// Returns a BatchResult containing outcomes for all futures.
680        pub async fn all_settled<T, Fut>(
681            &self,
682            futures: Vec<Fut>,
683        ) -> Result<crate::concurrency::BatchResult<T>, DurableError>
684        where
685            T: serde::Serialize + serde::de::DeserializeOwned + Send + Clone + 'static,
686            Fut: Future<Output = Result<T, DurableError>> + Send + 'static,
687        {
688            let op_id = self.next_op_id();
689            crate::handlers::promise::all_settled_handler(
690                futures,
691                &self.state,
692                &op_id,
693                &self.logger,
694            )
695            .await
696        }
697    }
698
699    // =========================================================================
700    // all! macro tests
701    // Validates: Requirements 1.1, 1.2, 1.3, 1.4, 1.5
702    // =========================================================================
703
704    /// Test that all! macro returns Vec<T> with all results in order when all succeed.
705    /// Validates: Requirements 1.1, 1.2, 1.5
706    #[tokio::test]
707    async fn test_all_macro_success_returns_vec_in_order() {
708        let client = create_mock_client();
709        let state = create_test_state(client);
710        let ctx = MockContext::new(state);
711
712        let result = crate::all!(
713            ctx,
714            Box::pin(async { Ok::<_, DurableError>(1) })
715                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
716            Box::pin(async { Ok(2) }),
717            Box::pin(async { Ok(3) }),
718        )
719        .await;
720
721        assert!(result.is_ok());
722        let values = result.unwrap();
723        assert_eq!(values, vec![1, 2, 3]);
724    }
725
726    /// Test that all! macro works with a single future.
727    /// Validates: Requirements 1.4
728    #[tokio::test]
729    async fn test_all_macro_single_future() {
730        let client = create_mock_client();
731        let state = create_test_state(client);
732        let ctx = MockContext::new(state);
733
734        let result = crate::all!(
735            ctx,
736            Box::pin(async { Ok::<_, DurableError>(42) })
737                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
738        )
739        .await;
740
741        assert!(result.is_ok());
742        assert_eq!(result.unwrap(), vec![42]);
743    }
744
745    /// Test that all! macro works with two futures.
746    /// Validates: Requirements 1.4, 1.5
747    #[tokio::test]
748    async fn test_all_macro_two_futures() {
749        let client = create_mock_client();
750        let state = create_test_state(client);
751        let ctx = MockContext::new(state);
752
753        let result = crate::all!(
754            ctx,
755            Box::pin(async { Ok::<_, DurableError>("hello".to_string()) })
756                as Pin<Box<dyn Future<Output = Result<String, DurableError>> + Send>>,
757            Box::pin(async { Ok("world".to_string()) }),
758        )
759        .await;
760
761        assert!(result.is_ok());
762        assert_eq!(
763            result.unwrap(),
764            vec!["hello".to_string(), "world".to_string()]
765        );
766    }
767
768    /// Test that all! macro returns first error when any future fails.
769    /// Validates: Requirements 1.3
770    #[tokio::test]
771    async fn test_all_macro_failure_returns_first_error() {
772        let client = create_mock_client();
773        let state = create_test_state(client);
774        let ctx = MockContext::new(state);
775
776        let result = crate::all!(
777            ctx,
778            Box::pin(async { Ok::<_, DurableError>(1) })
779                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
780            Box::pin(async { Err(DurableError::execution("test error")) }),
781            Box::pin(async { Ok(3) }),
782        )
783        .await;
784
785        assert!(result.is_err());
786        let error = result.unwrap_err();
787        let error_msg = format!("{}", error);
788        assert!(error_msg.contains("failed") || error_msg.contains("error"));
789    }
790
791    /// Test that all! macro returns error when first future fails.
792    /// Validates: Requirements 1.3
793    #[tokio::test]
794    async fn test_all_macro_first_future_fails() {
795        let client = create_mock_client();
796        let state = create_test_state(client);
797        let ctx = MockContext::new(state);
798
799        let result = crate::all!(
800            ctx,
801            Box::pin(async { Err::<i32, _>(DurableError::execution("first error")) })
802                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
803            Box::pin(async { Ok(2) }),
804        )
805        .await;
806
807        assert!(result.is_err());
808    }
809
810    /// Test that all! macro supports trailing comma.
811    /// Validates: Requirements 1.1
812    #[tokio::test]
813    async fn test_all_macro_trailing_comma() {
814        let client = create_mock_client();
815        let state = create_test_state(client);
816        let ctx = MockContext::new(state);
817
818        // Note the trailing comma after the last future
819        let result = crate::all!(
820            ctx,
821            Box::pin(async { Ok::<_, DurableError>(1) })
822                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
823            Box::pin(async { Ok(2) }),
824        )
825        .await;
826
827        assert!(result.is_ok());
828        assert_eq!(result.unwrap(), vec![1, 2]);
829    }
830
831    /// Test that all! macro works with different output types (String).
832    /// Validates: Requirements 1.1, 1.6
833    #[tokio::test]
834    async fn test_all_macro_string_type() {
835        let client = create_mock_client();
836        let state = create_test_state(client);
837        let ctx = MockContext::new(state);
838
839        let result = crate::all!(
840            ctx,
841            Box::pin(async { Ok::<_, DurableError>("a".to_string()) })
842                as Pin<Box<dyn Future<Output = Result<String, DurableError>> + Send>>,
843            Box::pin(async { Ok("b".to_string()) }),
844            Box::pin(async { Ok("c".to_string()) }),
845        )
846        .await;
847
848        assert!(result.is_ok());
849        assert_eq!(
850            result.unwrap(),
851            vec!["a".to_string(), "b".to_string(), "c".to_string()]
852        );
853    }
854
855    /// Property 1: Macro expansion equivalence test for all! macro.
856    /// Verifies that all! macro produces the same result as manually boxing futures.
857    /// Validates: Requirements 1.1, 1.2, 1.3, 1.4, 1.5
858    #[tokio::test]
859    async fn test_all_macro_equivalence_with_manual_boxing() {
860        // Test with macro
861        let client1 = create_mock_client();
862        let state1 = create_test_state(client1);
863        let ctx1 = MockContext::new(state1);
864
865        let macro_result = crate::all!(
866            ctx1,
867            Box::pin(async { Ok::<_, DurableError>(10) })
868                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
869            Box::pin(async { Ok(20) }),
870            Box::pin(async { Ok(30) }),
871        )
872        .await;
873
874        // Test with manual boxing (same as what macro expands to)
875        let client2 = create_mock_client();
876        let state2 = create_test_state(client2);
877        let ctx2 = MockContext::new(state2);
878
879        let futures: Vec<Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>> = vec![
880            Box::pin(async { Ok::<_, DurableError>(10) }),
881            Box::pin(async { Ok(20) }),
882            Box::pin(async { Ok(30) }),
883        ];
884        let manual_result = ctx2.all(futures).await;
885
886        // Both should succeed with the same values
887        assert!(macro_result.is_ok());
888        assert!(manual_result.is_ok());
889        assert_eq!(macro_result.unwrap(), manual_result.unwrap());
890    }
891
892    /// Property 1: Macro expansion equivalence test for all! macro with failure.
893    /// Verifies that all! macro produces the same error behavior as manually boxing futures.
894    /// Validates: Requirements 1.1, 1.3
895    #[tokio::test]
896    async fn test_all_macro_equivalence_with_manual_boxing_failure() {
897        // Test with macro
898        let client1 = create_mock_client();
899        let state1 = create_test_state(client1);
900        let ctx1 = MockContext::new(state1);
901
902        let macro_result = crate::all!(
903            ctx1,
904            Box::pin(async { Ok::<_, DurableError>(1) })
905                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
906            Box::pin(async { Err(DurableError::execution("test error")) }),
907        )
908        .await;
909
910        // Test with manual boxing
911        let client2 = create_mock_client();
912        let state2 = create_test_state(client2);
913        let ctx2 = MockContext::new(state2);
914
915        let futures: Vec<Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>> = vec![
916            Box::pin(async { Ok::<_, DurableError>(1) }),
917            Box::pin(async { Err(DurableError::execution("test error")) }),
918        ];
919        let manual_result = ctx2.all(futures).await;
920
921        // Both should fail
922        assert!(macro_result.is_err());
923        assert!(manual_result.is_err());
924    }
925
926    // =========================================================================
927    // any! macro tests
928    // =========================================================================
929
930    /// Test that any! macro returns the first successful result.
931    /// Validates: Requirements 2.1, 2.2
932    #[tokio::test]
933    async fn test_any_macro_first_success_returned() {
934        let client = create_mock_client();
935        let state = create_test_state(client);
936        let ctx = MockContext::new(state);
937
938        // Use the any! macro with multiple futures where first succeeds
939        let result = crate::any!(
940            ctx,
941            Box::pin(async { Ok::<_, DurableError>(1) })
942                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
943            Box::pin(async { Ok(2) }),
944            Box::pin(async { Ok(3) }),
945        )
946        .await;
947
948        assert!(result.is_ok());
949        let value = result.unwrap();
950        // One of the successful values should be returned
951        assert!(value == 1 || value == 2 || value == 3);
952    }
953
954    /// Test that any! macro returns first success even when some fail.
955    /// Validates: Requirements 2.2, 2.4
956    #[tokio::test]
957    async fn test_any_macro_success_among_failures() {
958        let client = create_mock_client();
959        let state = create_test_state(client);
960        let ctx = MockContext::new(state);
961
962        let result = crate::any!(
963            ctx,
964            Box::pin(async { Err::<i32, _>(DurableError::execution("error 1")) })
965                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
966            Box::pin(async { Ok(42) }),
967            Box::pin(async { Err(DurableError::execution("error 2")) }),
968        )
969        .await;
970
971        assert!(result.is_ok());
972        assert_eq!(result.unwrap(), 42);
973    }
974
975    /// Test that any! macro returns combined error when all futures fail.
976    /// Validates: Requirements 2.3
977    #[tokio::test]
978    async fn test_any_macro_all_failures_returns_combined_error() {
979        let client = create_mock_client();
980        let state = create_test_state(client);
981        let ctx = MockContext::new(state);
982
983        let result = crate::any!(
984            ctx,
985            Box::pin(async { Err::<i32, _>(DurableError::execution("error 1")) })
986                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
987            Box::pin(async { Err(DurableError::execution("error 2")) }),
988            Box::pin(async { Err(DurableError::execution("error 3")) }),
989        )
990        .await;
991
992        assert!(result.is_err());
993        let error = result.unwrap_err();
994        // The error message should contain information about all failures
995        let error_msg = format!("{}", error);
996        assert!(error_msg.contains("All") || error_msg.contains("failed"));
997    }
998
999    /// Test that any! macro works with a single future.
1000    /// Validates: Requirements 2.4
1001    #[tokio::test]
1002    async fn test_any_macro_single_future_success() {
1003        let client = create_mock_client();
1004        let state = create_test_state(client);
1005        let ctx = MockContext::new(state);
1006
1007        let result = crate::any!(
1008            ctx,
1009            Box::pin(async { Ok::<_, DurableError>(99) })
1010                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1011        )
1012        .await;
1013
1014        assert!(result.is_ok());
1015        assert_eq!(result.unwrap(), 99);
1016    }
1017
1018    /// Test that any! macro works with a single failing future.
1019    /// Validates: Requirements 2.3, 2.4
1020    #[tokio::test]
1021    async fn test_any_macro_single_future_failure() {
1022        let client = create_mock_client();
1023        let state = create_test_state(client);
1024        let ctx = MockContext::new(state);
1025
1026        let result = crate::any!(
1027            ctx,
1028            Box::pin(async { Err::<i32, _>(DurableError::execution("single error")) })
1029                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1030        )
1031        .await;
1032
1033        assert!(result.is_err());
1034    }
1035
1036    /// Test that any! macro supports trailing comma.
1037    /// Validates: Requirements 2.1
1038    #[tokio::test]
1039    async fn test_any_macro_trailing_comma() {
1040        let client = create_mock_client();
1041        let state = create_test_state(client);
1042        let ctx = MockContext::new(state);
1043
1044        // Note the trailing comma after the last future
1045        let result = crate::any!(
1046            ctx,
1047            Box::pin(async { Ok::<_, DurableError>(1) })
1048                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1049            Box::pin(async { Ok(2) }),
1050        )
1051        .await;
1052
1053        assert!(result.is_ok());
1054    }
1055
1056    /// Test that any! macro works with different output types (String).
1057    /// Validates: Requirements 2.1, 2.4
1058    #[tokio::test]
1059    async fn test_any_macro_string_type() {
1060        let client = create_mock_client();
1061        let state = create_test_state(client);
1062        let ctx = MockContext::new(state);
1063
1064        let result = crate::any!(
1065            ctx,
1066            Box::pin(async { Ok::<_, DurableError>("hello".to_string()) })
1067                as Pin<Box<dyn Future<Output = Result<String, DurableError>> + Send>>,
1068            Box::pin(async { Ok("world".to_string()) }),
1069        )
1070        .await;
1071
1072        assert!(result.is_ok());
1073        let value = result.unwrap();
1074        assert!(value == "hello" || value == "world");
1075    }
1076
1077    // =========================================================================
1078    // Property 1: Macro expansion equivalence test
1079    // Validates: Requirements 1.1, 2.1, 3.1, 4.1
1080    // =========================================================================
1081
1082    /// Test that any! macro produces the same result as manually boxing futures.
1083    /// This validates that the macro expansion is equivalent to manual boxing.
1084    #[tokio::test]
1085    async fn test_any_macro_equivalence_with_manual_boxing() {
1086        // Test with macro
1087        let client1 = create_mock_client();
1088        let state1 = create_test_state(client1);
1089        let ctx1 = MockContext::new(state1);
1090
1091        let macro_result = crate::any!(
1092            ctx1,
1093            Box::pin(async { Err::<i32, _>(DurableError::execution("fail")) })
1094                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1095            Box::pin(async { Ok(42) }),
1096        )
1097        .await;
1098
1099        // Test with manual boxing (same as what macro expands to)
1100        let client2 = create_mock_client();
1101        let state2 = create_test_state(client2);
1102        let ctx2 = MockContext::new(state2);
1103
1104        let futures: Vec<Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>> = vec![
1105            Box::pin(async { Err::<i32, _>(DurableError::execution("fail")) }),
1106            Box::pin(async { Ok(42) }),
1107        ];
1108        let manual_result = ctx2.any(futures).await;
1109
1110        // Both should succeed with the same value
1111        assert!(macro_result.is_ok());
1112        assert!(manual_result.is_ok());
1113        assert_eq!(macro_result.unwrap(), manual_result.unwrap());
1114    }
1115
1116    // =========================================================================
1117    // race! macro tests
1118    // Validates: Requirements 3.1, 3.2, 3.3
1119    // =========================================================================
1120
1121    /// Test that race! macro returns the first result to settle (success case).
1122    /// Validates: Requirements 3.1, 3.2
1123    #[tokio::test]
1124    async fn test_race_macro_first_success_settles() {
1125        let client = create_mock_client();
1126        let state = create_test_state(client);
1127        let ctx = MockContext::new(state);
1128
1129        // First future completes immediately with success
1130        let result = crate::race!(
1131            ctx,
1132            Box::pin(async { Ok::<_, DurableError>(1) })
1133                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1134            Box::pin(async {
1135                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1136                Ok(2)
1137            }),
1138        )
1139        .await;
1140
1141        assert!(result.is_ok());
1142        // First one should win since it completes immediately
1143        assert_eq!(result.unwrap(), 1);
1144    }
1145
1146    /// Test that race! macro returns the first result to settle (failure case).
1147    /// Validates: Requirements 3.1, 3.2
1148    #[tokio::test]
1149    async fn test_race_macro_first_failure_settles() {
1150        let client = create_mock_client();
1151        let state = create_test_state(client);
1152        let ctx = MockContext::new(state);
1153
1154        // First future completes immediately with failure
1155        let result = crate::race!(
1156            ctx,
1157            Box::pin(async { Err::<i32, _>(DurableError::execution("fast error")) })
1158                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1159            Box::pin(async {
1160                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1161                Ok(2)
1162            }),
1163        )
1164        .await;
1165
1166        // The error should be returned since it settled first
1167        assert!(result.is_err());
1168    }
1169
1170    /// Test that race! macro works with a single future.
1171    /// Validates: Requirements 3.3
1172    #[tokio::test]
1173    async fn test_race_macro_single_future_success() {
1174        let client = create_mock_client();
1175        let state = create_test_state(client);
1176        let ctx = MockContext::new(state);
1177
1178        let result = crate::race!(
1179            ctx,
1180            Box::pin(async { Ok::<_, DurableError>(42) })
1181                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1182        )
1183        .await;
1184
1185        assert!(result.is_ok());
1186        assert_eq!(result.unwrap(), 42);
1187    }
1188
1189    /// Test that race! macro works with a single failing future.
1190    /// Validates: Requirements 3.2, 3.3
1191    #[tokio::test]
1192    async fn test_race_macro_single_future_failure() {
1193        let client = create_mock_client();
1194        let state = create_test_state(client);
1195        let ctx = MockContext::new(state);
1196
1197        let result = crate::race!(
1198            ctx,
1199            Box::pin(async { Err::<i32, _>(DurableError::execution("single error")) })
1200                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1201        )
1202        .await;
1203
1204        assert!(result.is_err());
1205    }
1206
1207    /// Test that race! macro supports trailing comma.
1208    /// Validates: Requirements 3.1
1209    #[tokio::test]
1210    async fn test_race_macro_trailing_comma() {
1211        let client = create_mock_client();
1212        let state = create_test_state(client);
1213        let ctx = MockContext::new(state);
1214
1215        // Note the trailing comma after the last future
1216        let result = crate::race!(
1217            ctx,
1218            Box::pin(async { Ok::<_, DurableError>(1) })
1219                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1220            Box::pin(async { Ok(2) }),
1221        )
1222        .await;
1223
1224        assert!(result.is_ok());
1225    }
1226
1227    /// Test that race! macro works with different output types (String).
1228    /// Validates: Requirements 3.1, 3.4
1229    #[tokio::test]
1230    async fn test_race_macro_string_type() {
1231        let client = create_mock_client();
1232        let state = create_test_state(client);
1233        let ctx = MockContext::new(state);
1234
1235        let result = crate::race!(
1236            ctx,
1237            Box::pin(async { Ok::<_, DurableError>("hello".to_string()) })
1238                as Pin<Box<dyn Future<Output = Result<String, DurableError>> + Send>>,
1239            Box::pin(async {
1240                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1241                Ok("world".to_string())
1242            }),
1243        )
1244        .await;
1245
1246        assert!(result.is_ok());
1247        assert_eq!(result.unwrap(), "hello");
1248    }
1249
1250    /// Test that race! macro works with multiple futures where success settles first.
1251    /// Validates: Requirements 3.1, 3.2, 3.3
1252    #[tokio::test]
1253    async fn test_race_macro_multiple_futures_success_first() {
1254        let client = create_mock_client();
1255        let state = create_test_state(client);
1256        let ctx = MockContext::new(state);
1257
1258        let result = crate::race!(
1259            ctx,
1260            Box::pin(async { Ok::<_, DurableError>(1) })
1261                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1262            Box::pin(async { Ok(2) }),
1263            Box::pin(async { Ok(3) }),
1264        )
1265        .await;
1266
1267        assert!(result.is_ok());
1268        // One of the values should be returned (first to settle)
1269        let value = result.unwrap();
1270        assert!(value == 1 || value == 2 || value == 3);
1271    }
1272
1273    /// Test that race! macro produces the same result as manually boxing futures.
1274    /// This validates that the macro expansion is equivalent to manual boxing.
1275    /// Validates: Requirements 3.1
1276    #[tokio::test]
1277    async fn test_race_macro_equivalence_with_manual_boxing() {
1278        // Test with macro
1279        let client1 = create_mock_client();
1280        let state1 = create_test_state(client1);
1281        let ctx1 = MockContext::new(state1);
1282
1283        let macro_result = crate::race!(
1284            ctx1,
1285            Box::pin(async { Ok::<_, DurableError>(10) })
1286                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1287            Box::pin(async {
1288                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1289                Ok(20)
1290            }),
1291        )
1292        .await;
1293
1294        // Test with manual boxing (same as what macro expands to)
1295        let client2 = create_mock_client();
1296        let state2 = create_test_state(client2);
1297        let ctx2 = MockContext::new(state2);
1298
1299        let futures: Vec<Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>> = vec![
1300            Box::pin(async { Ok::<_, DurableError>(10) }),
1301            Box::pin(async {
1302                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1303                Ok(20)
1304            }),
1305        ];
1306        let manual_result = ctx2.race(futures).await;
1307
1308        // Both should succeed with the same value (first to settle)
1309        assert!(macro_result.is_ok());
1310        assert!(manual_result.is_ok());
1311        assert_eq!(macro_result.unwrap(), manual_result.unwrap());
1312    }
1313
1314    /// Test that race! macro produces the same error behavior as manually boxing futures.
1315    /// Validates: Requirements 3.1, 3.2
1316    #[tokio::test]
1317    async fn test_race_macro_equivalence_with_manual_boxing_failure() {
1318        // Test with macro
1319        let client1 = create_mock_client();
1320        let state1 = create_test_state(client1);
1321        let ctx1 = MockContext::new(state1);
1322
1323        let macro_result = crate::race!(
1324            ctx1,
1325            Box::pin(async { Err::<i32, _>(DurableError::execution("fast error")) })
1326                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1327            Box::pin(async {
1328                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1329                Ok(20)
1330            }),
1331        )
1332        .await;
1333
1334        // Test with manual boxing
1335        let client2 = create_mock_client();
1336        let state2 = create_test_state(client2);
1337        let ctx2 = MockContext::new(state2);
1338
1339        let futures: Vec<Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>> = vec![
1340            Box::pin(async { Err::<i32, _>(DurableError::execution("fast error")) }),
1341            Box::pin(async {
1342                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1343                Ok(20)
1344            }),
1345        ];
1346        let manual_result = ctx2.race(futures).await;
1347
1348        // Both should fail (error settled first)
1349        assert!(macro_result.is_err());
1350        assert!(manual_result.is_err());
1351    }
1352
1353    // =========================================================================
1354    // all_settled! macro tests
1355    // Validates: Requirements 4.1, 4.2, 4.3, 4.4
1356    // =========================================================================
1357
1358    /// Test that all_settled! macro returns BatchResult<T> with all outcomes.
1359    /// Validates: Requirements 4.1, 4.2
1360    #[tokio::test]
1361    async fn test_all_settled_macro_returns_batch_result() {
1362        let client = create_mock_client();
1363        let state = create_test_state(client);
1364        let ctx = MockContext::new(state);
1365
1366        let result = crate::all_settled!(
1367            ctx,
1368            Box::pin(async { Ok::<_, DurableError>(1) })
1369                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1370            Box::pin(async { Ok(2) }),
1371            Box::pin(async { Ok(3) }),
1372        )
1373        .await;
1374
1375        assert!(result.is_ok());
1376        let batch = result.unwrap();
1377        assert_eq!(batch.items.len(), 3);
1378        assert_eq!(batch.success_count(), 3);
1379        assert_eq!(batch.failure_count(), 0);
1380        assert!(batch.all_succeeded());
1381    }
1382
1383    /// Test that all_settled! macro preserves order of results.
1384    /// Property 2: Result order preservation
1385    /// Validates: Requirements 4.4
1386    #[tokio::test]
1387    async fn test_all_settled_macro_preserves_order() {
1388        let client = create_mock_client();
1389        let state = create_test_state(client);
1390        let ctx = MockContext::new(state);
1391
1392        let result = crate::all_settled!(
1393            ctx,
1394            Box::pin(async { Ok::<_, DurableError>(10) })
1395                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1396            Box::pin(async { Ok(20) }),
1397            Box::pin(async { Ok(30) }),
1398        )
1399        .await;
1400
1401        assert!(result.is_ok());
1402        let batch = result.unwrap();
1403
1404        // Verify order is preserved by checking indices and values
1405        assert_eq!(batch.items.len(), 3);
1406        assert_eq!(batch.items[0].index, 0);
1407        assert_eq!(batch.items[0].get_result(), Some(&10));
1408        assert_eq!(batch.items[1].index, 1);
1409        assert_eq!(batch.items[1].get_result(), Some(&20));
1410        assert_eq!(batch.items[2].index, 2);
1411        assert_eq!(batch.items[2].get_result(), Some(&30));
1412    }
1413
1414    /// Test that all_settled! macro collects both successes and failures.
1415    /// Validates: Requirements 4.1, 4.2
1416    #[tokio::test]
1417    async fn test_all_settled_macro_mixed_results() {
1418        let client = create_mock_client();
1419        let state = create_test_state(client);
1420        let ctx = MockContext::new(state);
1421
1422        let result = crate::all_settled!(
1423            ctx,
1424            Box::pin(async { Ok::<_, DurableError>(1) })
1425                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1426            Box::pin(async { Err(DurableError::execution("test error")) }),
1427            Box::pin(async { Ok(3) }),
1428        )
1429        .await;
1430
1431        assert!(result.is_ok());
1432        let batch = result.unwrap();
1433        assert_eq!(batch.items.len(), 3);
1434        assert_eq!(batch.success_count(), 2);
1435        assert_eq!(batch.failure_count(), 1);
1436        assert!(!batch.all_succeeded());
1437        assert!(batch.has_failures());
1438    }
1439
1440    /// Test that all_settled! macro works with a single future.
1441    /// Validates: Requirements 4.3
1442    #[tokio::test]
1443    async fn test_all_settled_macro_single_future() {
1444        let client = create_mock_client();
1445        let state = create_test_state(client);
1446        let ctx = MockContext::new(state);
1447
1448        let result = crate::all_settled!(
1449            ctx,
1450            Box::pin(async { Ok::<_, DurableError>(42) })
1451                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1452        )
1453        .await;
1454
1455        assert!(result.is_ok());
1456        let batch = result.unwrap();
1457        assert_eq!(batch.items.len(), 1);
1458        assert_eq!(batch.success_count(), 1);
1459        assert_eq!(batch.items[0].get_result(), Some(&42));
1460    }
1461
1462    /// Test that all_settled! macro works with a single failing future.
1463    /// Validates: Requirements 4.2, 4.3
1464    #[tokio::test]
1465    async fn test_all_settled_macro_single_failure() {
1466        let client = create_mock_client();
1467        let state = create_test_state(client);
1468        let ctx = MockContext::new(state);
1469
1470        let result = crate::all_settled!(
1471            ctx,
1472            Box::pin(async { Err::<i32, _>(DurableError::execution("single error")) })
1473                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1474        )
1475        .await;
1476
1477        assert!(result.is_ok());
1478        let batch = result.unwrap();
1479        assert_eq!(batch.items.len(), 1);
1480        assert_eq!(batch.failure_count(), 1);
1481        assert!(batch.items[0].is_failed());
1482    }
1483
1484    /// Test that all_settled! macro supports trailing comma.
1485    /// Validates: Requirements 4.1
1486    #[tokio::test]
1487    async fn test_all_settled_macro_trailing_comma() {
1488        let client = create_mock_client();
1489        let state = create_test_state(client);
1490        let ctx = MockContext::new(state);
1491
1492        // Note the trailing comma after the last future
1493        let result = crate::all_settled!(
1494            ctx,
1495            Box::pin(async { Ok::<_, DurableError>(1) })
1496                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1497            Box::pin(async { Ok(2) }),
1498        )
1499        .await;
1500
1501        assert!(result.is_ok());
1502        assert_eq!(result.unwrap().items.len(), 2);
1503    }
1504
1505    /// Test that all_settled! macro works with different output types (String).
1506    /// Validates: Requirements 4.1, 4.5
1507    #[tokio::test]
1508    async fn test_all_settled_macro_string_type() {
1509        let client = create_mock_client();
1510        let state = create_test_state(client);
1511        let ctx = MockContext::new(state);
1512
1513        let result = crate::all_settled!(
1514            ctx,
1515            Box::pin(async { Ok::<_, DurableError>("a".to_string()) })
1516                as Pin<Box<dyn Future<Output = Result<String, DurableError>> + Send>>,
1517            Box::pin(async { Ok("b".to_string()) }),
1518            Box::pin(async { Ok("c".to_string()) }),
1519        )
1520        .await;
1521
1522        assert!(result.is_ok());
1523        let batch = result.unwrap();
1524        assert_eq!(batch.items.len(), 3);
1525        assert_eq!(batch.items[0].get_result(), Some(&"a".to_string()));
1526        assert_eq!(batch.items[1].get_result(), Some(&"b".to_string()));
1527        assert_eq!(batch.items[2].get_result(), Some(&"c".to_string()));
1528    }
1529
1530    /// Test that all_settled! macro produces the same result as manually boxing futures.
1531    /// Property 1: Macro expansion equivalence
1532    /// Validates: Requirements 4.1
1533    #[tokio::test]
1534    async fn test_all_settled_macro_equivalence_with_manual_boxing() {
1535        // Test with macro
1536        let client1 = create_mock_client();
1537        let state1 = create_test_state(client1);
1538        let ctx1 = MockContext::new(state1);
1539
1540        let macro_result = crate::all_settled!(
1541            ctx1,
1542            Box::pin(async { Ok::<_, DurableError>(10) })
1543                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1544            Box::pin(async { Err(DurableError::execution("test error")) }),
1545            Box::pin(async { Ok(30) }),
1546        )
1547        .await;
1548
1549        // Test with manual boxing (same as what macro expands to)
1550        let client2 = create_mock_client();
1551        let state2 = create_test_state(client2);
1552        let ctx2 = MockContext::new(state2);
1553
1554        let futures: Vec<Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>> = vec![
1555            Box::pin(async { Ok::<_, DurableError>(10) }),
1556            Box::pin(async { Err(DurableError::execution("test error")) }),
1557            Box::pin(async { Ok(30) }),
1558        ];
1559        let manual_result = ctx2.all_settled(futures).await;
1560
1561        // Both should succeed with BatchResult
1562        assert!(macro_result.is_ok());
1563        assert!(manual_result.is_ok());
1564
1565        let macro_batch = macro_result.unwrap();
1566        let manual_batch = manual_result.unwrap();
1567
1568        // Both should have same counts
1569        assert_eq!(macro_batch.items.len(), manual_batch.items.len());
1570        assert_eq!(macro_batch.success_count(), manual_batch.success_count());
1571        assert_eq!(macro_batch.failure_count(), manual_batch.failure_count());
1572    }
1573
1574    /// Test that all_settled! macro collects all failures without short-circuiting.
1575    /// Validates: Requirements 4.1, 4.2
1576    #[tokio::test]
1577    async fn test_all_settled_macro_all_failures() {
1578        let client = create_mock_client();
1579        let state = create_test_state(client);
1580        let ctx = MockContext::new(state);
1581
1582        let result = crate::all_settled!(
1583            ctx,
1584            Box::pin(async { Err::<i32, _>(DurableError::execution("error 1")) })
1585                as Pin<Box<dyn Future<Output = Result<i32, DurableError>> + Send>>,
1586            Box::pin(async { Err(DurableError::execution("error 2")) }),
1587            Box::pin(async { Err(DurableError::execution("error 3")) }),
1588        )
1589        .await;
1590
1591        // all_settled should still succeed (it collects all outcomes)
1592        assert!(result.is_ok());
1593        let batch = result.unwrap();
1594        assert_eq!(batch.items.len(), 3);
1595        assert_eq!(batch.success_count(), 0);
1596        assert_eq!(batch.failure_count(), 3);
1597        assert!(!batch.all_succeeded());
1598    }
1599}