1use crate::ast::{
71 ImportSpec, ImportStatement, Member, MonDocument, MonValue, MonValueKind,
72 SymbolTable as AstSymbolTable, TypeDef, TypeSpec,
73};
74use crate::error::{ResolverError, ValidationError};
75use log::warn;
76use miette::NamedSource;
77use std::collections::HashMap;
78use std::path::{Path, PathBuf};
79use std::sync::Arc;
80
81pub struct Resolver {
140 resolved_documents: HashMap<PathBuf, MonDocument>,
142 resolving_stack: Vec<(PathBuf, Option<ImportStatement>)>,
144 pub symbol_table: AstSymbolTable,
146 pub anchors: HashMap<String, MonValue>,
148
149 builtin_schemas_path: PathBuf,
150}
151
152impl Resolver {
153 #[must_use]
157 pub fn new() -> Self {
158 Self::with_builtin_path(Self::default_builtin_path())
159 }
160
161 pub fn with_builtin_path(path: PathBuf) -> Self {
165 Resolver {
166 resolved_documents: HashMap::new(),
167 resolving_stack: Vec::new(),
168 symbol_table: AstSymbolTable::new(),
169 anchors: HashMap::new(),
170 builtin_schemas_path: path,
171 }
172 }
173
174 fn default_builtin_path() -> PathBuf {
182 if let Ok(path) = std::env::var("MON_BUILTIN_PATH") {
185 return PathBuf::from(path);
186 }
187
188 if let Some(home) = std::env::var_os("HOME") {
190 let user_schemas = PathBuf::from(home).join(".mon/schemas");
191 if user_schemas.exists() {
192 return user_schemas;
193 }
194 }
195
196 #[cfg(unix)]
198 {
199 let system_path = PathBuf::from("/usr/share/mon/schemas");
200 if system_path.exists() {
201 return system_path;
202 }
203 }
204
205 warn!(
207 "Could not determine default schemas path, defaulting to current directory.
208 This is probably not intended"
209 );
210 PathBuf::from(".")
211 }
212
213 fn resolve_import_path(&self, import_path: &str, current_dir: &Path) -> PathBuf {
220 if let Some(builtin_path) = import_path.strip_prefix("mon:") {
222 return self
224 .builtin_schemas_path
225 .join(builtin_path)
226 .with_extension("mon");
227 }
228
229 current_dir.join(import_path)
231 }
232 pub fn resolve(
254 &mut self,
255 document: MonDocument,
256 source_text: &str,
257 file_path: PathBuf,
258 causing_import: Option<ImportStatement>,
259 ) -> Result<MonDocument, ResolverError> {
260 if let Some((_, Some(existing_causing_import))) =
262 self.resolving_stack.iter().find(|(p, _)| p == &file_path)
263 {
264 let cycle_str = self
265 .resolving_stack
266 .iter()
267 .map(|(p, _)| p.to_string_lossy().to_string())
268 .collect::<Vec<String>>()
269 .join(" -> ");
270 return Err(ResolverError::CircularDependency {
271 cycle: format!("{} -> {}", cycle_str, file_path.to_string_lossy()),
272 src: NamedSource::new(file_path.to_string_lossy(), source_text.to_string()).into(),
273 span: (
274 existing_causing_import.pos_start,
275 existing_causing_import.pos_end - existing_causing_import.pos_start,
276 )
277 .into(),
278 });
279 }
280 self.resolving_stack
281 .push((file_path.clone(), causing_import)); let current_dir = file_path.parent().unwrap_or_else(|| Path::new("."));
285 let source_arc = Arc::new(source_text.to_string());
286 for import_statement in &document.imports {
287 let imported_path_str = import_statement.path.trim_matches('"');
288
289 let absolute_imported_path = self.resolve_import_path(imported_path_str, current_dir);
291 if self
292 .resolved_documents
293 .contains_key(&absolute_imported_path)
294 {
295 continue;
296 }
297 let imported_source_text =
298 std::fs::read_to_string(&absolute_imported_path).map_err(|_| {
299 ResolverError::ModuleNotFound {
300 path: imported_path_str.to_string(),
301 src: Arc::from(NamedSource::new(
302 file_path.to_string_lossy(),
303 source_arc.to_string(),
304 )),
305 span: (
306 import_statement.pos_start,
307 import_statement.pos_end - import_statement.pos_start,
308 )
309 .into(),
310 }
311 })?;
312 let mut parser = crate::parser::Parser::new_with_name(
314 &imported_source_text,
315 absolute_imported_path.to_string_lossy().to_string(),
316 )?;
317
318 let imported_document = parser.parse_document()?;
319 let resolved_imported_document = self.resolve(
320 imported_document,
321 &imported_source_text,
322 absolute_imported_path.clone(),
323 Some(import_statement.clone()),
324 )?;
325 self.resolved_documents
326 .insert(absolute_imported_path, resolved_imported_document);
327 }
328
329 for import_statement in &document.imports {
331 if let ImportSpec::Named(specifiers) = &import_statement.spec {
332 let imported_path_str = import_statement.path.trim_matches('"');
333 let absolute_imported_path = current_dir.join(imported_path_str);
334 if let Some(imported_doc) = self.resolved_documents.get(&absolute_imported_path) {
335 if let MonValueKind::Object(members) = &imported_doc.root.kind {
336 for specifier in specifiers {
337 if !specifier.is_anchor {
338 for member in members {
339 if let Member::TypeDefinition(td) = member {
340 if td.name == specifier.name {
341 self.symbol_table
342 .types
343 .insert(specifier.name.clone(), td.clone());
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351 }
352 }
353
354 if let MonValueKind::Object(members) = &document.root.kind {
356 for member in members {
357 match member {
358 Member::TypeDefinition(type_def) => {
359 self.symbol_table
360 .types
361 .insert(type_def.name.clone(), type_def.clone());
362 }
363 Member::Pair(pair) => {
364 if let Some(anchor_name) = &pair.value.anchor {
365 self.anchors.insert(anchor_name.clone(), pair.value.clone());
366 }
367 }
368 _ => {}
369 }
370 }
371 }
372
373 let resolved_root = self.resolve_value(document.root, &file_path, source_text)?;
375
376 let final_resolved_root =
380 self.validate_document_root(resolved_root, &document.imports, &file_path, source_text)?;
381
382 let resolved_doc = MonDocument {
383 root: final_resolved_root,
384 imports: document.imports, };
386
387 self.resolving_stack.pop();
389
390 Ok(resolved_doc)
391 }
392 fn resolve_value(
395 &mut self,
396 mut value: MonValue,
397 file_path: &PathBuf,
398 source_text: &str,
399 ) -> Result<MonValue, ResolverError> {
400 let alias_span = value.get_source_span();
401
402 match &mut value.kind {
403 MonValueKind::Alias(alias_name) => {
404 let anchor_value = self.anchors.get(alias_name).ok_or_else(|| {
406 ResolverError::AnchorNotFound {
408 name: alias_name.clone(),
409 src: Arc::from(NamedSource::new(
410 file_path.to_string_lossy(),
411 source_text.to_string(),
412 )),
413 span: alias_span,
414 }
415 })?;
416 Ok(anchor_value.clone()) }
418 MonValueKind::Object(members) => {
419 let mut resolved_members = Vec::new();
420 for member in members.drain(..) {
421 match member {
422 Member::Spread(spread_name) => {
423 let anchor_value = self.anchors.get(&spread_name).ok_or_else(|| {
425 ResolverError::AnchorNotFound {
427 name: spread_name.clone(),
428 src: Arc::from(NamedSource::new(
429 file_path.to_string_lossy(),
430 source_text.to_string(),
431 )),
432 span: alias_span,
433 }
434 })?;
435 if let MonValueKind::Object(spread_members) = &anchor_value.kind {
436 let spread_members_clone = spread_members.clone();
437 for spread_member in spread_members_clone {
438 resolved_members.push(self.resolve_value_member(
440 spread_member,
441 file_path,
442 source_text,
443 )?);
444 }
445 } else {
446 return Err(ResolverError::SpreadOnNonObject {
447 name: spread_name.clone(),
448 src: Arc::from(NamedSource::new(
449 file_path.to_string_lossy(),
450 source_text.to_string(),
451 )),
452 span: alias_span,
453 });
454 }
455 }
456 _ => {
457 resolved_members.push(self.resolve_value_member(
459 member,
460 file_path,
461 source_text,
462 )?);
463 }
464 }
465 }
466 let mut final_members_map: HashMap<String, Member> = HashMap::new();
468 for member in resolved_members {
469 if let Member::Pair(pair) = member {
470 final_members_map.insert(pair.key.clone(), Member::Pair(pair));
471 } else {
472 final_members_map.insert(format!("{member:?}"), member);
475 }
477 }
478 let final_members = final_members_map.into_values().collect();
479 Ok(MonValue {
480 kind: MonValueKind::Object(final_members),
481 anchor: value.anchor,
482 pos_start: value.pos_start,
483 pos_end: value.pos_end,
484 })
485 }
486 MonValueKind::Array(elements) => {
487 let mut resolved_elements = Vec::new();
488 for element in elements.drain(..) {
489 match element.kind {
490 MonValueKind::ArraySpread(spread_name) => {
491 let anchor_value = self.anchors.get(&spread_name).ok_or_else(|| {
493 ResolverError::AnchorNotFound {
495 name: spread_name.clone(),
496 src: Arc::from(NamedSource::new(
497 file_path.to_string_lossy(),
498 source_text.to_string(),
499 )),
500 span: alias_span,
501 }
502 })?;
503 if let MonValueKind::Array(spread_elements) = &anchor_value.kind {
504 let spread_elements_clone = spread_elements.clone();
505 for spread_element in spread_elements_clone {
506 resolved_elements.push(self.resolve_value(
508 spread_element,
509 file_path,
510 source_text,
511 )?);
512 }
513 } else {
514 return Err(ResolverError::SpreadOnNonArray {
516 name: spread_name.clone(),
517 src: Arc::from(NamedSource::new(
518 file_path.to_string_lossy(),
519 source_text.to_string(),
520 )),
521 span: alias_span,
522 });
523 }
524 }
525 _ => {
526 resolved_elements.push(self.resolve_value(
528 element,
529 file_path,
530 source_text,
531 )?);
532 }
533 }
534 }
535 Ok(MonValue {
536 kind: MonValueKind::Array(resolved_elements),
537 anchor: value.anchor,
538 pos_start: value.pos_start,
539 pos_end: value.pos_end,
540 })
541 }
542 _ => Ok(value), }
544 }
545
546 fn resolve_value_member(
548 &mut self,
549 mut member: Member,
550 file_path: &PathBuf,
551 source_text: &str,
552 ) -> Result<Member, ResolverError> {
553 match &mut member {
554 Member::Pair(pair) => {
555 pair.value = self.resolve_value(pair.value.clone(), file_path, source_text)?;
556 Ok(member)
557 }
558 _ => Ok(member),
560 }
561 }
562
563 fn validate_document_root(
565 &mut self,
566 mut root_value: MonValue,
567 imports: &[ImportStatement], file_path: &PathBuf,
569 source_text: &str,
570 ) -> Result<MonValue, ResolverError> {
571 if let MonValueKind::Object(members) = &mut root_value.kind {
572 for member in members.iter_mut() {
573 if let Member::Pair(pair) = member {
574 if let Some(type_spec) = &pair.validation {
575 self.validate_value(
577 &mut pair.value,
578 type_spec,
579 &pair.key,
580 imports, file_path,
582 source_text,
583 )?;
584 }
585 }
586 }
587 }
588 Ok(root_value)
589 }
590
591 fn validate_value(
593 &mut self,
594 value: &mut MonValue,
595 type_spec: &TypeSpec,
596 field_name: &str, imports: &[ImportStatement], file_path: &PathBuf,
599 source_text: &str,
600 ) -> Result<(), ResolverError> {
601 match type_spec {
602 TypeSpec::Simple(type_name, _) => {
603 match type_name.as_str() {
605 "String" => {
606 if !matches!(value.kind, MonValueKind::String(_)) {
607 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
608 field_name: field_name.to_string(),
609 expected_type: "String".to_string(),
610 found_type: format!("{:?}", value.kind),
611 src: Arc::from(NamedSource::new(
612 file_path.to_string_lossy(),
613 source_text.to_string(),
614 )),
615 span: (value.pos_start, value.pos_end - value.pos_start).into(),
616 }));
617 }
618 }
619 "Number" => {
620 if !matches!(value.kind, MonValueKind::Number(_)) {
621 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
622 field_name: field_name.to_string(),
623 expected_type: "Number".to_string(),
624 found_type: format!("{:?}", value.kind),
625 src: Arc::from(NamedSource::new(
626 file_path.to_string_lossy(),
627 source_text.to_string(),
628 )),
629 span: (value.pos_start, value.pos_end - value.pos_start).into(),
630 }));
631 }
632 }
633 "Boolean" => {
634 if !matches!(value.kind, MonValueKind::Boolean(_)) {
635 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
636 field_name: field_name.to_string(),
637 expected_type: "Boolean".to_string(),
638 found_type: format!("{:?}", value.kind),
639 src: Arc::from(NamedSource::new(
640 file_path.to_string_lossy(),
641 source_text.to_string(),
642 )),
643 span: (value.pos_start, value.pos_end - value.pos_start).into(),
644 }));
645 }
646 }
647 "Null" => {
648 if !matches!(value.kind, MonValueKind::Null) {
649 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
650 field_name: field_name.to_string(),
651 expected_type: "Null".to_string(),
652 found_type: format!("{:?}", value.kind),
653 src: Arc::from(NamedSource::new(
654 file_path.to_string_lossy(),
655 source_text.to_string(),
656 )),
657 span: (value.pos_start, value.pos_end - value.pos_start).into(),
658 }));
659 }
660 }
661 "Object" => {
662 if !matches!(value.kind, MonValueKind::Object(_)) {
663 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
664 field_name: field_name.to_string(),
665 expected_type: "Object".to_string(),
666 found_type: format!("{:?}", value.kind),
667 src: Arc::from(NamedSource::new(
668 file_path.to_string_lossy(),
669 source_text.to_string(),
670 )),
671 span: (value.pos_start, value.pos_end - value.pos_start).into(),
672 }));
673 }
674 }
675 "Array" => {
676 if !matches!(value.kind, MonValueKind::Array(_)) {
677 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
678 field_name: field_name.to_string(),
679 expected_type: "Array".to_string(),
680 found_type: format!("{:?}", value.kind),
681 src: Arc::from(NamedSource::new(
682 file_path.to_string_lossy(),
683 source_text.to_string(),
684 )),
685 span: (value.pos_start, value.pos_end - value.pos_start).into(),
686 }));
687 }
688 }
689 "Any" => { }
690 _ => {
691 let (namespace, type_name_part) =
693 if let Some((ns, tn)) = type_name.split_once('.') {
694 (Some(ns), tn)
695 } else {
696 (None, type_name.as_str())
697 };
698
699 let type_def = if let Some(namespace) = namespace {
700 let import_statement = imports
702 .iter()
703 .find(|i| {
704 if let ImportSpec::Namespace(ns) = &i.spec {
705 ns == namespace
706 } else {
707 false
708 }
709 })
710 .ok_or_else(|| {
711 ResolverError::Validation(ValidationError::UndefinedType {
712 type_name: type_name.clone(),
713 src: Arc::from(NamedSource::new(
714 file_path.to_string_lossy(),
715 source_text.to_string(),
716 )),
717 span: (value.pos_start, value.pos_end - value.pos_start)
718 .into(),
719 })
720 })?;
721
722 let imported_path_str = import_statement.path.trim_matches('"');
723 let parent_dir = file_path.parent().ok_or_else(|| {
724 ResolverError::ModuleNotFound {
727 path: import_statement.path.clone(),
728 src: Arc::from(NamedSource::new(
729 file_path.to_string_lossy(),
730 source_text.to_string(),
731 )),
732 span: (
733 import_statement.pos_start,
734 import_statement.pos_end - import_statement.pos_start,
735 )
736 .into(),
737 }
738 })?;
739 let absolute_imported_path = parent_dir.join(imported_path_str);
740
741 let imported_doc = self
742 .resolved_documents
743 .get(&absolute_imported_path)
744 .ok_or_else(|| {
745 ResolverError::ModuleNotFound {
748 path: absolute_imported_path.to_string_lossy().to_string(),
749 src: Arc::from(NamedSource::new(
750 file_path.to_string_lossy(),
751 source_text.to_string(),
752 )),
753 span: (value.pos_start, value.pos_end - value.pos_start)
754 .into(),
755 }
756 })?;
757
758 if let MonValueKind::Object(members) = &imported_doc.root.kind {
759 members.iter().find_map(|m| {
760 if let Member::TypeDefinition(td) = m {
761 if td.name == type_name_part {
762 return Some(td.def_type.clone());
763 }
764 }
765 None
766 })
767 } else {
768 None
769 }
770 } else {
771 self.symbol_table
772 .types
773 .get(type_name_part)
774 .map(|td| td.def_type.clone())
775 };
776
777 if let Some(type_def) = type_def {
778 match type_def {
779 TypeDef::Struct(struct_def) => {
780 if let MonValueKind::Object(value_members) = &mut value.kind {
782 let mut value_map: HashMap<String, &mut MonValue> =
783 HashMap::new();
784 for member in value_members.iter_mut() {
785 if let Member::Pair(pair) = member {
786 value_map.insert(pair.key.clone(), &mut pair.value);
787 }
788 }
789
790 let mut new_members = Vec::new();
791 for field_def in &struct_def.fields {
792 if let Some(field_value) =
793 value_map.get_mut(&field_def.name)
794 {
795 self.validate_value(
797 field_value,
798 &field_def.type_spec,
799 &field_def.name,
800 imports, file_path,
802 source_text,
803 )?;
804 } else {
805 if field_def.default_value.is_none() {
807 return Err(ResolverError::Validation(
808 ValidationError::MissingField {
809 field_name: field_def.name.clone(),
810 struct_name: type_name.clone(),
811 src: Arc::from(NamedSource::new(
812 file_path.to_string_lossy(),
813 source_text.to_string(),
814 )),
815 span: (
816 value.pos_start,
817 value.pos_end - value.pos_start,
818 )
819 .into(),
820 },
821 ));
822 }
823 if let Some(default_value) =
826 &field_def.default_value
827 {
828 new_members.push(Member::Pair(
829 crate::ast::Pair {
830 key: field_def.name.clone(),
831 value: default_value.clone(),
832 validation: None,
833 },
834 ));
835 }
836 }
837 }
838 value_members.extend(new_members);
839
840 for member in value_members.iter() {
842 if let Member::Pair(pair) = member {
843 if !struct_def
844 .fields
845 .iter()
846 .any(|f| f.name == pair.key)
847 {
848 return Err(ResolverError::Validation(
849 ValidationError::UnexpectedField {
850 field_name: pair.key.clone(),
851 struct_name: type_name.clone(),
852 src: Arc::from(NamedSource::new(
853 file_path.to_string_lossy(),
854 source_text.to_string(),
855 )),
856 span: (
857 value.pos_start,
858 value.pos_end - value.pos_start,
859 )
860 .into(),
861 },
862 ));
863 }
864 }
865 }
866 } else {
867 return Err(ResolverError::Validation(
868 ValidationError::TypeMismatch {
869 field_name: field_name.to_string(),
870 expected_type: type_name.clone(),
871 found_type: format!("{:?}", value.kind),
872 src: Arc::from(NamedSource::new(
873 file_path.to_string_lossy(),
874 source_text.to_string(),
875 )),
876 span: (
877 value.pos_start,
878 value.pos_end - value.pos_start,
879 )
880 .into(),
881 },
882 ));
883 }
884 }
885 TypeDef::Enum(enum_def) => {
886 if let MonValueKind::EnumValue {
888 enum_name,
889 variant_name,
890 } = &value.kind
891 {
892 if enum_name != type_name {
893 return Err(ResolverError::Validation(
894 ValidationError::TypeMismatch {
895 field_name: field_name.to_string(),
896 expected_type: format!("enum {}", type_name),
897 found_type: format!("enum {}", enum_name),
898 src: Arc::from(NamedSource::new(
899 file_path.to_string_lossy(),
900 source_text.to_string(),
901 )),
902 span: (
903 value.pos_start,
904 value.pos_end - value.pos_start,
905 )
906 .into(),
907 },
908 ));
909 }
910 if !enum_def.variants.contains(variant_name) {
911 return Err(ResolverError::Validation(
912 ValidationError::UndefinedEnumVariant {
913 variant_name: variant_name.clone(),
914 enum_name: type_name.clone(),
915 src: Arc::from(NamedSource::new(
916 file_path.to_string_lossy(),
917 source_text.to_string(),
918 )),
919 span: (
920 value.pos_start,
921 value.pos_end - value.pos_start,
922 )
923 .into(),
924 },
925 ));
926 }
927 } else {
928 return Err(ResolverError::Validation(
929 ValidationError::TypeMismatch {
930 field_name: field_name.to_string(),
931 expected_type: format!("enum {}", type_name),
932 found_type: format!("{:?}", value.kind),
933 src: Arc::from(NamedSource::new(
934 file_path.to_string_lossy(),
935 source_text.to_string(),
936 )),
937 span: (
938 value.pos_start,
939 value.pos_end - value.pos_start,
940 )
941 .into(),
942 },
943 ));
944 }
945 }
946 }
947 } else {
948 return Err(ResolverError::Validation(
949 ValidationError::UndefinedType {
950 type_name: type_name.clone(),
951 src: Arc::from(NamedSource::new(
952 file_path.to_string_lossy(),
953 source_text.to_string(),
954 )),
955 span: (value.pos_start, value.pos_end - value.pos_start).into(),
956 },
957 ));
958 }
959 }
960 }
961 }
962 TypeSpec::Collection(collection_types, _) => {
963 if let MonValueKind::Array(elements) = &mut value.kind {
965 self.validate_collection(
966 elements,
967 collection_types,
968 field_name,
969 imports, file_path,
971 source_text,
972 )?;
973 } else {
974 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
975 field_name: field_name.to_string(),
976 expected_type: "Array".to_string(),
977 found_type: format!("{:?}", value.kind),
978 src: Arc::from(NamedSource::new(
979 file_path.to_string_lossy(),
980 source_text.to_string(),
981 )),
982 span: (value.pos_start, value.pos_end - value.pos_start).into(),
983 }));
984 }
985 }
986 TypeSpec::Spread(_, _) => {
987 return Ok(());
989 }
990 }
991 Ok(())
992 }
993
994 fn validate_collection(
996 &mut self,
997 elements: &mut [MonValue],
998 collection_types: &[TypeSpec],
999 field_name: &str,
1000 imports: &[ImportStatement], file_path: &PathBuf,
1002 source_text: &str,
1003 ) -> Result<(), ResolverError> {
1004 if collection_types.len() == 1 && !matches!(collection_types[0], TypeSpec::Spread(_, _)) {
1006 self.validate_value(
1007 &mut elements[0],
1008 &collection_types[0],
1009 field_name,
1010 imports, file_path,
1012 source_text,
1013 )?;
1014 return Ok(());
1015 }
1016
1017 if collection_types.len() == 1 && matches!(collection_types[0], TypeSpec::Spread(_, _)) {
1019 if let TypeSpec::Spread(inner_type, _) = &collection_types[0] {
1020 for element in elements {
1021 self.validate_value(
1022 element,
1023 inner_type,
1024 field_name,
1025 imports,
1026 file_path,
1027 source_text,
1028 )?;
1029 }
1030 return Ok(());
1031 }
1032 }
1033
1034 let has_spread = collection_types
1036 .iter()
1037 .any(|t| matches!(t, TypeSpec::Spread(_, _)));
1038 if !has_spread {
1039 if elements.len() != collection_types.len() {
1040 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
1042 field_name: field_name.to_string(),
1043 expected_type: format!("tuple with {} elements", collection_types.len()),
1044 found_type: format!("tuple with {} elements", elements.len()),
1045 src: Arc::from(NamedSource::new(
1046 file_path.to_string_lossy(),
1047 source_text.to_string(),
1048 )),
1049 span: (
1050 elements.first().map_or(0, |e| e.pos_start),
1051 elements.last().map_or(0, |e| e.pos_end)
1052 - elements.first().map_or(0, |e| e.pos_start),
1053 )
1054 .into(),
1055 }));
1056 }
1057 for (i, element) in elements.iter_mut().enumerate() {
1058 self.validate_value(
1059 element,
1060 &collection_types[i],
1061 field_name,
1062 imports, file_path,
1064 source_text,
1065 )?;
1066 }
1067 return Ok(());
1068 }
1069
1070 if collection_types.len() == 2
1072 && !matches!(collection_types[0], TypeSpec::Spread(_, _))
1073 && matches!(collection_types[1], TypeSpec::Spread(_, _))
1074 {
1075 if elements.is_empty() {
1076 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
1077 field_name: field_name.to_string(),
1078 expected_type: "array with at least 1 element".to_string(),
1079 found_type: "empty array".to_string(),
1080 src: Arc::from(NamedSource::new(
1081 file_path.to_string_lossy(),
1082 source_text.to_string(),
1083 )),
1084 span: (
1085 elements.first().map_or(0, |e| e.pos_start),
1086 elements.last().map_or(0, |e| e.pos_end)
1087 - elements.first().map_or(0, |e| e.pos_start),
1088 )
1089 .into(),
1090 }));
1091 }
1092 self.validate_value(
1093 &mut elements[0],
1094 &collection_types[0],
1095 field_name,
1096 imports, file_path,
1098 source_text,
1099 )?;
1100 if let TypeSpec::Spread(inner_type, _) = &collection_types[1] {
1101 for element in &mut elements[1..] {
1102 self.validate_value(
1103 element,
1104 inner_type,
1105 field_name,
1106 imports,
1107 file_path,
1108 source_text,
1109 )?;
1110 }
1111 }
1112 return Ok(());
1113 }
1114
1115 if collection_types.len() == 2
1117 && matches!(collection_types[0], TypeSpec::Spread(_, _))
1118 && !matches!(collection_types[1], TypeSpec::Spread(_, _))
1119 {
1120 if elements.is_empty() {
1121 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
1122 field_name: field_name.to_string(),
1123 expected_type: "array with at least 1 element".to_string(),
1124 found_type: "empty array".to_string(),
1125 src: Arc::from(NamedSource::new(
1126 file_path.to_string_lossy(),
1127 source_text.to_string(),
1128 )),
1129 span: (
1130 elements.first().map_or(0, |e| e.pos_start),
1131 elements.last().map_or(0, |e| e.pos_end)
1132 - elements.first().map_or(0, |e| e.pos_start),
1133 )
1134 .into(),
1135 }));
1136 }
1137 let (head, last) = elements.split_at_mut(elements.len() - 1);
1138 self.validate_value(
1139 last.first_mut().unwrap(), &collection_types[1],
1141 field_name,
1142 imports, file_path,
1144 source_text,
1145 )?;
1146 if let TypeSpec::Spread(inner_type, _) = &collection_types[0] {
1147 for element in head {
1148 self.validate_value(
1149 element,
1150 inner_type,
1151 field_name,
1152 imports,
1153 file_path,
1154 source_text,
1155 )?;
1156 }
1157 }
1158 return Ok(());
1159 }
1160
1161 Err(ResolverError::Validation(
1163 ValidationError::UnimplementedCollectionValidation {
1164 field_name: field_name.to_string(),
1165 src: Arc::from(NamedSource::new(
1166 file_path.to_string_lossy(),
1167 source_text.to_string(),
1168 )),
1169 span: (
1170 elements.first().map_or(0, |e| e.pos_start),
1171 elements.last().map_or(0, |e| e.pos_end)
1172 - elements.first().map_or(0, |e| e.pos_start),
1173 )
1174 .into(),
1175 },
1176 ))
1177 }
1178}
1179
1180impl Default for Resolver {
1181 fn default() -> Self {
1182 Self::new()
1183 }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use crate::parser::Parser;
1189 use crate::resolver::Resolver;
1190 use miette::Report;
1191 use std::fs;
1192 use std::path::{Path, PathBuf};
1193
1194 fn resolve_ok(source: &str, file_name: &str) -> crate::ast::MonDocument {
1195 let mut parser = Parser::new_with_name(source, file_name.to_string()).unwrap();
1196 let document = parser.parse_document().unwrap();
1197 let mut resolver = Resolver::new();
1198 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1199 path.push(file_name);
1200 match resolver.resolve(document, source, path, None) {
1201 Ok(doc) => doc,
1202 Err(err) => {
1203 let report = Report::from(err);
1204 panic!("{report:#}");
1205 }
1206 }
1207 }
1208
1209 fn resolve_err(source: &str, file_name: &str) -> crate::error::ResolverError {
1210 let mut parser = Parser::new_with_name(source, file_name.to_string()).unwrap();
1211 let document = parser.parse_document().unwrap();
1212 let mut resolver = Resolver::new();
1213 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1214 path.push(file_name);
1215 match resolver.resolve(document, source, path, None) {
1216 Ok(_) => panic!("Expected a ResolverError, but got Ok"),
1217 Err(err) => err,
1218 }
1219 }
1220
1221 #[test]
1222 fn test_simple_alias_resolution() {
1223 let source = r"{ &my_value: 123, alias_value: *my_value }";
1224 let doc = resolve_ok(source, "test.mon");
1225
1226 let root_object = match doc.root.kind {
1227 crate::ast::MonValueKind::Object(members) => members,
1228 _ => panic!("Expected an object"),
1229 };
1230
1231 let alias_member = root_object
1233 .iter()
1234 .find(|m| {
1235 if let crate::ast::Member::Pair(p) = m {
1236 p.key == "alias_value"
1237 } else {
1238 false
1239 }
1240 })
1241 .unwrap();
1242
1243 if let crate::ast::Member::Pair(p) = alias_member {
1244 assert_eq!(p.value.kind, crate::ast::MonValueKind::Number(123.0));
1245 } else {
1246 panic!("Expected a pair member");
1247 }
1248 }
1249
1250 #[test]
1251 fn test_object_spread_resolution() {
1252 let source = r#"{
1253 &base_config: { host: "localhost", port: 8080 },
1254 app_config: {
1255 ...*base_config,
1256 port: 9000, // Override
1257 debug: true,
1258 }
1259 }"#;
1260 let doc = resolve_ok(source, "test.mon");
1261
1262 let root_object = match doc.root.kind {
1263 crate::ast::MonValueKind::Object(members) => members,
1264 _ => panic!("Expected an object"),
1265 };
1266
1267 let app_config_member = root_object
1268 .iter()
1269 .find(|m| {
1270 if let crate::ast::Member::Pair(p) = m {
1271 p.key == "app_config"
1272 } else {
1273 false
1274 }
1275 })
1276 .unwrap();
1277
1278 if let crate::ast::Member::Pair(p) = app_config_member {
1279 let app_config_object = match &p.value.kind {
1280 crate::ast::MonValueKind::Object(members) => members,
1281 _ => panic!("Expected app_config to be an object"),
1282 };
1283
1284 let host_member = app_config_object
1286 .iter()
1287 .find(|m| {
1288 if let crate::ast::Member::Pair(p) = m {
1289 p.key == "host"
1290 } else {
1291 false
1292 }
1293 })
1294 .unwrap();
1295 if let crate::ast::Member::Pair(p) = host_member {
1296 assert_eq!(
1297 p.value.kind,
1298 crate::ast::MonValueKind::String("localhost".to_string())
1299 );
1300 } else {
1301 panic!("Expected host to be a pair");
1302 }
1303
1304 let port_member = app_config_object
1306 .iter()
1307 .find(|m| {
1308 if let crate::ast::Member::Pair(p) = m {
1309 p.key == "port"
1310 } else {
1311 false
1312 }
1313 })
1314 .unwrap();
1315 if let crate::ast::Member::Pair(p) = port_member {
1316 assert_eq!(p.value.kind, crate::ast::MonValueKind::Number(9000.0));
1317 } else {
1318 panic!("Expected port to be a pair");
1319 }
1320
1321 let debug_member = app_config_object
1323 .iter()
1324 .find(|m| {
1325 if let crate::ast::Member::Pair(p) = m {
1326 p.key == "debug"
1327 } else {
1328 false
1329 }
1330 })
1331 .unwrap();
1332 if let crate::ast::Member::Pair(p) = debug_member {
1333 assert_eq!(p.value.kind, crate::ast::MonValueKind::Boolean(true));
1334 } else {
1335 panic!("Expected debug to be a pair");
1336 }
1337 } else {
1338 panic!("Expected app_config to be a pair member");
1339 }
1340 }
1341
1342 #[test]
1343 fn test_array_spread_resolution() {
1344 let source = r#"{
1345 &base_tags: ["tag1", "tag2"],
1346 item_tags: [
1347 "start",
1348 ...*base_tags,
1349 "end",
1350 ]
1351 }"#;
1352 let doc = resolve_ok(source, "test.mon");
1353
1354 let root_object = match doc.root.kind {
1355 crate::ast::MonValueKind::Object(members) => members,
1356 _ => panic!("Expected an object"),
1357 };
1358
1359 let item_tags_member = root_object
1360 .iter()
1361 .find(|m| {
1362 if let crate::ast::Member::Pair(p) = m {
1363 p.key == "item_tags"
1364 } else {
1365 false
1366 }
1367 })
1368 .unwrap();
1369
1370 if let crate::ast::Member::Pair(p) = item_tags_member {
1371 let item_tags_array = match &p.value.kind {
1372 crate::ast::MonValueKind::Array(elements) => elements,
1373 _ => panic!("Expected item_tags to be an array"),
1374 };
1375
1376 assert_eq!(item_tags_array.len(), 4);
1377 assert_eq!(
1378 item_tags_array[0].kind,
1379 crate::ast::MonValueKind::String("start".to_string())
1380 );
1381 assert_eq!(
1382 item_tags_array[1].kind,
1383 crate::ast::MonValueKind::String("tag1".to_string())
1384 );
1385 assert_eq!(
1386 item_tags_array[2].kind,
1387 crate::ast::MonValueKind::String("tag2".to_string())
1388 );
1389 assert_eq!(
1390 item_tags_array[3].kind,
1391 crate::ast::MonValueKind::String("end".to_string())
1392 );
1393 } else {
1394 panic!("Expected item_tags to be a pair member");
1395 }
1396 }
1397
1398 #[test]
1399 fn test_struct_validation_with_defaults_and_collections_ok() {
1400 let source = r###"
1401 {
1402 User: #struct {
1403 id(Number),
1404 name(String),
1405 email(String) = "default@example.com",
1406 is_active(Boolean) = true,
1407 roles([String...]),
1408 permissions([String, Number]),
1409 log_data([String, Any...]),
1410 status_history([Boolean..., String]),
1411 },
1412
1413 // Valid user with defaults
1414 user1 :: User = {
1415 id: 1,
1416 name: "Alice",
1417 roles: ["admin", "editor"],
1418 permissions: ["read", 1],
1419 log_data: ["login", { timestamp: "...", ip: "..." }],
1420 status_history: [true, false, "active"],
1421 },
1422
1423 // Valid user, omitting optional fields
1424 user2 :: User = {
1425 id: 2,
1426 name: "Bob",
1427 roles: [],
1428 permissions: ["write", 2],
1429 log_data: ["logout"],
1430 status_history: ["inactive"],
1431 },
1432 }
1433 "###;
1434
1435 let doc = resolve_ok(source, "test_validation.mon");
1437 let root_object = match doc.root.kind {
1438 crate::ast::MonValueKind::Object(members) => members,
1439 _ => panic!("Expected an object"),
1440 };
1441
1442 let user1_member = root_object
1443 .iter()
1444 .find(|m| {
1445 if let crate::ast::Member::Pair(p) = m {
1446 p.key == "user1"
1447 } else {
1448 false
1449 }
1450 })
1451 .unwrap();
1452
1453 if let crate::ast::Member::Pair(p) = user1_member {
1454 let user1_object = match &p.value.kind {
1455 crate::ast::MonValueKind::Object(members) => members,
1456 _ => panic!("Expected user1 to be an object"),
1457 };
1458
1459 let email_member = user1_object
1461 .iter()
1462 .find(|m| {
1463 if let crate::ast::Member::Pair(p) = m {
1464 p.key == "email"
1465 } else {
1466 false
1467 }
1468 })
1469 .unwrap();
1470 if let crate::ast::Member::Pair(p) = email_member {
1471 assert_eq!(
1472 p.value.kind,
1473 crate::ast::MonValueKind::String("default@example.com".to_string())
1474 );
1475 } else {
1476 panic!("Expected email to be a pair");
1477 }
1478
1479 let is_active_member = user1_object
1481 .iter()
1482 .find(|m| {
1483 if let crate::ast::Member::Pair(p) = m {
1484 p.key == "is_active"
1485 } else {
1486 false
1487 }
1488 })
1489 .unwrap();
1490 if let crate::ast::Member::Pair(p) = is_active_member {
1491 assert_eq!(p.value.kind, crate::ast::MonValueKind::Boolean(true));
1492 } else {
1493 panic!("Expected is_active to be a pair");
1494 }
1495 } else {
1496 panic!("Expected user1 to be a pair member");
1497 }
1498 }
1499
1500 #[test]
1501 fn test_struct_validation_missing_required_field() {
1502 let source = r###"
1503 {
1504 User: #struct { id(Number), name(String) },
1505 invalid_user :: User = { id: 3 },
1506 }
1507 "###;
1508 let err = resolve_err(source, "test_validation.mon");
1509 match err {
1510 crate::error::ResolverError::Validation(
1511 crate::error::ValidationError::MissingField { field_name, .. },
1512 ) => {
1513 assert_eq!(field_name, "name");
1514 }
1515 _ => panic!("Expected MissingField error, but got {err:?}"),
1516 }
1517 }
1518
1519 #[test]
1520 fn test_struct_validation_wrong_id_type() {
1521 let source = r###"
1522 {
1523 User: #struct { id(Number), name(String) },
1524 invalid_user :: User = { id: "four", name: "Charlie" },
1525 }
1526 "###;
1527 let err = resolve_err(source, "test_validation.mon");
1528 match err {
1529 crate::error::ResolverError::Validation(
1530 crate::error::ValidationError::TypeMismatch {
1531 field_name,
1532 expected_type,
1533 found_type,
1534 ..
1535 },
1536 ) => {
1537 assert_eq!(field_name, "id");
1538 assert_eq!(expected_type, "Number");
1539 assert!(found_type.contains("String"));
1540 }
1541 _ => panic!("Expected TypeMismatch error for id, but got {err:?}"),
1542 }
1543 }
1544
1545 #[test]
1546 fn test_struct_validation_unexpected_field() {
1547 let source = r###"
1548 {
1549 User: #struct { id(Number), name(String) },
1550 invalid_user :: User = { id: 5, name: "David", age: 30 },
1551 }
1552 "###;
1553 let err = resolve_err(source, "test_validation.mon");
1554 match err {
1555 crate::error::ResolverError::Validation(
1556 crate::error::ValidationError::UnexpectedField { field_name, .. },
1557 ) => {
1558 assert_eq!(field_name, "age");
1559 }
1560 _ => panic!("Expected UnexpectedField error, but got {err:?}"),
1561 }
1562 }
1563
1564 #[test]
1565 fn test_struct_validation_roles_type_mismatch() {
1566 let source = r###"
1567 {
1568 User: #struct { roles([String...]) },
1569 invalid_user :: User = { roles: ["viewer", 123] },
1570 }
1571 "###;
1572 let err = resolve_err(source, "test_validation.mon");
1573 match err {
1574 crate::error::ResolverError::Validation(
1575 crate::error::ValidationError::TypeMismatch {
1576 field_name,
1577 expected_type,
1578 found_type,
1579 ..
1580 },
1581 ) => {
1582 assert_eq!(field_name, "roles");
1583 assert_eq!(expected_type, "String");
1584 assert!(found_type.contains("Number"));
1585 }
1586 _ => panic!("Expected TypeMismatch error for roles, but got {err:?}"),
1587 }
1588 }
1589
1590 #[test]
1591 fn test_struct_validation_permissions_length_mismatch() {
1592 let source = r###"
1593 {
1594 User: #struct { permissions([String, Number]) },
1595 invalid_user :: User = { permissions: ["read"] },
1596 }
1597 "###;
1598 let err = resolve_err(source, "test_validation.mon");
1599 match err {
1600 crate::error::ResolverError::Validation(
1601 crate::error::ValidationError::TypeMismatch {
1602 field_name,
1603 expected_type,
1604 found_type,
1605 ..
1606 },
1607 ) => {
1608 assert_eq!(field_name, "permissions");
1609 assert!(expected_type.contains("tuple with 2 elements"));
1610 assert!(found_type.contains("tuple with 1 elements"));
1611 }
1612 _ => panic!("Expected TypeMismatch error for permissions length, but got {err:?}"),
1613 }
1614 }
1615
1616 #[test]
1617 fn test_struct_validation_permissions_type_mismatch() {
1618 let source = r###"
1619 {
1620 User: #struct { permissions([String, Number]) },
1621 invalid_user :: User = { permissions: [8, "write"] },
1622 }
1623 "###;
1624 let err = resolve_err(source, "test_validation.mon");
1625 match err {
1626 crate::error::ResolverError::Validation(
1627 crate::error::ValidationError::TypeMismatch {
1628 field_name,
1629 expected_type,
1630 found_type,
1631 ..
1632 },
1633 ) => {
1634 assert_eq!(field_name, "permissions");
1635 assert_eq!(expected_type, "String");
1636 assert!(found_type.contains("Number"));
1637 }
1638 _ => panic!("Expected TypeMismatch error for permissions types, but got {err:?}"),
1639 }
1640 }
1641
1642 #[test]
1643 fn test_struct_validation_log_data_first_type_mismatch() {
1644 let source = r###"
1645 {
1646 User: #struct { log_data([String, Any...]) },
1647 invalid_user :: User = { log_data: [123, "event"] },
1648 }
1649 "###;
1650 let err = resolve_err(source, "test_validation.mon");
1651 match err {
1652 crate::error::ResolverError::Validation(
1653 crate::error::ValidationError::TypeMismatch {
1654 field_name,
1655 expected_type,
1656 found_type,
1657 ..
1658 },
1659 ) => {
1660 assert_eq!(field_name, "log_data");
1661 assert_eq!(expected_type, "String");
1662 assert!(found_type.contains("Number"));
1663 }
1664 _ => panic!("Expected TypeMismatch error for log_data first type, but got {err:?}"),
1665 }
1666 }
1667
1668 #[test]
1669 fn test_struct_validation_status_history_last_type_mismatch() {
1670 let source = r###"
1671 {
1672 User: #struct { status_history([Boolean..., String]) },
1673 invalid_user :: User = { status_history: [true, 123] },
1674 }
1675 "###;
1676 let err = resolve_err(source, "test_validation.mon");
1677 match err {
1678 crate::error::ResolverError::Validation(
1679 crate::error::ValidationError::TypeMismatch {
1680 field_name,
1681 expected_type,
1682 found_type,
1683 ..
1684 },
1685 ) => {
1686 assert_eq!(field_name, "status_history");
1687 assert_eq!(expected_type, "String");
1688 assert!(found_type.contains("Number"));
1689 }
1690 _ => {
1691 panic!("Expected TypeMismatch error for status_history last type, but got {err:?}")
1692 }
1693 }
1694 }
1695
1696 #[test]
1697 fn test_nested_struct_validation_ok() {
1698 let source = r###"
1699 {
1700 Profile: #struct {
1701 username(String),
1702 email(String),
1703 },
1704 User: #struct {
1705 id(Number),
1706 profile(Profile),
1707 },
1708
1709 // Valid nested struct
1710 user1 :: User = {
1711 id: 1,
1712 profile: {
1713 username: "alice",
1714 email: "alice@example.com",
1715 },
1716 },
1717 }
1718 "###;
1719
1720 resolve_ok(source, "test_nested_ok.mon");
1721 }
1722
1723 #[test]
1724 fn test_nested_struct_validation_err() {
1725 let source = r###"
1726 {
1727 Profile: #struct {
1728 username(String),
1729 email(String),
1730 },
1731 User: #struct {
1732 id(Number),
1733 profile(Profile),
1734 },
1735
1736 // Invalid: Nested struct has wrong type for username
1737 user2 :: User = {
1738 id: 2,
1739 profile: {
1740 username: 123,
1741 email: "bob@example.com",
1742 },
1743 },
1744 }
1745 "###;
1746
1747 let err = resolve_err(source, "test_nested_err.mon");
1748 match err {
1749 crate::error::ResolverError::Validation(
1750 crate::error::ValidationError::TypeMismatch {
1751 field_name,
1752 expected_type,
1753 found_type,
1754 ..
1755 },
1756 ) => {
1757 assert_eq!(field_name, "username");
1758 assert_eq!(expected_type, "String");
1759 assert!(found_type.contains("Number"));
1760 }
1761 _ => panic!("Expected TypeMismatch error for username, but got {err:?}"),
1762 }
1763 }
1764
1765 #[test]
1766 fn test_cross_file_validation() {
1767 let source = fs::read_to_string("tests/cross_file_main.mon").unwrap();
1768 resolve_ok(&source, "tests/cross_file_main.mon");
1769 }
1770
1771 #[test]
1772 fn test_parser_for_schemas_file() {
1773 let source = fs::read_to_string("tests/cross_file_schemas.mon").unwrap();
1774 let mut parser = Parser::new_with_name(&source, "test.mon".to_string()).unwrap();
1775 let _ = parser.parse_document().unwrap();
1776 }
1777
1778 #[test]
1779 fn test_named_import_validation() {
1780 let source = fs::read_to_string("tests/named_import_main.mon").unwrap();
1781 resolve_ok(&source, "tests/named_import_main.mon");
1782 }
1783
1784 use super::*;
1785 use tempfile::TempDir;
1786 fn test_resolver_with_builtin(builtin_path: PathBuf) -> Resolver {
1788 Resolver::with_builtin_path(builtin_path)
1789 }
1790 fn create_test_file(dir: &Path, name: &str, content: &str) -> PathBuf {
1792 let path = dir.join(name);
1793 fs::write(&path, content).unwrap();
1794 path
1795 }
1796 #[test]
1797 fn test_mon_uri_resolution() {
1798 let temp_dir = TempDir::new().unwrap();
1799 let builtin_path = temp_dir.path().join("schemas");
1800 fs::create_dir_all(builtin_path.join("types")).unwrap();
1801 create_test_file(
1803 &builtin_path.join("types"),
1804 "linter.mon",
1805 r#"{
1806 LintConfig: #struct {
1807 max_depth(Number) = 5
1808 }
1809 }"#,
1810 );
1811 let resolver = test_resolver_with_builtin(builtin_path.clone());
1812 let resolved = resolver.resolve_import_path("mon:types/linter", Path::new("."));
1814 assert_eq!(resolved, builtin_path.join("types/linter.mon"));
1815 }
1816 #[test]
1817 fn test_relative_path_unchanged() {
1818 let resolver = Resolver::new();
1819 let current_dir = Path::new("/project/src");
1820 let resolved = resolver.resolve_import_path("./config.mon", current_dir);
1822 assert_eq!(resolved, PathBuf::from("/project/src/./config.mon"));
1823 let resolved = resolver.resolve_import_path("../shared/types.mon", current_dir);
1824 assert_eq!(resolved, PathBuf::from("/project/src/../shared/types.mon"));
1825 }
1826 #[test]
1827 fn test_absolute_path_preserved() {
1828 let resolver = Resolver::new();
1829 let current_dir = Path::new("/project");
1830 let resolved = resolver.resolve_import_path("/usr/share/schemas/common.mon", current_dir);
1831 assert_eq!(resolved, PathBuf::from("/usr/share/schemas/common.mon"));
1832 }
1833 #[test]
1834 fn test_mon_uri_nested_paths() {
1835 let temp_dir = TempDir::new().unwrap();
1836 let builtin_path = temp_dir.path().join("schemas");
1837 fs::create_dir_all(builtin_path.join("types/database")).unwrap();
1838 let resolver = test_resolver_with_builtin(builtin_path.clone());
1839 let resolved = resolver.resolve_import_path("mon:types/database/postgres", Path::new("."));
1841 assert_eq!(resolved, builtin_path.join("types/database/postgres.mon"));
1842 }
1843 #[test]
1844 fn test_mon_uri_with_full_resolution() {
1845 let temp_dir = TempDir::new().unwrap();
1846 let builtin_path = temp_dir.path().join("schemas");
1847 fs::create_dir_all(builtin_path.join("types")).unwrap();
1848 let schema_content = r#"{
1850 TestType: #struct {
1851 field(String)
1852 }
1853 }"#;
1854 create_test_file(&builtin_path.join("types"), "test.mon", schema_content);
1855 let main_content = r###"
1857 import { TestType } from "mon:types/test"
1858
1859 {
1860 value :: TestType = { field: "hello" }
1861 }
1862 "###;
1863 let main_path = create_test_file(temp_dir.path(), "main.mon", main_content);
1864 let mut parser = crate::parser::Parser::new_with_name(
1866 main_content,
1867 main_path.to_string_lossy().to_string(),
1868 )
1869 .unwrap();
1870 let doc = parser.parse_document().unwrap();
1871 let mut resolver = test_resolver_with_builtin(builtin_path);
1872 let result = resolver.resolve(doc, main_content, main_path.clone(), None);
1873 assert!(result.is_ok(), "Resolution should succeed");
1874
1875 assert!(resolver.symbol_table.types.contains_key("TestType"));
1877 }
1878 #[test]
1879 fn test_mixed_imports() {
1880 let temp_dir = TempDir::new().unwrap();
1881 let builtin_path = temp_dir.path().join("schemas");
1882 fs::create_dir_all(builtin_path.join("types")).unwrap();
1883 create_test_file(
1885 &builtin_path.join("types"),
1886 "builtin.mon",
1887 r#"{ BuiltinType: #struct { id(Number) } }"#,
1888 );
1889 create_test_file(
1891 temp_dir.path(),
1892 "local.mon",
1893 r#"{ LocalType: #struct { name(String) } }"#,
1894 );
1895 let main_content = r###"
1897 import { BuiltinType } from "mon:types/builtin"
1898 import { LocalType } from "./local.mon"
1899
1900 {
1901 builtin :: BuiltinType = { id: 1 },
1902 local :: LocalType = { name: "test" }
1903 }
1904 "###;
1905 let main_path = create_test_file(temp_dir.path(), "main.mon", main_content);
1906 let mut parser = crate::parser::Parser::new_with_name(
1907 main_content,
1908 main_path.to_string_lossy().to_string(),
1909 )
1910 .unwrap();
1911 let doc = parser.parse_document().unwrap();
1912 let mut resolver = test_resolver_with_builtin(builtin_path);
1913 let result = resolver.resolve(doc, main_content, main_path, None);
1914 assert!(result.is_ok());
1915 assert!(resolver.symbol_table.types.contains_key("BuiltinType"));
1916 assert!(resolver.symbol_table.types.contains_key("LocalType"));
1917 }
1918 #[test]
1919 fn test_mon_uri_not_found() {
1920 let temp_dir = TempDir::new().unwrap();
1921 let builtin_path = temp_dir.path().join("schemas");
1922 fs::create_dir_all(&builtin_path).unwrap();
1923 let main_content = r###"
1924 import { Missing } from "mon:types/nonexistent"
1925 {}
1926 "###;
1927 let main_path = create_test_file(temp_dir.path(), "main.mon", main_content);
1928 let mut parser = crate::parser::Parser::new_with_name(
1929 main_content,
1930 main_path.to_string_lossy().to_string(),
1931 )
1932 .unwrap();
1933 let doc = parser.parse_document().unwrap();
1934 let mut resolver = test_resolver_with_builtin(builtin_path);
1935 let result = resolver.resolve(doc, main_content, main_path, None);
1936 assert!(matches!(result, Err(ResolverError::ModuleNotFound { .. })))
1937 }
1938 #[test]
1939 fn test_default_builtin_path() {
1940 let path = Resolver::default_builtin_path();
1942 assert!(path.is_absolute() || path == Path::new("."));
1943 }
1944 #[test]
1945 fn test_mon_uri_auto_adds_extension() {
1946 let temp_dir = TempDir::new().unwrap();
1947 let builtin_path = temp_dir.path().join("schemas");
1948
1949 let resolver = test_resolver_with_builtin(builtin_path.clone());
1950 let resolved = resolver.resolve_import_path("mon:config/app", Path::new("."));
1952 assert_eq!(resolved, builtin_path.join("config/app.mon"));
1953 assert!(resolved.to_string_lossy().ends_with(".mon"));
1954 }
1955 #[test]
1956 fn test_environment_variable_override() {
1957 std::env::set_var("MON_BUILTIN_PATH", "/custom/builtin/path");
1959
1960 let path = Resolver::default_builtin_path();
1961
1962 std::env::remove_var("MON_BUILTIN_PATH");
1964
1965 assert_eq!(path, PathBuf::from("/custom/builtin/path"));
1966 }
1967 #[test]
1968 fn test_circular_dependency_with_mon_imports() {
1969 let temp_dir = TempDir::new().unwrap();
1970 let builtin_path = temp_dir.path().join("schemas");
1971 fs::create_dir_all(&builtin_path).unwrap();
1972 create_test_file(
1974 &builtin_path,
1975 "a.mon",
1976 r###"import { B } from "mon:b" { A: #struct { b(B) } }"###,
1977 );
1978 create_test_file(
1979 &builtin_path,
1980 "b.mon",
1981 r###"import { A } from "mon:a" { B: #struct { a(A) } }"###,
1982 );
1983 let main_content = r###"import { A } from "mon:a" {}"###;
1984 let main_path = create_test_file(temp_dir.path(), "main.mon", main_content);
1985 let mut parser = crate::parser::Parser::new_with_name(
1986 main_content,
1987 main_path.to_string_lossy().to_string(),
1988 )
1989 .unwrap();
1990 let doc = parser.parse_document().unwrap();
1991 let mut resolver = test_resolver_with_builtin(builtin_path);
1992 let result = resolver.resolve(doc, main_content, main_path, None);
1993 assert!(matches!(
1994 result,
1995 Err(ResolverError::CircularDependency { .. })
1996 ));
1997 }
1998}