raz_config/
schema.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
5#[serde(transparent)]
6pub struct ConfigVersion(pub u32);
7
8impl ConfigVersion {
9    pub const V1: Self = Self(1);
10    pub const CURRENT: Self = Self(crate::CONFIG_CURRENT_VERSION);
11
12    pub fn is_compatible(&self, other: &Self) -> bool {
13        self.0 <= other.0
14    }
15
16    pub fn needs_migration(&self, target: &Self) -> bool {
17        self.0 < target.0
18    }
19}
20
21impl fmt::Display for ConfigVersion {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        write!(f, "v{}", self.0)
24    }
25}
26
27impl Default for ConfigVersion {
28    fn default() -> Self {
29        Self::CURRENT
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct ConfigSchema {
35    pub version: ConfigVersion,
36    pub required_fields: Vec<String>,
37    pub optional_fields: Vec<String>,
38    pub deprecated_fields: Vec<String>,
39}
40
41impl ConfigSchema {
42    pub fn v1() -> Self {
43        Self {
44            version: ConfigVersion::V1,
45            required_fields: vec![
46                "version".to_string(),
47                "enabled".to_string(),
48                "providers".to_string(),
49            ],
50            optional_fields: vec![
51                "cache_dir".to_string(),
52                "cache_ttl".to_string(),
53                "parallel_execution".to_string(),
54                "max_concurrent_jobs".to_string(),
55                "providers_config".to_string(),
56                "filters".to_string(),
57                "ui".to_string(),
58                "commands".to_string(),
59                "overrides".to_string(),
60            ],
61            deprecated_fields: vec![],
62        }
63    }
64
65    pub fn for_version(version: ConfigVersion) -> Self {
66        match version.0 {
67            1 => Self::v1(),
68            _ => Self::v1(), // Default to latest
69        }
70    }
71
72    pub fn validate_fields(&self, fields: &[String]) -> Result<(), Vec<String>> {
73        let mut missing = Vec::new();
74
75        for required in &self.required_fields {
76            if !fields.contains(required) {
77                missing.push(required.clone());
78            }
79        }
80
81        if missing.is_empty() {
82            Ok(())
83        } else {
84            Err(missing)
85        }
86    }
87
88    pub fn get_deprecated_fields(&self, fields: &[String]) -> Vec<String> {
89        fields
90            .iter()
91            .filter(|f| self.deprecated_fields.contains(f))
92            .cloned()
93            .collect()
94    }
95}