Skip to main content

tako_rs_core/
config.rs

1//! Configuration loading from environment variables.
2//!
3//! Provides a `Config<T>` wrapper that can be loaded from environment variables
4//! and injected as router state for access in handlers.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use tako::config::Config;
10//! use serde::Deserialize;
11//!
12//! #[derive(Deserialize, Clone)]
13//! struct AppConfig {
14//!     database_url: String,
15//!     port: u16,
16//!     debug: bool,
17//! }
18//!
19//! // Load from environment variables (DATABASE_URL, PORT, DEBUG)
20//! // let config = Config::<AppConfig>::from_env().expect("missing config");
21//! ```
22
23use serde::de::DeserializeOwned;
24
25/// A typed configuration wrapper loaded from environment variables.
26///
27/// `Config<T>` reads environment variables and deserializes them into a
28/// strongly-typed struct. Variable names are matched by converting struct field names
29/// to `SCREAMING_SNAKE_CASE`.
30#[derive(Debug, Clone)]
31pub struct Config<T: Clone>(pub T);
32
33impl<T: DeserializeOwned + Clone> Config<T> {
34  /// Loads configuration from environment variables.
35  ///
36  /// Field names are matched against environment variable names case-insensitively
37  /// (`database_url` ↔ `DATABASE_URL`). Non-string fields (`u16`, `bool`, …) are
38  /// parsed via the `envy` crate's per-field deserializers, so a typed
39  /// `port: u16` reads `PORT=8080` natively without relying on JSON number
40  /// coercion (which the previous serde_json-roundtrip implementation got wrong).
41  pub fn from_env() -> Result<Self, ConfigError> {
42    let config: T = envy::from_env::<T>().map_err(|e| ConfigError(e.to_string()))?;
43    Ok(Config(config))
44  }
45
46  /// Loads configuration from environment variables that share a common prefix.
47  ///
48  /// Useful when several configs coexist in the process — set
49  /// `MYAPP_DATABASE_URL`, `MYAPP_PORT`, … and call `Config::from_env_prefixed("MYAPP_")`.
50  pub fn from_env_prefixed(prefix: &str) -> Result<Self, ConfigError> {
51    let config: T = envy::prefixed(prefix)
52      .from_env::<T>()
53      .map_err(|e| ConfigError(e.to_string()))?;
54    Ok(Config(config))
55  }
56
57  /// Creates a Config from an existing value.
58  pub fn new(config: T) -> Self {
59    Config(config)
60  }
61
62  /// Returns a reference to the inner config value.
63  pub fn inner(&self) -> &T {
64    &self.0
65  }
66
67  /// Consumes the wrapper and returns the inner value.
68  pub fn into_inner(self) -> T {
69    self.0
70  }
71}
72
73/// Error type for configuration loading.
74#[derive(Debug, Clone)]
75pub struct ConfigError(pub String);
76
77impl std::fmt::Display for ConfigError {
78  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79    write!(f, "configuration error: {}", self.0)
80  }
81}
82
83impl std::error::Error for ConfigError {}