1use rskit_validation::Validate;
2use serde::{Deserialize, Serialize};
3use validator::{ValidationError, ValidationErrors};
4
5#[derive(Debug, Clone, Deserialize)]
7pub struct ServiceConfig {
8 #[serde(default = "ServiceConfig::default_name")]
10 pub name: String,
11
12 #[serde(default)]
14 pub environment: Environment,
15
16 #[serde(default = "ServiceConfig::default_version")]
18 pub version: String,
19
20 #[serde(default = "ServiceConfig::default_address")]
22 pub address: String,
23
24 #[serde(default = "ServiceConfig::default_port")]
26 pub port: u16,
27
28 #[serde(default)]
30 pub debug: bool,
31
32 #[serde(default)]
34 pub logging: LoggingConfig,
35}
36
37impl Validate for ServiceConfig {
38 fn validate(&self) -> Result<(), ValidationErrors> {
39 let mut errors = ValidationErrors::new();
40 if self.name.trim().is_empty() {
41 errors.add("name", ValidationError::new("length"));
42 }
43 if errors.is_empty() {
44 Ok(())
45 } else {
46 Err(errors)
47 }
48 }
49}
50
51impl ServiceConfig {
52 fn default_name() -> String {
53 "service".to_string()
54 }
55 fn default_version() -> String {
56 rskit_version::package_version().to_string()
57 }
58 fn default_address() -> String {
59 "0.0.0.0".to_string()
60 }
61 fn default_port() -> u16 {
62 50051
63 }
64}
65
66impl Default for ServiceConfig {
67 fn default() -> Self {
68 Self {
69 name: Self::default_name(),
70 environment: Environment::default(),
71 version: Self::default_version(),
72 address: Self::default_address(),
73 port: Self::default_port(),
74 debug: false,
75 logging: LoggingConfig::default(),
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
82#[non_exhaustive]
83#[serde(rename_all = "lowercase")]
84pub enum Environment {
85 #[default]
87 Development,
88 Staging,
90 Production,
92}
93
94impl Environment {
95 pub fn is_production(&self) -> bool {
97 *self == Environment::Production
98 }
99}
100
101impl std::fmt::Display for Environment {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 match self {
104 Environment::Development => f.write_str("development"),
105 Environment::Staging => f.write_str("staging"),
106 Environment::Production => f.write_str("production"),
107 }
108 }
109}
110
111#[derive(Debug, Clone, Deserialize)]
113pub struct LoggingConfig {
114 #[serde(default = "LoggingConfig::default_level")]
116 pub level: String,
117
118 #[serde(default)]
120 pub format: LogFormat,
121
122 pub service_name: Option<String>,
124
125 #[serde(default)]
127 pub output: LogOutput,
128
129 #[serde(default)]
131 pub with_caller: bool,
132}
133
134impl Default for LoggingConfig {
135 fn default() -> Self {
136 Self {
137 level: Self::default_level(),
138 format: LogFormat::default(),
139 service_name: None,
140 output: LogOutput::default(),
141 with_caller: false,
142 }
143 }
144}
145
146impl LoggingConfig {
147 fn default_level() -> String {
148 "info".to_string()
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
154#[non_exhaustive]
155#[serde(rename_all = "lowercase")]
156pub enum LogFormat {
157 Json,
159 #[default]
161 Console,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
166#[non_exhaustive]
167#[serde(tag = "type", rename_all = "lowercase")]
168pub enum LogOutput {
169 #[default]
171 Stdout,
172 Stderr,
174 File {
176 path: String,
178 },
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
188 fn environment_display_development() {
189 assert_eq!(Environment::Development.to_string(), "development");
190 }
191
192 #[test]
193 fn environment_display_staging() {
194 assert_eq!(Environment::Staging.to_string(), "staging");
195 }
196
197 #[test]
198 fn environment_display_production() {
199 assert_eq!(Environment::Production.to_string(), "production");
200 }
201
202 #[test]
203 fn environment_default_is_development() {
204 assert_eq!(Environment::default(), Environment::Development);
205 }
206
207 #[test]
208 fn environment_is_production_returns_true_for_production() {
209 assert!(Environment::Production.is_production());
210 }
211
212 #[test]
213 fn environment_is_production_returns_false_for_development() {
214 assert!(!Environment::Development.is_production());
215 }
216
217 #[test]
218 fn environment_is_production_returns_false_for_staging() {
219 assert!(!Environment::Staging.is_production());
220 }
221
222 #[test]
223 fn environment_deserialize_from_lowercase_string() {
224 let dev: Environment = serde_json::from_str(r#""development""#).unwrap();
225 assert_eq!(dev, Environment::Development);
226
227 let stg: Environment = serde_json::from_str(r#""staging""#).unwrap();
228 assert_eq!(stg, Environment::Staging);
229
230 let prd: Environment = serde_json::from_str(r#""production""#).unwrap();
231 assert_eq!(prd, Environment::Production);
232 }
233
234 #[test]
235 fn environment_deserialize_unknown_string_fails() {
236 let result: Result<Environment, _> = serde_json::from_str(r#""unknown""#);
237 assert!(result.is_err());
238 }
239
240 #[test]
241 fn environment_clone_and_eq() {
242 let env = Environment::Staging;
243 let cloned = env.clone();
244 assert_eq!(env, cloned);
245 }
246
247 #[test]
250 fn service_config_default_name() {
251 let cfg = ServiceConfig::default();
252 assert_eq!(cfg.name, "service");
253 }
254
255 #[test]
256 fn service_config_default_environment() {
257 let cfg = ServiceConfig::default();
258 assert_eq!(cfg.environment, Environment::Development);
259 }
260
261 #[test]
262 fn service_config_default_debug_false() {
263 let cfg = ServiceConfig::default();
264 assert!(!cfg.debug);
265 }
266
267 #[test]
268 fn service_config_default_version_is_cargo_pkg() {
269 let cfg = ServiceConfig::default();
270 assert_eq!(cfg.version, rskit_version::package_version());
271 }
272
273 #[test]
274 fn service_config_default_logging() {
275 let cfg = ServiceConfig::default();
276 assert_eq!(cfg.logging.level, "info");
277 assert_eq!(cfg.logging.format, LogFormat::Console);
278 assert_eq!(cfg.logging.output, LogOutput::Stdout);
279 }
280
281 #[test]
282 fn service_config_validation_empty_name_fails() {
283 use rskit_validation::Validate;
284 let cfg = ServiceConfig {
285 name: String::new(),
286 ..Default::default()
287 };
288 assert!(cfg.validate().is_err());
289 }
290
291 #[test]
292 fn service_config_validation_valid_name_passes() {
293 use rskit_validation::Validate;
294 let cfg = ServiceConfig::default();
295 assert!(cfg.validate().is_ok());
296 }
297
298 #[test]
299 fn service_config_validation_long_name_passes() {
300 use rskit_validation::Validate;
301 let cfg = ServiceConfig {
302 name: "a".repeat(1000),
303 ..Default::default()
304 };
305 assert!(cfg.validate().is_ok());
306 }
307
308 #[test]
309 fn service_config_all_fields_accessible() {
310 let cfg = ServiceConfig::default();
311 let _ = &cfg.name;
312 let _ = &cfg.environment;
313 let _ = &cfg.version;
314 let _ = &cfg.address;
315 let _ = cfg.port;
316 let _ = cfg.debug;
317 let _ = &cfg.logging;
318 }
319
320 #[test]
321 fn service_config_default_address() {
322 let cfg = ServiceConfig::default();
323 assert_eq!(cfg.address, "0.0.0.0");
324 }
325
326 #[test]
327 fn service_config_default_port() {
328 let cfg = ServiceConfig::default();
329 assert_eq!(cfg.port, 50051);
330 }
331
332 #[test]
333 fn service_config_debug_format() {
334 let cfg = ServiceConfig::default();
335 let debug_str = format!("{:?}", cfg);
336 assert!(debug_str.contains("ServiceConfig"));
337 assert!(debug_str.contains("service"));
338 }
339
340 #[test]
343 fn logging_config_default_level() {
344 let cfg = LoggingConfig::default();
345 assert_eq!(cfg.level, "info");
346 }
347
348 #[test]
349 fn logging_config_default_format() {
350 let cfg = LoggingConfig::default();
351 assert_eq!(cfg.format, LogFormat::Console);
352 }
353
354 #[test]
355 fn logging_config_default_output() {
356 let cfg = LoggingConfig::default();
357 assert_eq!(cfg.output, LogOutput::Stdout);
358 }
359
360 #[test]
361 fn logging_config_default_service_name_is_none() {
362 let cfg = LoggingConfig::default();
363 assert!(cfg.service_name.is_none());
364 }
365
366 #[test]
367 fn logging_config_default_with_caller_false() {
368 let cfg = LoggingConfig::default();
369 assert!(!cfg.with_caller);
370 }
371
372 #[test]
375 fn log_format_default_is_console() {
376 assert_eq!(LogFormat::default(), LogFormat::Console);
377 }
378
379 #[test]
380 fn log_format_json_variant() {
381 let fmt = LogFormat::Json;
382 assert_ne!(fmt, LogFormat::Console);
383 }
384
385 #[test]
386 fn log_format_deserialize_json() {
387 let fmt: LogFormat = serde_json::from_str(r#""json""#).unwrap();
388 assert_eq!(fmt, LogFormat::Json);
389 }
390
391 #[test]
392 fn log_format_deserialize_console() {
393 let fmt: LogFormat = serde_json::from_str(r#""console""#).unwrap();
394 assert_eq!(fmt, LogFormat::Console);
395 }
396
397 #[test]
400 fn log_output_default_is_stdout() {
401 assert_eq!(LogOutput::default(), LogOutput::Stdout);
402 }
403
404 #[test]
405 fn log_output_stderr_variant() {
406 let out = LogOutput::Stderr;
407 assert_ne!(out, LogOutput::Stdout);
408 }
409
410 #[test]
411 fn log_output_file_variant() {
412 let out = LogOutput::File {
413 path: "/var/log/app.log".to_string(),
414 };
415 assert_eq!(
416 out,
417 LogOutput::File {
418 path: "/var/log/app.log".to_string()
419 }
420 );
421 }
422
423 #[test]
424 fn log_output_deserialize_stdout() {
425 let out: LogOutput = serde_json::from_str(r#"{"type":"stdout"}"#).unwrap();
426 assert_eq!(out, LogOutput::Stdout);
427 }
428
429 #[test]
430 fn log_output_deserialize_stderr() {
431 let out: LogOutput = serde_json::from_str(r#"{"type":"stderr"}"#).unwrap();
432 assert_eq!(out, LogOutput::Stderr);
433 }
434
435 #[test]
436 fn log_output_deserialize_file() {
437 let out: LogOutput =
438 serde_json::from_str(r#"{"type":"file","path":"/logs/app.log"}"#).unwrap();
439 assert_eq!(
440 out,
441 LogOutput::File {
442 path: "/logs/app.log".to_string()
443 }
444 );
445 }
446}