1#![allow(clippy::unused_self)]
7use crate::error::{AamlError, ErrorDiagnostics};
8use crate::pipeline::PipelineHashMap;
9use crate::pipeline::execution_descriptor::ExecutionContext;
10use crate::pipeline::lexer::Lexer;
11use crate::pipeline::new_pipeline_hash_map;
12use crate::pipeline::parser::Parser;
13use crate::pipeline::tasks::{ParseTask, TaskError, TaskExecutionResult, ValidationTask};
14use crate::pipeline::utils::resolve_relative_path;
15use bumpalo::Bump;
16use smol_str::SmolStr;
17
18pub trait ValidateExecutor: Send + Sync {
23 fn execute_validation(
33 &self,
34 task: &ValidationTask,
35 context: &ExecutionContext,
36 ) -> Result<bool, AamlError>;
37
38 fn execute_batch(
51 &self,
52 tasks: &[ValidationTask],
53 context: &ExecutionContext,
54 ) -> TaskExecutionResult {
55 let mut errors = Vec::new();
56
57 for task in tasks {
58 match self.execute_validation(task, context) {
59 Ok(true) => {}
60 Ok(false) => {
61 errors.push(TaskError {
63 line: task.line(),
64 message: format!("Validation failed: {}", task.description()),
65 task_description: task.description(),
66 aaml_error: None,
67 });
68 }
69 Err(e) => {
70 errors.push(TaskError {
71 line: task.line(),
72 message: format!("Validation error: {e}"),
73 task_description: task.description(),
74 aaml_error: Some(format!("{e:?}")),
75 });
76 }
77 }
78 }
79
80 TaskExecutionResult {
81 success: errors.is_empty(),
82 errors,
83 stats: ExecutionStats::default(),
84 }
85 }
86}
87
88pub use crate::pipeline::tasks::ExecutionStats;
90
91pub trait ParserExecutor: Send + Sync {
96 fn execute_parse<'a>(
106 &self,
107 task: &ParseTask<'_>,
108 arena: &'a Bump,
109 context: &mut ExecutionContext<'a>,
110 ) -> Result<(), AamlError>;
111
112 fn execute_batch<'a>(
121 &self,
122 tasks: &[ParseTask<'_>],
123 arena: &'a Bump,
124 context: &mut ExecutionContext<'a>,
125 ) -> TaskExecutionResult {
126 let mut errors = Vec::new();
127
128 for task in tasks {
129 match self.execute_parse(task, arena, context) {
130 Ok(()) => {}
131 Err(e) => {
132 errors.push(TaskError {
133 line: task.line(),
134 message: format!("Parse error: {e}"),
135 task_description: task.description(),
136 aaml_error: Some(format!("{e:?}")),
137 });
138 }
139 }
140 }
141
142 TaskExecutionResult {
143 success: errors.is_empty(),
144 errors,
145 stats: ExecutionStats::default(),
146 }
147 }
148}
149
150pub struct DefaultValidateExecutor {
154 }
156
157impl DefaultValidateExecutor {
158 #[must_use]
159 pub const fn new() -> Self {
160 Self {}
161 }
162
163 fn type_exists(context: &ExecutionContext, type_name: &str) -> bool {
165 context.types.contains_key(type_name) || Self::is_builtin_type(type_name)
166 }
167
168 fn is_builtin_type(type_name: &str) -> bool {
170 matches!(type_name, "string" | "i32" | "f64" | "bool" | "color")
171 }
172
173 fn validate_type_value(
175 &self,
176 value: &str,
177 type_name: &str,
178 context: &ExecutionContext,
179 ) -> Result<(), AamlError> {
180 crate::pipeline::utils::validate_type_value(value, type_name, context)
181 }
182
183 fn validate_type_match(
184 &self,
185 key: &str,
186 value: &str,
187 type_name: &str,
188 context: &ExecutionContext,
189 ) -> Result<bool, AamlError> {
190 if !Self::type_exists(context, type_name) {
191 return Err(AamlError::InvalidType {
192 type_name: type_name.to_string(),
193 details: format!("Type not found in registry for key '{key}'"),
194 provided: value.to_string(),
195 diagnostics: Some(Box::new(ErrorDiagnostics::new(
196 "Unknown type",
197 format!("Type '{type_name}' is not registered"),
198 "Register the type using @type directive".to_string(),
199 ))),
200 });
201 }
202
203 if let Err(e) = self.validate_type_value(value, type_name, context) {
204 return Err(AamlError::InvalidType {
205 type_name: type_name.to_string(),
206 details: format!("Validation failed for key '{key}'"),
207 provided: value.to_string(),
208 diagnostics: Some(Box::new(ErrorDiagnostics::new(
209 "Type validation failed",
210 e.to_string(),
211 format!("Ensure '{value}' conforms to type '{type_name}'"),
212 ))),
213 });
214 }
215
216 Ok(true)
217 }
218
219 fn verify_schema_exists(
220 schema_name: &str,
221 context: &ExecutionContext,
222 ) -> Result<bool, AamlError> {
223 if context.schemas.contains_key(schema_name) {
224 return Ok(true);
225 }
226
227 Err(AamlError::NotFound {
228 key: schema_name.to_string(),
229 context: "Schema not found in registry".to_string(),
230 diagnostics: Some(Box::new(ErrorDiagnostics::new(
231 "Schema not defined",
232 format!("Schema '{schema_name}' referenced but not defined"),
233 "Define it using @schema directive".to_string(),
234 ))),
235 })
236 }
237
238 fn verify_file_exists(path: &str) -> Result<bool, AamlError> {
239 if std::path::Path::new(path).exists() {
240 return Ok(true);
241 }
242
243 Err(AamlError::IoError {
244 details: format!("Imported file '{path}' not found"),
245 diagnostics: Some(Box::new(ErrorDiagnostics::new(
246 "File missing",
247 format!("The file '{path}' does not exist."),
248 "Check the file path in your import directive.",
249 ))),
250 })
251 }
252
253 fn check_no_circular_reference(
254 &self,
255 key: &str,
256 context: &ExecutionContext,
257 ) -> Result<bool, AamlError> {
258 let mut current_key: &str = key;
259 let mut visited = std::collections::HashSet::new();
260
261 while let Some(next_val) = context.map.get(current_key) {
262 if !visited.insert(current_key) {
263 return Err(AamlError::CircularDependency {
264 path: format!("{key} -> {next_val}"),
265 diagnostics: Some(Box::new(ErrorDiagnostics::new(
266 "Circular reference detected",
267 format!("Key '{key}' references itself directly or indirectly"),
268 "Break the reference loop".to_string(),
269 ))),
270 });
271 }
272
273 if context.map.contains_key(&**next_val) {
274 current_key = next_val;
275 } else {
276 break;
277 }
278 }
279
280 Ok(true)
281 }
282
283 #[inline]
284 fn derive_schema_names(derive_path: &str) -> impl Iterator<Item = &str> {
285 derive_path.split("::").skip(1)
286 }
287
288 #[inline]
289 fn derived_required_key(current_key: &str, field: &str) -> String {
290 if current_key.is_empty() {
291 field.to_string()
292 } else {
293 format!("{current_key}.{field}")
294 }
295 }
296
297 fn ensure_derived_schema_complete(
298 schema_name: &str,
299 current_key: &str,
300 context: &ExecutionContext,
301 ) -> Result<(), AamlError> {
302 let schema = context
303 .schemas
304 .get(schema_name)
305 .ok_or_else(|| AamlError::NotFound {
306 key: schema_name.to_string(),
307 context: "schema derivation".to_string(),
308 diagnostics: Some(Box::new(ErrorDiagnostics::new(
309 "Schema not defined",
310 format!("Schema '{schema_name}' referenced in derive chain but not defined"),
311 "Ensure the file being derived from defines this schema",
312 ))),
313 })?;
314
315 for (field, (type_name, is_optional)) in &schema.fields {
316 if *is_optional {
317 continue;
318 }
319
320 let full_key = Self::derived_required_key(current_key, field);
321 if context.map.contains_key(full_key.as_str()) {
322 continue;
323 }
324
325 return Err(AamlError::SchemaValidationError {
326 schema: schema_name.to_string(),
327 field: field.to_string(),
328 type_name: type_name.to_string(),
329 details: format!(
330 "Missing required field '{field}' from derived schema '{schema_name}'"
331 ),
332 diagnostics: Some(Box::new(ErrorDiagnostics::new(
333 "Incomplete derivation",
334 format!("Derived object missing required field: {field}"),
335 "Add the field to satisfy the derived schema",
336 ))),
337 });
338 }
339
340 Ok(())
341 }
342
343 fn check_derive_completeness(
344 &self,
345 derive_path: &str,
346 current_key: &str,
347 context: &ExecutionContext,
348 ) -> Result<bool, AamlError> {
349 if !derive_path.contains("::") {
350 return Ok(true);
351 }
352
353 for schema_name in Self::derive_schema_names(derive_path) {
354 Self::ensure_derived_schema_complete(schema_name, current_key, context)?;
355 }
356
357 Ok(true)
358 }
359
360 fn validate_against_schema(
361 &self,
362 schema_name: &str,
363 key: &str,
364 value: &str,
365 context: &ExecutionContext,
366 ) -> Result<bool, AamlError> {
367 let schema_info =
368 context
369 .schemas
370 .get(schema_name)
371 .ok_or_else(|| AamlError::SchemaValidationError {
372 schema: schema_name.to_string(),
373 field: key.to_string(),
374 type_name: "schema".to_string(),
375 details: format!("Schema '{schema_name}' not found"),
376 diagnostics: None,
377 })?;
378
379 if let Err(e) = crate::pipeline::utils::validate_inline_object_against_schema(
380 value,
381 schema_info,
382 context,
383 ) {
384 return Err(AamlError::SchemaValidationError {
385 schema: schema_name.to_string(),
386 field: key.to_string(),
387 type_name: "schema".to_string(),
388 details: e.to_string(),
389 diagnostics: None,
390 });
391 }
392
393 Ok(true)
394 }
395
396 fn check_schema_completeness(
397 schema_name: &str,
398 missing_fields: &[std::borrow::Cow<'_, str>],
399 ) -> Result<bool, AamlError> {
400 if missing_fields.is_empty() {
401 return Ok(true);
402 }
403
404 let missing_str = missing_fields
405 .iter()
406 .map(AsRef::as_ref)
407 .collect::<Vec<_>>()
408 .join(", ");
409 Err(AamlError::SchemaValidationError {
410 schema: schema_name.to_string(),
411 field: missing_str.clone(),
412 type_name: "required".to_string(),
413 details: format!("Schema incomplete: missing required fields: {missing_str}"),
414 diagnostics: None,
415 })
416 }
417
418 fn validate_list_elements(
419 key: &str,
420 items: &[crate::pipeline::parser::ValueNode<'_>],
421 element_type: &str,
422 context: &ExecutionContext,
423 ) -> Result<bool, AamlError> {
424 if element_type == "any" || element_type == "list" || element_type == "object" {
425 return Ok(true);
426 }
427
428 for item in items {
429 if let Err(e) = crate::pipeline::utils::validate_type_value(
430 &item.to_string(),
431 element_type,
432 context,
433 ) {
434 return Err(AamlError::InvalidType {
435 type_name: element_type.to_string(),
436 details: format!("List element invalid in '{key}'"),
437 provided: item.to_string(),
438 diagnostics: Some(Box::new(ErrorDiagnostics::new(
439 "List element validation failed",
440 e.to_string(),
441 format!("All elements in list must be of type '{element_type}'"),
442 ))),
443 });
444 }
445 }
446
447 Ok(true)
448 }
449
450 fn validate_object_structure(
451 key: &str,
452 pairs: &[(
453 std::borrow::Cow<'_, str>,
454 crate::pipeline::parser::ValueNode<'_>,
455 )],
456 ) -> Result<bool, AamlError> {
457 if pairs.is_empty() {
458 return Err(AamlError::InvalidValue {
459 details: format!("Empty object for key '{key}'"),
460 expected: "non-empty object".to_string(),
461 diagnostics: None,
462 });
463 }
464
465 Ok(true)
466 }
467}
468
469impl Default for DefaultValidateExecutor {
470 fn default() -> Self {
471 Self::new()
472 }
473}
474
475impl ValidateExecutor for DefaultValidateExecutor {
476 fn execute_validation(
477 &self,
478 task: &ValidationTask,
479 context: &ExecutionContext,
480 ) -> Result<bool, AamlError> {
481 match task {
482 ValidationTask::CheckTypeMatch {
483 key,
484 value,
485 type_name,
486 line: _,
487 } => self.validate_type_match(key, value, type_name, context),
488 ValidationTask::VerifySchemaExists {
489 schema_name,
490 line: _,
491 } => Self::verify_schema_exists(schema_name, context),
492 ValidationTask::VerifyFileExists { path, line: _ } => Self::verify_file_exists(path),
493 ValidationTask::CheckNoCircularReference { key, line: _ } => {
494 self.check_no_circular_reference(key, context)
495 }
496 ValidationTask::CheckDeriveCompleteness {
497 derive_path,
498 current_key,
499 line: _,
500 } => self.check_derive_completeness(derive_path, current_key, context),
501 ValidationTask::ValidateAgainstSchema {
502 schema_name,
503 key,
504 value,
505 line: _,
506 } => self.validate_against_schema(schema_name, key, value, context),
507 ValidationTask::CheckSchemaCompleteness {
508 schema_name,
509 missing_fields,
510 line: _,
511 } => Self::check_schema_completeness(schema_name, missing_fields),
512 ValidationTask::ValidateListElements {
513 key,
514 items,
515 element_type,
516 line: _,
517 } => Self::validate_list_elements(key, items, element_type, context),
518 ValidationTask::ValidateObjectStructure {
519 key,
520 pairs,
521 line: _,
522 } => Self::validate_object_structure(key, pairs),
523 }
524 }
525}
526
527pub struct DefaultParserExecutor {
532 }
534
535impl DefaultParserExecutor {
536 #[must_use]
537 pub const fn new() -> Self {
538 Self {}
539 }
540
541 fn process_variable(
542 variable_name: &str,
543 value: &str,
544 line: usize,
545 context: &mut ExecutionContext<'_>,
546 ) {
547 context.set_value(variable_name, value, line);
548 }
549
550 fn manage_scope(scope: &str, is_entry: bool, context: &mut ExecutionContext<'_>) {
551 if is_entry {
552 context.push_scope(scope.to_string());
553 } else {
554 context.pop_scope();
555 }
556 }
557
558 fn execute_directive(
559 directive_name: &str,
560 arguments: &str,
561 line: usize,
562 ) -> Result<(), AamlError> {
563 match directive_name {
564 "import" | "derive" => Ok(()),
565 _ => Err(AamlError::ParseError {
566 line,
567 content: format!("@{directive_name} {arguments}"),
568 details: format!("Unknown directive: @{directive_name}"),
569 diagnostics: Some(Box::new(ErrorDiagnostics::new(
570 "Unknown directive",
571 format!("Directive '@{directive_name}' is not recognized"),
572 "Known directives: @import, @derive, @schema, @type",
573 ))),
574 }),
575 }
576 }
577
578 fn register_type(
579 type_name: &str,
580 type_spec: &str,
581 line: usize,
582 context: &mut ExecutionContext<'_>,
583 ) -> Result<(), AamlError> {
584 let inferred_default = if type_spec.starts_with("list<") {
585 Some("[]".to_string())
586 } else {
587 None
588 };
589
590 context.register_type(crate::pipeline::execution_descriptor::TypeInfo {
591 name: type_name.into(),
592 spec: type_spec.into(),
593 validator: None,
594 default_value: inferred_default.map(Into::into),
595 metadata: new_pipeline_hash_map(),
596 line,
597 })
598 }
599
600 fn parse_schema_fields(fields: &str) -> PipelineHashMap<SmolStr, (SmolStr, bool)> {
601 let mut schema_fields = new_pipeline_hash_map();
602
603 for field_def in fields.split(',') {
604 let parts: Vec<&str> = field_def.trim().split(':').collect();
605 if parts.len() != 2 {
606 continue;
607 }
608
609 let field_name = parts[0].trim().to_string();
610 let type_name = parts[1].trim().to_string();
611 let is_optional = field_name.ends_with('*');
612 let clean_name = if is_optional {
613 field_name.trim_end_matches('*').trim().to_string()
614 } else {
615 field_name
616 };
617
618 schema_fields.insert(clean_name.into(), (type_name.into(), is_optional));
619 }
620
621 schema_fields
622 }
623
624 fn auto_register_list_types(
625 schema_fields: &PipelineHashMap<SmolStr, (SmolStr, bool)>,
626 line: usize,
627 context: &mut ExecutionContext<'_>,
628 ) -> Result<(), AamlError> {
629 let field_type_names: Vec<SmolStr> = schema_fields
630 .values()
631 .map(|(type_name, _)| type_name.clone())
632 .collect();
633
634 for type_name in field_type_names {
635 if !type_name.starts_with("list<") || context.types.contains_key(type_name.as_str()) {
636 continue;
637 }
638
639 context.register_type(crate::pipeline::execution_descriptor::TypeInfo {
640 name: type_name.clone(),
641 spec: type_name,
642 validator: None,
643 default_value: Some("[]".into()),
644 metadata: new_pipeline_hash_map(),
645 line,
646 })?;
647 }
648 Ok(())
649 }
650
651 fn register_schema(
652 schema_name: &str,
653 fields: &str,
654 line: usize,
655 context: &mut ExecutionContext<'_>,
656 ) -> Result<(), AamlError> {
657 let schema_fields = Self::parse_schema_fields(fields);
658 Self::auto_register_list_types(&schema_fields, line, context)?;
659
660 context.register_schema(crate::pipeline::execution_descriptor::SchemaInfo {
661 name: schema_name.into(),
662 fields: schema_fields,
663 line,
664 });
665
666 context.register_type(crate::pipeline::execution_descriptor::TypeInfo {
667 name: schema_name.into(),
668 spec: "schema".into(),
669 validator: None,
670 default_value: Some("{}".into()),
671 metadata: new_pipeline_hash_map(),
672 line,
673 })
674 }
675
676 fn resolve_derive_import<'a>(
677 &self,
678 derive_path: &str,
679 arena: &'a Bump,
680 context: &mut ExecutionContext<'a>,
681 ) -> Result<(), AamlError> {
682 let parts: Vec<&str> = derive_path.split("::").collect();
683 if parts.is_empty() {
684 return Err(AamlError::DirectiveError {
685 directive: "derive".to_string(),
686 message: "Empty derive path".to_string(),
687 diagnostics: None,
688 });
689 }
690
691 let raw_path = parts[0];
692 let resolved =
693 resolve_relative_path(raw_path, crate::pipeline::source_dir::get().as_deref());
694 let file_path = resolved.to_string_lossy().to_string();
695 if !context.is_imported(&file_path) {
696 let content_string =
697 std::fs::read_to_string(&resolved).map_err(|e| AamlError::IoError {
698 details: format!("Failed to read imported file '{file_path}': {e}"),
699 diagnostics: Some(Box::new(ErrorDiagnostics::new(
700 "Import failed",
701 format!("Could not read file '{file_path}'"),
702 "Check if the file exists and is readable",
703 ))),
704 })?;
705
706 let lexer = crate::pipeline::lexer::DefaultLexer::new();
707 let parser = crate::pipeline::parser::DefaultParser::new();
708 let _ctx_guard = crate::error::push_error_render_context(&file_path, &content_string);
709
710 let imported_content = arena.alloc_str(&content_string);
711
712 let tokens = lexer.tokenize(imported_content)?;
713 let parse_output = parser.parse_with_recovery(&tokens);
714 if let Some(first_error) = parse_output.errors.into_iter().next() {
715 return Err(first_error);
716 }
717
718 let _nested_guard = crate::pipeline::source_dir::SourceDirGuard::new(
721 resolved.parent().map(std::path::Path::to_path_buf),
722 );
723
724 let sub_tasks = parser.generate_parse_tasks(&parse_output.ast);
725 for sub_task in sub_tasks {
726 if let ParseTask::ProcessVariable { .. } = sub_task {
727 continue;
729 }
730 self.execute_parse(&sub_task, arena, context)?;
731 }
732 context.record_import(file_path);
733 }
734
735 Ok(())
736 }
737
738 fn resolve_module_reference(
739 module_name: &str,
740 scope: &str,
741 context: &ExecutionContext<'_>,
742 ) -> Result<(), AamlError> {
743 if !context.imported_files.contains(module_name)
744 && !context.schemas.contains_key(module_name)
745 {
746 return Err(AamlError::NotFound {
747 key: module_name.to_string(),
748 context: format!("module reference in scope '{scope}'"),
749 diagnostics: Some(Box::new(ErrorDiagnostics::new(
750 "Module not found",
751 format!("The module '{module_name}' has not been imported or defined"),
752 "Check for a missing @import directive",
753 ))),
754 });
755 }
756
757 Ok(())
758 }
759}
760
761impl Default for DefaultParserExecutor {
762 fn default() -> Self {
763 Self::new()
764 }
765}
766
767impl ParserExecutor for DefaultParserExecutor {
768 fn execute_parse<'a>(
769 &self,
770 task: &ParseTask<'_>,
771 arena: &'a Bump,
772 context: &mut ExecutionContext<'a>,
773 ) -> Result<(), AamlError> {
774 match task {
775 ParseTask::ProcessVariable {
776 variable_name,
777 value,
778 scope: _,
779 line,
780 } => {
781 Self::process_variable(variable_name, value, *line, context);
782 Ok(())
783 }
784
785 ParseTask::ManageScope {
786 scope,
787 is_entry,
788 line: _,
789 } => {
790 Self::manage_scope(scope, *is_entry, context);
791 Ok(())
792 }
793
794 ParseTask::ExecuteDirective {
795 directive_name,
796 arguments,
797 line,
798 } => Self::execute_directive(directive_name, arguments, *line),
799
800 ParseTask::RegisterType {
801 type_name,
802 type_spec,
803 line,
804 } => Self::register_type(type_name, type_spec, *line, context),
805
806 ParseTask::RegisterSchema {
807 schema_name,
808 fields,
809 line,
810 } => Self::register_schema(schema_name, fields, *line, context),
811
812 ParseTask::ResolveDeriveImport {
813 derive_path,
814 line: _,
815 } => self.resolve_derive_import(derive_path, arena, context),
816
817 ParseTask::ResolveModuleReference {
818 module_name,
819 scope,
820 line: _,
821 } => Self::resolve_module_reference(module_name, scope, context),
822 }
823 }
824}