Crates.io | verdure-macros |
lib.rs | verdure-macros |
version | 0.0.5 |
created_at | 2025-08-04 08:18:12.014395+00 |
updated_at | 2025-08-15 07:20:19.898209+00 |
description | An ecosystem framework for Rust. |
homepage | https://github.com/zooshee/verdure |
repository | https://github.com/zooshee/verdure |
max_upload_size | |
id | 1780457 |
size | 30,264 |
English | 简体中文
Verdure - An ecosystem framework for Rust
True to its name, Verdure represents a vibrant and thriving ecosystem framework, dedicated to facilitating convenient and efficient Rust development through a comprehensive, integrated suite of tools and patterns.
The project is currently in the foundational development phase. We are looking for enthusiastic contributors to join us in building it.
Application Framework:
Web & Network:
Data & Persistence:
Security & Authentication:
verdure-security: Authentication and authorization framework
verdure-oauth: OAuth2 and OpenID Connect integration
#[derive(Component)]
and #[autowired]
for declarative configuration#[config_default]
and #[config_default_t]
attributesAuto-Configuration: Out-of-the-box application bootstrapping and configuration management 🚧
Web Framework: MVC patterns and REST API development
Data Access: Repository patterns and ORM integration
Security Framework: Authentication and authorization
AOP (Aspect-Oriented Programming): Cross-cutting concern support
Message-Driven Architecture: Event-driven programming patterns
Observability: Metrics, tracing, and health checks
And much more...
verdure = "0.0.5"
inventory = "0.3"
The underlying implementation heavily relies on inventory. Our thanks go to the authors of this excellent repository.
application.yml
example file:
server:
name: TestApp
port: 8080
datasource:
host: 127.0.0.1
username: root
password: 123456
database: test
Structs with the Configuration
derive are automatically registered as Component
instances and will automatically read configuration and load it. If the key does not exist in the configuration file, it will use config_default
or config_default_t
. If there is no default value, it will be None
.
Note that field types must be wrapped with Option<T>
.
Supported Configuration Formats:
.yml
, .yaml
files.toml
files.properties
filesDefault Value Attributes:
#[config_default(value)]
: Provide literal default values#[config_default_t(expression)]
: Provide expression-based default values, supporting complex calculationsuse std::sync::Arc;
use verdure::event::{ContextAwareEventListener, ContextInitializingEvent};
use verdure::{ApplicationContext, ComponentFactory, Configuration};
#[derive(Debug, Configuration)]
#[configuration("server")]
struct ServerConfig {
// server.name
name: Option<String>,
// server.port
#[config_default(8080)]
port: Option<u32>,
}
#[derive(Debug, Configuration)]
#[configuration("datasource")]
struct DatasourceConfig {
// datasource.host
#[config_default_t(Some(get_host()))]
host: Option<String>,
// datasource.port
#[config_default(3306)]
port: Option<u32>,
// datasource.username
#[config_default("......")]
username: Option<String>,
// datasource.password
#[config_default("......")]
password: Option<String>,
// datasource.database
#[config_default("test_db")]
database: Option<String>,
}
fn get_host() -> String {
"127.0.0.1".to_string()
}
Configuration Precedence (from highest to lowest):
set_config()
add_config_source()
(last added wins)Event System:
ContextInitializingEvent
: Triggered when context initialization beginsContextInitializedEvent
: Triggered when context initialization completesConfigurationChangedEvent
: Triggered when configuration changes at runtime#### ApplicationContext Initialization
```rust
struct ApplicationStartEvent;
impl ContextAwareEventListener<ContextInitializingEvent> for ApplicationStartEvent {
fn on_context_event(&self, _event: &ContextInitializingEvent, context: &ApplicationContext) {
let container = context.container();
let datasource_config = container
.get_component::<DatasourceConfig>()
.expect("datasource config not found")
.clone();
// ... do something with datasource_config
// context.register_component(Arc::new(datasource_component));
}
}
fn init_context() -> Arc<ApplicationContext> {
let context = ApplicationContext::builder()
// Load configuration files (supports YAML, TOML, Properties formats)
.with_config_file("application.yml")
.build();
match context {
Ok(context) => {
context.subscribe_to_context_events(ApplicationStartEvent);
match context.initialize() {
Ok(_) => Arc::new(context),
Err(e) => {
panic!("failed to initialize context: {}", e);
}
}
}
Err(e) => panic!("failed to new context: {}", e),
}
}
fn main() {
let context = init_context();
let server_config = context
.get_component::<ServerConfig>()
.expect("datasource config not found");
println!("server config: {:?}", server_config);
// ... do something with context
// get more component......
}
Recommended to use ApplicationContext
use std::sync::Arc;
fn init_container() {
let container = ComponentContainer::new();
match container.initialize() {
Ok(_) => Arc::new(container),
Err(e) => panic!("Failed to initialize container {}", e)
}
}
Adding the #[derive(Component)]
macro to a struct automatically registers it with the container as a singleton by default. For fields marked with the #[autowired]
attribute, an instance will be automatically retrieved from the container and injected.
use verdure::Component;
#[derive(Component)]
struct TestA {
#[autowired]
test_b: Arc<TestB>,
test_c: Option<TestC>,
test_d: TestD
}
#[derive(Component)]
struct TestB {
a: i32,
b: i32,
}
struct TestC {
a: i32
}
#[derive(Default)]
struct TestD {
a: i32,
}
There are two important points to note:
Arc<T>
.Option<T>
or implement the Default
trait.#[derive(Debug)]
struct Config {
name: &'static str,
port: u16
}
fn main() {
let container = init_container();
container.register_component(Arc::new(config));
let config = container.get_component::<Config>().unwrap();
println!("config: {:?}", config);
}
fn handle_container_lifecycle(event: &ContainerLifecycleEvent) {
match event {
ContainerLifecycleEvent::InitializationStarted {
container,
component_count,
} => {
// You can register necessary components during initialization here.
}
ContainerLifecycleEvent::InitializationCompleted {
container: _,
component_count,
duration,
} => {
println!(
"Container initialization completed\nComponent count: {}\nTime taken: {:?}",
component_count, duration
);
}
ContainerLifecycleEvent::ComponentCreated {
container: _,
component_name,
component_type_id,
creation_duration,
} => {
println!(
"Component created\nName: {}\nType ID: {:?}\nCreation time: {:?}",
component_name, component_type_id, creation_duration
);
}
}
}
lifecycle_listener!("app_container_listener", handle_container_lifecycle);