1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4use serde::{Deserialize, Serialize};
5pub use crate::dna::hel::error::HlxError;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct HlxConfig {
16 pub sections: HashMap<String, HlxSection>,
18 pub metadata: HashMap<String, serde_json::Value>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct HlxSection {
25 pub properties: HashMap<String, serde_json::Value>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub metadata: Option<HashMap<String, serde_json::Value>>,
30}
31
32#[derive(Debug, Clone, PartialEq)]
36pub struct SourceLocation {
37 pub line: usize,
38 pub column: usize,
39 pub position: usize,
40}
41
42#[derive(Debug, Clone)]
44pub struct ConfigParseError {
45 pub message: String,
46 pub location: Option<SourceLocation>,
47 pub context: String,
48}
49
50impl std::fmt::Display for ConfigParseError {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 if let Some(loc) = &self.location {
53 write!(f, "{} at line {}, column {}", self.message, loc.line, loc.column)?;
54 if !self.context.is_empty() {
55 write!(f, " (context: {})", self.context)?;
56 }
57 } else {
58 write!(f, "{}", self.message)?;
59 }
60 Ok(())
61 }
62}
63
64impl std::error::Error for ConfigParseError {}
65
66#[derive(Debug, Clone, PartialEq)]
68pub enum ConfigToken {
69 Identifier(String),
70 String(String),
71 Number(f64),
72 Bool(bool),
73 LeftBrace,
74 RightBrace,
75 LeftBracket,
76 RightBracket,
77 Equals,
78 Comma,
79 Comment(String),
80 Newline,
81 Eof,
82}
83
84impl ConfigToken {
85 pub fn as_string(&self) -> Option<&str> {
86 match self {
87 ConfigToken::String(s) => Some(s),
88 ConfigToken::Identifier(s) => Some(s),
89 _ => None,
90 }
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct TokenWithLocation {
97 pub token: ConfigToken,
98 pub location: SourceLocation,
99}
100
101#[derive(Debug, Clone)]
103pub enum ConfigValue {
104 String(String),
105 Number(f64),
106 Bool(bool),
107 Array(Vec<ConfigValue>),
108 Object(HashMap<String, ConfigValue>),
109 Null,
110}
111
112impl ConfigValue {
113 pub fn to_json_value(&self) -> serde_json::Value {
114 match self {
115 ConfigValue::String(s) => serde_json::Value::String(s.clone()),
116 ConfigValue::Number(n) => serde_json::Value::Number(serde_json::Number::from_f64(*n).unwrap_or(serde_json::Number::from(0))),
117 ConfigValue::Bool(b) => serde_json::Value::Bool(*b),
118 ConfigValue::Array(arr) => serde_json::Value::Array(arr.iter().map(|v| v.to_json_value()).collect()),
119 ConfigValue::Object(obj) => {
120 let mut map = serde_json::Map::new();
121 for (k, v) in obj {
122 map.insert(k.clone(), v.to_json_value());
123 }
124 serde_json::Value::Object(map)
125 }
126 ConfigValue::Null => serde_json::Value::Null,
127 }
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct ConfigBlock {
134 pub name: String,
135 pub properties: HashMap<String, ConfigValue>,
136 pub location: SourceLocation,
137}
138
139pub struct EnhancedConfigParser {
141 tokens: Vec<TokenWithLocation>,
142 current: usize,
143}
144
145impl EnhancedConfigParser {
146 pub fn new(source: &str) -> Result<Self, ConfigParseError> {
148 let tokens = Self::tokenize(source)?;
149 Ok(Self { tokens, current: 0 })
150 }
151
152 fn tokenize(source: &str) -> Result<Vec<TokenWithLocation>, ConfigParseError> {
154 let mut tokens = Vec::new();
155 let mut chars = source.chars().peekable();
156 let mut line = 1;
157 let mut column = 1;
158 let mut position = 0;
159
160 while let Some(&ch) = chars.peek() {
161 let start_location = SourceLocation { line, column, position };
162
163 match ch {
164 '#' => {
165 let mut comment = String::new();
167 chars.next(); column += 1;
169 position += 1;
170 while let Some(&c) = chars.peek() {
171 if c == '\n' {
172 break;
173 }
174 comment.push(c);
175 chars.next();
176 column += 1;
177 position += 1;
178 }
179 tokens.push(TokenWithLocation {
180 token: ConfigToken::Comment(comment),
181 location: start_location,
182 });
183 }
184 '"' => {
185 chars.next(); column += 1;
188 position += 1;
189 let mut string = String::new();
190 let mut escaped = false;
191 while let Some(c) = chars.next() {
192 column += 1;
193 position += 1;
194 if escaped {
195 match c {
196 'n' => string.push('\n'),
197 't' => string.push('\t'),
198 'r' => string.push('\r'),
199 '"' => string.push('"'),
200 '\\' => string.push('\\'),
201 _ => string.push(c),
202 }
203 escaped = false;
204 } else if c == '\\' {
205 escaped = true;
206 } else if c == '"' {
207 break;
208 } else {
209 string.push(c);
210 }
211 }
212 tokens.push(TokenWithLocation {
213 token: ConfigToken::String(string),
214 location: start_location,
215 });
216 }
217 '0'..='9' | '-' => {
218 let mut num_str = String::new();
220 let mut has_dot = false;
221 while let Some(&c) = chars.peek() {
222 if c.is_ascii_digit() || c == '.' || c == '-' {
223 if c == '.' {
224 if has_dot {
225 break;
226 }
227 has_dot = true;
228 }
229 num_str.push(c);
230 chars.next();
231 column += 1;
232 position += 1;
233 } else {
234 break;
235 }
236 }
237 match num_str.parse::<f64>() {
238 Ok(num) => tokens.push(TokenWithLocation {
239 token: ConfigToken::Number(num),
240 location: start_location,
241 }),
242 Err(_) => {
243 return Err(ConfigParseError {
244 message: format!("Invalid number: {}", num_str),
245 location: Some(start_location),
246 context: "expected valid numeric value".to_string(),
247 });
248 }
249 }
250 }
251 'a'..='z' | 'A'..='Z' | '_' => {
252 let mut ident = String::new();
254 while let Some(&c) = chars.peek() {
255 if c.is_alphanumeric() || c == '_' {
256 ident.push(c);
257 chars.next();
258 column += 1;
259 position += 1;
260 } else {
261 break;
262 }
263 }
264 let token = match ident.as_str() {
265 "true" => ConfigToken::Bool(true),
266 "false" => ConfigToken::Bool(false),
267 "null" => ConfigToken::Identifier("null".to_string()),
268 _ => ConfigToken::Identifier(ident),
269 };
270 tokens.push(TokenWithLocation {
271 token,
272 location: start_location,
273 });
274 }
275 '=' => {
276 tokens.push(TokenWithLocation {
277 token: ConfigToken::Equals,
278 location: start_location,
279 });
280 chars.next();
281 column += 1;
282 position += 1;
283 }
284 '{' => {
285 tokens.push(TokenWithLocation {
286 token: ConfigToken::LeftBrace,
287 location: start_location,
288 });
289 chars.next();
290 column += 1;
291 position += 1;
292 }
293 '}' => {
294 tokens.push(TokenWithLocation {
295 token: ConfigToken::RightBrace,
296 location: start_location,
297 });
298 chars.next();
299 column += 1;
300 position += 1;
301 }
302 '[' => {
303 tokens.push(TokenWithLocation {
304 token: ConfigToken::LeftBracket,
305 location: start_location,
306 });
307 chars.next();
308 column += 1;
309 position += 1;
310 }
311 ']' => {
312 tokens.push(TokenWithLocation {
313 token: ConfigToken::RightBracket,
314 location: start_location,
315 });
316 chars.next();
317 column += 1;
318 position += 1;
319 }
320 ',' => {
321 tokens.push(TokenWithLocation {
322 token: ConfigToken::Comma,
323 location: start_location,
324 });
325 chars.next();
326 column += 1;
327 position += 1;
328 }
329 '\n' => {
330 tokens.push(TokenWithLocation {
331 token: ConfigToken::Newline,
332 location: start_location,
333 });
334 chars.next();
335 line += 1;
336 column = 1;
337 position += 1;
338 }
339 ' ' | '\t' | '\r' => {
340 chars.next();
342 column += 1;
343 position += 1;
344 }
345 _ => {
346 return Err(ConfigParseError {
347 message: format!("Unexpected character: {}", ch),
348 location: Some(start_location),
349 context: "expected valid token".to_string(),
350 });
351 }
352 }
353 }
354
355 tokens.push(TokenWithLocation {
356 token: ConfigToken::Eof,
357 location: SourceLocation { line, column, position },
358 });
359
360 Ok(tokens)
361 }
362
363 pub fn parse(&mut self) -> Result<HlxConfig, ConfigParseError> {
365 let mut config = HlxConfig {
366 sections: HashMap::new(),
367 metadata: HashMap::new(),
368 };
369
370 while !self.is_at_end() {
371 match self.current_token().token.clone() {
372 ConfigToken::Comment(_) => {
373 self.advance();
374 }
375 ConfigToken::Newline => {
376 self.advance();
377 }
378 ConfigToken::Identifier(ref ident) => {
379 if self.peek_next().map(|t| t.token == ConfigToken::LeftBrace).unwrap_or(false) {
380 let block = self.parse_block()?;
382 let section = HlxSection {
383 properties: block.properties.into_iter()
384 .map(|(k, v)| (k, v.to_json_value()))
385 .collect(),
386 metadata: None,
387 };
388 config.sections.insert(block.name, section);
389 } else if self.peek_next().map(|t| t.token == ConfigToken::Equals).unwrap_or(false) {
390 let key = ident.clone();
392 self.advance(); self.advance(); let value = self.parse_value()?;
395 config.metadata.insert(key, value.to_json_value());
396 } else {
397 self.advance();
398 }
399 }
400 ConfigToken::String(ref s) => {
401 if self.peek_next().map(|t| t.token == ConfigToken::LeftBrace).unwrap_or(false) {
402 let block = self.parse_block()?;
404 let section = HlxSection {
405 properties: block.properties.into_iter()
406 .map(|(k, v)| (k, v.to_json_value()))
407 .collect(),
408 metadata: None,
409 };
410 config.sections.insert(block.name, section);
411 } else if self.peek_next().map(|t| t.token == ConfigToken::Equals).unwrap_or(false) {
412 let key = s.clone();
414 self.advance(); self.advance(); let value = self.parse_value()?;
417 config.metadata.insert(key, value.to_json_value());
418 } else {
419 self.advance();
420 }
421 }
422 _ => {
423 self.advance();
424 }
425 }
426 }
427
428 Ok(config)
429 }
430
431 fn parse_block(&mut self) -> Result<ConfigBlock, ConfigParseError> {
433 let start_location = self.current_token().location.clone();
434 let name = match &self.current_token().token {
435 ConfigToken::Identifier(s) => s.clone(),
436 ConfigToken::String(s) => s.clone(),
437 _ => {
438 return Err(ConfigParseError {
439 message: "Expected identifier or string for block name".to_string(),
440 location: Some(self.current_token().location.clone()),
441 context: "block syntax: name { ... }".to_string(),
442 });
443 }
444 };
445
446 self.advance(); self.expect_token(ConfigToken::LeftBrace)?;
448
449 let mut properties = HashMap::new();
450
451 while !self.is_at_end() && self.current_token().token != ConfigToken::RightBrace {
452 match &self.current_token().token {
453 ConfigToken::Comment(_) | ConfigToken::Newline => {
454 self.advance();
455 continue;
456 }
457 ConfigToken::Identifier(key) | ConfigToken::String(key) => {
458 let prop_key = key.clone();
459 self.advance(); if self.current_token().token == ConfigToken::LeftBrace {
462 let nested_block = self.parse_block()?;
464 let mut nested_props = HashMap::new();
465 for (k, v) in nested_block.properties {
466 nested_props.insert(k, v);
467 }
468 properties.insert(prop_key, ConfigValue::Object(nested_props));
469 } else {
470 self.expect_token(ConfigToken::Equals)?;
471 let value = self.parse_value()?;
472 properties.insert(prop_key, value);
473 }
474 }
475 _ => {
476 return Err(ConfigParseError {
477 message: "Expected property key or closing brace".to_string(),
478 location: Some(self.current_token().location.clone()),
479 context: "property syntax: key = value".to_string(),
480 });
481 }
482 }
483 }
484
485 self.expect_token(ConfigToken::RightBrace)?;
486
487 Ok(ConfigBlock {
488 name,
489 properties,
490 location: start_location,
491 })
492 }
493
494 fn parse_value(&mut self) -> Result<ConfigValue, ConfigParseError> {
496 match &self.current_token().token {
497 ConfigToken::String(s) => {
498 let value = ConfigValue::String(s.clone());
499 self.advance();
500 Ok(value)
501 }
502 ConfigToken::Number(n) => {
503 let value = ConfigValue::Number(*n);
504 self.advance();
505 Ok(value)
506 }
507 ConfigToken::Bool(b) => {
508 let value = ConfigValue::Bool(*b);
509 self.advance();
510 Ok(value)
511 }
512 ConfigToken::Identifier(s) if s == "null" => {
513 self.advance();
514 Ok(ConfigValue::Null)
515 }
516 ConfigToken::LeftBracket => {
517 self.parse_array()
518 }
519 ConfigToken::LeftBrace => {
520 self.parse_object()
521 }
522 _ => {
523 Err(ConfigParseError {
524 message: "Expected value (string, number, bool, array, or object)".to_string(),
525 location: Some(self.current_token().location.clone()),
526 context: "supported value types: strings, numbers, booleans, arrays [...], objects {...}".to_string(),
527 })
528 }
529 }
530 }
531
532 fn parse_array(&mut self) -> Result<ConfigValue, ConfigParseError> {
534 self.expect_token(ConfigToken::LeftBracket)?;
535 let mut values = Vec::new();
536
537 while !self.is_at_end() && self.current_token().token != ConfigToken::RightBracket {
538 if matches!(self.current_token().token, ConfigToken::Comment(_) | ConfigToken::Newline | ConfigToken::Comma) {
539 self.advance();
540 continue;
541 }
542
543 values.push(self.parse_value()?);
544 }
545
546 self.expect_token(ConfigToken::RightBracket)?;
547 Ok(ConfigValue::Array(values))
548 }
549
550 fn parse_object(&mut self) -> Result<ConfigValue, ConfigParseError> {
552 self.expect_token(ConfigToken::LeftBrace)?;
553 let mut properties = HashMap::new();
554
555 while !self.is_at_end() && self.current_token().token != ConfigToken::RightBrace {
556 if matches!(self.current_token().token, ConfigToken::Comment(_) | ConfigToken::Newline | ConfigToken::Comma) {
557 self.advance();
558 continue;
559 }
560
561 let key = match &self.current_token().token {
562 ConfigToken::Identifier(s) | ConfigToken::String(s) => s.clone(),
563 _ => {
564 return Err(ConfigParseError {
565 message: "Expected property key".to_string(),
566 location: Some(self.current_token().location.clone()),
567 context: "object property syntax: key = value".to_string(),
568 });
569 }
570 };
571
572 self.advance(); self.expect_token(ConfigToken::Equals)?;
574 let value = self.parse_value()?;
575 properties.insert(key, value);
576 }
577
578 self.expect_token(ConfigToken::RightBrace)?;
579 Ok(ConfigValue::Object(properties))
580 }
581
582 fn current_token(&self) -> &TokenWithLocation {
584 &self.tokens[self.current]
585 }
586
587 fn is_at_end(&self) -> bool {
589 self.current >= self.tokens.len() || matches!(self.current_token().token, ConfigToken::Eof)
590 }
591
592 fn advance(&mut self) {
594 if !self.is_at_end() {
595 self.current += 1;
596 }
597 }
598
599 fn peek_next(&self) -> Option<&TokenWithLocation> {
601 if self.current + 1 < self.tokens.len() {
602 Some(&self.tokens[self.current + 1])
603 } else {
604 None
605 }
606 }
607
608 fn expect_token(&mut self, expected: ConfigToken) -> Result<(), ConfigParseError> {
610 if self.current_token().token == expected {
611 self.advance();
612 Ok(())
613 } else {
614 Err(ConfigParseError {
615 message: format!("Expected {:?}, found {:?}", expected, self.current_token().token),
616 location: Some(self.current_token().location.clone()),
617 context: format!("expected token: {:?}", expected),
618 })
619 }
620 }
621}
622
623pub struct HlxConfigHandler;
625
626impl HlxConfigHandler {
627 pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<HlxConfig, HlxError> {
629 let content = fs::read_to_string(&path)
630 .map_err(|e| HlxError::io_error(
631 format!("Failed to read HLX config file: {}", e),
632 format!("Check if file exists and is readable: {}", path.as_ref().display())
633 ))?;
634
635 Self::parse_content(&content)
636 }
637
638 pub fn write_to_file<P: AsRef<Path>>(config: &HlxConfig, path: P) -> Result<(), HlxError> {
640 let content = Self::serialize_config(config)?;
641
642 fs::write(&path, content)
643 .map_err(|e| HlxError::io_error(
644 format!("Failed to write HLX config file: {}", e),
645 format!("Check write permissions: {}", path.as_ref().display())
646 ))
647 }
648
649 pub fn parse_content(content: &str) -> Result<HlxConfig, HlxError> {
651 match EnhancedConfigParser::new(content) {
653 Ok(mut parser) => {
654 match parser.parse() {
655 Ok(config) => return Ok(config),
656 Err(parse_err) => {
657 eprintln!("Enhanced parser failed: {}, falling back to legacy parser", parse_err);
659 return Self::parse_content_legacy(content);
660 }
661 }
662 }
663 Err(lex_err) => {
664 eprintln!("Enhanced parser lexer failed: {}, falling back to legacy parser", lex_err);
666 return Self::parse_content_legacy(content);
667 }
668 }
669 }
670
671 fn parse_content_legacy(content: &str) -> Result<HlxConfig, HlxError> {
673 let mut sections = HashMap::new();
674 let mut metadata = HashMap::new();
675 let mut current_section = None;
676
677 for line in content.lines() {
678 let line = line.trim();
679
680 if line.is_empty() || line.starts_with('#') {
682 continue;
683 }
684
685 if line.starts_with('[') && line.ends_with(']') {
686 let section_name = &line[1..line.len() - 1];
688 current_section = Some(section_name.to_string());
689 sections.insert(section_name.to_string(), HlxSection {
690 properties: HashMap::new(),
691 metadata: None,
692 });
693 } else if let Some(section_name) = ¤t_section {
694 if let Some((key, value)) = Self::parse_property(line) {
696 if let Some(section) = sections.get_mut(section_name) {
697 section.properties.insert(key, value);
698 }
699 }
700 } else {
701 if let Some((key, value)) = Self::parse_property(line) {
703 metadata.insert(key, value);
704 }
705 }
706 }
707
708 Ok(HlxConfig { sections, metadata })
709 }
710
711 pub fn serialize_config(config: &HlxConfig) -> Result<String, HlxError> {
713 let mut output = String::new();
714
715 for (key, value) in &config.metadata {
717 output.push_str(&format!("{} = {}\n", key, Self::serialize_value(value)));
718 }
719
720 for (section_name, section) in &config.sections {
722 output.push_str(&format!("\n[{}]\n", section_name));
723
724 for (key, value) in §ion.properties {
725 output.push_str(&format!("{} = {}\n", key, Self::serialize_value(value)));
726 }
727 }
728
729 Ok(output)
730 }
731
732 fn parse_property(line: &str) -> Option<(String, serde_json::Value)> {
734 let parts: Vec<&str> = line.splitn(2, '=').map(|s| s.trim()).collect();
735 if parts.len() == 2 {
736 let key = parts[0].to_string();
737 let value_str = parts[1];
738
739 if let Ok(value) = serde_json::from_str(value_str) {
741 Some((key, value))
742 } else {
743 Some((key, serde_json::Value::String(value_str.to_string())))
744 }
745 } else {
746 None
747 }
748 }
749
750 fn serialize_value(value: &serde_json::Value) -> String {
752 match value {
753 serde_json::Value::String(s) => format!("\"{}\"", s),
754 serde_json::Value::Number(n) => n.to_string(),
755 serde_json::Value::Bool(b) => b.to_string(),
756 _ => value.to_string(),
757 }
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764
765 #[test]
766 fn test_parse_simple_config() {
767 let content = r#"
768# Global metadata
769version = "1.0"
770name = "test"
771
772[database]
773host = "localhost"
774port = 5432
775
776[logging]
777level = "info"
778file = "/var/log/app.log"
779"#;
780
781 let config = HlxConfigHandler::parse_content(content).unwrap();
782
783 assert_eq!(config.metadata.get("version").unwrap().as_str().unwrap(), "1.0");
784 assert_eq!(config.metadata.get("name").unwrap().as_str().unwrap(), "test");
785
786 assert!(config.sections.contains_key("database"));
787 assert!(config.sections.contains_key("logging"));
788
789 let db_section = &config.sections["database"];
790 assert_eq!(db_section.properties.get("host").unwrap().as_str().unwrap(), "localhost");
791 assert_eq!(db_section.properties.get("port").unwrap().as_i64().unwrap(), 5432);
792 }
793
794 #[test]
795 fn test_parse_enhanced_hlx_syntax() {
796 let content = r#"
797# Enhanced HLX syntax test
798version = "2.0"
799enabled = true
800
801project "test-project" {
802 name = "Test Project"
803 version = "1.0.0"
804 description = "A test project"
805
806 database {
807 host = "localhost"
808 port = 5432
809 credentials = {
810 username = "admin"
811 password = "secret"
812 }
813 features = ["ssl", "pooling", "metrics"]
814 }
815
816 agents = [
817 "agent1",
818 "agent2",
819 "agent3"
820 ]
821
822 settings {
823 debug = true
824 timeout = 30.5
825 retries = 3
826 }
827}
828
829logging {
830 level = "info"
831 file = "/var/log/app.log"
832 format = "json"
833}
834"#;
835
836 let config = HlxConfigHandler::parse_content(content).unwrap();
837
838 assert_eq!(config.metadata.get("version").unwrap().as_str().unwrap(), "2.0");
840 assert_eq!(config.metadata.get("enabled").unwrap().as_bool().unwrap(), true);
841
842 assert!(config.sections.contains_key("test-project"));
844 let project = &config.sections["test-project"];
845 assert_eq!(project.properties.get("name").unwrap().as_str().unwrap(), "Test Project");
846 assert_eq!(project.properties.get("version").unwrap().as_str().unwrap(), "1.0.0");
847
848 let db_obj = project.properties.get("database").unwrap().as_object().unwrap();
850 assert_eq!(db_obj.get("host").unwrap().as_str().unwrap(), "localhost");
851 assert_eq!(db_obj.get("port").unwrap().as_i64().unwrap(), 5432);
852
853 let creds = db_obj.get("credentials").unwrap().as_object().unwrap();
855 assert_eq!(creds.get("username").unwrap().as_str().unwrap(), "admin");
856 assert_eq!(creds.get("password").unwrap().as_str().unwrap(), "secret");
857
858 let features = db_obj.get("features").unwrap().as_array().unwrap();
860 assert_eq!(features.len(), 3);
861 assert_eq!(features[0].as_str().unwrap(), "ssl");
862 assert_eq!(features[1].as_str().unwrap(), "pooling");
863 assert_eq!(features[2].as_str().unwrap(), "metrics");
864
865 let agents = project.properties.get("agents").unwrap().as_array().unwrap();
867 assert_eq!(agents.len(), 3);
868 assert_eq!(agents[0].as_str().unwrap(), "agent1");
869 assert_eq!(agents[1].as_str().unwrap(), "agent2");
870 assert_eq!(agents[2].as_str().unwrap(), "agent3");
871
872 let settings = project.properties.get("settings").unwrap().as_object().unwrap();
874 assert_eq!(settings.get("debug").unwrap().as_bool().unwrap(), true);
875 assert_eq!(settings.get("timeout").unwrap().as_f64().unwrap(), 30.5);
876 assert_eq!(settings.get("retries").unwrap().as_i64().unwrap(), 3);
877
878 assert!(config.sections.contains_key("logging"));
880 let logging = &config.sections["logging"];
881 assert_eq!(logging.properties.get("level").unwrap().as_str().unwrap(), "info");
882 assert_eq!(logging.properties.get("file").unwrap().as_str().unwrap(), "/var/log/app.log");
883 assert_eq!(logging.properties.get("format").unwrap().as_str().unwrap(), "json");
884 }
885
886 #[test]
887 fn test_enhanced_parser_error_reporting() {
888 let content = r#"
890project "test" {
891 name = "test"
892 invalid_syntax = [
893"#;
894
895 let result = EnhancedConfigParser::new(content);
896 assert!(result.is_err());
897 if let Err(err) = result {
898 assert!(err.message.contains("Unexpected character") || err.message.contains("Expected"));
899 }
900 }
901
902 #[test]
903 fn test_backward_compatibility() {
904 let content = r#"
906# Legacy format
907version = "1.0"
908name = "legacy"
909
910[database]
911host = "localhost"
912port = 5432
913
914[logging]
915level = "info"
916"#;
917
918 let config = HlxConfigHandler::parse_content(content).unwrap();
919
920 assert_eq!(config.metadata.get("version").unwrap().as_str().unwrap(), "1.0");
922 assert_eq!(config.metadata.get("name").unwrap().as_str().unwrap(), "legacy");
923 assert!(config.sections.contains_key("database"));
924 assert!(config.sections.contains_key("logging"));
925 }
926
927 #[test]
928 fn test_serialize_config() {
929 let mut config = HlxConfig::default();
930 config.metadata.insert("version".to_string(), serde_json::Value::String("1.0".to_string()));
931
932 let mut db_section = HlxSection::default();
933 db_section.properties.insert("host".to_string(), serde_json::Value::String("localhost".to_string()));
934
935 config.sections.insert("database".to_string(), db_section);
936
937 let output = HlxConfigHandler::serialize_config(&config).unwrap();
938 assert!(output.contains("version = \"1.0\""));
939 assert!(output.contains("[database]"));
940 assert!(output.contains("host = \"localhost\""));
941 }
942
943 #[test]
944 fn test_complex_hlx_config() {
945 let content = r#"
946# Complex HLX configuration
947app_name = "ComplexApp"
948version = "3.0.0"
949
950server "web-server" {
951 host = "0.0.0.0"
952 port = 8080
953 ssl = true
954
955 endpoints = [
956 "/api/v1",
957 "/api/v2",
958 "/health"
959 ]
960
961 middleware {
962 cors = {
963 enabled = true
964 origins = ["*"]
965 methods = ["GET", "POST", "PUT", "DELETE"]
966 }
967
968 rate_limit = {
969 requests_per_minute = 1000
970 burst_limit = 100
971 }
972 }
973
974 database {
975 primary = {
976 host = "db1.example.com"
977 port = 5432
978 name = "app_db"
979 }
980
981 replicas = [
982 {
983 host = "db2.example.com"
984 port = 5432
985 },
986 {
987 host = "db3.example.com"
988 port = 5432
989 }
990 ]
991 }
992}
993
994workers = [
995 {
996 name = "queue-worker"
997 type = "async"
998 concurrency = 10
999 },
1000 {
1001 name = "cache-worker"
1002 type = "sync"
1003 concurrency = 5
1004 }
1005]
1006"#;
1007
1008 let config = HlxConfigHandler::parse_content(content).unwrap();
1009
1010 assert!(config.sections.contains_key("web-server"));
1012 let server = &config.sections["web-server"];
1013
1014 let middleware = server.properties.get("middleware").unwrap().as_object().unwrap();
1016 let cors = middleware.get("cors").unwrap().as_object().unwrap();
1017 assert_eq!(cors.get("enabled").unwrap().as_bool().unwrap(), true);
1018 let origins = cors.get("origins").unwrap().as_array().unwrap();
1019 assert_eq!(origins[0].as_str().unwrap(), "*");
1020
1021 let db = server.properties.get("database").unwrap().as_object().unwrap();
1023 let primary = db.get("primary").unwrap().as_object().unwrap();
1024 assert_eq!(primary.get("host").unwrap().as_str().unwrap(), "db1.example.com");
1025
1026 let replicas = db.get("replicas").unwrap().as_array().unwrap();
1028 assert_eq!(replicas.len(), 2);
1029 let replica1 = replicas[0].as_object().unwrap();
1030 assert_eq!(replica1.get("host").unwrap().as_str().unwrap(), "db2.example.com");
1031
1032 let workers = config.metadata.get("workers").unwrap().as_array().unwrap();
1034 assert_eq!(workers.len(), 2);
1035 let worker1 = workers[0].as_object().unwrap();
1036 assert_eq!(worker1.get("name").unwrap().as_str().unwrap(), "queue-worker");
1037 assert_eq!(worker1.get("concurrency").unwrap().as_i64().unwrap(), 10);
1038 }
1039}
1040
1041impl Default for HlxConfig {
1042 fn default() -> Self {
1043 Self {
1044 sections: HashMap::new(),
1045 metadata: HashMap::new(),
1046 }
1047 }
1048}
1049
1050impl Default for HlxSection {
1051 fn default() -> Self {
1052 Self {
1053 properties: HashMap::new(),
1054 metadata: None,
1055 }
1056 }
1057}