mockforge_plugin_core/manifest/
schema.rs

1//! Plugin configuration schema definitions
2//!
3//! This module defines the schema for plugin configuration, including
4//! property types, validation rules, and schema validation.
5
6use crate::{PluginError, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// Plugin configuration schema
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ConfigSchema {
13    /// Schema format version
14    pub version: String,
15    /// Configuration properties
16    pub properties: HashMap<String, ConfigProperty>,
17    /// Required properties
18    pub required: Vec<String>,
19}
20
21impl Default for ConfigSchema {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl ConfigSchema {
28    /// Create new config schema
29    pub fn new() -> Self {
30        Self {
31            version: "1.0".to_string(),
32            properties: HashMap::new(),
33            required: Vec::new(),
34        }
35    }
36
37    /// Add a property to the schema
38    pub fn add_property(&mut self, name: String, property: ConfigProperty) {
39        self.properties.insert(name, property);
40    }
41
42    /// Mark a property as required
43    pub fn require(&mut self, name: &str) {
44        if !self.required.contains(&name.to_string()) {
45            self.required.push(name.to_string());
46        }
47    }
48
49    /// Validate the schema
50    pub fn validate(&self) -> Result<()> {
51        if self.version != "1.0" {
52            return Err(PluginError::config_error(&format!(
53                "Unsupported schema version: {}",
54                self.version
55            )));
56        }
57
58        // Validate all properties
59        for (name, property) in &self.properties {
60            property.validate(name)?;
61        }
62
63        // Validate required properties exist
64        for required_name in &self.required {
65            if !self.properties.contains_key(required_name) {
66                return Err(PluginError::config_error(&format!(
67                    "Required property '{}' not defined in schema",
68                    required_name
69                )));
70            }
71        }
72
73        Ok(())
74    }
75
76    /// Validate configuration against schema
77    pub fn validate_config(&self, config: &serde_json::Value) -> Result<()> {
78        if let serde_json::Value::Object(config_obj) = config {
79            // Check required properties
80            for required_name in &self.required {
81                if !config_obj.contains_key(required_name) {
82                    return Err(PluginError::config_error(&format!(
83                        "Missing required configuration property: {}",
84                        required_name
85                    )));
86                }
87            }
88
89            // Validate each provided property
90            for (key, value) in config_obj {
91                if let Some(property) = self.properties.get(key) {
92                    property.validate_value(value)?;
93                } else {
94                    return Err(PluginError::config_error(&format!(
95                        "Unknown configuration property: {}",
96                        key
97                    )));
98                }
99            }
100
101            Ok(())
102        } else {
103            Err(PluginError::config_error("Configuration must be an object"))
104        }
105    }
106
107    /// Get property by name
108    pub fn get_property(&self, name: &str) -> Option<&ConfigProperty> {
109        self.properties.get(name)
110    }
111
112    /// Check if property is required
113    pub fn is_required(&self, name: &str) -> bool {
114        self.required.contains(&name.to_string())
115    }
116}
117
118/// Configuration property definition
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct ConfigProperty {
121    /// Property type
122    #[serde(rename = "type")]
123    pub property_type: PropertyType,
124    /// Property description
125    pub description: Option<String>,
126    /// Default value
127    pub default: Option<serde_json::Value>,
128    /// Validation rules
129    pub validation: Option<PropertyValidation>,
130    /// Whether the property is deprecated
131    pub deprecated: Option<bool>,
132}
133
134impl ConfigProperty {
135    /// Create new property
136    pub fn new(property_type: PropertyType) -> Self {
137        Self {
138            property_type,
139            description: None,
140            default: None,
141            validation: None,
142            deprecated: None,
143        }
144    }
145
146    /// Set description
147    pub fn with_description(mut self, description: String) -> Self {
148        self.description = Some(description);
149        self
150    }
151
152    /// Set default value
153    pub fn with_default(mut self, default: serde_json::Value) -> Self {
154        self.default = Some(default);
155        self
156    }
157
158    /// Set validation
159    pub fn with_validation(mut self, validation: PropertyValidation) -> Self {
160        self.validation = Some(validation);
161        self
162    }
163
164    /// Mark as deprecated
165    pub fn deprecated(mut self) -> Self {
166        self.deprecated = Some(true);
167        self
168    }
169
170    /// Validate property definition
171    pub fn validate(&self, name: &str) -> Result<()> {
172        // Validate default value matches type
173        if let Some(default) = &self.default {
174            self.validate_value(default).map_err(|e| {
175                PluginError::config_error(&format!(
176                    "Invalid default value for property '{}': {}",
177                    name, e
178                ))
179            })?;
180        }
181
182        Ok(())
183    }
184
185    /// Validate a value against this property
186    pub fn validate_value(&self, value: &serde_json::Value) -> Result<()> {
187        // Type validation
188        match &self.property_type {
189            PropertyType::String => {
190                if !value.is_string() {
191                    return Err(PluginError::config_error("Expected string value"));
192                }
193            }
194            PropertyType::Number => {
195                if !value.is_number() {
196                    return Err(PluginError::config_error("Expected number value"));
197                }
198            }
199            PropertyType::Boolean => {
200                if !value.is_boolean() {
201                    return Err(PluginError::config_error("Expected boolean value"));
202                }
203            }
204            PropertyType::Array => {
205                if !value.is_array() {
206                    return Err(PluginError::config_error("Expected array value"));
207                }
208            }
209            PropertyType::Object => {
210                if !value.is_object() {
211                    return Err(PluginError::config_error("Expected object value"));
212                }
213            }
214        }
215
216        // Custom validation rules
217        if let Some(validation) = &self.validation {
218            validation.validate_value(value)?;
219        }
220
221        Ok(())
222    }
223}
224
225/// Property type enumeration
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub enum PropertyType {
228    /// String type for text values
229    #[serde(rename = "string")]
230    String,
231    /// Number type for numeric values (integers and floats)
232    #[serde(rename = "number")]
233    Number,
234    /// Boolean type for true/false values
235    #[serde(rename = "boolean")]
236    Boolean,
237    /// Array type for ordered collections
238    #[serde(rename = "array")]
239    Array,
240    /// Object type for key-value maps
241    #[serde(rename = "object")]
242    Object,
243}
244
245/// Property validation rules
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct PropertyValidation {
248    /// Minimum value (for numbers)
249    pub min: Option<f64>,
250    /// Maximum value (for numbers)
251    pub max: Option<f64>,
252    /// Minimum length (for strings/arrays)
253    pub min_length: Option<usize>,
254    /// Maximum length (for strings/arrays)
255    pub max_length: Option<usize>,
256    /// Regular expression pattern (for strings)
257    pub pattern: Option<String>,
258    /// Allowed values (enum)
259    pub enum_values: Option<Vec<serde_json::Value>>,
260}
261
262impl Default for PropertyValidation {
263    fn default() -> Self {
264        Self::new()
265    }
266}
267
268impl PropertyValidation {
269    /// Create new validation
270    pub fn new() -> Self {
271        Self {
272            min: None,
273            max: None,
274            min_length: None,
275            max_length: None,
276            pattern: None,
277            enum_values: None,
278        }
279    }
280
281    /// Validate a value against these rules
282    pub fn validate_value(&self, value: &serde_json::Value) -> Result<()> {
283        // Number validations
284        if let Some(num) = value.as_f64() {
285            if let Some(min) = self.min {
286                if num < min {
287                    return Err(PluginError::config_error(&format!(
288                        "Value {} is less than minimum {}",
289                        num, min
290                    )));
291                }
292            }
293            if let Some(max) = self.max {
294                if num > max {
295                    return Err(PluginError::config_error(&format!(
296                        "Value {} is greater than maximum {}",
297                        num, max
298                    )));
299                }
300            }
301        }
302
303        // String validations
304        if let Some(s) = value.as_str() {
305            if let Some(min_len) = self.min_length {
306                if s.len() < min_len {
307                    return Err(PluginError::config_error(&format!(
308                        "String length {} is less than minimum {}",
309                        s.len(),
310                        min_len
311                    )));
312                }
313            }
314            if let Some(max_len) = self.max_length {
315                if s.len() > max_len {
316                    return Err(PluginError::config_error(&format!(
317                        "String length {} is greater than maximum {}",
318                        s.len(),
319                        max_len
320                    )));
321                }
322            }
323            if let Some(pattern) = &self.pattern {
324                let regex = regex::Regex::new(pattern).map_err(|e| {
325                    PluginError::config_error(&format!("Invalid regex pattern: {}", e))
326                })?;
327                if !regex.is_match(s) {
328                    return Err(PluginError::config_error(&format!(
329                        "String '{}' does not match pattern '{}'",
330                        s, pattern
331                    )));
332                }
333            }
334        }
335
336        // Array validations
337        if let Some(arr) = value.as_array() {
338            if let Some(min_len) = self.min_length {
339                if arr.len() < min_len {
340                    return Err(PluginError::config_error(&format!(
341                        "Array length {} is less than minimum {}",
342                        arr.len(),
343                        min_len
344                    )));
345                }
346            }
347            if let Some(max_len) = self.max_length {
348                if arr.len() > max_len {
349                    return Err(PluginError::config_error(&format!(
350                        "Array length {} is greater than maximum {}",
351                        arr.len(),
352                        max_len
353                    )));
354                }
355            }
356        }
357
358        // Enum validations
359        if let Some(enum_values) = &self.enum_values {
360            let mut found = false;
361            for allowed_value in enum_values {
362                if value == allowed_value {
363                    found = true;
364                    break;
365                }
366            }
367            if !found {
368                return Err(PluginError::config_error(&format!(
369                    "Value {} is not in allowed values",
370                    value
371                )));
372            }
373        }
374
375        Ok(())
376    }
377}
378
379#[cfg(test)]
380mod tests {
381
382    #[test]
383    fn test_module_compiles() {
384        // Basic compilation test
385    }
386}