tfparser_core/graph/
registry.rs1use std::{collections::HashMap, path::Path, sync::Arc};
18
19use crate::{
20 eval::EvaluatedComponent,
21 ir::{ModuleSource, Span},
22};
23
24#[derive(Clone, Debug)]
27#[non_exhaustive]
28pub struct ExternalModuleRef {
29 pub source_raw: Arc<str>,
31 pub source: ModuleSource,
33 pub first_seen: Span,
36}
37
38#[derive(Clone, Debug, Default)]
47#[non_exhaustive]
48pub struct ModuleRegistry {
49 pub local_modules: HashMap<Arc<Path>, EvaluatedComponent>,
53 pub external_refs: Vec<ExternalModuleRef>,
55}
56
57impl ModuleRegistry {
58 #[must_use]
60 pub fn new() -> Self {
61 Self::default()
62 }
63
64 pub fn insert_local(&mut self, canonical: Arc<Path>, component: EvaluatedComponent) {
68 self.local_modules.insert(canonical, component);
69 }
70
71 pub fn record_external(&mut self, source_raw: Arc<str>, source: ModuleSource, span: Span) {
74 if self
75 .external_refs
76 .iter()
77 .any(|e| e.source_raw == source_raw)
78 {
79 return;
80 }
81 self.external_refs.push(ExternalModuleRef {
82 source_raw,
83 source,
84 first_seen: span,
85 });
86 }
87
88 #[must_use]
90 pub fn get_local(&self, canonical: &Path) -> Option<&EvaluatedComponent> {
91 self.local_modules.get(canonical)
92 }
93
94 #[must_use]
96 pub fn local_count(&self) -> usize {
97 self.local_modules.len()
98 }
99
100 #[must_use]
102 pub fn external_count(&self) -> usize {
103 self.external_refs.len()
104 }
105}
106
107#[cfg(test)]
108#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
109mod tests {
110 use std::path::PathBuf;
111
112 use super::*;
113 use crate::{
114 eval::EvaluatedComponent,
115 ir::{Component, ComponentId, ComponentKind, Span},
116 };
117
118 fn evaluated_component(path: &str) -> EvaluatedComponent {
119 EvaluatedComponent {
120 raw: Arc::new(
121 Component::builder()
122 .id(ComponentId::from_index(0))
123 .path(Arc::<Path>::from(PathBuf::from(path)))
124 .kind(ComponentKind::Module)
125 .build(),
126 ),
127 variables: Vec::new(),
128 locals: Vec::new(),
129 providers: Vec::new(),
130 resources: Vec::new(),
131 modules: Vec::new(),
132 outputs: Vec::new(),
133 diagnostics: Vec::new(),
134 }
135 }
136
137 #[test]
138 fn test_should_insert_and_lookup_local_module() {
139 let mut reg = ModuleRegistry::new();
140 let canonical: Arc<Path> = Arc::from(PathBuf::from("/repo/modules/s3"));
141 reg.insert_local(Arc::clone(&canonical), evaluated_component("modules/s3"));
142 assert_eq!(reg.local_count(), 1);
143 assert!(reg.get_local(&canonical).is_some());
144 }
145
146 #[test]
147 fn test_should_dedup_external_refs_by_source_raw() {
148 let mut reg = ModuleRegistry::new();
149 let span = Span::synthetic();
150 reg.record_external(
151 Arc::<str>::from("terraform-aws-modules/eks/aws"),
152 ModuleSource::Registry(Arc::<str>::from("terraform-aws-modules/eks/aws")),
153 span.clone(),
154 );
155 reg.record_external(
156 Arc::<str>::from("terraform-aws-modules/eks/aws"),
157 ModuleSource::Registry(Arc::<str>::from("terraform-aws-modules/eks/aws")),
158 span,
159 );
160 assert_eq!(reg.external_count(), 1);
161 }
162}