Skip to main content

aam_core/pipeline/
tasks.rs

1//! Task enums and lazy evaluation structures for Command Pattern implementation.
2//!
3//! This module defines the declarative task types that replace direct AAML mutations.
4//! Tasks are generated by Validator, Parser, and other stages, then executed by
5//! dedicated executors (`ValidateExecutor`, `ParserExecutor`, Executer).
6
7use crate::pipeline::parser::ValueNode;
8
9/// A declarative validation task generated by the Validator.
10///
11/// Validation tasks represent specific checks that need to be performed on AST nodes.
12/// They are generated during the validation phase and executed by `ValidateExecutor`.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ValidationTask<'a> {
15    /// Check if a value matches a registered type's constraints.
16    ///
17    /// # Fields
18    /// - `key`: The configuration key being validated
19    /// - `value`: The value to validate
20    /// - `type_name`: Name of the type to validate against (e.g., "i32", "string", "vector2")
21    /// - `line`: Source line number for diagnostics
22    CheckTypeMatch {
23        key: std::borrow::Cow<'a, str>,
24        value: std::borrow::Cow<'a, str>,
25        type_name: std::borrow::Cow<'a, str>,
26        line: usize,
27    },
28
29    /// Verify that a schema referenced in the configuration is defined.
30    ///
31    /// # Fields
32    /// - `schema_name`: Name of the schema to check
33    /// - `line`: Source line number
34    VerifySchemaExists {
35        schema_name: std::borrow::Cow<'a, str>,
36        line: usize,
37    },
38
39    /// Verify that a file referenced by an import directive exists.
40    ///
41    /// # Fields
42    /// - `path`: Path to the file
43    /// - `line`: Source line number
44    VerifyFileExists {
45        path: std::borrow::Cow<'a, str>,
46        line: usize,
47    },
48
49    /// Validate that an assignment complies with a registered schema.
50    ///
51    /// # Fields
52    /// - `schema_name`: Name of the schema to validate against
53    /// - `key`: Key being validated
54    /// - `value`: Value being validated
55    /// - `line`: Source line number
56    ValidateAgainstSchema {
57        schema_name: std::borrow::Cow<'a, str>,
58        key: std::borrow::Cow<'a, str>,
59        value: std::borrow::Cow<'a, str>,
60        line: usize,
61    },
62
63    /// Check that all required fields of a schema are present.
64    ///
65    /// # Fields
66    /// - `schema_name`: Schema name
67    /// - `missing_fields`: Required fields not found
68    /// - `line`: Source line number
69    CheckSchemaCompleteness {
70        schema_name: std::borrow::Cow<'a, str>,
71        missing_fields: Vec<std::borrow::Cow<'a, str>>,
72        line: usize,
73    },
74
75    /// Verify no circular references in lookup chains.
76    ///
77    /// # Fields
78    /// - `key`: Key being looked up
79    /// - `line`: Source line number
80    CheckNoCircularReference {
81        key: std::borrow::Cow<'a, str>,
82        line: usize,
83    },
84
85    /// Verify that an object deriving from schemas implements all required fields.
86    ///
87    /// # Fields
88    /// - `derive_path`: Derivation path (`file.aam::Schema1::Schema2`)
89    /// - `current_key`: The context key doing the derivation
90    /// - `line`: Source line number
91    CheckDeriveCompleteness {
92        derive_path: std::borrow::Cow<'a, str>,
93        current_key: std::borrow::Cow<'a, str>,
94        line: usize,
95    },
96
97    /// Validate an inline list matches expected element types.
98    ///
99    /// # Fields
100    /// - `key`: Configuration key
101    /// - `items`: List items as strings
102    /// - `element_type`: Expected type of each element
103    /// - `line`: Source line number
104    ValidateListElements {
105        key: std::borrow::Cow<'a, str>,
106        items: std::sync::Arc<[ValueNode<'a>]>,
107        element_type: std::borrow::Cow<'a, str>,
108        line: usize,
109    },
110
111    /// Validate an inline object's keys and values.
112    ///
113    /// # Fields
114    /// - `key`: Configuration key
115    /// - `pairs`: Key-value pairs in the object
116    /// - `line`: Source line number
117    ValidateObjectStructure {
118        key: std::borrow::Cow<'a, str>,
119        pairs: std::sync::Arc<[(std::borrow::Cow<'a, str>, ValueNode<'a>)]>,
120        line: usize,
121    },
122}
123
124impl ValidationTask<'_> {
125    /// Returns the line number associated with this task for error reporting.
126    #[must_use]
127    pub const fn line(&self) -> usize {
128        match self {
129            ValidationTask::CheckTypeMatch { line, .. }
130            | ValidationTask::VerifySchemaExists { line, .. }
131            | ValidationTask::VerifyFileExists { line, .. }
132            | ValidationTask::ValidateAgainstSchema { line, .. }
133            | ValidationTask::CheckSchemaCompleteness { line, .. }
134            | ValidationTask::CheckDeriveCompleteness { line, .. }
135            | ValidationTask::CheckNoCircularReference { line, .. }
136            | ValidationTask::ValidateListElements { line, .. }
137            | ValidationTask::ValidateObjectStructure { line, .. } => *line,
138        }
139    }
140
141    /// Returns a human-readable description of this task.
142    #[must_use]
143    pub fn description(&self) -> String {
144        match self {
145            ValidationTask::CheckTypeMatch { type_name, .. } => {
146                format!("Check type match against '{type_name}'")
147            }
148            ValidationTask::VerifySchemaExists { schema_name, .. } => {
149                format!("Verify schema '{schema_name}' exists")
150            }
151            ValidationTask::VerifyFileExists { path, .. } => {
152                format!("Verify file '{path}' exists")
153            }
154            ValidationTask::ValidateAgainstSchema { schema_name, .. } => {
155                format!("Validate against schema '{schema_name}'")
156            }
157            ValidationTask::CheckSchemaCompleteness { schema_name, .. } => {
158                format!("Check completeness of schema '{schema_name}'")
159            }
160            ValidationTask::CheckDeriveCompleteness { derive_path, .. } => {
161                format!("Check derive completeness for '{derive_path}'")
162            }
163            ValidationTask::CheckNoCircularReference { .. } => {
164                "Check for circular references".to_string()
165            }
166            ValidationTask::ValidateListElements { element_type, .. } => {
167                format!("Validate list elements are type '{element_type}'")
168            }
169            ValidationTask::ValidateObjectStructure { .. } => {
170                "Validate inline object structure".to_string()
171            }
172        }
173    }
174}
175
176/// A declarative parsing task generated by the Parser.
177///
178/// Parse tasks represent operations that need to be performed on parsed modules,
179/// variables, and scopes. They enable deferred processing and analysis.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub enum ParseTask<'a> {
182    /// Resolve a module reference in a scope.
183    ///
184    /// # Fields
185    /// - `module_name`: Name of the module to resolve
186    /// - `scope`: Current scope path
187    /// - `line`: Source line number
188    ResolveModuleReference {
189        module_name: std::borrow::Cow<'a, str>,
190        scope: std::borrow::Cow<'a, str>,
191        line: usize,
192    },
193
194    /// Resolve an imported file and apply its types and schemas to the context.
195    ///
196    /// # Fields
197    /// - `derive_path`: Raw derive path (e.g. `file.aam::SchemaName`)
198    /// - `line`: Source line number
199    ResolveDeriveImport {
200        derive_path: std::borrow::Cow<'a, str>,
201        line: usize,
202    },
203
204    /// Process variable assignments in a scope.
205    ///
206    /// # Fields
207    /// - `variable_name`: Name of the variable
208    /// - `value`: Variable value
209    /// - `scope`: Scope path
210    /// - `line`: Source line number
211    ProcessVariable {
212        variable_name: std::borrow::Cow<'a, str>,
213        value: std::borrow::Cow<'a, str>,
214        scope: std::borrow::Cow<'a, str>,
215        line: usize,
216    },
217
218    /// Manage the scope lifecycle (entry/exit).
219    ///
220    /// # Fields
221    /// - `scope`: Scope path
222    /// - `is_entry`: true for scope entry, false for exit
223    /// - `line`: Source line number
224    ManageScope {
225        scope: std::borrow::Cow<'a, str>,
226        is_entry: bool,
227        line: usize,
228    },
229
230    /// Execute a directive (e.g., @import, @derive).
231    ///
232    /// # Fields
233    /// - `directive_name`: Name of the directive
234    /// - `arguments`: Directive arguments
235    /// - `line`: Source line number
236    ExecuteDirective {
237        directive_name: std::borrow::Cow<'a, str>,
238        arguments: std::borrow::Cow<'a, str>,
239        line: usize,
240    },
241
242    /// Register a type definition from @type directive.
243    ///
244    /// # Fields
245    /// - `type_name`: Name of the type being registered
246    /// - `type_spec`: Type specification
247    /// - `line`: Source line number
248    RegisterType {
249        type_name: std::borrow::Cow<'a, str>,
250        type_spec: std::borrow::Cow<'a, str>,
251        line: usize,
252    },
253
254    /// Register a schema definition from the @schema directive.
255    ///
256    /// # Fields
257    /// - `schema_name`: Name of the schema
258    /// - `fields`: Field definitions
259    /// - `line`: Source line number
260    RegisterSchema {
261        schema_name: std::borrow::Cow<'a, str>,
262        fields: std::borrow::Cow<'a, str>,
263        line: usize,
264    },
265}
266
267impl ParseTask<'_> {
268    /// Returns the line number associated with this task.
269    #[must_use]
270    pub const fn line(&self) -> usize {
271        match self {
272            ParseTask::ResolveModuleReference { line, .. }
273            | ParseTask::ResolveDeriveImport { line, .. }
274            | ParseTask::ProcessVariable { line, .. }
275            | ParseTask::ManageScope { line, .. }
276            | ParseTask::ExecuteDirective { line, .. }
277            | ParseTask::RegisterType { line, .. }
278            | ParseTask::RegisterSchema { line, .. } => *line,
279        }
280    }
281
282    /// Returns a human-readable description.
283    #[must_use]
284    pub fn description(&self) -> String {
285        match self {
286            ParseTask::ResolveModuleReference { module_name, .. } => {
287                format!("Resolve module reference '{module_name}'")
288            }
289            ParseTask::ResolveDeriveImport { derive_path, .. } => {
290                format!("Resolve derive import '{derive_path}'")
291            }
292            ParseTask::ProcessVariable { variable_name, .. } => {
293                format!("Process variable '{variable_name}'")
294            }
295            ParseTask::ManageScope {
296                scope, is_entry, ..
297            } => {
298                let action = if *is_entry { "Enter" } else { "Exit" };
299                format!("{action} scope '{scope}'")
300            }
301            ParseTask::ExecuteDirective { directive_name, .. } => {
302                format!("Execute directive '@{directive_name}'")
303            }
304            ParseTask::RegisterType { type_name, .. } => {
305                format!("Register type '{type_name}'")
306            }
307            ParseTask::RegisterSchema { schema_name, .. } => {
308                format!("Register schema '{schema_name}'")
309            }
310        }
311    }
312
313    /// Performs checks that do not require mutable execution context.
314    ///
315    /// # Errors
316    ///
317    /// Returns `Err(TaskError)` when the task is invalid in a stateless context
318    /// (for example, executing a non-whitelisted directive without execution
319    /// context).
320    pub fn validate_stateless(&self) -> Result<(), TaskError> {
321        if let ParseTask::ExecuteDirective {
322            directive_name,
323            line,
324            ..
325        } = self
326            && !matches!(directive_name.as_ref(), "import" | "derive")
327        {
328            return Err(TaskError {
329                line: *line,
330                message: format!("Unknown directive: @{directive_name}"),
331                task_description: self.description(),
332                aaml_error: None,
333            });
334        }
335
336        Ok(())
337    }
338}
339
340/// A declarative execution task for the final execution phase.
341///
342/// Execution tasks represent the final operations to be performed to materialize
343/// the configuration, independent of AAML struct manipulation.
344#[derive(Debug, Clone)]
345pub enum ExecutionTask<'a> {
346    /// Set a configuration value in the output.
347    ///
348    /// # Fields
349    /// - `key`: Configuration key
350    /// - `value`: Value to set
351    /// - `line`: Source line number
352    SetValue {
353        key: std::borrow::Cow<'a, str>,
354        value: std::borrow::Cow<'a, str>,
355        line: usize,
356    },
357
358    /// Merge a value with an existing one (+=).
359    ///
360    /// # Fields
361    /// - `key`: Key to merge
362    /// - `value`: Value to merge in
363    /// - `line`: Source line number
364    MergeValue {
365        key: std::borrow::Cow<'a, str>,
366        value: std::borrow::Cow<'a, str>,
367        line: usize,
368    },
369
370    /// Apply schema validation to a subtree.
371    ///
372    /// # Fields
373    /// - `schema_name`: Schema to apply
374    /// - `root_keys`: Keys that are part of this schema
375    /// - `line`: Source line number
376    ApplySchema {
377        schema_name: std::borrow::Cow<'a, str>,
378        root_keys: Vec<std::borrow::Cow<'a, str>>,
379        line: usize,
380    },
381
382    /// Execute inheritance from a parent configuration.
383    ///
384    /// # Fields
385    /// - `parent_key`: Parent configuration key
386    /// - `child_key`: Child configuration key
387    /// - `line`: Source line number
388    ExecuteInheritance {
389        derive_path: std::borrow::Cow<'a, str>,
390        child_key: std::borrow::Cow<'a, str>,
391        line: usize,
392    },
393
394    /// Import configuration from an external file.
395    ///
396    /// # Fields
397    /// - `file_path`: Path to import
398    /// - `merge_strategy`: How to merge ("override" or "merge")
399    /// - `line`: Source line number
400    ImportFile {
401        file_path: std::borrow::Cow<'a, str>,
402        merge_strategy: std::borrow::Cow<'a, str>,
403        line: usize,
404    },
405
406    /// Resolve a reference to another key.
407    ///
408    /// # Fields
409    /// - `source_key`: Key containing the reference
410    /// - `target_key`: Key being referenced
411    /// - `line`: Source line number
412    ResolveReference {
413        source_key: std::borrow::Cow<'a, str>,
414        target_key: std::borrow::Cow<'a, str>,
415        line: usize,
416    },
417}
418
419impl ExecutionTask<'_> {
420    /// Returns the line number associated with this task.
421    #[must_use]
422    pub const fn line(&self) -> usize {
423        match self {
424            ExecutionTask::SetValue { line, .. }
425            | ExecutionTask::MergeValue { line, .. }
426            | ExecutionTask::ApplySchema { line, .. }
427            | ExecutionTask::ExecuteInheritance { line, .. }
428            | ExecutionTask::ImportFile { line, .. }
429            | ExecutionTask::ResolveReference { line, .. } => *line,
430        }
431    }
432
433    /// Returns a human-readable description.
434    #[must_use]
435    pub fn description(&self) -> String {
436        match self {
437            ExecutionTask::SetValue { key, .. } => format!("Set value for key '{key}'"),
438            ExecutionTask::MergeValue { key, .. } => format!("Merge value for key '{key}'"),
439            ExecutionTask::ApplySchema { schema_name, .. } => {
440                format!("Apply schema '{schema_name}'")
441            }
442            ExecutionTask::ExecuteInheritance {
443                derive_path,
444                child_key,
445                ..
446            } => {
447                format!("Execute inheritance: '{child_key}' <- '{derive_path}'")
448            }
449            ExecutionTask::ImportFile { file_path, .. } => {
450                format!("Import file '{file_path}'")
451            }
452            ExecutionTask::ResolveReference {
453                source_key,
454                target_key,
455                ..
456            } => {
457                format!("Resolve reference: '{source_key}' -> '{target_key}'")
458            }
459        }
460    }
461}
462
463/// Result of executing a batch of tasks, with error aggregation support for LSP.
464#[derive(Debug, Clone)]
465pub struct TaskExecutionResult {
466    /// Whether all tasks executed successfully
467    pub success: bool,
468
469    /// Collected errors with line/context information
470    pub errors: Vec<TaskError>,
471
472    /// Execution statistics
473    pub stats: ExecutionStats,
474}
475
476/// A single task execution error with full diagnostic context.
477#[derive(Debug, Clone)]
478pub struct TaskError {
479    /// Associated task line number
480    pub line: usize,
481
482    /// Error message
483    pub message: String,
484
485    /// Task description for context
486    pub task_description: String,
487
488    /// AAML error details
489    pub aaml_error: Option<String>,
490}
491
492/// Execution statistics for performance monitoring.
493#[derive(Debug, Clone, Default)]
494pub struct ExecutionStats {
495    /// Total tasks executed
496    pub total_tasks: usize,
497
498    /// Successfully executed tasks
499    pub successful_tasks: usize,
500
501    /// Failed tasks
502    pub failed_tasks: usize,
503
504    /// Time spent in the validation phase (milliseconds)
505    pub validation_ms: u64,
506
507    /// Time spent in the parsing phase (milliseconds)
508    pub parsing_ms: u64,
509
510    /// Time spent in the execution phase (milliseconds)
511    pub execution_ms: u64,
512}
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517    use std::borrow::Cow;
518
519    #[test]
520    fn test_validation_task_line_numbers() {
521        let task = ValidationTask::CheckTypeMatch {
522            key: Cow::from("foo".to_string()),
523            value: Cow::from("bar"),
524            type_name: Cow::from("string".to_string()),
525            line: 42,
526        };
527        assert_eq!(task.line(), 42);
528    }
529
530    #[test]
531    fn test_parse_task_description() {
532        let task = ParseTask::ExecuteDirective {
533            directive_name: Cow::from("import"),
534            arguments: Cow::from("base.aam"),
535            line: 10,
536        };
537        assert_eq!(task.description(), "Execute directive '@import'");
538    }
539
540    #[test]
541    fn test_execution_task_inheritance() {
542        let task = ExecutionTask::ExecuteInheritance {
543            derive_path: Cow::from("base.aam::Parent"),
544            child_key: Cow::from("child"),
545            line: 5,
546        };
547        assert_eq!(
548            task.description(),
549            "Execute inheritance: 'child' <- 'base.aam::Parent'"
550        );
551    }
552}