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    #[derive(Default)]
207    struct AnotherContext {
208        value: i32,
209    }
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::default();
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 mut ctx = TestAppContext::default();
312        ctx.command_count = 42;
313
314        let count = read_command_count(&ctx).unwrap();
315        assert_eq!(count, 42);
316    }
317
318    #[test]
319    fn test_integration_stateful_workflow() {
320        // Simulate a series of commands that build up state
321        fn init_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
322            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
323
324            app_ctx
325                .data
326                .insert("initialized".to_string(), "true".to_string());
327            app_ctx.last_command = Some("init".to_string());
328            Ok(())
329        }
330
331        fn process_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
332            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
333
334            // Check initialization
335            if app_ctx.data.get("initialized") != Some(&"true".to_string()) {
336                return Err("Not initialized".to_string());
337            }
338
339            app_ctx
340                .data
341                .insert("processed".to_string(), "true".to_string());
342            app_ctx.last_command = Some("process".to_string());
343            Ok(())
344        }
345
346        fn finalize_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
347            let app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
348
349            // Check processing
350            if app_ctx.data.get("processed") != Some(&"true".to_string()) {
351                return Err("Not processed".to_string());
352            }
353
354            app_ctx
355                .data
356                .insert("finalized".to_string(), "true".to_string());
357            app_ctx.last_command = Some("finalize".to_string());
358            Ok(())
359        }
360
361        let mut ctx = TestAppContext::default();
362
363        // Execute workflow
364        init_command(&mut ctx).unwrap();
365        process_command(&mut ctx).unwrap();
366        finalize_command(&mut ctx).unwrap();
367
368        // Verify final state
369        assert_eq!(ctx.data.get("initialized"), Some(&"true".to_string()));
370        assert_eq!(ctx.data.get("processed"), Some(&"true".to_string()));
371        assert_eq!(ctx.data.get("finalized"), Some(&"true".to_string()));
372        assert_eq!(ctx.last_command, Some("finalize".to_string()));
373    }
374
375    #[test]
376    fn test_integration_error_propagation() {
377        fn failing_command(ctx: &mut dyn ExecutionContext) -> Result<(), String> {
378            let _app_ctx = downcast_mut::<TestAppContext>(ctx).ok_or("Invalid context")?;
379
380            // Simulate command failure
381            Err("Command failed".to_string())
382        }
383
384        let mut ctx = TestAppContext::default();
385
386        let result = failing_command(&mut ctx);
387        assert!(result.is_err());
388        assert_eq!(result.unwrap_err(), "Command failed");
389    }
390
391    #[test]
392    fn test_boxed_context() {
393        // Test using Box<dyn ExecutionContext>
394        let ctx: Box<dyn ExecutionContext> = Box::new(TestAppContext::default());
395
396        // Should be able to downcast
397        let downcasted = downcast_ref::<TestAppContext>(&*ctx);
398        assert!(downcasted.is_some());
399    }
400
401    #[test]
402    fn test_boxed_context_mutation() {
403        let mut ctx: Box<dyn ExecutionContext> = Box::new(TestAppContext::default());
404
405        // Downcast and modify
406        if let Some(app_ctx) = downcast_mut::<TestAppContext>(&mut *ctx) {
407            app_ctx.command_count = 100;
408        }
409
410        // Verify modification
411        let app_ctx = downcast_ref::<TestAppContext>(&*ctx).unwrap();
412        assert_eq!(app_ctx.command_count, 100);
413    }
414
415    /// Test complex nested context structure
416    #[derive(Default)]
417    struct NestedContext {
418        outer: HashMap<String, InnerContext>,
419    }
420
421    #[derive(Default, Clone)]
422    struct InnerContext {
423        values: Vec<i32>,
424    }
425
426    impl ExecutionContext for NestedContext {
427        fn as_any(&self) -> &dyn Any {
428            self
429        }
430
431        fn as_any_mut(&mut self) -> &mut dyn Any {
432            self
433        }
434    }
435
436    #[test]
437    fn test_nested_context_manipulation() {
438        fn add_value(ctx: &mut dyn ExecutionContext, key: &str, value: i32) -> Result<(), String> {
439            let nested = downcast_mut::<NestedContext>(ctx).ok_or("Invalid context")?;
440
441            nested
442                .outer
443                .entry(key.to_string())
444                .or_insert_with(InnerContext::default)
445                .values
446                .push(value);
447
448            Ok(())
449        }
450
451        let mut ctx = NestedContext::default();
452
453        add_value(&mut ctx, "group1", 10).unwrap();
454        add_value(&mut ctx, "group1", 20).unwrap();
455        add_value(&mut ctx, "group2", 30).unwrap();
456
457        assert_eq!(ctx.outer.get("group1").unwrap().values, vec![10, 20]);
458        assert_eq!(ctx.outer.get("group2").unwrap().values, vec![30]);
459    }
460
461    #[test]
462    fn test_context_with_lifetime_data() {
463        // Test that contexts can hold references (with proper lifetimes)
464        #[derive(Default)]
465        struct RefContext {
466            owned_data: String,
467        }
468
469        impl ExecutionContext for RefContext {
470            fn as_any(&self) -> &dyn Any {
471                self
472            }
473
474            fn as_any_mut(&mut self) -> &mut dyn Any {
475                self
476            }
477        }
478
479        let mut ctx = RefContext {
480            owned_data: "test".to_string(),
481        };
482
483        let ctx_ref: &mut dyn ExecutionContext = &mut ctx;
484
485        if let Some(ref_ctx) = downcast_mut::<RefContext>(ctx_ref) {
486            ref_ctx.owned_data.push_str(" modified");
487        }
488
489        assert_eq!(ctx.owned_data, "test modified");
490    }
491}