mockforge_plugin_core/manifest/
schema.rs1use crate::{PluginError, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ConfigSchema {
13 pub version: String,
15 pub properties: HashMap<String, ConfigProperty>,
17 pub required: Vec<String>,
19}
20
21impl Default for ConfigSchema {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl ConfigSchema {
28 pub fn new() -> Self {
30 Self {
31 version: "1.0".to_string(),
32 properties: HashMap::new(),
33 required: Vec::new(),
34 }
35 }
36
37 pub fn add_property(&mut self, name: String, property: ConfigProperty) {
39 self.properties.insert(name, property);
40 }
41
42 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 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 for (name, property) in &self.properties {
60 property.validate(name)?;
61 }
62
63 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 pub fn validate_config(&self, config: &serde_json::Value) -> Result<()> {
78 if let serde_json::Value::Object(config_obj) = config {
79 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 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 pub fn get_property(&self, name: &str) -> Option<&ConfigProperty> {
109 self.properties.get(name)
110 }
111
112 pub fn is_required(&self, name: &str) -> bool {
114 self.required.contains(&name.to_string())
115 }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct ConfigProperty {
121 #[serde(rename = "type")]
123 pub property_type: PropertyType,
124 pub description: Option<String>,
126 pub default: Option<serde_json::Value>,
128 pub validation: Option<PropertyValidation>,
130 pub deprecated: Option<bool>,
132}
133
134impl ConfigProperty {
135 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 pub fn with_description(mut self, description: String) -> Self {
148 self.description = Some(description);
149 self
150 }
151
152 pub fn with_default(mut self, default: serde_json::Value) -> Self {
154 self.default = Some(default);
155 self
156 }
157
158 pub fn with_validation(mut self, validation: PropertyValidation) -> Self {
160 self.validation = Some(validation);
161 self
162 }
163
164 pub fn deprecated(mut self) -> Self {
166 self.deprecated = Some(true);
167 self
168 }
169
170 pub fn validate(&self, name: &str) -> Result<()> {
172 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 pub fn validate_value(&self, value: &serde_json::Value) -> Result<()> {
187 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 if let Some(validation) = &self.validation {
218 validation.validate_value(value)?;
219 }
220
221 Ok(())
222 }
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub enum PropertyType {
228 #[serde(rename = "string")]
230 String,
231 #[serde(rename = "number")]
233 Number,
234 #[serde(rename = "boolean")]
236 Boolean,
237 #[serde(rename = "array")]
239 Array,
240 #[serde(rename = "object")]
242 Object,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct PropertyValidation {
248 pub min: Option<f64>,
250 pub max: Option<f64>,
252 pub min_length: Option<usize>,
254 pub max_length: Option<usize>,
256 pub pattern: Option<String>,
258 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 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 pub fn validate_value(&self, value: &serde_json::Value) -> Result<()> {
283 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 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 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 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 }
386}