1use std::env;
6use std::net::SocketAddr;
7
8use crate::error::{Result, SdkError};
9
10#[derive(Debug, Clone)]
12pub struct SdkConfig {
13 pub instance_id: String,
15 pub tenant_id: String,
17 pub server_addr: SocketAddr,
19 pub server_name: String,
21 pub skip_cert_verification: bool,
23 pub connect_timeout_ms: u64,
25 pub request_timeout_ms: u64,
27 pub signal_poll_interval_ms: u64,
29 pub heartbeat_interval_ms: u64,
34}
35
36impl SdkConfig {
37 pub fn from_env() -> Result<Self> {
52 let instance_id = env::var("RUNTARA_INSTANCE_ID")
53 .map_err(|_| SdkError::Config("RUNTARA_INSTANCE_ID is required".to_string()))?;
54
55 let tenant_id = env::var("RUNTARA_TENANT_ID")
56 .map_err(|_| SdkError::Config("RUNTARA_TENANT_ID is required".to_string()))?;
57
58 let server_addr = env::var("RUNTARA_SERVER_ADDR")
59 .unwrap_or_else(|_| "127.0.0.1:8001".to_string())
60 .parse()
61 .map_err(|e| SdkError::Config(format!("invalid RUNTARA_SERVER_ADDR: {}", e)))?;
62
63 let server_name =
64 env::var("RUNTARA_SERVER_NAME").unwrap_or_else(|_| "localhost".to_string());
65
66 let skip_cert_verification = env::var("RUNTARA_SKIP_CERT_VERIFICATION")
67 .map(|v| v == "true" || v == "1")
68 .unwrap_or(false);
69
70 let connect_timeout_ms = env::var("RUNTARA_CONNECT_TIMEOUT_MS")
71 .ok()
72 .and_then(|v| v.parse().ok())
73 .unwrap_or(10_000);
74
75 let request_timeout_ms = env::var("RUNTARA_REQUEST_TIMEOUT_MS")
76 .ok()
77 .and_then(|v| v.parse().ok())
78 .unwrap_or(30_000);
79
80 let signal_poll_interval_ms = env::var("RUNTARA_SIGNAL_POLL_INTERVAL_MS")
81 .ok()
82 .and_then(|v| v.parse().ok())
83 .unwrap_or(1_000);
84
85 let heartbeat_interval_ms = env::var("RUNTARA_HEARTBEAT_INTERVAL_MS")
86 .ok()
87 .and_then(|v| v.parse().ok())
88 .unwrap_or(30_000);
89
90 Ok(Self {
91 instance_id,
92 tenant_id,
93 server_addr,
94 server_name,
95 skip_cert_verification,
96 connect_timeout_ms,
97 request_timeout_ms,
98 signal_poll_interval_ms,
99 heartbeat_interval_ms,
100 })
101 }
102
103 pub fn localhost(instance_id: impl Into<String>, tenant_id: impl Into<String>) -> Self {
109 Self {
110 instance_id: instance_id.into(),
111 tenant_id: tenant_id.into(),
112 server_addr: "127.0.0.1:8001".parse().unwrap(),
113 server_name: "localhost".to_string(),
114 skip_cert_verification: true,
115 connect_timeout_ms: 10_000,
116 request_timeout_ms: 30_000,
117 signal_poll_interval_ms: 1_000,
118 heartbeat_interval_ms: 30_000,
119 }
120 }
121
122 pub fn new(instance_id: impl Into<String>, tenant_id: impl Into<String>) -> Self {
124 Self {
125 instance_id: instance_id.into(),
126 tenant_id: tenant_id.into(),
127 server_addr: "127.0.0.1:8001".parse().unwrap(),
128 server_name: "localhost".to_string(),
129 skip_cert_verification: false,
130 connect_timeout_ms: 10_000,
131 request_timeout_ms: 30_000,
132 signal_poll_interval_ms: 1_000,
133 heartbeat_interval_ms: 30_000,
134 }
135 }
136
137 pub fn with_server_addr(mut self, addr: SocketAddr) -> Self {
139 self.server_addr = addr;
140 self
141 }
142
143 pub fn with_server_name(mut self, name: impl Into<String>) -> Self {
145 self.server_name = name.into();
146 self
147 }
148
149 pub fn with_skip_cert_verification(mut self, skip: bool) -> Self {
151 self.skip_cert_verification = skip;
152 self
153 }
154
155 pub fn with_signal_poll_interval_ms(mut self, interval_ms: u64) -> Self {
157 self.signal_poll_interval_ms = interval_ms;
158 self
159 }
160
161 pub fn with_heartbeat_interval_ms(mut self, interval_ms: u64) -> Self {
164 self.heartbeat_interval_ms = interval_ms;
165 self
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
178 fn test_localhost_config() {
179 let config = SdkConfig::localhost("test-instance", "test-tenant");
180 assert_eq!(config.instance_id, "test-instance");
181 assert_eq!(config.tenant_id, "test-tenant");
182 assert!(config.skip_cert_verification);
183 assert_eq!(config.server_addr, "127.0.0.1:8001".parse().unwrap());
184 }
185
186 #[test]
187 fn test_new_config() {
188 let config = SdkConfig::new("my-instance", "my-tenant");
189 assert_eq!(config.instance_id, "my-instance");
190 assert_eq!(config.tenant_id, "my-tenant");
191 assert!(!config.skip_cert_verification);
193 assert_eq!(config.server_addr, "127.0.0.1:8001".parse().unwrap());
194 assert_eq!(config.server_name, "localhost");
195 }
196
197 #[test]
198 fn test_config_with_string_types() {
199 let config = SdkConfig::new(String::from("inst"), String::from("tenant"));
200 assert_eq!(config.instance_id, "inst");
201 assert_eq!(config.tenant_id, "tenant");
202 }
203
204 #[test]
205 fn test_localhost_config_with_string_types() {
206 let config = SdkConfig::localhost(String::from("inst"), String::from("tenant"));
207 assert_eq!(config.instance_id, "inst");
208 assert_eq!(config.tenant_id, "tenant");
209 }
210
211 #[test]
216 fn test_builder_pattern() {
217 let config = SdkConfig::new("inst", "tenant")
218 .with_server_addr("192.168.1.1:8000".parse().unwrap())
219 .with_skip_cert_verification(true)
220 .with_signal_poll_interval_ms(500);
221
222 assert_eq!(config.server_addr, "192.168.1.1:8000".parse().unwrap());
223 assert!(config.skip_cert_verification);
224 assert_eq!(config.signal_poll_interval_ms, 500);
225 }
226
227 #[test]
228 fn test_with_server_addr() {
229 let config =
230 SdkConfig::new("inst", "tenant").with_server_addr("10.0.0.1:9000".parse().unwrap());
231 assert_eq!(config.server_addr, "10.0.0.1:9000".parse().unwrap());
232 }
233
234 #[test]
235 fn test_with_server_name() {
236 let config = SdkConfig::new("inst", "tenant").with_server_name("my-server.example.com");
237 assert_eq!(config.server_name, "my-server.example.com");
238 }
239
240 #[test]
241 fn test_with_server_name_string() {
242 let config =
243 SdkConfig::new("inst", "tenant").with_server_name(String::from("server.local"));
244 assert_eq!(config.server_name, "server.local");
245 }
246
247 #[test]
248 fn test_with_skip_cert_verification_true() {
249 let config = SdkConfig::new("inst", "tenant").with_skip_cert_verification(true);
250 assert!(config.skip_cert_verification);
251 }
252
253 #[test]
254 fn test_with_skip_cert_verification_false() {
255 let config = SdkConfig::localhost("inst", "tenant").with_skip_cert_verification(false);
257 assert!(!config.skip_cert_verification);
258 }
259
260 #[test]
261 fn test_with_signal_poll_interval_ms() {
262 let config = SdkConfig::new("inst", "tenant").with_signal_poll_interval_ms(250);
263 assert_eq!(config.signal_poll_interval_ms, 250);
264 }
265
266 #[test]
267 fn test_builder_chaining() {
268 let config = SdkConfig::new("inst", "tenant")
269 .with_server_addr("172.16.0.1:7000".parse().unwrap())
270 .with_server_name("custom-server")
271 .with_skip_cert_verification(true)
272 .with_signal_poll_interval_ms(100)
273 .with_heartbeat_interval_ms(5000);
274
275 assert_eq!(config.server_addr, "172.16.0.1:7000".parse().unwrap());
276 assert_eq!(config.server_name, "custom-server");
277 assert!(config.skip_cert_verification);
278 assert_eq!(config.signal_poll_interval_ms, 100);
279 assert_eq!(config.heartbeat_interval_ms, 5000);
280 }
281
282 #[test]
287 fn test_heartbeat_interval_default() {
288 let config = SdkConfig::new("inst", "tenant");
289 assert_eq!(config.heartbeat_interval_ms, 30_000);
290 }
291
292 #[test]
293 fn test_heartbeat_interval_localhost_default() {
294 let config = SdkConfig::localhost("inst", "tenant");
295 assert_eq!(config.heartbeat_interval_ms, 30_000);
296 }
297
298 #[test]
299 fn test_heartbeat_interval_builder() {
300 let config = SdkConfig::new("inst", "tenant").with_heartbeat_interval_ms(15_000);
301 assert_eq!(config.heartbeat_interval_ms, 15_000);
302 }
303
304 #[test]
305 fn test_heartbeat_interval_disabled() {
306 let config = SdkConfig::new("inst", "tenant").with_heartbeat_interval_ms(0);
307 assert_eq!(config.heartbeat_interval_ms, 0);
308 }
309
310 #[test]
311 fn test_heartbeat_interval_custom_value() {
312 let config = SdkConfig::new("inst", "tenant").with_heartbeat_interval_ms(60_000);
313 assert_eq!(config.heartbeat_interval_ms, 60_000);
314 }
315
316 #[test]
321 fn test_connect_timeout_default() {
322 let config = SdkConfig::new("inst", "tenant");
323 assert_eq!(config.connect_timeout_ms, 10_000);
324 }
325
326 #[test]
327 fn test_request_timeout_default() {
328 let config = SdkConfig::new("inst", "tenant");
329 assert_eq!(config.request_timeout_ms, 30_000);
330 }
331
332 #[test]
333 fn test_signal_poll_interval_default() {
334 let config = SdkConfig::new("inst", "tenant");
335 assert_eq!(config.signal_poll_interval_ms, 1_000);
336 }
337
338 #[test]
343 fn test_config_clone() {
344 let config = SdkConfig::new("inst", "tenant")
345 .with_server_addr("10.0.0.1:8080".parse().unwrap())
346 .with_skip_cert_verification(true);
347
348 let cloned = config.clone();
349 assert_eq!(cloned.instance_id, config.instance_id);
350 assert_eq!(cloned.tenant_id, config.tenant_id);
351 assert_eq!(cloned.server_addr, config.server_addr);
352 assert_eq!(cloned.skip_cert_verification, config.skip_cert_verification);
353 }
354
355 #[test]
356 fn test_config_debug() {
357 let config = SdkConfig::new("test-inst", "test-tenant");
358 let debug_str = format!("{:?}", config);
359 assert!(debug_str.contains("test-inst"));
360 assert!(debug_str.contains("test-tenant"));
361 assert!(debug_str.contains("server_addr"));
362 }
363
364 #[test]
369 fn test_empty_instance_id() {
370 let config = SdkConfig::new("", "tenant");
371 assert_eq!(config.instance_id, "");
372 }
373
374 #[test]
375 fn test_empty_tenant_id() {
376 let config = SdkConfig::new("inst", "");
377 assert_eq!(config.tenant_id, "");
378 }
379
380 #[test]
381 fn test_special_characters_in_ids() {
382 let config = SdkConfig::new("inst-123_test", "tenant/special:chars");
383 assert_eq!(config.instance_id, "inst-123_test");
384 assert_eq!(config.tenant_id, "tenant/special:chars");
385 }
386
387 #[test]
388 fn test_ipv6_server_addr() {
389 let config =
390 SdkConfig::new("inst", "tenant").with_server_addr("[::1]:8001".parse().unwrap());
391 assert_eq!(config.server_addr, "[::1]:8001".parse().unwrap());
392 }
393}