Expand description
Verdure Application Context - Context Management for the Verdure Ecosystem
This crate provides application context management as a core part of the Verdure ecosystem framework. It serves as the central hub for application-wide state, configuration, and environment management that integrates with all other Verdure modules.
This module provides the foundation for configuration-driven development and environment-aware component behavior across the entire Verdure ecosystem.
§Core Features
- Application Context: Centralized application state management
- Configuration Management: Hierarchical configuration system with multiple sources
- Event Broadcasting: Application-wide event system for decoupled communication
- IoC Integration: Seamless integration with the Verdure IoC container
- Type-Safe Configuration: Strongly-typed configuration value access
§Quick Start
§Basic Usage
use verdure_context::{ApplicationContext, ConfigSource};
use std::collections::HashMap;
// Create and configure application context
let context = ApplicationContext::builder()
.with_property("app.name", "MyApp")
.with_property("app.port", "8080")
.build()
.unwrap();
// Initialize the context
context.initialize().unwrap();
// Access configuration
let app_name = context.get_config("app.name");
let port: i64 = context.get_config_as("app.port").unwrap();
println!("Starting {} on port {}", app_name, port);§Configuration from Files
Verdure Context supports multiple configuration file formats:
§TOML Configuration
use verdure_context::ApplicationContext;
let context = ApplicationContext::builder()
.with_toml_config_file("config/app.toml")
.build()
.unwrap();§YAML Configuration
use verdure_context::ApplicationContext;
let context = ApplicationContext::builder()
.with_yaml_config_file("config/app.yaml")
.build()
.unwrap();§Properties Configuration
use verdure_context::ApplicationContext;
let context = ApplicationContext::builder()
.with_properties_config_file("config/app.properties")
.build()
.unwrap();§Auto-Detection
use verdure_context::ApplicationContext;
// Format is auto-detected based on file extension
let context = ApplicationContext::builder()
.with_config_file("config/app.yaml") // YAML
.with_config_file("config/db.properties") // Properties
.with_config_file("config/server.toml") // TOML
.build()
.unwrap();§Event System
use verdure_context::{ApplicationContext, Event, EventListener};
use std::any::Any;
// Define an event
#[derive(Debug, Clone)]
struct UserRegisteredEvent {
pub user_id: u64,
pub email: String,
}
impl Event for UserRegisteredEvent {
fn name(&self) -> &'static str {
"UserRegistered"
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
// Create event listener
struct EmailNotificationListener;
impl EventListener<UserRegisteredEvent> for EmailNotificationListener {
fn on_event(&self, event: &UserRegisteredEvent) {
println!("Sending welcome email to user {} ({})",
event.user_id, event.email);
}
}
// Set up context with event handling
let mut context = ApplicationContext::new();
context.subscribe_to_events(EmailNotificationListener);
// Publish events
let event = UserRegisteredEvent {
user_id: 123,
email: "user@example.com".to_string(),
};
context.publish_event(&event);§IoC Container Integration
use verdure_context::ApplicationContext;
use std::sync::Arc;
#[derive(Debug)]
struct DatabaseService {
connection_url: String,
}
let context = ApplicationContext::builder()
.with_property("database.url", "postgres://localhost/myapp")
.build()
.unwrap();
// Register components with the container
let db_service = Arc::new(DatabaseService {
connection_url: context.get_config("database.url"),
});
context.container().register_component(db_service);
// Retrieve components
let retrieved: Option<Arc<DatabaseService>> = context.get_component();
assert!(retrieved.is_some());§Advanced Features
§Configuration Sources Priority
Configuration sources are resolved in the following order (highest to lowest precedence):
- Runtime Properties: Values set via
set_config() - Configuration Sources: Sources added via
add_config_source()(last added wins) - Environment Variables: System environment variables
- Configuration Files: Files loaded via various methods (last added wins)
- TOML files (
.toml) - YAML files (
.yaml,.yml) - Properties files (
.properties)
- TOML files (
§Supported Configuration Formats
§TOML Format Example
# app.toml
[app]
name = "MyApplication"
port = 8080
debug = true
[database]
host = "localhost"
port = 5432
name = "myapp"§YAML Format Example
# app.yaml
app:
name: MyApplication
port: 8080
debug: true
features:
- auth
- logging
database:
host: localhost
port: 5432
name: myapp§Properties Format Example
# app.properties
app.name=MyApplication
app.port=8080
app.debug=true
database.host=localhost
database.port=5432
database.name=myappAll formats are converted to a flat key-value structure using dot notation
(e.g., app.name, database.host) for consistent access patterns.
§Ecosystem Context Events
The context system publishes several built-in events that applications can listen to. There are two ways to listen to events:
- Regular Event Listeners: Receive only the event data
- Context-Aware Event Listeners: Receive both the event and ApplicationContext reference
§Built-in Lifecycle Events
§ContextInitializingEvent
When: Fired at the very beginning of context initialization, before any actual work begins.
Purpose: Allows listeners to prepare for context startup or log initialization start.
Data: Configuration sources count, active profiles count, and timestamp.
§ContextInitializedEvent
When: Fired after the context is fully initialized, including all configuration sources, profiles, and IoC container.
Purpose: Ideal for application startup tasks that require a fully configured context.
Data: Final configuration sources count, active profiles count, and timestamp.
§ProfileActivatedEvent
When: Fired whenever a profile is activated during context building.
Purpose: Allows listeners to react to environment changes or profile-specific setup.
Data: Profile name, properties count in the profile, and timestamp.
§ConfigurationChangedEvent
When: Fired when configuration values are updated at runtime after context initialization.
Purpose: Enables reactive configuration updates and change tracking.
Data: Configuration key, old value (if any), new value, and timestamp.
§Context-Aware Event Listeners (Recommended for Lifecycle Events)
Context-aware listeners can access the ApplicationContext during event handling, making them perfect for lifecycle events where you need to interact with the context:
use verdure_context::{ApplicationContext, ContextInitializedEvent, ContextAwareEventListener};
struct StartupTasks;
impl ContextAwareEventListener<ContextInitializedEvent> for StartupTasks {
fn on_context_event(&self, event: &ContextInitializedEvent, context: &ApplicationContext) {
println!("🚀 Context initialized with {} sources!", event.config_sources_count);
// Access configuration
let app_name = context.get_config("app.name");
println!("📱 Starting application: {}", app_name);
// Access IoC container for dependency injection setup
let container = context.container();
// Setup your components...
// Check environment
let env = context.environment();
println!("🌍 Running in {} environment", env);
}
}
let mut context = ApplicationContext::builder()
.with_property("app.name", "MyApp")
.build()
.unwrap();
context.subscribe_to_context_events(StartupTasks);
context.initialize().unwrap(); // Triggers the context-aware listener§ContextInitializingEvent Usage
Listen to preparation phases before initialization:
use verdure_context::{ApplicationContext, ContextInitializingEvent, ContextAwareEventListener};
struct PreStartupListener;
impl ContextAwareEventListener<ContextInitializingEvent> for PreStartupListener {
fn on_context_event(&self, event: &ContextInitializingEvent, context: &ApplicationContext) {
println!("🔧 Context initializing with {} sources...",
event.config_sources_count);
// Pre-initialization tasks
let startup_time = event.timestamp;
println!("⏰ Startup began at: {:?}", startup_time);
}
}§ConfigurationChangedEvent Usage
Track runtime configuration changes:
use verdure_context::{ApplicationContext, ConfigurationChangedEvent, EventListener};
struct ConfigListener;
impl EventListener<ConfigurationChangedEvent> for ConfigListener {
fn on_event(&self, event: &ConfigurationChangedEvent) {
match &event.old_value {
Some(old) => println!("⚙️ Configuration '{}' changed from '{}' to '{}'",
event.key, old, event.new_value),
None => println!("➕ Configuration '{}' set to '{}'",
event.key, event.new_value),
}
}
}
let mut context = ApplicationContext::new();
context.subscribe_to_events(ConfigListener);
context.set_config("app.mode", "production");§Event System Architecture
The event system supports both regular and context-aware listeners simultaneously:
- Regular listeners (
EventListener<T>) receive only the event data - Context-aware listeners (
ContextAwareEventListener<T>) receive both event data and ApplicationContext reference - Both types can be registered for the same event type
- Events are published to all registered listeners of the appropriate type
- Lifecycle events automatically provide context access for enhanced integration capabilities
Re-exports§
pub use config::ConfigManager;pub use config::ConfigSource;pub use config::ConfigValue;pub use context::ApplicationContext;pub use context::ApplicationContextBuilder;pub use error::ContextError;pub use error::ContextResult;pub use event::AnyContextAwareEventListener;pub use event::AnyEventListener;pub use event::ConfigurationChangedEvent;pub use event::ContextAwareEventListener;pub use event::ContextInitializedEvent;pub use event::ContextInitializingEvent;pub use event::Event;pub use event::EventListener;pub use event::EventPublisher;