misty_parser/validator/workspace.rs
1//! The Workspace module contains the logic to build a Misty workspace for validation purposes.
2//!
3//! The validation method uses a fail-fast strategy instead of returning all cumulative errors, this
4//! is simply to reduce complexity. It's not a hard requirement and can be improved later.
5//!
6//! As `package-remote` modules are not yet here, we only validate against `package-local` modules.
7use crate::ParserError;
8use crate::validator::ValidationError;
9use crate::validator::imports::attest_imports;
10use crate::validator::type_resolver::resolve_data_type;
11use misty_ast::{Definition, File};
12use std::collections::HashMap;
13
14/// The project workspace.
15///
16/// It contains all modules imported and validates them against each other.
17pub struct Workspace {
18 /// Modules found in the current workspace.
19 package_local_modules: HashMap<String, File>,
20
21 /// Flag that marks this Workspace as validated.
22 validated: bool,
23}
24
25impl Default for Workspace {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl Workspace {
32 /// Creates a new, empty workspace.
33 pub fn new() -> Self {
34 Self {
35 package_local_modules: HashMap::new(),
36 validated: false,
37 }
38 }
39
40 /// Adds a new local module to the workspace.
41 pub fn add_local_module(&mut self, module_path: &str, module: File) {
42 self.package_local_modules
43 .insert(module_path.to_string(), module);
44 }
45
46 /// Validates all modules in the workspace.
47 ///
48 /// This function is fail-fast. Meaning, it will fail at each first error it founds.
49 #[tracing::instrument(skip(self))]
50 pub fn validate(&mut self) -> Result<(), ParserError> {
51 for file in self.package_local_modules.values() {
52 self.validate_file(file)?;
53 }
54
55 self.validated = true;
56 Ok(())
57 }
58
59 /// Validates a single file.
60 ///
61 /// This function is fail-fast. It will return the first error it finds as soon as it does.
62 #[tracing::instrument(skip(self, file))]
63 fn validate_file(&self, file: &File) -> Result<(), ValidationError> {
64 // Run a fast check in the imports of the file to attest that at the very least, the module
65 // is in scope.
66 attest_imports(&file.imports, &self.package_local_modules)?;
67
68 // Validate all the definitions in the file.
69 for definition in &file.definitions {
70 match definition {
71 // Enums do not reference a type, so they are skipped from validation.
72 Definition::Enum(_) => (),
73 Definition::Interface(interface) => {
74 for function in &interface.functions {
75 // Resolve the input argument type.
76 resolve_data_type(&self.package_local_modules, file, &function.input.1)?;
77
78 // Resolve the output argument type.
79 if let Some((_, argument)) = &function.output {
80 resolve_data_type(&self.package_local_modules, file, argument)?;
81 }
82 }
83 }
84 Definition::Schema(schema) => {
85 for field in &schema.fields {
86 resolve_data_type(&self.package_local_modules, file, &field.field_type)?;
87 }
88 }
89 }
90 }
91
92 Ok(())
93 }
94
95 /// Gets a reference to the local modules in the workspace.
96 pub fn package_local_modules(&self) -> &HashMap<String, File> {
97 &self.package_local_modules
98 }
99
100 /// Checks if the workspace has been validated.
101 pub fn validated(&self) -> bool {
102 self.validated
103 }
104}