Skip to main content

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}