Skip to main content

rskit_workload/
config.rs

1//! Provider-agnostic workload configuration.
2//!
3//! Mirrors gokit's `workload.Config` — the same shape across kits
4//! so a workload app is structurally identical regardless of language.
5
6use std::collections::HashMap;
7
8use rskit_errors::{AppError, AppResult};
9use serde::{Deserialize, Serialize};
10
11/// Provider filled in by [`WorkloadConfig::default`]
12/// and [`WorkloadConfig::apply_defaults`] when [`WorkloadConfig::provider`] is empty.
13///
14/// [`WorkloadConfig::validate`] still rejects an empty provider,
15/// so call `apply_defaults` before validating configs that may omit it.
16pub const DEFAULT_PROVIDER: &str = "docker";
17
18/// Provider-agnostic workload configuration.
19#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
20#[serde(default)]
21pub struct WorkloadConfig {
22    /// Whether the workload component is active. When `false`, the component starts as a healthy no-op
23    /// and builds no backend.
24    pub enabled: bool,
25    /// Backend name looked up in an injected [`crate::WorkloadRegistry`].
26    pub provider: String,
27    /// Labels applied to every workload the manager deploys.
28    pub default_labels: HashMap<String, String>,
29}
30
31impl Default for WorkloadConfig {
32    fn default() -> Self {
33        Self {
34            enabled: false,
35            provider: DEFAULT_PROVIDER.to_owned(),
36            default_labels: HashMap::new(),
37        }
38    }
39}
40
41impl WorkloadConfig {
42    /// Normalize `provider` by trimming surrounding whitespace, filling it with the default when empty,
43    /// so stored and logged config is deterministic.
44    pub fn apply_defaults(&mut self) {
45        let trimmed = self.provider.trim();
46        if trimmed.is_empty() {
47            DEFAULT_PROVIDER.clone_into(&mut self.provider);
48        } else if trimmed.len() != self.provider.len() {
49            self.provider = trimmed.to_owned();
50        }
51    }
52
53    /// Validate the core configuration.
54    ///
55    /// # Errors
56    ///
57    /// Returns [`rskit_errors::ErrorCode::MissingField`] when `provider` is empty.
58    pub fn validate(&self) -> AppResult<()> {
59        if self.provider.trim().is_empty() {
60            return Err(AppError::missing_field("workload.provider"));
61        }
62        Ok(())
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn default_is_disabled_with_docker_provider() {
72        let cfg = WorkloadConfig::default();
73        assert!(!cfg.enabled);
74        assert_eq!(cfg.provider, "docker");
75        assert!(cfg.default_labels.is_empty());
76    }
77
78    #[test]
79    fn apply_defaults_fills_empty_provider() {
80        let mut cfg = WorkloadConfig {
81            provider: "   ".to_string(),
82            ..Default::default()
83        };
84        cfg.apply_defaults();
85        assert_eq!(cfg.provider, "docker");
86    }
87
88    #[test]
89    fn apply_defaults_keeps_explicit_provider() {
90        let mut cfg = WorkloadConfig {
91            provider: "kubernetes".to_string(),
92            ..Default::default()
93        };
94        cfg.apply_defaults();
95        assert_eq!(cfg.provider, "kubernetes");
96    }
97
98    #[test]
99    fn apply_defaults_trims_surrounding_whitespace() {
100        let mut cfg = WorkloadConfig {
101            provider: "  docker  ".to_string(),
102            ..Default::default()
103        };
104        cfg.apply_defaults();
105        assert_eq!(cfg.provider, "docker");
106    }
107
108    #[test]
109    fn validate_rejects_empty_provider() {
110        let cfg = WorkloadConfig {
111            provider: String::new(),
112            ..Default::default()
113        };
114        let err = cfg.validate().unwrap_err();
115        assert_eq!(err.code(), rskit_errors::ErrorCode::MissingField);
116    }
117
118    #[test]
119    fn validate_accepts_named_provider() {
120        let cfg = WorkloadConfig {
121            provider: "docker".to_string(),
122            ..Default::default()
123        };
124        assert!(cfg.validate().is_ok());
125    }
126}