| Crates.io | serviceconf |
| lib.rs | serviceconf |
| version | 0.2.2 |
| created_at | 2025-10-21 05:59:53.070469+00 |
| updated_at | 2025-10-21 07:58:39.13059+00 |
| description | Configuration library for microservices: load settings from environment variables with file-based secrets support |
| homepage | |
| repository | https://github.com/lambdalisue/rs-serviceconf |
| max_upload_size | |
| id | 1893318 |
| size | 48,958 |
Environment variable configuration with file-based secrets support
serviceconf provides a declarative API for loading configuration from environment
variables with native support for file-based secrets (Kubernetes Secrets, Docker Secrets).
The primary feature that distinguishes this library from other environment variable
configuration libraries is the #[conf(from_file)] attribute, which allows reading
secrets from files while falling back to direct environment variables for local development.
#[derive(ServiceConf)]Default trait and explicit valuesuse serviceconf::ServiceConf;
#[derive(Debug, ServiceConf)]
struct Config {
// File-based secret: reads from API_KEY or API_KEY_FILE
#[conf(from_file)]
pub api_key: String,
// Default value if not set
#[conf(default = 8080)]
pub port: u16,
}
let config = Config::from_env().unwrap();
println!("Port: {}", config.port);
Local development (direct environment variable):
export API_KEY=dev-key-123
export PORT=3000
Production (Kubernetes/Docker with file-based secret):
export API_KEY_FILE=/run/secrets/api-key
export PORT=8080
The #[conf(from_file)] attribute allows reading secrets from files mounted by
Kubernetes or Docker, avoiding the security risks of exposing secrets directly in
environment variables.
Why file-based secrets?
Loading priority:
API_KEY) - for local developmentAPI_KEY_FILE) - for productionapiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
api-key: "prod-api-key-123"
db-password: "secure-password"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice
spec:
template:
spec:
containers:
- name: app
image: myservice:latest
env:
- name: API_KEY_FILE
value: /etc/secrets/api-key
- name: DATABASE_PASSWORD_FILE
value: /etc/secrets/db-password
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: app-secrets
items:
- key: api-key
path: api-key
- key: db-password
path: db-password
With this Kubernetes configuration, your Rust application can simply use:
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
struct Config {
#[conf(from_file)]
pub api_key: String,
#[conf(from_file)]
pub database_password: String,
}
Default (using FromStr):
DATABASE_URL=postgres://localhost/dbMAX_CONNECTIONS=42DEBUG=trueCustom deserializers - specify with #[conf(deserializer = "function")]:
#[conf(deserializer = "serde_json::from_str")]#[conf(deserializer = "toml::from_str")]#[conf(prefix = "PREFIX_")]Add a prefix to all environment variable names in the struct.
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
#[conf(prefix = "MYAPP_")]
struct Config {
pub database_url: String, // Reads from MYAPP_DATABASE_URL
pub api_key: String, // Reads from MYAPP_API_KEY
}
Environment variables:
export MYAPP_DATABASE_URL=postgres://localhost/db
export MYAPP_API_KEY=secret123
#[conf(from_file)] - File-based SecretsLoad from {VAR_NAME}_FILE in addition to the environment variable.
This is the primary feature of serviceconf for handling file-based secrets
in Kubernetes and Docker environments.
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
pub struct Config {
// Reads from API_KEY or API_KEY_FILE
#[conf(from_file)]
pub api_key: String,
}
#[conf(name = "CUSTOM_NAME")]Specify an environment variable name different from the field name.
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
pub struct Config {
// Load from REDIS_URL environment variable
#[conf(name = "REDIS_URL")]
pub redis_connection_string: String,
}
#[conf(default)]Use Default::default() if the environment variable is not set.
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
struct Config {
#[conf(default)]
pub port: u16, // Uses 0 (u16::default()) if PORT not set
#[conf(default)]
pub host: String, // Uses "" (String::default()) if HOST not set
}
#[conf(default = value)]Specify an explicit default value when the environment variable is not set.
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
struct Config {
#[conf(default = "127.0.0.1:8080".to_string())]
pub server_addr: String,
#[conf(default = 10)]
pub max_connections: u32,
#[conf(default = false)]
pub enable_tls: bool,
}
#[conf(deserializer = "function")]Use a custom deserializer function for complex types or custom parsing logic.
The deserializer function must have the signature:
fn deserialize(s: &str) -> Result<T, impl std::fmt::Display>
Example with JSON:
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
struct Config {
// Parse JSON array
#[conf(deserializer = "serde_json::from_str")]
pub tags: Vec<String>,
}
Environment variable:
export TAGS='["prod","api","v2"]'
Example with custom function:
use serviceconf::ServiceConf;
// Custom comma-separated parser
fn comma_separated(s: &str) -> Result<Vec<String>, String> {
Ok(s.split(',').map(|s| s.trim().to_string()).collect())
}
#[derive(ServiceConf)]
struct Config {
#[conf(deserializer = "comma_separated")]
pub features: Vec<String>,
}
Environment variable:
export FEATURES=feature1,feature2,feature3
Use Option<T> for optional fields. Returns None if the environment variable is not set.
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
struct Config {
pub api_key: Option<String>, // None if API_KEY not set
pub max_retries: Option<u32>, // None if MAX_RETRIES not set
}
| Type | When Env Var Missing | When Env Var Set |
|---|---|---|
T (no attribute) |
Error | Parsed with FromStr |
T + #[conf(default)] |
Default::default() |
Parsed with FromStr |
T + #[conf(default = value)] |
Uses value |
Parsed with FromStr |
Option<T> |
None |
Some(parsed_value) |
T + #[conf(deserializer = "fn")] |
Error | Parsed with custom function |
Multiple attributes can be combined to create powerful configurations:
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
#[conf(prefix = "APP_")]
struct Config {
// Combines: prefix + custom name + from_file + Option
#[conf(name = "DB_URL")]
#[conf(from_file)]
pub database_url: Option<String>, // Reads from APP_DB_URL or APP_DB_URL_FILE
// Combines: prefix + default
#[conf(default = 8080)]
pub port: u16, // Reads from APP_PORT, defaults to 8080
}
Invalid combinations (compile errors):
Option<T> + #[conf(default)] or #[conf(default = value)] → Option already defaults to NoneThe from_env() method returns a Result that can be handled appropriately:
use serviceconf::ServiceConf;
#[derive(ServiceConf)]
struct Config {
pub api_key: String,
}
match Config::from_env() {
Ok(config) => println!("Config loaded successfully"),
Err(e) => eprintln!("Failed to load config: {}", e),
}
Example error messages:
Environment variable 'DATABASE_URL' is required but not setFailed to parse environment variable 'PORT' as u16: invalid digit found in stringFailed to read file '/etc/secrets/key' for environment variable 'API_KEY_FILE': No such file or directory