use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::models::k8s::K8sMetadata;
use crate::models::validation::{ConfigValidator, Diagnostic, Severity, YamlType};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PVCResources {
#[serde(default)]
pub requests: HashMap<String, String>,
#[serde(default)]
pub limits: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PVCSpec {
#[serde(rename = "accessModes")]
pub access_modes: Vec<String>,
#[serde(default)]
pub resources: Option<PVCResources>,
#[serde(default, rename = "storageClassName")]
pub storage_class_name: Option<String>,
#[serde(default, rename = "volumeMode")]
pub volume_mode: Option<String>,
#[serde(default, rename = "volumeName")]
pub volume_name: Option<String>,
#[serde(default)]
pub selector: Option<serde_json::Value>,
#[serde(default, rename = "dataSource")]
pub data_source: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sPVC {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub kind: String,
pub metadata: K8sMetadata,
pub spec: PVCSpec,
}
const VALID_ACCESS_MODES: &[&str] = &[
"ReadWriteOnce", "ReadOnlyMany", "ReadWriteMany", "ReadWriteOncePod",
];
impl ConfigValidator for K8sPVC {
fn yaml_type(&self) -> YamlType { YamlType::K8sPVC }
fn validate_structure(&self) -> Vec<Diagnostic> {
let mut diags = Vec::new();
if self.spec.access_modes.is_empty() {
diags.push(Diagnostic {
severity: Severity::Error,
message: "accessModes is required and cannot be empty".into(),
path: Some("spec > accessModes".into()),
});
}
for mode in &self.spec.access_modes {
if !VALID_ACCESS_MODES.contains(&mode.as_str()) {
diags.push(Diagnostic {
severity: Severity::Error,
message: format!("Invalid accessMode '{}' — expected one of: {}", mode, VALID_ACCESS_MODES.join(", ")),
path: Some("spec > accessModes".into()),
});
}
}
diags
}
fn validate_semantics(&self) -> Vec<Diagnostic> {
let mut diags = Vec::new();
if self.spec.resources.is_none() {
diags.push(Diagnostic {
severity: Severity::Warning,
message: "No resources.requests.storage — PVC size is undefined".into(),
path: Some("spec > resources".into()),
});
} else if let Some(res) = &self.spec.resources
&& !res.requests.contains_key("storage") {
diags.push(Diagnostic {
severity: Severity::Warning,
message: "No resources.requests.storage — PVC size is undefined".into(),
path: Some("spec > resources > requests".into()),
});
}
if self.spec.storage_class_name.is_none() {
diags.push(Diagnostic {
severity: Severity::Info,
message: "No storageClassName — cluster default StorageClass will be used".into(),
path: Some("spec > storageClassName".into()),
});
}
diags
}
}