tfparser_core/lib.rs
1//! # tfparser-core
2//!
3//! Parse a Terraform / Terragrunt source repository into a typed in-memory IR
4//! that can be exported as Parquet — without running `terraform plan`.
5//!
6//! ## Quick start
7//!
8//! One-shot parse with all defaults:
9//!
10//! ```no_run
11//! # fn main() -> tfparser_core::Result<()> {
12//! let workspace = tfparser_core::parse("./my-tf-repo")?;
13//! println!(
14//! "{} components / {} modules / {} resources",
15//! workspace.components.len(),
16//! workspace.modules.len(),
17//! workspace.components.iter().map(|c| c.resources.len()).sum::<usize>(),
18//! );
19//! # Ok(()) }
20//! ```
21//!
22//! Builder for full control + Parquet export in one call:
23//!
24//! ```no_run
25//! # fn main() -> tfparser_core::Result<()> {
26//! use std::sync::Arc;
27//! use std::path::Path;
28//! use tfparser_core::{Parser, EnvVarMode, ExportOptions};
29//!
30//! let parser = Parser::builder()
31//! .workspace_root("./my-tf-repo")
32//! .environment("production")
33//! .default_region("us-west-2")?
34//! .env_var_mode(EnvVarMode::Passthrough)
35//! .allow_env("TF_VAR_environment")
36//! .var("region", "us-east-1")
37//! .strict_providers(true)
38//! .build()?;
39//!
40//! let export_opts = ExportOptions::builder()
41//! .out_dir(Arc::<Path>::from(Path::new("./out")))
42//! .overwrite(true)
43//! .build();
44//! let (workspace, report) = parser.parse_and_export(&export_opts)?;
45//! eprintln!("wrote {} rows in {} ms", report.total_rows, report.elapsed.as_millis());
46//! # let _ = workspace;
47//! # Ok(()) }
48//! ```
49//!
50//! Bring everything in scope with the prelude:
51//!
52//! ```
53//! use tfparser_core::prelude::*;
54//! ```
55//!
56//! ## Surface map
57//!
58//! | I want to … | Reach for |
59//! | ----------- | --------- |
60//! | parse a repo with defaults | [`parse`] |
61//! | parse + tune env / vars / limits | [`Parser::builder`] |
62//! | parse and write Parquet in one call | [`Parser::parse_and_export`] |
63//! | inspect the parsed IR | [`ir::Workspace`], [`ir::Component`], [`ir::Resource`] |
64//! | swap in a stub for tests | implement [`Pipeline`] / [`Exporter`] |
65//! | configure parquet output (compression, manifest, tables) | [`ExportOptions::builder`] + [`ParquetExporter`] |
66//! | load an AWS profile map | [`load_aws_config`] / [`load_yaml_profile_map`] |
67//!
68//! ## Engineering invariants
69//!
70//! - `#![forbid(unsafe_code)]` at the crate root — no `unsafe`, ever.
71//! - No `unwrap` / `expect` / `panic` reachable from external input; the workspace lints deny those
72//! clippy categories for every member.
73//! - Every public type is `#[non_exhaustive]` so future fields are additive.
74//! - Public `Debug` impls redact sensitive fields (provider secrets, resolved values that may carry
75//! credentials).
76//!
77//! See `./specs/91-impl-plan.md` for the build-order rationale and
78//! `./specs/10-data-model.md` for the IR contract pinned in this crate.
79
80#![forbid(unsafe_code)]
81#![warn(missing_docs)]
82
83pub mod diagnostic;
84pub mod discovery;
85pub mod error;
86pub mod eval;
87pub mod exporter;
88pub mod graph;
89pub mod ir;
90pub mod loader;
91pub mod parser;
92pub mod pipeline;
93pub mod prelude;
94pub mod projection;
95pub mod provider;
96pub mod terragrunt;
97pub(crate) mod util;
98
99pub use diagnostic::{Diagnostic, LimitKind, Severity};
100pub use error::{Error, Result, ValidationError};
101pub use eval::{
102 EnvVarMode, EvalContext, EvalError, EvalLimits, EvaluatedComponent, Evaluator, FuncRegistry,
103 HclEvaluator,
104};
105pub use exporter::{
106 CompressionOpt, ExportOptions, ExportReport, ExportedFile, Exporter, ParquetExporter,
107 SecondaryTable,
108};
109pub use graph::{
110 DefaultGraphBuilder, ExternalModuleRef, GraphBuilder, GraphContext, GraphError, ModuleRegistry,
111};
112pub use ir::{
113 AccountId, Address, AssumeRole, AttributeMap, BinaryOp, BlockKind, Component, ComponentId,
114 ComponentKind, DependencyBlock, Edge, EdgeKind, Environment, Expression, FileExt,
115 GenerateBlock, IncludePath, Local, Map, Module, ModuleCall, ModuleId, ModuleSource, Output,
116 ProviderBlock, ProviderRef, Region, Resource, ResourceKind, SourceFile, Span, StateBackend,
117 SymbolKind, Symbolic, TerragruntConfig, UnaryOp, Value, Variable, Workspace,
118};
119pub use parser::{Parser, ParserBuilder, parse};
120pub use pipeline::{DefaultPipeline, Pipeline, PipelineOptions};
121pub use provider::{
122 DefaultProviderResolver, ProfileEntry, ProfileMap, ProviderContext, ProviderError,
123 ProviderResolver, SharedProfileMap, empty_profile_map, extract_account_id, load_aws_config,
124 load_yaml_profile_map,
125};
126pub use terragrunt::{FsTerragruntResolver, TerragruntError, TerragruntResolver, TgContext};
127
128#[cfg(test)]
129mod thread_safety {
130 //! Static `Send + Sync` assertions for the public surface that crosses
131 //! thread boundaries via `rayon` (per [99-key-decisions.md] D14).
132 //!
133 //! [99-key-decisions.md]: ../../specs/99-key-decisions.md
134
135 use super::*;
136
137 const fn assert_send_sync<T: Send + Sync + 'static>() {}
138
139 #[test]
140 fn test_public_types_are_send_sync() {
141 assert_send_sync::<Workspace>();
142 assert_send_sync::<Component>();
143 assert_send_sync::<Module>();
144 assert_send_sync::<Resource>();
145 assert_send_sync::<Diagnostic>();
146 assert_send_sync::<Error>();
147 assert_send_sync::<ValidationError>();
148 assert_send_sync::<PipelineOptions>();
149 assert_send_sync::<Parser>();
150 assert_send_sync::<ParserBuilder>();
151 // Trait objects of `Pipeline` are the cross-thread shape downstream
152 // crates (server, future CLI) will hold.
153 assert_send_sync::<Box<dyn Pipeline>>();
154 }
155}