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}