Skip to main content

dynamic_cli/context/
mod.rs

1//! Execution context module
2//!
3//! This module provides traits and utilities for managing execution context
4//! in CLI/REPL applications built with dynamic-cli.
5//!
6//! # Overview
7//!
8//! The execution context is shared state that persists across command executions.
9//! Each application defines its own context type that implements the
10//! [`ExecutionContext`] trait.
11//!
12//! # Key Concepts
13//!
14//! ## Execution Context
15//!
16//! The context holds application-specific state that commands can read and modify:
17//!
18//! ```
19//! use dynamic_cli::context::ExecutionContext;
20//! use std::any::Any;
21//!
22//! #[derive(Default)]
23//! struct AppContext {
24//!     session_id: String,
25//!     user_data: Vec<String>,
26//!     settings: std::collections::HashMap<String, String>,
27//! }
28//!
29//! impl ExecutionContext for AppContext {
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//!
40//! ## Type-Safe Downcasting
41//!
42//! Since the framework works with trait objects, commands must downcast
43//! the context to access their specific type:
44//!
45//! ```
46//! use dynamic_cli::context::{ExecutionContext, downcast_mut};
47//! # use std::any::Any;
48//! # #[derive(Default)]
49//! # struct AppContext { counter: u32 }
50//! # impl ExecutionContext for AppContext {
51//! #     fn as_any(&self) -> &dyn Any { self }
52//! #     fn as_any_mut(&mut self) -> &mut dyn Any { self }
53//! # }
54//!
55//! fn my_command(context: &mut dyn ExecutionContext) -> Result<(), String> {
56//!     // Downcast to concrete type
57//!     let app_ctx = downcast_mut::<AppContext>(context)
58//!         .ok_or("Invalid context type")?;
59//!
60//!     // Use the context
61//!     app_ctx.counter += 1;
62//!
63//!     Ok(())
64//! }
65//! ```
66//!
67//! ## Thread Safety
68//!
69//! All contexts must be `Send + Sync` to support:
70//! - Multi-threaded command execution
71//! - Async/await patterns
72//! - Future framework extensibility
73//!
74//! # Common Patterns
75//!
76//! ## Stateless Context
77//!
78//! For simple applications that don't need state:
79//!
80//! ```
81//! use dynamic_cli::context::ExecutionContext;
82//! use std::any::Any;
83//!
84//! #[derive(Default)]
85//! struct EmptyContext;
86//!
87//! impl ExecutionContext for EmptyContext {
88//!     fn as_any(&self) -> &dyn Any { self }
89//!     fn as_any_mut(&mut self) -> &mut dyn Any { self }
90//! }
91//! ```
92//!
93//! ## Stateful Context
94//!
95//! For applications that maintain state:
96//!
97//! ```
98//! use dynamic_cli::context::ExecutionContext;
99//! use std::any::Any;
100//! use std::collections::HashMap;
101//!
102//! struct DatabaseContext {
103//!     connection_pool: Vec<String>, // Simplified example
104//!     cache: HashMap<String, String>,
105//!     transaction_count: u64,
106//! }
107//!
108//! impl Default for DatabaseContext {
109//!     fn default() -> Self {
110//!         Self {
111//!             connection_pool: vec!["conn1".to_string()],
112//!             cache: HashMap::new(),
113//!             transaction_count: 0,
114//!         }
115//!     }
116//! }
117//!
118//! impl ExecutionContext for DatabaseContext {
119//!     fn as_any(&self) -> &dyn Any { self }
120//!     fn as_any_mut(&mut self) -> &mut dyn Any { self }
121//! }
122//! ```
123//!
124//! ## Error Handling in Commands
125//!
126//! Best practice for handling downcast failures:
127//!
128//! ```
129//! use dynamic_cli::context::{ExecutionContext, downcast_mut};
130//! # use std::any::Any;
131//! # struct MyContext { value: i32 }
132//! # impl ExecutionContext for MyContext {
133//! #     fn as_any(&self) -> &dyn Any { self }
134//! #     fn as_any_mut(&mut self) -> &mut dyn Any { self }
135//! # }
136//!
137//! fn robust_handler(
138//!     context: &mut dyn ExecutionContext
139//! ) -> Result<(), Box<dyn std::error::Error>> {
140//!     let ctx = downcast_mut::<MyContext>(context)
141//!         .ok_or("Context type mismatch: expected MyContext")?;
142//!
143//!     ctx.value += 1;
144//!     Ok(())
145//! }
146//! ```
147//!
148//! # Architecture Notes
149//!
150//! ## Why Use Trait Objects?
151//!
152//! The framework uses `dyn ExecutionContext` because:
153//! 1. Each application defines its own context type
154//! 2. The framework can't know concrete types at compile time
155//! 3. This provides maximum flexibility for users
156//!
157//! ## Why Require Send + Sync?
158//!
159//! Thread safety bounds enable:
160//! - Sharing contexts across threads
161//! - Compatibility with async runtimes (tokio, async-std)
162//! - Future features like parallel command execution
163//!
164//! ## Performance Considerations
165//!
166//! - Downcasting has minimal overhead (type ID comparison)
167//! - Context access is not on the hot path for most commands
168//! - The trait object indirection is negligible compared to I/O operations
169//!
170//! # See Also
171//!
172//! - [`ExecutionContext`]: Core trait for contexts
173//! - [`downcast_ref()`]: Helper function for immutable downcasting
174//! - [`downcast_mut()`]: Helper function for mutable downcasting
175
176pub mod traits;
177
178// Re-export commonly used types for convenience
179pub use traits::{downcast_mut, downcast_ref, ExecutionContext};
180
181#[cfg(test)]
182mod tests {
183    use super::{downcast_mut, downcast_ref, ExecutionContext};
184    use std::any::Any;
185    use std::collections::HashMap;
186
187    /// Example context for testing
188    #[derive(Default)]
189    struct TestAppContext {
190        command_count: u32,
191        last_command: Option<String>,
192        data: HashMap<String, String>,
193    }
194
195    impl ExecutionContext for TestAppContext {
196        fn as_any(&self) -> &dyn Any {
197            self
198        }
199
200        fn as_any_mut(&mut self) -> &mut dyn Any {
201            self
202        }
203    }
204
205    /// Another context type for testing type safety.
206    ///
207    /// Unit struct: only its distinct *type* matters for this
208    /// wrong-context-type test, no payload is ever read.
209    struct AnotherContext;
210
211    impl ExecutionContext for AnotherContext {
212        fn as_any(&self) -> &dyn Any {
213            self
214        }
215
216        fn as_any_mut(&mut self) -> &mut dyn Any {
217            self
218        }
219    }
220
221    #[test]
222    fn test_module_exports() {
223        // Verify that types are exported correctly from the module
224        let ctx = TestAppContext::default();
225
226        // Should be able to use the context as a trait object
227        let _ctx_ref: &dyn ExecutionContext = &ctx;
228
229        // Should be able to use free functions
230        let ctx_ref: &dyn ExecutionContext = &ctx;
231        let _downcasted = downcast_ref::<TestAppContext>(ctx_ref);
232    }
233
234    #[test]
235    fn test_integration_command_pattern() {
236        /// Simulate a command handler
237        fn increment_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
238            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
239
240            app_ctx.command_count += 1;
241            app_ctx.last_command = Some("increment".to_string());
242
243            Ok(())
244        }
245
246        let mut ctx = TestAppContext::default();
247
248        // Execute command
249        increment_command(&mut ctx).unwrap();
250
251        assert_eq!(ctx.command_count, 1);
252        assert_eq!(ctx.last_command, Some("increment".to_string()));
253    }
254
255    #[test]
256    fn test_integration_multiple_commands() {
257        fn command_a(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
258            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
259
260            app_ctx
261                .data
262                .insert("key_a".to_string(), "value_a".to_string());
263            app_ctx.command_count += 1;
264            Ok(())
265        }
266
267        fn command_b(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
268            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
269
270            app_ctx
271                .data
272                .insert("key_b".to_string(), "value_b".to_string());
273            app_ctx.command_count += 1;
274            Ok(())
275        }
276
277        let mut ctx = TestAppContext::default();
278
279        // Execute multiple commands
280        command_a(&mut ctx).unwrap();
281        command_b(&mut ctx).unwrap();
282
283        assert_eq!(ctx.command_count, 2);
284        assert_eq!(ctx.data.len(), 2);
285        assert_eq!(ctx.data.get("key_a"), Some(&"value_a".to_string()));
286        assert_eq!(ctx.data.get("key_b"), Some(&"value_b".to_string()));
287    }
288
289    #[test]
290    fn test_integration_wrong_context_type() {
291        fn command_expecting_test_context(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
292            downcast_mut::<TestAppContext>(ctx).ok_or("Expected TestAppContext")?;
293            Ok(())
294        }
295
296        let mut wrong_ctx = AnotherContext;
297
298        // Should fail because we're passing the wrong context type
299        let result = command_expecting_test_context(&mut wrong_ctx);
300        assert!(result.is_err());
301    }
302
303    #[test]
304    fn test_integration_read_only_access() {
305        fn read_command_count(ctx: &dyn ExecutionContext) -> Result<u32, String> {
306            let app_ctx = downcast_ref::<TestAppContext>(ctx).ok_or("Invalid context")?;
307
308            Ok(app_ctx.command_count)
309        }
310
311        let ctx = TestAppContext {
312            command_count: 42,
313            ..Default::default()
314        };
315
316        let count = read_command_count(&ctx).unwrap();
317        assert_eq!(count, 42);
318    }
319
320    #[test]
321    fn test_integration_stateful_workflow() {
322        // Simulate a series of commands that build up state
323        fn init_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
324            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
325
326            app_ctx
327                .data
328                .insert("initialized".to_string(), "true".to_string());
329            app_ctx.last_command = Some("init".to_string());
330            Ok(())
331        }
332
333        fn process_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
334            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
335
336            // Check initialization
337            if app_ctx.data.get("initialized") != Some(&"true".to_string()) {
338                return Err("Not initialized".to_string());
339            }
340
341            app_ctx
342                .data
343                .insert("processed".to_string(), "true".to_string());
344            app_ctx.last_command = Some("process".to_string());
345            Ok(())
346        }
347
348        fn finalize_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
349            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
350
351            // Check processing
352            if app_ctx.data.get("processed") != Some(&"true".to_string()) {
353                return Err("Not processed".to_string());
354            }
355
356            app_ctx
357                .data
358                .insert("finalized".to_string(), "true".to_string());
359            app_ctx.last_command = Some("finalize".to_string());
360            Ok(())
361        }
362
363        let mut ctx = TestAppContext::default();
364
365        // Execute workflow
366        init_command(&mut ctx).unwrap();
367        process_command(&mut ctx).unwrap();
368        finalize_command(&mut ctx).unwrap();
369
370        // Verify final state
371        assert_eq!(ctx.data.get("initialized"), Some(&"true".to_string()));
372        assert_eq!(ctx.data.get("processed"), Some(&"true".to_string()));
373        assert_eq!(ctx.data.get("finalized"), Some(&"true".to_string()));
374        assert_eq!(ctx.last_command, Some("finalize".to_string()));
375    }
376
377    #[test]
378    fn test_integration_error_propagation() {
379        fn failing_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
380            let _app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
381
382            // Simulate command failure
383            Err("Command failed".to_string())
384        }
385
386        let mut ctx = TestAppContext::default();
387
388        let result = failing_command(&mut ctx);
389        assert!(result.is_err());
390        assert_eq!(result.unwrap_err(), "Command failed");
391    }
392
393    #[test]
394    fn test_boxed_context() {
395        // Test using Box<dyn ExecutionContext>
396        let ctx: Box<dyn ExecutionContext> = Box::new(TestAppContext::default());
397
398        // Should be able to downcast
399        let downcasted = downcast_ref::<TestAppContext>(&*ctx);
400        assert!(downcasted.is_some());
401    }
402
403    #[test]
404    fn test_boxed_context_mutation() {
405        let mut ctx: Box<dyn ExecutionContext> = Box::new(TestAppContext::default());
406
407        // Downcast and modify
408        if let Some(app_ctx) = downcast_mut::<TestAppContext>(&mut *ctx) {
409            app_ctx.command_count = 100;
410        }
411
412        // Verify modification
413        let app_ctx = downcast_ref::<TestAppContext>(&*ctx).unwrap();
414        assert_eq!(app_ctx.command_count, 100);
415    }
416
417    /// Test complex nested context structure
418    #[derive(Default)]
419    struct NestedContext {
420        outer: HashMap<String, InnerContext>,
421    }
422
423    #[derive(Default, Clone)]
424    struct InnerContext {
425        values: Vec<i32>,
426    }
427
428    impl ExecutionContext for NestedContext {
429        fn as_any(&self) -> &dyn Any {
430            self
431        }
432
433        fn as_any_mut(&mut self) -> &mut dyn Any {
434            self
435        }
436    }
437
438    #[test]
439    fn test_nested_context_manipulation() {
440        fn add_value(ctx: &mut dyn ExecutionContext, key: &str, value: i32) -> Result<(), String> {
441            let nested = downcast_mut::<NestedContext>(ctx).ok_or("Invalid context")?;
442
443            nested
444                .outer
445                .entry(key.to_string())
446                .or_insert_with(InnerContext::default)
447                .values
448                .push(value);
449
450            Ok(())
451        }
452
453        let mut ctx = NestedContext::default();
454
455        add_value(&mut ctx, "group1", 10).unwrap();
456        add_value(&mut ctx, "group1", 20).unwrap();
457        add_value(&mut ctx, "group2", 30).unwrap();
458
459        assert_eq!(ctx.outer.get("group1").unwrap().values, vec![10, 20]);
460        assert_eq!(ctx.outer.get("group2").unwrap().values, vec![30]);
461    }
462
463    #[test]
464    fn test_context_with_lifetime_data() {
465        // Test that contexts can hold references (with proper lifetimes)
466        #[derive(Default)]
467        struct RefContext {
468            owned_data: String,
469        }
470
471        impl ExecutionContext for RefContext {
472            fn as_any(&self) -> &dyn Any {
473                self
474            }
475
476            fn as_any_mut(&mut self) -> &mut dyn Any {
477                self
478            }
479        }
480
481        let mut ctx = RefContext {
482            owned_data: "test".to_string(),
483        };
484
485        let ctx_ref: &mut dyn ExecutionContext = &mut ctx;
486
487        if let Some(ref_ctx) = downcast_mut::<RefContext>(ctx_ref) {
488            ref_ctx.owned_data.push_str(" modified");
489        }
490
491        assert_eq!(ctx.owned_data, "test modified");
492    }
493}