1pub mod embedded {
2 include!(concat!(env!("OUT_DIR"), "/embedded_data.rs"));
3}
4
5#[cfg(feature = "uniffi-bindings")]
6uniffi::setup_scaffolding!();
7
8pub mod additional_schema_source;
9#[cfg(feature = "maintenance")]
10pub mod additional_specs;
11#[cfg(feature = "maintenance")]
12pub mod cfnlint_tables;
13#[cfg(feature = "maintenance")]
14pub mod codegen_cel;
15#[cfg(feature = "maintenance")]
16pub mod codegen_schema_validator;
17pub mod compiled_schema;
18#[cfg(feature = "maintenance")]
19pub mod extensions;
20#[cfg(feature = "maintenance")]
21pub mod process;
22#[cfg(feature = "maintenance")]
23pub mod regions;
24pub mod rule_data;
25#[cfg(feature = "maintenance")]
26pub mod schema;
27#[cfg(feature = "maintenance")]
28mod source_versions;
29pub mod types;
30
31pub use additional_schema_source::{AdditionalSchemaSource, SchemaSourceError};
32
33#[cfg(feature = "maintenance")]
34use log::{error, info};
35#[cfg(feature = "maintenance")]
36use std::fs;
37#[cfg(feature = "maintenance")]
38use std::path::{Path, PathBuf};
39
40#[cfg(feature = "maintenance")]
43pub fn rule_source_dir_to_name(dir_name: &str) -> String {
44 dir_name.to_lowercase().replace('_', "-")
45}
46
47#[cfg(feature = "maintenance")]
49pub fn resolve_rule_source_dir(path: Option<&str>) -> anyhow::Result<PathBuf> {
50 let p = path.ok_or_else(|| anyhow::anyhow!("Pass --cfn-lint-root to the cfn-lint repo root"))?;
51 let pb = PathBuf::from(p);
52 anyhow::ensure!(pb.exists(), "Rule-source directory not found: {}", pb.display());
53 Ok(pb)
54}
55
56#[cfg(feature = "maintenance")]
57#[derive(Debug, Default)]
58pub struct SyncStats {
59 pub files_written: usize,
60 pub files_skipped: usize,
61 pub errors: Vec<String>,
62}
63
64#[cfg(feature = "maintenance")]
65impl SyncStats {
66 pub fn log(&self, label: &str) {
67 info!(
68 "{}: {} written, {} skipped, {} errors",
69 label,
70 self.files_written,
71 self.files_skipped,
72 self.errors.len()
73 );
74 for e in &self.errors {
75 error!("{}: {}", label, e);
76 }
77 }
78
79 pub fn fail_on_errors(&self, label: &str) -> anyhow::Result<()> {
80 self.log(label);
81 if !self.errors.is_empty() {
82 anyhow::bail!("{} completed with {} error(s):\n {}", label, self.errors.len(), self.errors.join("\n "));
83 }
84 Ok(())
85 }
86}
87
88#[cfg(feature = "maintenance")]
89fn write_source_versions(path: &Path, versions: source_versions::SourceVersions) -> anyhow::Result<()> {
90 let versions = source_versions::SourceVersions::new(versions.cfn_lint_version, versions.resource_schema_version)
91 .map_err(anyhow::Error::msg)?;
92 let mut contents = serde_json::to_string_pretty(&versions)?;
93 contents.push('\n');
94 fs::write(path, contents)?;
95 Ok(())
96}
97
98#[cfg(feature = "maintenance")]
99pub fn sync_upstream(upstream_dir: &Path, rule_source_root: &str) -> anyhow::Result<()> {
100 info!("=== Sync phase ===");
101
102 let generated_root = upstream_dir
103 .parent()
104 .ok_or_else(|| anyhow::anyhow!("upstream directory must have a parent: {}", upstream_dir.display()))?;
105 let generated_data = generated_root.join("generated").join("data");
106 fs::create_dir_all(&generated_data)?;
107 let source_versions_path = generated_data.join(source_versions::SOURCE_VERSIONS_FILE);
108 let rule_source_dir = resolve_rule_source_dir(Some(rule_source_root))?;
109
110 info!("Step 1: Downloading enhanced CloudFormation schemas (fully patched, with region maps)");
111 let (schema_stats, resource_schema_version) = schema::download_schemas(upstream_dir)?;
112 schema_stats.fail_on_errors("Download")?;
113
114 info!("Step 2: Building region resource types from downloaded provider maps");
115 regions::sync_regions(&schema::providers_dir(upstream_dir), &generated_data)?.fail_on_errors("Regions")?;
116
117 info!("Step 3: Syncing extensions from {}", rule_source_dir.display());
118 extensions::sync_extensions(&rule_source_dir, &upstream_dir.join("extensions"), &generated_data)?
119 .fail_on_errors("Extensions")?;
120
121 info!("Step 4: Syncing additional specs from {}", rule_source_dir.display());
122 additional_specs::sync_additional_specs(&rule_source_dir, &generated_data, upstream_dir)?
123 .fail_on_errors("AdditionalSpecs")?;
124
125 info!("Step 5: Extracting data tables embedded in cfn-lint rule code");
126 let (table_stats, cfn_lint_version) = cfnlint_tables::sync_cfnlint_tables(&rule_source_dir, &generated_data)?;
127 table_stats.fail_on_errors("CfnLintTables")?;
128
129 verify_files_exist_and_populated(REQUIRED_SYNC_FILES, &generated_data, "Sync")?;
130 let source_versions =
131 source_versions::SourceVersions::new(cfn_lint_version, resource_schema_version).map_err(anyhow::Error::msg)?;
132 write_source_versions(&source_versions_path, source_versions)?;
133 info!("Recorded complete data source provenance in {}", source_versions_path.display());
134
135 Ok(())
136}
137
138#[cfg(feature = "maintenance")]
139pub fn generate_all(upstream_dir: &Path, generated_dir: &Path, handwritten_dir: &Path) -> anyhow::Result<()> {
140 info!("=== Generate phase ===");
141
142 info!("Step 1: Processing schemas (extensions, metadata)");
143 process::process_schemas(upstream_dir, generated_dir, handwritten_dir)?.fail_on_errors("Process")?;
144
145 info!("Step 2: Generating CEL rules");
146 codegen_cel::generate(generated_dir, handwritten_dir)?;
147
148 info!("Step 3: Generating compiled schemas for schema-validator");
149 codegen_schema_validator::generate(generated_dir, upstream_dir)?;
150
151 info!("Step 4: Verifying all expected output files");
152 verify_outputs(generated_dir, handwritten_dir)?;
153
154 Ok(())
155}
156
157#[cfg(feature = "maintenance")]
162const REQUIRED_SYNC_FILES: &[&str] = &[
163 "aws_amazonmq_broker_instancetype_enum",
165 "aws_appstream_fleet_instancetype_enum",
166 "aws_dax_cluster_nodetype_enum",
167 "aws_docdb_dbinstance_dbinstanceclass_enum",
168 "aws_ec2_instance_instancetype_enum",
169 "aws_elasticache_cachecluster_cachenodetype_enum",
170 "aws_emr_cluster_instancetypeconfig_instancetype_enum",
171 "aws_gamelift_fleet_ec2instancetype_enum",
172 "aws_managedblockchain_node_nodeconfiguration_instancetype_enum",
173 "aws_neptune_dbinstance_dbinstanceclass_enum",
174 "aws_rds_dbcluster_dbclusterinstanceclass_enum",
175 "aws_rds_dbinstance_db_instance_class",
176 "aws_rds_dbinstance_dbinstanceclass_enum",
177 "aws_redshift_cluster_nodetype_enum",
178 "aws_sagemaker_processing_instancetype_enum",
179 "aws_sagemaker_hosting_instancetype_enum",
180 "aws_sagemaker_transform_instancetype_enum",
181 "aws_sagemaker_cluster_instancetype_enum",
182 "aws_elasticsearch_domain_elasticsearchclusterconfig_instancetype_enum",
183 "aws_opensearchservice_domain_clusterconfig_instancetype_enum",
184 "iam_action_resource_patterns",
186 "lambda_runtimes",
187 "region_resource_types",
188 "stateful_resource_types",
189 "getatt_additions",
191 "retention_period_requirements",
192 "codepipeline_action_artifact_counts",
193 "cfnlint_rule_tables",
194];
195
196#[cfg(feature = "maintenance")]
198const REQUIRED_GENERATE_DATA_FILES: &[&str] =
199 &["resource_lifecycle", "schema_metadata", "primary_identifiers", "getatt_attributes", "known_resource_types"];
200
201#[cfg(feature = "maintenance")]
203const REQUIRED_SCHEMA_VALIDATOR_FILES: &[&str] = &["compiled_schemas", "ref_types", "extensions", "region_enums"];
204
205#[cfg(feature = "maintenance")]
206const REQUIRED_CEL_RULE_FILES: &[&str] = &["generated_rules"];
207
208#[cfg(feature = "maintenance")]
209const REQUIRED_HANDWRITTEN_FILES: &[&str] = &[
210 "deprecated_resource_types",
211 "getatt_return_type_overrides",
212 "schema_dependent_excluded_overrides",
213 "secretsmanager_arn_fields",
214 "sensitive_ports",
215];
216
217#[cfg(feature = "maintenance")]
218fn verify_sync_outputs(data_dir: &Path) -> anyhow::Result<()> {
219 source_versions::SourceVersions::read(&data_dir.join(source_versions::SOURCE_VERSIONS_FILE))
220 .map_err(anyhow::Error::msg)?;
221 verify_files_exist_and_populated(REQUIRED_SYNC_FILES, data_dir, "Sync")
222}
223
224#[cfg(feature = "maintenance")]
225fn verify_outputs(generated_dir: &Path, handwritten_dir: &Path) -> anyhow::Result<()> {
226 let data_dir = generated_dir.join("data");
227 let schema_validator_dir = generated_dir.join("schema-validator");
228 let cel_rules_dir = generated_dir.join("cel-rules");
229
230 verify_sync_outputs(&data_dir)?;
231 verify_files_exist_and_populated(REQUIRED_GENERATE_DATA_FILES, &data_dir, "Generate data")?;
232 verify_files_exist_and_populated(REQUIRED_SCHEMA_VALIDATOR_FILES, &schema_validator_dir, "Schema validator")?;
233 verify_files_exist_and_populated(REQUIRED_CEL_RULE_FILES, &cel_rules_dir, "Generated CEL rules")?;
234 verify_files_exist_and_populated(REQUIRED_HANDWRITTEN_FILES, handwritten_dir, "Handwritten")?;
235
236 let total = REQUIRED_SYNC_FILES.len()
237 + REQUIRED_GENERATE_DATA_FILES.len()
238 + REQUIRED_SCHEMA_VALIDATOR_FILES.len()
239 + REQUIRED_CEL_RULE_FILES.len()
240 + REQUIRED_HANDWRITTEN_FILES.len()
241 + 1;
242 info!("Verified {total} required data files");
243 Ok(())
244}
245
246#[cfg(feature = "maintenance")]
247fn verify_files_exist_and_populated(names: &[&str], directory: &Path, label: &str) -> anyhow::Result<()> {
248 let mut missing = Vec::new();
249 let mut stubs = Vec::new();
250
251 for name in names {
252 let path = directory.join(format!("{name}.json"));
253 if !path.is_file() {
254 missing.push(name.to_string());
255 continue;
256 }
257
258 if is_stub(&path) {
259 stubs.push(name.to_string());
260 }
261 }
262
263 if !missing.is_empty() || !stubs.is_empty() {
264 let mut msg = String::new();
265 if !missing.is_empty() {
266 msg.push_str(&format!(" Missing: {}\n", missing.join(", ")));
267 }
268 if !stubs.is_empty() {
269 msg.push_str(&format!(" Empty stubs: {}\n", stubs.join(", ")));
270 }
271 anyhow::bail!(
272 "{label} verification failed - {}/{} files not populated:\n{msg}",
273 missing.len() + stubs.len(),
274 names.len()
275 );
276 }
277 Ok(())
278}
279
280#[cfg(feature = "maintenance")]
282fn is_stub(path: &Path) -> bool {
283 match fs::read_to_string(path) {
284 Ok(content) => {
285 let trimmed = content.trim();
286 trimmed == "{}" || trimmed == "[]"
287 }
288 Err(_) => true,
289 }
290}