ricecoder_storage/markdown_config/
yaml_parser.rs1use crate::markdown_config::error::{MarkdownConfigError, MarkdownConfigResult};
4use serde::de::DeserializeOwned;
5
6#[derive(Debug, Clone)]
8pub struct YamlParser;
9
10impl YamlParser {
11 pub fn new() -> Self {
13 Self
14 }
15
16 pub fn parse<T: DeserializeOwned>(&self, yaml: &str) -> MarkdownConfigResult<T> {
18 serde_yaml::from_str(yaml).map_err(|e| {
19 MarkdownConfigError::yaml_error(format!("Failed to parse YAML: {}", e))
20 })
21 }
22
23 pub fn validate_structure(&self, yaml: &str) -> MarkdownConfigResult<()> {
25 serde_yaml::from_str::<serde_yaml::Value>(yaml).map_err(|e| {
27 MarkdownConfigError::yaml_error(format!("Invalid YAML structure: {}", e))
28 })?;
29 Ok(())
30 }
31
32 pub fn has_required_fields(&self, yaml: &str, required_fields: &[&str]) -> MarkdownConfigResult<()> {
34 let value: serde_yaml::Value = serde_yaml::from_str(yaml).map_err(|e| {
35 MarkdownConfigError::yaml_error(format!("Failed to parse YAML: {}", e))
36 })?;
37
38 let mapping = value.as_mapping().ok_or_else(|| {
39 MarkdownConfigError::validation_error("YAML must be a mapping (object)")
40 })?;
41
42 for field in required_fields {
43 let key = serde_yaml::Value::String(field.to_string());
44 if !mapping.contains_key(&key) {
45 return Err(MarkdownConfigError::missing_field(*field));
46 }
47 }
48
49 Ok(())
50 }
51
52 pub fn get_field(&self, yaml: &str, field: &str) -> MarkdownConfigResult<Option<String>> {
54 let value: serde_yaml::Value = serde_yaml::from_str(yaml).map_err(|e| {
55 MarkdownConfigError::yaml_error(format!("Failed to parse YAML: {}", e))
56 })?;
57
58 let mapping = value.as_mapping().ok_or_else(|| {
59 MarkdownConfigError::validation_error("YAML must be a mapping (object)")
60 })?;
61
62 let key = serde_yaml::Value::String(field.to_string());
63 Ok(mapping.get(&key).and_then(|v| v.as_str().map(|s| s.to_string())))
64 }
65
66 pub fn validate_schema(
68 &self,
69 yaml: &str,
70 required_fields: &[&str],
71 ) -> MarkdownConfigResult<()> {
72 self.validate_structure(yaml)?;
74
75 self.has_required_fields(yaml, required_fields)?;
77
78 Ok(())
79 }
80
81 pub fn get_all_validation_errors(
83 &self,
84 yaml: &str,
85 required_fields: &[&str],
86 ) -> Vec<MarkdownConfigError> {
87 let mut errors = Vec::new();
88
89 if let Err(e) = self.validate_structure(yaml) {
91 errors.push(e);
92 return errors; }
94
95 for field in required_fields {
97 if let Err(e) = self.has_required_fields(yaml, &[field]) {
98 errors.push(e);
99 }
100 }
101
102 errors
103 }
104}
105
106impl Default for YamlParser {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use serde::{Deserialize, Serialize};
116
117 #[derive(Debug, Serialize, Deserialize, PartialEq)]
118 struct TestConfig {
119 name: String,
120 value: i32,
121 }
122
123 #[test]
124 fn test_parse_valid_yaml() {
125 let parser = YamlParser::new();
126 let yaml = "name: test\nvalue: 42";
127
128 let result: TestConfig = parser.parse(yaml).unwrap();
129 assert_eq!(result.name, "test");
130 assert_eq!(result.value, 42);
131 }
132
133 #[test]
134 fn test_parse_invalid_yaml() {
135 let parser = YamlParser::new();
136 let yaml = "name: test\n invalid: [unclosed";
137
138 let result: Result<TestConfig, _> = parser.parse(yaml);
139 assert!(result.is_err());
140 }
141
142 #[test]
143 fn test_validate_structure_valid() {
144 let parser = YamlParser::new();
145 let yaml = "name: test\nvalue: 42";
146
147 let result = parser.validate_structure(yaml);
148 assert!(result.is_ok());
149 }
150
151 #[test]
152 fn test_validate_structure_invalid() {
153 let parser = YamlParser::new();
154 let yaml = "name: test\n invalid: [unclosed";
155
156 let result = parser.validate_structure(yaml);
157 assert!(result.is_err());
158 }
159
160 #[test]
161 fn test_has_required_fields_present() {
162 let parser = YamlParser::new();
163 let yaml = "name: test\nvalue: 42\ndescription: optional";
164
165 let result = parser.has_required_fields(yaml, &["name", "value"]);
166 assert!(result.is_ok());
167 }
168
169 #[test]
170 fn test_has_required_fields_missing() {
171 let parser = YamlParser::new();
172 let yaml = "name: test";
173
174 let result = parser.has_required_fields(yaml, &["name", "value"]);
175 assert!(result.is_err());
176 }
177
178 #[test]
179 fn test_get_field_exists() {
180 let parser = YamlParser::new();
181 let yaml = "name: test-value\nother: data";
182
183 let result = parser.get_field(yaml, "name").unwrap();
184 assert_eq!(result, Some("test-value".to_string()));
185 }
186
187 #[test]
188 fn test_get_field_missing() {
189 let parser = YamlParser::new();
190 let yaml = "name: test-value";
191
192 let result = parser.get_field(yaml, "missing").unwrap();
193 assert_eq!(result, None);
194 }
195
196 #[test]
197 fn test_get_field_non_string() {
198 let parser = YamlParser::new();
199 let yaml = "name: test\nvalue: 42";
200
201 let result = parser.get_field(yaml, "value").unwrap();
202 assert_eq!(result, None); }
204
205 #[test]
206 fn test_parse_complex_yaml() {
207 let parser = YamlParser::new();
208 let _yaml = r#"
209name: complex-agent
210description: A complex agent
211model: gpt-4
212temperature: 0.7
213max_tokens: 2000
214tools:
215 - tool1
216 - tool2
217 - tool3
218"#;
219
220 let result: TestConfig = parser.parse("name: test\nvalue: 42").unwrap();
221 assert_eq!(result.name, "test");
222 }
223
224 #[test]
225 fn test_parse_yaml_with_nested_objects() {
226 let parser = YamlParser::new();
227 let yaml = r#"
228name: test
229config:
230 nested: value
231 deep:
232 deeper: data
233"#;
234
235 let result = parser.validate_structure(yaml);
236 assert!(result.is_ok());
237 }
238
239 #[test]
240 fn test_parse_yaml_with_arrays() {
241 let parser = YamlParser::new();
242 let yaml = r#"
243name: test
244items:
245 - item1
246 - item2
247 - item3
248"#;
249
250 let result = parser.validate_structure(yaml);
251 assert!(result.is_ok());
252 }
253
254 #[test]
255 fn test_validate_schema_all_required_present() {
256 let parser = YamlParser::new();
257 let yaml = "name: test\nvalue: 42\ndescription: optional";
258
259 let result = parser.validate_schema(yaml, &["name", "value"]);
260 assert!(result.is_ok());
261 }
262
263 #[test]
264 fn test_validate_schema_missing_required() {
265 let parser = YamlParser::new();
266 let yaml = "name: test";
267
268 let result = parser.validate_schema(yaml, &["name", "value", "description"]);
269 assert!(result.is_err());
270 }
271
272 #[test]
273 fn test_get_all_validation_errors_valid() {
274 let parser = YamlParser::new();
275 let yaml = "name: test\nvalue: 42";
276
277 let errors = parser.get_all_validation_errors(yaml, &["name", "value"]);
278 assert_eq!(errors.len(), 0);
279 }
280
281 #[test]
282 fn test_get_all_validation_errors_invalid_structure() {
283 let parser = YamlParser::new();
284 let yaml = "name: test\n invalid: [unclosed";
285
286 let errors = parser.get_all_validation_errors(yaml, &["name"]);
287 assert!(!errors.is_empty());
288 }
289
290 #[test]
291 fn test_get_all_validation_errors_missing_fields() {
292 let parser = YamlParser::new();
293 let yaml = "name: test";
294
295 let errors = parser.get_all_validation_errors(yaml, &["name", "value", "description"]);
296 assert!(!errors.is_empty());
297 }
298
299 #[test]
300 fn test_parse_yaml_with_special_characters() {
301 let parser = YamlParser::new();
302 let yaml = r#"
303name: "test-agent"
304description: "Agent with special chars: @#$%^&*()"
305"#;
306
307 let result = parser.validate_structure(yaml);
308 assert!(result.is_ok());
309 }
310
311 #[test]
312 fn test_parse_yaml_with_quotes() {
313 let parser = YamlParser::new();
314 let yaml = r#"
315name: 'single-quoted'
316description: "double-quoted"
317"#;
318
319 let result = parser.validate_structure(yaml);
320 assert!(result.is_ok());
321 }
322
323 #[test]
324 fn test_parse_yaml_with_multiline_strings() {
325 let parser = YamlParser::new();
326 let yaml = r#"
327name: test
328description: |
329 This is a multiline
330 description that spans
331 multiple lines
332"#;
333
334 let result = parser.validate_structure(yaml);
335 assert!(result.is_ok());
336 }
337
338 #[test]
339 fn test_parse_yaml_with_numbers() {
340 let parser = YamlParser::new();
341 let yaml = r#"
342name: test
343integer: 42
344float: 3.14
345negative: -10
346"#;
347
348 let result = parser.validate_structure(yaml);
349 assert!(result.is_ok());
350 }
351
352 #[test]
353 fn test_parse_yaml_with_booleans() {
354 let parser = YamlParser::new();
355 let yaml = r#"
356name: test
357enabled: true
358disabled: false
359"#;
360
361 let result = parser.validate_structure(yaml);
362 assert!(result.is_ok());
363 }
364
365 #[test]
366 fn test_parse_yaml_with_null() {
367 let parser = YamlParser::new();
368 let yaml = r#"
369name: test
370optional: null
371"#;
372
373 let result = parser.validate_structure(yaml);
374 assert!(result.is_ok());
375 }
376
377 #[test]
378 fn test_parse_empty_yaml() {
379 let parser = YamlParser::new();
380 let yaml = "";
381
382 let result = parser.validate_structure(yaml);
383 assert!(result.is_ok());
384 }
385
386 #[test]
387 fn test_parse_yaml_only_comments() {
388 let parser = YamlParser::new();
389 let yaml = r#"
390# This is a comment
391# Another comment
392"#;
393
394 let result = parser.validate_structure(yaml);
395 assert!(result.is_ok());
396 }
397
398 #[test]
399 fn test_parse_yaml_with_anchors_and_aliases() {
400 let parser = YamlParser::new();
401 let yaml = r#"
402defaults: &defaults
403 name: default
404 value: 0
405
406config:
407 <<: *defaults
408 name: custom
409"#;
410
411 let result = parser.validate_structure(yaml);
412 assert!(result.is_ok());
413 }
414
415 #[test]
416 fn test_has_required_fields_empty_list() {
417 let parser = YamlParser::new();
418 let yaml = "name: test";
419
420 let result = parser.has_required_fields(yaml, &[]);
421 assert!(result.is_ok());
422 }
423
424 #[test]
425 fn test_get_field_from_empty_yaml() {
426 let parser = YamlParser::new();
427 let yaml = "";
428
429 let result = parser.get_field(yaml, "name");
430 assert!(result.is_err());
431 }
432
433 #[test]
434 fn test_parse_yaml_consistency() {
435 let parser = YamlParser::new();
436 let yaml = "name: test\nvalue: 42";
437
438 let result1: TestConfig = parser.parse(yaml).unwrap();
439 let result2: TestConfig = parser.parse(yaml).unwrap();
440
441 assert_eq!(result1, result2);
442 }
443
444 #[test]
445 fn test_parse_yaml_with_unicode() {
446 let parser = YamlParser::new();
447 let yaml = r#"
448name: 测试
449description: 日本語のテスト
450"#;
451
452 let result = parser.validate_structure(yaml);
453 assert!(result.is_ok());
454 }
455
456 #[test]
457 fn test_parse_yaml_with_windows_line_endings() {
458 let parser = YamlParser::new();
459 let yaml = "name: test\r\nvalue: 42";
460
461 let result: Result<TestConfig, _> = parser.parse(yaml);
462 assert!(result.is_ok());
463 }
464}