1use std::collections::HashMap;
7
8use serde_json::Value;
9
10use super::bundled;
11use super::resolver::YamlType;
12
13pub const SCHEMA_URLS: &[(&str, &str)] = &[
18 ("k8s/deployment", "https://kubernetesjsonschema.dev/v{version}/_definitions.json#/definitions/io.k8s.api.apps.v1.Deployment"),
20 ("k8s/service", "https://kubernetesjsonschema.dev/v{version}/service.json"),
21 ("k8s/configmap", "https://kubernetesjsonschema.dev/v{version}/configmap.json"),
22 ("k8s/secret", "https://kubernetesjsonschema.dev/v{version}/secret.json"),
23 ("k8s/ingress", "https://kubernetesjsonschema.dev/v{version}/ingress.json"),
24 ("k8s/horizontalpodautoscaler", "https://kubernetesjsonschema.dev/v{version}/horizontalpodautoscaler.json"),
25 ("k8s/cronjob", "https://kubernetesjsonschema.dev/v{version}/cronjob.json"),
26 ("k8s/job", "https://kubernetesjsonschema.dev/v{version}/job.json"),
27 ("k8s/persistentvolumeclaim", "https://kubernetesjsonschema.dev/v{version}/persistentvolumeclaim.json"),
28 ("k8s/networkpolicy", "https://kubernetesjsonschema.dev/v{version}/networkpolicy.json"),
29 ("k8s/statefulset", "https://kubernetesjsonschema.dev/v{version}/statefulset.json"),
30 ("k8s/daemonset", "https://kubernetesjsonschema.dev/v{version}/daemonset.json"),
31 ("k8s/role", "https://kubernetesjsonschema.dev/v{version}/role.json"),
32 ("k8s/clusterrole", "https://kubernetesjsonschema.dev/v{version}/clusterrole.json"),
33 ("k8s/rolebinding", "https://kubernetesjsonschema.dev/v{version}/rolebinding.json"),
34 ("k8s/clusterrolebinding", "https://kubernetesjsonschema.dev/v{version}/clusterrolebinding.json"),
35 ("k8s/serviceaccount", "https://kubernetesjsonschema.dev/v{version}/serviceaccount.json"),
36 ("k8s/generic", "https://kubernetesjsonschema.dev/v{version}/_definitions.json"),
37
38 ("gitlab-ci", "https://json.schemastore.org/gitlab-ci.json"),
40 ("github-actions", "https://json.schemastore.org/github-workflow.json"),
41 ("docker-compose", "https://json.schemastore.org/docker-compose.json"),
42
43 ("prometheus", "https://json.schemastore.org/prometheus.json"),
45 ("alertmanager", "https://json.schemastore.org/alertmanager.json"),
46
47 ("helm-values", "https://json.schemastore.org/chart.json"),
49 ("ansible", "https://json.schemastore.org/ansible-playbook.json"),
50 ("openapi", "https://json.schemastore.org/openapi-3.0.json"),
51];
52
53pub const DEFAULT_K8S_VERSION: &str = "1.30.0";
55
56#[derive(Debug, Clone)]
58pub enum SchemaError {
59 NotFound(String),
61 FetchFailed(String, String),
63 ParseFailed(String),
65 ValidationFailed(String),
67}
68
69impl std::fmt::Display for SchemaError {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match self {
72 SchemaError::NotFound(key) => write!(f, "Schema not found: {}", key),
73 SchemaError::FetchFailed(url, err) => {
74 write!(f, "Failed to fetch schema from {}: {}", url, err)
75 }
76 SchemaError::ParseFailed(err) => write!(f, "Failed to parse schema: {}", err),
77 SchemaError::ValidationFailed(err) => write!(f, "Schema validation failed: {}", err),
78 }
79 }
80}
81
82impl std::error::Error for SchemaError {}
83
84pub struct SchemaRegistry {
106 cache: HashMap<String, Value>,
108 k8s_version: String,
110 use_bundled_fallback: bool,
112}
113
114impl Default for SchemaRegistry {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl SchemaRegistry {
121 pub fn new() -> Self {
125 Self {
126 cache: HashMap::new(),
127 k8s_version: DEFAULT_K8S_VERSION.to_string(),
128 use_bundled_fallback: true,
129 }
130 }
131
132 pub fn with_k8s_version(version: String) -> Self {
137 Self {
138 cache: HashMap::new(),
139 k8s_version: version,
140 use_bundled_fallback: true,
141 }
142 }
143
144 pub fn set_bundled_fallback(&mut self, enabled: bool) {
146 self.use_bundled_fallback = enabled;
147 }
148
149 pub fn set_k8s_version(&mut self, version: String) {
151 self.k8s_version = version;
152 self.cache.clear();
154 }
155
156pub fn get_schema_sync(&mut self, schema_type: &str) -> Result<Value, SchemaError> {
185 if let Some(cached) = self.cache.get(schema_type) {
187 return Ok(cached.clone());
188 }
189
190 if self.use_bundled_fallback
192 && let Some(bundled) = bundled::get_bundled_schema(schema_type) {
193 self.cache.insert(schema_type.to_string(), bundled.clone());
194 return Ok(bundled);
195 }
196
197 Err(SchemaError::NotFound(schema_type.to_string()))
199 }
200
201 pub fn get_schema_url(&self, schema_type: &str) -> Option<String> {
217 for (key, url_template) in SCHEMA_URLS {
218 if *key == schema_type {
219 let url = if url_template.contains("{version}") {
220 url_template.replace("{version}", &self.k8s_version)
221 } else {
222 url_template.to_string()
223 };
224 return Some(url);
225 }
226 }
227 None
228 }
229
230 pub fn cache_schema(&mut self, schema_type: &str, schema: Value) {
235 self.cache.insert(schema_type.to_string(), schema);
236 }
237
238 pub fn clear_cache(&mut self) {
240 self.cache.clear();
241 }
242
243 pub fn get_schema_for_type(&mut self, yaml_type: YamlType) -> Result<Value, SchemaError> {
246 self.get_schema_sync(yaml_type.to_schema_key())
247 }
248
249 pub fn list_schema_types(&self) -> Vec<&'static str> {
254 SCHEMA_URLS.iter().map(|(key, _)| *key).collect()
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_get_schema_url_k8s() {
264 let registry = SchemaRegistry::new();
265 let url = registry.get_schema_url("k8s/deployment").unwrap();
266 assert!(url.contains("kubernetesjsonschema.dev"));
267 assert!(url.contains("1.30.0"));
268 }
269
270 #[test]
271 fn test_get_schema_url_gitlab_ci() {
272 let registry = SchemaRegistry::new();
273 let url = registry.get_schema_url("gitlab-ci").unwrap();
274 assert_eq!(url, "https://json.schemastore.org/gitlab-ci.json");
275 }
276
277 #[test]
278 fn test_get_schema_url_unknown() {
279 let registry = SchemaRegistry::new();
280 let url = registry.get_schema_url("unknown/type");
281 assert!(url.is_none());
282 }
283
284 #[test]
285 fn test_custom_k8s_version() {
286 let registry = SchemaRegistry::with_k8s_version("1.28.0".to_string());
287 let url = registry.get_schema_url("k8s/service").unwrap();
288 assert!(url.contains("1.28.0"));
289 }
290
291 #[test]
292 fn test_list_schema_types() {
293 let registry = SchemaRegistry::new();
294 let types = registry.list_schema_types();
295 assert!(types.contains(&"k8s/deployment"));
296 assert!(types.contains(&"gitlab-ci"));
297 assert!(types.contains(&"docker-compose"));
298 }
299
300 #[test]
301 fn test_bundled_fallback() {
302 let mut registry = SchemaRegistry::new();
303 let result = registry.get_schema_sync("k8s/deployment");
305 assert!(result.is_ok());
306 }
307}