dynamic_cli/context/
traits.rs

1//! Execution context traits
2//!
3//! This module defines the core traits for managing execution context
4//! in CLI/REPL applications. The execution context is shared state
5//! that persists across command executions.
6//!
7//! # Design Philosophy
8//!
9//! The context system uses Rust's type system to provide:
10//! - **Type safety**: Contexts are strongly typed
11//! - **Flexibility**: Each application defines its own context type
12//! - **Thread safety**: Contexts must be `Send + Sync`
13//! - **Ergonomic downcasting**: Helper methods for type conversion
14//!
15//! # Example
16//!
17//! ```
18//! use dynamic_cli::context::{ExecutionContext, downcast_mut};
19//! use std::any::Any;
20//!
21//! // Define your application's context
22//! #[derive(Default)]
23//! struct MyContext {
24//!     counter: u32,
25//!     data: Vec<String>,
26//! }
27//!
28//! // Implement the ExecutionContext trait
29//! impl ExecutionContext for MyContext {
30//!     fn as_any(&self) -> &dyn Any {
31//!         self
32//!     }
33//!
34//!     fn as_any_mut(&mut self) -> &mut dyn Any {
35//!         self
36//!     }
37//! }
38//!
39//! // Use the context with downcasting
40//! fn use_context(ctx: &mut dyn ExecutionContext) {
41//!     // Downcast to concrete type
42//!     if let Some(my_ctx) = downcast_mut::<MyContext>(ctx) {
43//!         my_ctx.counter += 1;
44//!         my_ctx.data.push("Hello".to_string());
45//!     }
46//! }
47//! ```
48
49use std::any::Any;
50
51/// Execution context trait
52///
53/// This trait defines the interface for application-specific execution contexts.
54/// The context is shared state that persists across command executions in
55/// both CLI and REPL modes.
56///
57/// # Thread Safety
58///
59/// Contexts must implement `Send + Sync` to support:
60/// - Multi-threaded command execution
61/// - Async runtime compatibility
62/// - Future extensibility
63///
64/// # Type Erasure and Downcasting
65///
66/// Since the framework doesn't know the concrete context type at compile time,
67/// we use the `Any` trait for type-safe runtime downcasting. The `as_any()`
68/// and `as_any_mut()` methods enable converting the trait object back to
69/// the concrete type.
70///
71/// # Implementation Guide
72///
73/// Implementing this trait is straightforward - just return `self`:
74///
75/// ```
76/// use dynamic_cli::context::ExecutionContext;
77/// use std::any::Any;
78///
79/// struct MyContext {
80///     // Your application state
81///     config: String,
82/// }
83///
84/// impl ExecutionContext for MyContext {
85///     fn as_any(&self) -> &dyn Any {
86///         self
87///     }
88///
89///     fn as_any_mut(&mut self) -> &mut dyn Any {
90///         self
91///     }
92/// }
93/// ```
94///
95/// # Usage in Command Handlers
96///
97/// Command handlers receive a `&mut dyn ExecutionContext` and must
98/// downcast it to their expected type:
99///
100/// ```
101/// use dynamic_cli::context::{ExecutionContext, downcast_mut};
102/// use std::collections::HashMap;///
103/// #
104/// use rustyline::Context;
105///
106/// struct MyContext { value: i32 }
107/// # impl ExecutionContext for MyContext {
108/// #     fn as_any(&self) -> &dyn std::any::Any { self }
109/// #     fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
110/// # }
111/// #
112/// fn my_handler(
113///     context: &mut dyn ExecutionContext,
114///     args: &HashMap<String, String>,
115/// ) -> Result<(), Box<dyn std::error::Error>> {
116///     // Downcast to concrete type
117///     let ctx = downcast_mut::<MyContext>(context)
118///         .ok_or("Invalid context type")?;
119///
120///     // Use the context
121///     ctx.value += 1;
122///
123///     Ok(())
124/// }
125/// ```
126pub trait ExecutionContext: Send + Sync {
127    /// Convert this context to an `Any` trait object
128    ///
129    /// This method enables type-safe downcasting from the trait object
130    /// back to the concrete type. The framework uses this internally
131    /// to support generic command handlers.
132    ///
133    /// # Implementation
134    ///
135    /// Simply return `self` - the compiler handles the conversion:
136    ///
137    /// ```
138    /// # use dynamic_cli::context::ExecutionContext;
139    /// # use std::any::Any;
140    /// # struct MyContext;
141    /// impl ExecutionContext for MyContext {
142    ///     fn as_any(&self) -> &dyn Any {
143    ///         self  // Just return self
144    ///     }
145    ///     # fn as_any_mut(&mut self) -> &mut dyn Any { self }
146    /// }
147    /// ```
148    ///
149    /// # Returns
150    ///
151    /// A reference to this object as an `Any` trait object
152    fn as_any(&self) -> &dyn Any;
153
154    /// Convert this context to a mutable `Any` trait object
155    ///
156    /// This is the mutable version of [`as_any()`](Self::as_any),
157    /// enabling command handlers to modify the context state.
158    ///
159    /// # Implementation
160    ///
161    /// Simply return `self`:
162    ///
163    /// ```
164    /// # use dynamic_cli::context::ExecutionContext;
165    /// # use std::any::Any;
166    /// # struct MyContext;
167    /// impl ExecutionContext for MyContext {
168    ///     # fn as_any(&self) -> &dyn Any { self }
169    ///     fn as_any_mut(&mut self) -> &mut dyn Any {
170    ///         self  // Just return self
171    ///     }
172    /// }
173    /// ```
174    ///
175    /// # Returns
176    ///
177    /// A mutable reference to this object as an `Any` trait object
178    fn as_any_mut(&mut self) -> &mut dyn Any;
179}
180
181/// Attempt to downcast a context reference to a concrete type
182///
183/// This function safely converts a trait object reference to a
184/// reference of the concrete type `T`. If the context is not
185/// of type `T`, it returns `None`.
186///
187/// # Type Parameters
188///
189/// * `T` - The concrete type to downcast to. Must implement
190///   `ExecutionContext` and have a `'static` lifetime.
191///
192/// # Arguments
193///
194/// * `context` - Reference to the execution context trait object
195///
196/// # Returns
197///
198/// - `Some(&T)` if the context is of type `T`
199/// - `None` if the context is a different type
200///
201/// # Example
202///
203/// ```
204/// use dynamic_cli::context::{ExecutionContext, downcast_ref};
205/// use std::any::Any;
206///
207/// struct DatabaseContext {
208///     connection_string: String,
209/// }
210///
211/// impl ExecutionContext for DatabaseContext {
212///     fn as_any(&self) -> &dyn Any { self }
213///     fn as_any_mut(&mut self) -> &mut dyn Any { self }
214/// }
215///
216/// fn get_connection(ctx: &dyn ExecutionContext) -> Option<&str> {
217///     downcast_ref::<DatabaseContext>(ctx)
218///         .map(|db| db.connection_string.as_str())
219/// }
220/// ```
221///
222/// # Safety
223///
224/// This operation is safe because it uses Rust's `Any::downcast_ref`,
225/// which performs runtime type checking.
226pub fn downcast_ref<T: ExecutionContext + 'static>(context: &dyn ExecutionContext) -> Option<&T> {
227    context.as_any().downcast_ref::<T>()
228}
229
230/// Attempt to downcast a mutable context reference to a concrete type
231///
232/// This is the mutable version of [`downcast_ref()`]. It allows command
233/// handlers to modify the context state after downcasting.
234///
235/// # Type Parameters
236///
237/// * `T` - The concrete type to downcast to. Must implement
238///   `ExecutionContext` and have a `'static` lifetime.
239///
240/// # Arguments
241///
242/// * `context` - Mutable reference to the execution context trait object
243///
244/// # Returns
245///
246/// - `Some(&mut T)` if the context is of type `T`
247/// - `None` if the context is a different type
248///
249/// # Example
250///
251/// ```
252/// use dynamic_cli::context::{ExecutionContext, downcast_mut};
253/// use std::any::Any;
254///
255/// struct Counter {
256///     value: u32,
257/// }
258///
259/// impl ExecutionContext for Counter {
260///     fn as_any(&self) -> &dyn Any { self }
261///     fn as_any_mut(&mut self) -> &mut dyn Any { self }
262/// }
263///
264/// fn increment(ctx: &mut dyn ExecutionContext) {
265///     if let Some(counter) = downcast_mut::<Counter>(ctx) {
266///         counter.value += 1;
267///     }
268/// }
269/// ```
270///
271/// # Safety
272///
273/// This operation is safe because it uses Rust's `Any::downcast_mut`,
274/// which performs runtime type checking.
275pub fn downcast_mut<T: ExecutionContext + 'static>(
276    context: &mut dyn ExecutionContext,
277) -> Option<&mut T> {
278    context.as_any_mut().downcast_mut::<T>()
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    /// Test context type for basic operations
286    #[derive(Default, Debug, PartialEq)]
287    struct TestContext {
288        value: i32,
289        name: String,
290    }
291
292    impl ExecutionContext for TestContext {
293        fn as_any(&self) -> &dyn Any {
294            self
295        }
296
297        fn as_any_mut(&mut self) -> &mut dyn Any {
298            self
299        }
300    }
301
302    /// Alternative context type for testing type mismatch
303    #[derive(Default)]
304    struct OtherContext {
305        data: Vec<u8>,
306    }
307
308    impl ExecutionContext for OtherContext {
309        fn as_any(&self) -> &dyn Any {
310            self
311        }
312
313        fn as_any_mut(&mut self) -> &mut dyn Any {
314            self
315        }
316    }
317
318    #[test]
319    fn test_basic_context_implementation() {
320        let ctx = TestContext {
321            value: 42,
322            name: "test".to_string(),
323        };
324
325        // Verify the context was created correctly
326        assert_eq!(ctx.value, 42);
327        assert_eq!(ctx.name, "test");
328    }
329
330    #[test]
331    fn test_as_any_conversion() {
332        let ctx = TestContext::default();
333
334        // Convert to Any
335        let any_ref = ctx.as_any();
336
337        // Verify we can downcast back
338        let downcasted = any_ref.downcast_ref::<TestContext>();
339        assert!(downcasted.is_some());
340        assert_eq!(downcasted.unwrap().value, 0);
341    }
342
343    #[test]
344    fn test_as_any_mut_conversion() {
345        let mut ctx = TestContext::default();
346
347        // Convert to mutable Any
348        let any_mut = ctx.as_any_mut();
349
350        // Verify we can downcast back and modify
351        if let Some(test_ctx) = any_mut.downcast_mut::<TestContext>() {
352            test_ctx.value = 100;
353        }
354
355        assert_eq!(ctx.value, 100);
356    }
357
358    #[test]
359    fn test_downcast_ref_success() {
360        let ctx = TestContext {
361            value: 42,
362            name: "test".to_string(),
363        };
364
365        // Create trait object
366        let ctx_ref: &dyn ExecutionContext = &ctx;
367
368        // Downcast to concrete type
369        let downcasted = downcast_ref::<TestContext>(ctx_ref);
370
371        assert!(downcasted.is_some());
372        let concrete = downcasted.unwrap();
373        assert_eq!(concrete.value, 42);
374        assert_eq!(concrete.name, "test");
375    }
376
377    #[test]
378    fn test_downcast_ref_failure() {
379        let ctx = TestContext::default();
380
381        // Create trait object
382        let ctx_ref: &dyn ExecutionContext = &ctx;
383
384        // Try to downcast to wrong type
385        let downcasted = downcast_ref::<OtherContext>(ctx_ref);
386
387        // Should fail because TestContext is not OtherContext
388        assert!(downcasted.is_none());
389    }
390
391    #[test]
392    fn test_downcast_mut_success() {
393        let mut ctx = TestContext {
394            value: 10,
395            name: "initial".to_string(),
396        };
397
398        // Create mutable trait object
399        let ctx_mut: &mut dyn ExecutionContext = &mut ctx;
400
401        // Downcast and modify
402        if let Some(concrete) = downcast_mut::<TestContext>(ctx_mut) {
403            concrete.value = 20;
404            concrete.name = "modified".to_string();
405        }
406
407        // Verify modifications
408        assert_eq!(ctx.value, 20);
409        assert_eq!(ctx.name, "modified");
410    }
411
412    #[test]
413    fn test_downcast_mut_failure() {
414        let mut ctx = TestContext::default();
415
416        // Create mutable trait object
417        let ctx_mut: &mut dyn ExecutionContext = &mut ctx;
418
419        // Try to downcast to wrong type
420        let downcasted = downcast_mut::<OtherContext>(ctx_mut);
421
422        // Should fail because TestContext is not OtherContext
423        assert!(downcasted.is_none());
424    }
425
426    #[test]
427    fn test_multiple_downcasts() {
428        let mut ctx = TestContext::default();
429
430        // First downcast
431        {
432            let ctx_mut: &mut dyn ExecutionContext = &mut ctx;
433            if let Some(concrete) = downcast_mut::<TestContext>(ctx_mut) {
434                concrete.value = 1;
435            }
436        }
437
438        // Second downcast
439        {
440            let ctx_mut: &mut dyn ExecutionContext = &mut ctx;
441            if let Some(concrete) = downcast_mut::<TestContext>(ctx_mut) {
442                concrete.value += 1;
443            }
444        }
445
446        assert_eq!(ctx.value, 2);
447    }
448
449    #[test]
450    fn test_context_is_send_sync() {
451        // This test verifies that ExecutionContext requires Send + Sync
452        fn assert_send<T: Send>() {}
453        fn assert_sync<T: Sync>() {}
454
455        assert_send::<TestContext>();
456        assert_sync::<TestContext>();
457
458        // Verify the trait object is also Send + Sync
459        assert_send::<Box<dyn ExecutionContext>>();
460        assert_sync::<Box<dyn ExecutionContext>>();
461    }
462
463    #[test]
464    fn test_realistic_handler_scenario() {
465        // Simulate a command handler pattern
466        fn handler(ctx: &mut dyn ExecutionContext, increment: i32) -> Result<(), String> {
467            let concrete = downcast_mut::<TestContext>(ctx).ok_or("Invalid context type")?;
468
469            concrete.value += increment;
470            Ok(())
471        }
472
473        let mut ctx = TestContext::default();
474
475        // Execute handler multiple times
476        handler(&mut ctx, 10).unwrap();
477        handler(&mut ctx, 20).unwrap();
478        handler(&mut ctx, 30).unwrap();
479
480        assert_eq!(ctx.value, 60);
481    }
482
483    #[test]
484    fn test_handler_with_wrong_context_type() {
485        fn handler(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
486            downcast_mut::<TestContext>(ctx).ok_or("Wrong context type")?;
487            Ok(())
488        }
489
490        let mut ctx = OtherContext::default();
491
492        // Should fail because we're passing OtherContext
493        let result = handler(&mut ctx);
494        assert!(result.is_err());
495        assert_eq!(result.unwrap_err(), "Wrong context type");
496    }
497
498    /// Test context with complex state
499    #[derive(Default)]
500    struct ComplexContext {
501        counters: std::collections::HashMap<String, u64>,
502        flags: Vec<bool>,
503        optional_data: Option<String>,
504    }
505
506    impl ExecutionContext for ComplexContext {
507        fn as_any(&self) -> &dyn Any {
508            self
509        }
510
511        fn as_any_mut(&mut self) -> &mut dyn Any {
512            self
513        }
514    }
515
516    #[test]
517    fn test_complex_context_operations() {
518        let mut ctx = ComplexContext::default();
519
520        // Insert some data
521        ctx.counters.insert("visits".to_string(), 0);
522        ctx.flags.push(true);
523        ctx.optional_data = Some("data".to_string());
524
525        // Use as trait object
526        let ctx_ref: &mut dyn ExecutionContext = &mut ctx;
527
528        // Downcast and modify
529        if let Some(complex) = downcast_mut::<ComplexContext>(ctx_ref) {
530            *complex.counters.get_mut("visits").unwrap() += 1;
531            complex.flags[0] = false;
532            complex.optional_data = None;
533        }
534
535        // Verify changes
536        assert_eq!(*ctx.counters.get("visits").unwrap(), 1);
537        assert_eq!(ctx.flags[0], false);
538        assert!(ctx.optional_data.is_none());
539    }
540
541    #[test]
542    fn test_pattern_matching_with_downcast() {
543        let mut ctx = TestContext {
544            value: 0,
545            name: String::new(),
546        };
547
548        let ctx_ref: &mut dyn ExecutionContext = &mut ctx;
549
550        // Pattern matching style
551        match downcast_mut::<TestContext>(ctx_ref) {
552            Some(test_ctx) => {
553                test_ctx.value = 42;
554                test_ctx.name = "success".to_string();
555            }
556            None => panic!("Downcast failed"),
557        }
558
559        assert_eq!(ctx.value, 42);
560        assert_eq!(ctx.name, "success");
561    }
562
563    #[test]
564    fn test_option_combinators_with_downcast() {
565        let ctx = TestContext {
566            value: 100,
567            name: "test".to_string(),
568        };
569
570        let ctx_ref: &dyn ExecutionContext = &ctx;
571
572        // Using Option combinators
573        let value = downcast_ref::<TestContext>(ctx_ref)
574            .map(|c| c.value)
575            .unwrap_or(0);
576
577        assert_eq!(value, 100);
578    }
579
580    #[test]
581    fn test_default_implementation() {
582        // Test that Default works correctly
583        let ctx = TestContext::default();
584
585        assert_eq!(ctx.value, 0);
586        assert_eq!(ctx.name, "");
587    }
588}