use std::collections::HashMap;
use serde_json::Value;
use super::bundled;
use super::resolver::YamlType;
pub const SCHEMA_URLS: &[(&str, &str)] = &[
("k8s/deployment", "https://kubernetesjsonschema.dev/v{version}/_definitions.json#/definitions/io.k8s.api.apps.v1.Deployment"),
("k8s/service", "https://kubernetesjsonschema.dev/v{version}/service.json"),
("k8s/configmap", "https://kubernetesjsonschema.dev/v{version}/configmap.json"),
("k8s/secret", "https://kubernetesjsonschema.dev/v{version}/secret.json"),
("k8s/ingress", "https://kubernetesjsonschema.dev/v{version}/ingress.json"),
("k8s/horizontalpodautoscaler", "https://kubernetesjsonschema.dev/v{version}/horizontalpodautoscaler.json"),
("k8s/cronjob", "https://kubernetesjsonschema.dev/v{version}/cronjob.json"),
("k8s/job", "https://kubernetesjsonschema.dev/v{version}/job.json"),
("k8s/persistentvolumeclaim", "https://kubernetesjsonschema.dev/v{version}/persistentvolumeclaim.json"),
("k8s/networkpolicy", "https://kubernetesjsonschema.dev/v{version}/networkpolicy.json"),
("k8s/statefulset", "https://kubernetesjsonschema.dev/v{version}/statefulset.json"),
("k8s/daemonset", "https://kubernetesjsonschema.dev/v{version}/daemonset.json"),
("k8s/role", "https://kubernetesjsonschema.dev/v{version}/role.json"),
("k8s/clusterrole", "https://kubernetesjsonschema.dev/v{version}/clusterrole.json"),
("k8s/rolebinding", "https://kubernetesjsonschema.dev/v{version}/rolebinding.json"),
("k8s/clusterrolebinding", "https://kubernetesjsonschema.dev/v{version}/clusterrolebinding.json"),
("k8s/serviceaccount", "https://kubernetesjsonschema.dev/v{version}/serviceaccount.json"),
("k8s/generic", "https://kubernetesjsonschema.dev/v{version}/_definitions.json"),
("gitlab-ci", "https://json.schemastore.org/gitlab-ci.json"),
("github-actions", "https://json.schemastore.org/github-workflow.json"),
("docker-compose", "https://json.schemastore.org/docker-compose.json"),
("prometheus", "https://json.schemastore.org/prometheus.json"),
("alertmanager", "https://json.schemastore.org/alertmanager.json"),
("helm-values", "https://json.schemastore.org/chart.json"),
("ansible", "https://json.schemastore.org/ansible-playbook.json"),
("openapi", "https://json.schemastore.org/openapi-3.0.json"),
];
pub const DEFAULT_K8S_VERSION: &str = "1.30.0";
#[derive(Debug, Clone)]
pub enum SchemaError {
NotFound(String),
FetchFailed(String, String),
ParseFailed(String),
ValidationFailed(String),
}
impl std::fmt::Display for SchemaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SchemaError::NotFound(key) => write!(f, "Schema not found: {}", key),
SchemaError::FetchFailed(url, err) => {
write!(f, "Failed to fetch schema from {}: {}", url, err)
}
SchemaError::ParseFailed(err) => write!(f, "Failed to parse schema: {}", err),
SchemaError::ValidationFailed(err) => write!(f, "Schema validation failed: {}", err),
}
}
}
impl std::error::Error for SchemaError {}
pub struct SchemaRegistry {
cache: HashMap<String, Value>,
k8s_version: String,
use_bundled_fallback: bool,
}
impl Default for SchemaRegistry {
fn default() -> Self {
Self::new()
}
}
impl SchemaRegistry {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
k8s_version: DEFAULT_K8S_VERSION.to_string(),
use_bundled_fallback: true,
}
}
pub fn with_k8s_version(version: String) -> Self {
Self {
cache: HashMap::new(),
k8s_version: version,
use_bundled_fallback: true,
}
}
pub fn set_bundled_fallback(&mut self, enabled: bool) {
self.use_bundled_fallback = enabled;
}
pub fn set_k8s_version(&mut self, version: String) {
self.k8s_version = version;
self.cache.clear();
}
pub fn get_schema_sync(&mut self, schema_type: &str) -> Result<Value, SchemaError> {
if let Some(cached) = self.cache.get(schema_type) {
return Ok(cached.clone());
}
if self.use_bundled_fallback
&& let Some(bundled) = bundled::get_bundled_schema(schema_type) {
self.cache.insert(schema_type.to_string(), bundled.clone());
return Ok(bundled);
}
Err(SchemaError::NotFound(schema_type.to_string()))
}
pub fn get_schema_url(&self, schema_type: &str) -> Option<String> {
for (key, url_template) in SCHEMA_URLS {
if *key == schema_type {
let url = if url_template.contains("{version}") {
url_template.replace("{version}", &self.k8s_version)
} else {
url_template.to_string()
};
return Some(url);
}
}
None
}
pub fn cache_schema(&mut self, schema_type: &str, schema: Value) {
self.cache.insert(schema_type.to_string(), schema);
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
pub fn get_schema_for_type(&mut self, yaml_type: YamlType) -> Result<Value, SchemaError> {
self.get_schema_sync(yaml_type.to_schema_key())
}
pub fn list_schema_types(&self) -> Vec<&'static str> {
SCHEMA_URLS.iter().map(|(key, _)| *key).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_schema_url_k8s() {
let registry = SchemaRegistry::new();
let url = registry.get_schema_url("k8s/deployment").unwrap();
assert!(url.contains("kubernetesjsonschema.dev"));
assert!(url.contains("1.30.0"));
}
#[test]
fn test_get_schema_url_gitlab_ci() {
let registry = SchemaRegistry::new();
let url = registry.get_schema_url("gitlab-ci").unwrap();
assert_eq!(url, "https://json.schemastore.org/gitlab-ci.json");
}
#[test]
fn test_get_schema_url_unknown() {
let registry = SchemaRegistry::new();
let url = registry.get_schema_url("unknown/type");
assert!(url.is_none());
}
#[test]
fn test_custom_k8s_version() {
let registry = SchemaRegistry::with_k8s_version("1.28.0".to_string());
let url = registry.get_schema_url("k8s/service").unwrap();
assert!(url.contains("1.28.0"));
}
#[test]
fn test_list_schema_types() {
let registry = SchemaRegistry::new();
let types = registry.list_schema_types();
assert!(types.contains(&"k8s/deployment"));
assert!(types.contains(&"gitlab-ci"));
assert!(types.contains(&"docker-compose"));
}
#[test]
fn test_bundled_fallback() {
let mut registry = SchemaRegistry::new();
let result = registry.get_schema_sync("k8s/deployment");
assert!(result.is_ok());
}
}