rs_utcp/
config.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use std::collections::HashMap;
4use std::path::PathBuf;
5use std::sync::Arc;
6
7/// Trait for loading configuration variables from various sources.
8#[async_trait]
9pub trait UtcpVariablesConfig: Send + Sync {
10    /// Loads all variables from the source.
11    async fn load(&self) -> Result<HashMap<String, String>>;
12    /// Gets a single variable by key.
13    async fn get(&self, key: &str) -> Result<String>;
14}
15
16/// Configuration for the UTCP client, including variables and provider file paths.
17#[derive(Clone)]
18pub struct UtcpClientConfig {
19    /// Map of inline variables.
20    pub variables: HashMap<String, String>,
21    /// Path to the providers configuration file.
22    pub providers_file_path: Option<PathBuf>,
23    /// List of variable loaders to use.
24    pub load_variables_from: Vec<Arc<dyn UtcpVariablesConfig>>,
25}
26
27impl Default for UtcpClientConfig {
28    fn default() -> Self {
29        Self {
30            variables: HashMap::new(),
31            providers_file_path: None,
32            load_variables_from: Vec::new(),
33        }
34    }
35}
36
37impl UtcpClientConfig {
38    /// Creates a new default configuration.
39    pub fn new() -> Self {
40        Self::default()
41    }
42
43    /// Sets the path to the providers configuration file.
44    pub fn with_providers_file(mut self, path: PathBuf) -> Self {
45        self.providers_file_path = Some(path);
46        self
47    }
48
49    /// Adds a single variable to the configuration.
50    pub fn with_variable(mut self, key: String, value: String) -> Self {
51        self.variables.insert(key, value);
52        self
53    }
54
55    /// Adds multiple variables to the configuration.
56    pub fn with_variables(mut self, vars: HashMap<String, String>) -> Self {
57        self.variables.extend(vars);
58        self
59    }
60
61    /// v1.0-style helper to set manual/call template path (reuses providers_file_path).
62    pub fn with_manual_path(mut self, path: PathBuf) -> Self {
63        self.providers_file_path = Some(path);
64        self
65    }
66
67    /// Retrieves a variable value by key, checking inline variables, loaders, and environment variables in order.
68    pub async fn get_variable(&self, key: &str) -> Option<String> {
69        // Check inline variables first
70        if let Some(val) = self.variables.get(key) {
71            return Some(val.clone());
72        }
73
74        // Check variable loaders
75        for loader in &self.load_variables_from {
76            if let Ok(val) = loader.get(key).await {
77                return Some(val);
78            }
79        }
80
81        // Check environment variables
82        std::env::var(key).ok()
83    }
84}
85
86/// A variable loader that reads from a .env file.
87pub struct DotEnvLoader {
88    file_path: PathBuf,
89}
90
91impl DotEnvLoader {
92    /// Creates a new DotEnvLoader for the specified file path.
93    pub fn new(file_path: PathBuf) -> Self {
94        Self { file_path }
95    }
96}
97
98#[async_trait]
99impl UtcpVariablesConfig for DotEnvLoader {
100    async fn load(&self) -> Result<HashMap<String, String>> {
101        let contents = tokio::fs::read_to_string(&self.file_path).await?;
102        let mut vars = HashMap::new();
103
104        for line in contents.lines() {
105            let line = line.trim();
106            if line.is_empty() || line.starts_with('#') {
107                continue;
108            }
109
110            if let Some((key, value)) = line.split_once('=') {
111                vars.insert(
112                    key.trim().to_string(),
113                    value.trim().trim_matches('"').to_string(),
114                );
115            }
116        }
117
118        Ok(vars)
119    }
120
121    async fn get(&self, key: &str) -> Result<String> {
122        let vars = self.load().await?;
123        vars.get(key)
124            .cloned()
125            .ok_or_else(|| anyhow::anyhow!("Variable {} not found", key))
126    }
127}