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}