1use std::net::SocketAddr;
2
3#[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 pub fn from_env() -> Self {
18 Self::from_map(|k| std::env::var(k).ok())
19 }
20
21 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}