C5Store for Rust
C5Store is a Rust library providing a unified store for configuration and secrets. It aims to be a single point of access for your application's configuration needs, consolidating values from various sources (like YAML and TOML files or directories), handling environment variable overrides, managing secrets securely via built-in decryption, and allowing dynamic loading through providers.
The core idea is to simplify configuration management in complex applications by offering a hierarchical, type-aware, extensible, and environment-aware configuration layer.
Key Features
- Unified Access: Retrieve configuration values using simple dot-notation key paths (e.g.,
database.connection.pool_size). - Multiple Sources & Merging: Load configuration from YAML and TOML files, or entire directories containing such files. Configuration is intelligently merged based on load order.
- Environment Variable Overrides: Seamlessly override any configuration value using environment variables (e.g.,
C5_DATABASE__HOST=...). - Type-Safe Retrieval: Get values converted directly into expected Rust types using
get_into::<T>(), now returning aResultfor robust error handling. - Direct Struct Deserialization: Deserialize entire configuration branches directly into your custom Rust structs using
get_into_struct::<T>(). - Integrated Secrets Management (Optional Feature):
- Transparently decrypt secrets defined within configuration files using the
.c5encvalkey. - Supports pluggable decryption algorithms (includes
base64andecies_x25519). - Securely load decryption keys from files (including
.pem) or environment variables.
- Transparently decrypt secrets defined within configuration files using the
- Value Providers: Defer loading of specific configuration sections to external sources (e.g., files) using a provider system. Includes a built-in
C5FileValueProvider. - Periodic Refresh: Value providers can be configured to automatically refresh their data at specified intervals.
- Change Notifications: Subscribe to changes in configuration values at specific key paths or their ancestors. Notifications are debounced to prevent flooding.
- Hierarchical Structure: Access nested configuration values easily and create "branches" for context-specific views of the configuration.
.envFile Support (Optional Feature): Load environment variables from.envfiles at startup.- Extensible: Designed with traits for custom value providers and secret decryptors.
- Telemetry Hooks: Basic interfaces for integrating custom logging and statistics recording.
Getting Started
-
Add Dependency: Add
c5storeto yourCargo.toml. Enable optional features as needed:[] # Base library = "0.3.0" # Use v0.3.0+ for new features/error handling # Example enabling .env file support (optional) # c5store = { version = "0.3.0", features = ["dotenv"] } # Example disabling default secrets support (optional, smaller binary) # c5store = { version = "0.3.0", default-features = false } # Other necessary dependencies like serde, etc. = { = "1", = ["derive"] } -
Basic Usage:
use ; // Import ConfigError use PathBuf; use Deserialize; // Needed for get_into_struct // Example struct for deserialization
Configuration Files & Directories
C5Store loads configuration from specified paths in the create_c5store call. These paths can be:
- YAML files (
.yaml,.yml) - TOML files (
.toml) - Directories: All files within the directory with supported extensions (
.yaml,.yml,.toml) will be loaded and merged alphabetically.
Configuration sources are merged in the order they are processed (files listed explicitly first, then files within directories alphabetically). Values from later sources override values from earlier sources for the same key path. Maps (objects/tables) are merged recursively; other types are replaced entirely.
Example (config/common.yaml):
service:
name: MyAwesomeApp
port: 8080
database:
host: prod-db.example.com
pool_size: 50
Example (config/local.toml):
# Overrides common.yaml values
# Assumes local.toml is processed after common.yaml
= 9090 # Overrides port 8080
[]
= "localhost" # Overrides prod host
= "dev_user" # Adds a new key
# service.name and database.pool_size are inherited
Environment Variables & Loading Priority
C5Store supports overriding configuration values using environment variables after all files have been loaded and merged.
- Prefix: Variables starting with
C5_(by default, can be configured later if needed) are processed. - Separator: Double underscore (
__) is used to denote nesting levels (e.g.,C5_DATABASE__HOSTmaps todatabase.host). - Case: The key derived from the environment variable is converted to lowercase (e.g.,
C5_SERVICE__NAMEbecomesservice.name). - Value: Environment variable values are always treated as strings. Use
get_intoorget_into_structto convert them to the desired type.
Loading Priority (Highest to Lowest):
- Environment Variables (e.g.,
C5_...) - Configuration Files/Directories (processed in the order specified/discovered, with later files/directories overriding earlier ones).
- (Future) Default values set programmatically.
Optional Features (dotenv, secrets)
C5Store uses Cargo features to enable optional functionality, keeping the core library lean if certain features aren't needed.
dotenv:- Enables loading environment variables from a
.envfile at startup. - Requires the
dotenvycrate. - Enable using
features = ["dotenv"]inCargo.toml. - Specify the path to the
.envfile viaC5StoreOptions::dotenv_path. .envfiles are loaded before process environment variables are read, allowing process variables to override.envvariables.
- Enables loading environment variables from a
secrets:- Enables all secrets management functionality (loading
.c5encval,SecretOptions,SecretKeyStore, decryptors). - Requires crypto dependencies (
ecies_25519,curve25519-parser,sha2). - Enabled by default.
- Disable using
default-features = falseinCargo.tomlif secrets are not needed, resulting in a smaller binary.
- Enables all secrets management functionality (loading
[]
# Minimal - no .env, no secrets
# c5store = { version = "0.3.0", default-features = false }
# Default - secrets enabled
# c5store = "0.3.0"
# Secrets and .env support
# c5store = { version = "0.3.0", features = ["dotenv"] }
Secrets Management (secrets feature)
(This section requires the secrets feature, which is enabled by default).
Secrets are defined using a special .c5encval key within your YAML/TOML configuration.
Structure:
# YAML Example
some_secret_key:
.c5encval:
# TOML Example (requires inline table or separate table)
# [some_secret_key]
# ".c5encval" = ["<algorithm>", "<key_name>", "<base64_encrypted_data>"]
<algorithm>: Name of registeredSecretDecryptor(e.g.,"base64","ecies_x25519").<key_name>: Name used to look up the decryption key in theSecretKeyStore.<base64_encrypted_data>: The secret value, encrypted and then Base64 encoded.
Configuration (SecretOptions):
Configure secrets via the secret_opts field in C5StoreOptions.
use ;
// Only if using secrets explicitly
use ;
use EciesX25519;
use PathBuf;
// ... inside setup code ...
let mut options = default;
// Gate configuration if secrets might be disabled
let config_paths = vec!;
let = create_c5store?;
// Retrieving the secret automatically attempts decryption if secrets feature enabled
match store.
Value Providers
(Functionality unchanged from previous version, see earlier examples)
Value providers allow parts of your configuration to be loaded dynamically from external sources. Mark a section in YAML/TOML with .provider. Register providers using C5StoreMgr::set_value_provider.
Change Notifications
(Functionality unchanged from previous version, see earlier examples)
Subscribe to changes using C5Store::subscribe. Listeners are called after a debounce period.
License
This project is licensed under the Mozilla Public License Version 2.0 (MPL-2.0).
Contributing
Contributions welcome! Please open issues or PRs.
Changelog
See CHANGELOG.md for a history of notable changes. (Remember to update this file!)