xianyun_core/config/
version.rs1use thiserror::Error;
4
5#[derive(Debug, Error, PartialEq, Eq)]
6pub enum VersionError {
7 #[error("unsupported config version")]
8 Unsupported,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
12pub enum ConfigVersion {
13 #[default]
14 Unknown,
15 V2,
16}
17
18impl std::fmt::Display for ConfigVersion {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 ConfigVersion::Unknown => write!(f, "unknown"),
22 ConfigVersion::V2 => write!(f, "v2"),
23 }
24 }
25}
26
27pub fn detect_version_from_file(path: &str) -> Result<ConfigVersion, super::errors::ConfigError> {
31 let data = std::fs::read(path)
32 .map_err(|_| super::errors::ConfigError::ConfigFileNotFound(path.to_string()))?;
33 detect_version_from_bytes(&data)
34}
35
36pub fn detect_version_from_bytes(data: &[u8]) -> Result<ConfigVersion, super::errors::ConfigError> {
40 let raw: serde_yaml::Value = serde_yaml::from_slice(data)
41 .map_err(|e| super::errors::ConfigError::InvalidYAML(e.to_string()))?;
42
43 let version = detect_config_version(&raw)?;
44 match version {
45 2 => Ok(ConfigVersion::V2),
46 _ => Ok(ConfigVersion::Unknown),
47 }
48}
49
50fn detect_config_version(raw: &serde_yaml::Value) -> Result<i64, super::errors::ConfigError> {
55 let map = raw
56 .as_mapping()
57 .ok_or(super::errors::ConfigError::MissingVersion)?;
58
59 let version = map
60 .get(serde_yaml::Value::String("version".to_string()))
61 .ok_or(super::errors::ConfigError::MissingVersion)?;
62
63 version
64 .as_i64()
65 .ok_or(super::errors::ConfigError::InvalidVersion)
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::config::errors::ConfigError;
72 use crate::config::global::GlobalConfig;
73 use std::fs;
74
75 fn write_temp_config(content: &str) -> (tempfile::TempDir, String) {
76 let dir = tempfile::tempdir().unwrap();
77 let path = dir.path().join("caproni.yaml");
78 fs::write(&path, content).unwrap();
79 let path_str = path.to_string_lossy().into_owned();
80 (dir, path_str)
81 }
82
83 #[test]
85 fn version_detection_v2() {
86 let v2_config = r#"
87version: 2
88cluster:
89 driver: mock
90 network:
91 hostname: gitlab.test
92repositories:
93 test-repo:
94 repository: https://example.com/test.git
95 directory: ./test
96deployers:
97 test-deployer:
98 type: helm
99 helm:
100 namespace: default
101 repository:
102 name: test
103 url: https://charts.example.com
104 release:
105 name: test
106 chart: test/test
107reloaders:
108 test-reloader:
109 type: mirror
110 repository: test-repo
111 mirror:
112 target:
113 namespace: default
114 kubernetes_type: pod
115 name: test-pod
116 edit_mode_processes:
117 test-process:
118 command: echo test
119"#;
120 let (_dir, path) = write_temp_config(v2_config);
121
122 let gc = GlobalConfig {
123 config_file: path,
124 ..Default::default()
125 };
126 let cfg = crate::config::caproni::load_caproni_config(&gc).expect("load should succeed");
127
128 assert_eq!(cfg.version, 2);
129 assert_eq!(cfg.repositories.map(|r| r.len()).unwrap_or_default(), 1);
130 assert_eq!(cfg.deployers.map(|d| d.len()).unwrap_or_default(), 1);
131 assert_eq!(cfg.reloaders.map(|r| r.len()).unwrap_or_default(), 1);
132 }
133
134 #[test]
136 fn version_detection_unsupported_version() {
137 let invalid_config = "\nversion: 99\ncluster:\n driver: mock\n network:\n hostname: gitlab.test\nrepositories: {}\n";
138 let (_dir, path) = write_temp_config(invalid_config);
139
140 let gc = GlobalConfig {
141 config_file: path,
142 ..Default::default()
143 };
144 let err = crate::config::caproni::load_caproni_config(&gc).unwrap_err();
145 assert!(err.to_string().contains("unsupported config version: 99"));
146 }
147
148 #[test]
150 fn detect_version_from_file_errors_when_version_field_missing() {
151 let no_version_config = "cluster:\n driver: colima\n\nrepositories:\n test-repo:\n repository: https://example.com/test.git\n directory: ./test/\n";
152 let (_dir, path) = write_temp_config(no_version_config);
153
154 let err = detect_version_from_file(&path).unwrap_err();
155 assert_eq!(err, ConfigError::MissingVersion);
156 }
157
158 #[test]
159 fn detect_version_from_file_detects_v2_with_version_field() {
160 let v2_config = "version: 2\ncluster:\n driver: colima\n\nrepositories:\n test-repo:\n repository: https://example.com/test.git\n directory: ./test/\n";
161 let (_dir, path) = write_temp_config(v2_config);
162
163 let version = detect_version_from_file(&path).unwrap();
164 assert_eq!(version, ConfigVersion::V2);
165 }
166
167 #[test]
168 fn detect_version_from_file_returns_error_for_nonexistent_file() {
169 let err = detect_version_from_file("/nonexistent/caproni.yaml");
170 assert!(err.is_err());
171 }
172
173 #[test]
174 fn detect_version_from_file_returns_error_for_invalid_yaml() {
175 let (_dir, path) = write_temp_config("invalid: yaml: content: [[[");
176 assert!(detect_version_from_file(&path).is_err());
177 }
178
179 #[test]
181 fn detect_version_from_bytes_errors_when_version_field_missing() {
182 let no_version_config = b"cluster:\n driver: colima\n\nrepositories:\n test-repo:\n repository: https://example.com/test.git\n directory: ./test/\n";
183 let err = detect_version_from_bytes(no_version_config).unwrap_err();
184 assert_eq!(err, ConfigError::MissingVersion);
185 }
186
187 #[test]
188 fn detect_version_from_bytes_detects_v2() {
189 let v2_config = b"version: 2\ncluster:\n driver: colima\n";
190 let version = detect_version_from_bytes(v2_config).unwrap();
191 assert_eq!(version, ConfigVersion::V2);
192 }
193
194 #[test]
195 fn detect_version_from_bytes_returns_error_for_invalid_yaml() {
196 assert!(detect_version_from_bytes(b"invalid: yaml: [[[").is_err());
197 }
198
199 #[test]
201 fn config_version_string_cases() {
202 assert_eq!(ConfigVersion::Unknown.to_string(), "unknown");
203 assert_eq!(ConfigVersion::V2.to_string(), "v2");
204 }
205}