config_ro/
lib.rs

1//! A thread-safe configuration management library with JSON file support.
2//!
3//! This module provides a simple way to load and access configuration values from JSON files
4//! stored in a `configs/` directory. Configurations are cached globally for efficient access.
5//!
6//! # Examples
7//!
8//! ```
9//! use config_ro::Config;
10//!
11//! // Load the "database" configuration (reads from "configs/database.json")
12//! let config = Config::new("database");
13//!
14//! // Access nested values using dot notation
15//! let port: u16 = config.get("database.port").unwrap();
16//! let host: String = config.get("database.host").unwrap();
17//! ```
18use std::fs;
19
20use lazy_static::lazy_static;
21use serde_json::Value;
22use std::collections::HashMap;
23use std::sync::RwLock;
24use serde::de::DeserializeOwned;
25
26pub trait ConfigModule {
27    fn get(&self, name: &str) -> Option<&str>;
28}
29
30// Global config cache
31lazy_static! {
32    static ref CONFIGS: RwLock<HashMap<String, Value>> = RwLock::new(HashMap::new());
33}
34
35/// Configuration instance that provides access to cached configuration values
36///
37/// Each `Config` instance is associated with a specific configuration file
38/// and provides type-safe access to its values.
39pub struct Config {
40    name: String,
41}
42
43impl Config {
44    /// Creates or retrieves a cached configuration instance
45    ///
46    /// The configuration is loaded from `configs/{name}.json`. The file is parsed
47    /// only once and then cached for subsequent accesses.
48    ///
49    /// # Arguments
50    /// * `name` - Name of the configuration file (without extension)
51    ///
52    /// # Panics
53    /// - If the configuration file doesn't exist in `configs/` directory
54    /// - If the file contains invalid JSON
55    ///
56    /// # Examples
57    /// ```
58    /// use config_ro::Config;
59    /// let config = Config::new("app_settings");
60    /// ```
61    pub fn new(name: &str) -> Self {
62        let has = {
63            let configs = CONFIGS.read().unwrap();
64            configs.get(name).is_some()
65        };
66        if !has {
67            let mut configs = CONFIGS.write().unwrap();
68            configs.insert(name.to_string(), from_name(name));
69        }
70        Config {
71            name: name.to_string(),
72        }
73    }
74
75    /// Retrieves a configuration value by its path, supporting nested structures
76    ///
77    /// Uses dot notation to access nested values (e.g., "database.connection.port").
78    /// The value is automatically deserialized to the requested type.
79    ///
80    /// # Arguments
81    /// * `path` - Dot-separated path to the configuration value
82    ///
83    /// # Returns
84    /// `Some(T)` if the value exists and can be deserialized, `None` otherwise
85    ///
86    /// # Examples
87    /// ```
88    /// use config_ro::Config;
89    /// let config = Config::new("app");
90    ///
91    /// // Flat structure
92    /// let timeout: u32 = config.get("timeout").unwrap();
93    ///
94    /// // Nested structure
95    /// let db_port: u16 = config.get("database.connection.port").unwrap();
96    ///
97    /// // Optional values
98    /// let retry_count: Option<u8> = config.get("retries.count");
99    /// ```
100    pub fn get<T: DeserializeOwned>(&self, path: &str) -> Option<T> {
101        let configs = CONFIGS.read().unwrap();
102        let value = configs.get(&self.name)?;
103
104        let mut current = value;
105        for key in path.split('.') {
106            current = match current.get(key) {
107                Some(v) => v,
108                None => return None,
109            };
110        }
111
112        serde_json::from_value(current.clone()).ok()
113    }
114}
115
116fn from_name(name: &str) -> Value {
117    let filename = format!("configs/{}.json", name);
118    let content = fs::read_to_string(&filename)
119        .unwrap_or_else(|_| panic!("Failed to read config file: {}", filename));
120
121    serde_json::from_str(&content).unwrap_or_else(|_| panic!("Invalid JSON format in {}", filename))
122}
123