Skip to main content

fraiseql_server/config/
env.rs

1use std::{env, time::Duration};
2
3/// Resolve a value that may be an environment variable reference
4pub fn resolve_env_value(value: &str) -> Result<String, EnvError> {
5    if value.starts_with("${") && value.ends_with("}") {
6        let var_name = &value[2..value.len() - 1];
7
8        // Support default values: ${VAR:-default}
9        if let Some((name, default)) = var_name.split_once(":-") {
10            return env::var(name).or_else(|_| Ok(default.to_string()));
11        }
12
13        // Support required with message: ${VAR:?message}
14        if let Some((name, message)) = var_name.split_once(":?") {
15            return env::var(name).map_err(|_| EnvError::MissingVarWithMessage {
16                name:    name.to_string(),
17                message: message.to_string(),
18            });
19        }
20
21        env::var(var_name).map_err(|_| EnvError::MissingVar {
22            name: var_name.to_string(),
23        })
24    } else {
25        Ok(value.to_string())
26    }
27}
28
29/// Get value from environment variable name stored in config
30pub fn get_env_value(env_var_name: &str) -> Result<String, EnvError> {
31    env::var(env_var_name).map_err(|_| EnvError::MissingVar {
32        name: env_var_name.to_string(),
33    })
34}
35
36/// Parse size strings like "10MB", "1GB"
37pub fn parse_size(s: &str) -> Result<usize, ParseError> {
38    let s = s.trim();
39    let s_upper = s.to_uppercase();
40
41    let (num_str, multiplier) = if s_upper.ends_with("GB") {
42        (&s[..s.len() - 2], 1024 * 1024 * 1024)
43    } else if s_upper.ends_with("MB") {
44        (&s[..s.len() - 2], 1024 * 1024)
45    } else if s_upper.ends_with("KB") {
46        (&s[..s.len() - 2], 1024)
47    } else if s_upper.ends_with("B") {
48        (&s[..s.len() - 1], 1)
49    } else {
50        // Assume bytes if no unit
51        (s, 1)
52    };
53
54    let num: usize = num_str.trim().parse().map_err(|_| ParseError::InvalidSize {
55        value:  s.to_string(),
56        reason: "Invalid number".to_string(),
57    })?;
58
59    num.checked_mul(multiplier).ok_or_else(|| ParseError::InvalidSize {
60        value:  s.to_string(),
61        reason: "Value too large".to_string(),
62    })
63}
64
65/// Parse duration strings like "30s", "5m", "1h"
66pub fn parse_duration(s: &str) -> Result<Duration, ParseError> {
67    let s = s.trim().to_lowercase();
68
69    let (num_str, multiplier_ms) = if s.ends_with("ms") {
70        (&s[..s.len() - 2], 1u64)
71    } else if s.ends_with('s') {
72        (&s[..s.len() - 1], 1000)
73    } else if s.ends_with('m') {
74        (&s[..s.len() - 1], 60 * 1000)
75    } else if s.ends_with('h') {
76        (&s[..s.len() - 1], 60 * 60 * 1000)
77    } else if s.ends_with('d') {
78        (&s[..s.len() - 1], 24 * 60 * 60 * 1000)
79    } else {
80        return Err(ParseError::InvalidDuration {
81            value:  s,
82            reason: "Missing unit (ms, s, m, h, d)".to_string(),
83        });
84    };
85
86    let num: u64 = num_str.trim().parse().map_err(|_| ParseError::InvalidDuration {
87        value:  s.clone(),
88        reason: "Invalid number".to_string(),
89    })?;
90
91    Ok(Duration::from_millis(num * multiplier_ms))
92}
93
94#[derive(Debug, thiserror::Error)]
95pub enum EnvError {
96    #[error("Missing environment variable: {name}")]
97    MissingVar { name: String },
98
99    #[error("Missing environment variable {name}: {message}")]
100    MissingVarWithMessage { name: String, message: String },
101}
102
103#[derive(Debug, thiserror::Error)]
104pub enum ParseError {
105    #[error("Invalid size value '{value}': {reason}")]
106    InvalidSize { value: String, reason: String },
107
108    #[error("Invalid duration value '{value}': {reason}")]
109    InvalidDuration { value: String, reason: String },
110}