Skip to main content

tfparser_core/ir/
workspace.rs

1//! The top-level [`Workspace`] IR — what every component spec consumes.
2
3use std::{path::Path, sync::Arc};
4
5use serde::{Deserialize, Serialize};
6use typed_builder::TypedBuilder;
7
8use crate::{
9    diagnostic::Diagnostic,
10    ir::{Component, Edge, Environment, Module},
11};
12
13/// The fully-parsed workspace, ready for export.
14///
15/// Per [10-data-model.md § 2.1], the workspace owns:
16///
17/// - the set of components (apply-able roots),
18/// - the set of modules referenced from those components,
19/// - the discovered environments, and
20/// - any non-fatal diagnostics produced anywhere in the pipeline.
21///
22/// Construct via [`WorkspaceBuilder`].
23///
24/// [10-data-model.md § 2.1]: ../../specs/10-data-model.md
25#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TypedBuilder)]
26#[non_exhaustive]
27#[serde(rename_all = "camelCase")]
28#[builder(field_defaults(setter(into)))]
29pub struct Workspace {
30    /// Absolute canonical path of the workspace root.
31    #[serde(with = "crate::ir::path_serde::arc_path")]
32    pub root: Arc<Path>,
33
34    /// Components in path-ascending order (deterministic).
35    #[builder(default)]
36    pub components: Vec<Component>,
37
38    /// Referenced modules (whether the body was walked or not).
39    #[builder(default)]
40    pub modules: Vec<Module>,
41
42    /// Discovered environments.
43    #[builder(default)]
44    pub environments: Vec<Environment>,
45
46    /// Workspace-wide non-fatal diagnostics. Per-component diagnostics
47    /// (e.g. from the Terragrunt resolver) hang off their owning
48    /// `TerragruntConfig.diagnostics` instead.
49    #[builder(default)]
50    pub diagnostics: Vec<Diagnostic>,
51
52    /// Dependency edges populated by the graph phase (Phase 8 / M5). The
53    /// list is sorted by `(from, to, kind)` for deterministic Parquet
54    /// output (see [15-resource-graph.md § 4]).
55    ///
56    /// [15-resource-graph.md § 4]: ../../specs/15-resource-graph.md
57    #[builder(default)]
58    pub edges: Vec<Edge>,
59}
60
61#[cfg(test)]
62#[allow(clippy::unwrap_used)]
63mod tests {
64    use std::path::PathBuf;
65
66    use super::*;
67
68    #[test]
69    fn test_should_build_minimal_workspace() {
70        let w = Workspace::builder()
71            .root(Arc::<Path>::from(PathBuf::from("/tmp/repo")))
72            .build();
73        assert!(w.components.is_empty());
74        assert!(w.modules.is_empty());
75        assert!(w.environments.is_empty());
76        assert!(w.diagnostics.is_empty());
77    }
78
79    #[test]
80    fn test_should_serde_round_trip_workspace() {
81        let w = Workspace::builder()
82            .root(Arc::<Path>::from(PathBuf::from("/tmp/repo")))
83            .build();
84        let json = serde_json::to_string(&w).unwrap();
85        let back: Workspace = serde_json::from_str(&json).unwrap();
86        assert_eq!(w, back);
87    }
88}