Skip to main content

suture/
config.rs

1use std::net::SocketAddr;
2
3/// Proxy configuration, sourced from environment variables.
4#[derive(Debug, Clone)]
5pub struct Config {
6    pub listen: SocketAddr,
7    pub openai_base: String,
8    pub anthropic_base: String,
9    pub vertex_enabled: bool,
10    pub vertex_base: Option<String>,
11    pub bedrock_enabled: bool,
12    pub bedrock_base: Option<String>,
13}
14
15impl Config {
16    /// Read from the process environment.
17    pub fn from_env() -> Self {
18        Self::from_map(|k| std::env::var(k).ok())
19    }
20
21    /// Read from an arbitrary key->value lookup (testable).
22    pub fn from_map(get: impl Fn(&str) -> Option<String>) -> Self {
23        let listen = get("SUTURE_LISTEN")
24            .and_then(|s| s.parse().ok())
25            .unwrap_or_else(|| "127.0.0.1:8787".parse().unwrap());
26        let trim = |s: String| s.trim_end_matches('/').to_string();
27        let openai_base =
28            trim(get("SUTURE_OPENAI_BASE").unwrap_or_else(|| "https://api.openai.com".to_string()));
29        let anthropic_base = trim(
30            get("SUTURE_ANTHROPIC_BASE").unwrap_or_else(|| "https://api.anthropic.com".to_string()),
31        );
32        let vertex_enabled = get("SUTURE_VERTEX_ENABLED")
33            .as_deref()
34            .map(|s| {
35                matches!(
36                    s.trim().to_ascii_lowercase().as_str(),
37                    "1" | "true" | "yes" | "on"
38                )
39            })
40            .unwrap_or(false);
41        let vertex_base = get("SUTURE_VERTEX_BASE").map(&trim);
42        let bedrock_enabled = get("SUTURE_BEDROCK_ENABLED")
43            .as_deref()
44            .map(|s| {
45                matches!(
46                    s.trim().to_ascii_lowercase().as_str(),
47                    "1" | "true" | "yes" | "on"
48                )
49            })
50            .unwrap_or(false);
51        let bedrock_base = get("SUTURE_BEDROCK_BASE").map(trim);
52        Self {
53            listen,
54            openai_base,
55            anthropic_base,
56            vertex_enabled,
57            vertex_base,
58            bedrock_enabled,
59            bedrock_base,
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn defaults_when_env_absent() {
70        let c = Config::from_map(|_| None);
71        assert_eq!(c.openai_base, "https://api.openai.com");
72        assert_eq!(c.anthropic_base, "https://api.anthropic.com");
73        assert_eq!(c.listen.to_string(), "127.0.0.1:8787");
74    }
75
76    #[test]
77    fn overrides_from_env() {
78        let c = Config::from_map(|k| match k {
79            "SUTURE_LISTEN" => Some("0.0.0.0:9000".to_string()),
80            "SUTURE_OPENAI_BASE" => Some("http://localhost:1234".to_string()),
81            _ => None,
82        });
83        assert_eq!(c.listen.to_string(), "0.0.0.0:9000");
84        assert_eq!(c.openai_base, "http://localhost:1234");
85        assert_eq!(c.anthropic_base, "https://api.anthropic.com");
86    }
87
88    #[test]
89    fn trailing_slash_trimmed() {
90        let c = Config::from_map(|k| match k {
91            "SUTURE_OPENAI_BASE" => Some("http://x:1/".to_string()),
92            _ => None,
93        });
94        assert_eq!(c.openai_base, "http://x:1");
95    }
96
97    #[test]
98    fn vertex_disabled_by_default() {
99        let c = Config::from_map(|_| None);
100        assert!(!c.vertex_enabled);
101        assert_eq!(c.vertex_base, None);
102    }
103
104    #[test]
105    fn vertex_enabled_and_base_from_env() {
106        let c = Config::from_map(|k| match k {
107            "SUTURE_VERTEX_ENABLED" => Some("1".to_string()),
108            "SUTURE_VERTEX_BASE" => Some("http://localhost:1234/".to_string()),
109            _ => None,
110        });
111        assert!(c.vertex_enabled);
112        assert_eq!(c.vertex_base.as_deref(), Some("http://localhost:1234"));
113    }
114
115    #[test]
116    fn vertex_enabled_truthy_values() {
117        for v in ["1", "true", "TRUE", "yes", "on"] {
118            let c = Config::from_map(|k| {
119                if k == "SUTURE_VERTEX_ENABLED" {
120                    Some(v.to_string())
121                } else {
122                    None
123                }
124            });
125            assert!(c.vertex_enabled, "{v} should enable");
126        }
127        for v in ["0", "false", "no", ""] {
128            let c = Config::from_map(|k| {
129                if k == "SUTURE_VERTEX_ENABLED" {
130                    Some(v.to_string())
131                } else {
132                    None
133                }
134            });
135            assert!(!c.vertex_enabled, "{v:?} should not enable");
136        }
137    }
138
139    #[test]
140    fn bedrock_disabled_by_default() {
141        let c = Config::from_map(|_| None);
142        assert!(!c.bedrock_enabled);
143        assert_eq!(c.bedrock_base, None);
144    }
145
146    #[test]
147    fn bedrock_enabled_and_base_from_env() {
148        let c = Config::from_map(|k| match k {
149            "SUTURE_BEDROCK_ENABLED" => Some("true".to_string()),
150            "SUTURE_BEDROCK_BASE" => Some("http://localhost:7/".to_string()),
151            _ => None,
152        });
153        assert!(c.bedrock_enabled);
154        assert_eq!(c.bedrock_base.as_deref(), Some("http://localhost:7"));
155    }
156}