mod hcl_contract {
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ExtractedPair {
pub context: String,
pub value: String,
pub line: usize,
pub transport_decoded: bool,
}
mod parser {
include!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/structured/parsers/hcl.rs"
));
}
pub(crate) use parser::parse_hcl;
pub(crate) use parser::{strip_hcl_comments, strip_hcl_line_comment};
}
fn value_of<'a>(pairs: &'a [hcl_contract::ExtractedPair], context: &str) -> Option<&'a str> {
pairs
.iter()
.find(|pair| pair.context == context)
.map(|pair| pair.value.as_str())
}
fn line_of(pairs: &[hcl_contract::ExtractedPair], context: &str) -> Option<usize> {
pairs
.iter()
.find(|pair| pair.context == context)
.map(|pair| pair.line)
}
#[test]
fn variable_block_ignores_braces_inside_strings_and_comments() {
let text = r#"variable "database_password" {
description = "literal } brace should not close the block"
# literal { brace should not keep the block open
default = "super-secret-pass"
}
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(
value_of(&pairs, "database_password"),
Some("super-secret-pass")
);
assert_eq!(
value_of(&pairs, "default"),
None,
"default line must not be reprocessed as a flat assignment"
);
assert_eq!(line_of(&pairs, "database_password"), Some(4));
}
#[test]
fn unquoted_variable_header_keeps_variable_context() {
let text = r#"variable db_password {
default = "my-db-pass"
}
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(value_of(&pairs, "db_password"), Some("my-db-pass"));
assert_eq!(value_of(&pairs, "default"), None);
assert_eq!(line_of(&pairs, "db_password"), Some(2));
}
#[test]
fn assignment_comment_ending_with_brace_still_extracts() {
let text = r#"api_key = "my-secret-key" # comment ending with {
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(value_of(&pairs, "api_key"), Some("my-secret-key"));
assert_eq!(line_of(&pairs, "api_key"), Some(1));
}
#[test]
fn multiline_block_comments_do_not_emit_assignments() {
let text = r#"/*
api_key = "commented-out-secret"
*/
api_key = "live-secret"
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(value_of(&pairs, "api_key"), Some("live-secret"));
assert!(
pairs
.iter()
.all(|pair| pair.value != "commented-out-secret"),
"assignments inside block comments must not be extracted: {pairs:?}"
);
assert_eq!(line_of(&pairs, "api_key"), Some(4));
}
#[test]
fn interpolation_quotes_do_not_truncate_outer_string() {
let text = r#"api_key = "${lookup(local.secrets, "datadog_key")}-tail"
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(
value_of(&pairs, "api_key"),
Some(r#"${lookup(local.secrets, "datadog_key")}-tail"#)
);
}
#[test]
fn variable_default_map_extracts_nested_assignment_context() {
let text = r#"variable "api_credentials" {
default = {
username = "admin"
password = "super-secret-password-123"
}
}
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(
value_of(&pairs, "api_credentials.password"),
Some("super-secret-password-123")
);
assert_eq!(line_of(&pairs, "api_credentials.password"), Some(4));
}
#[test]
fn variable_default_heredoc_extracts_content_line() {
let text = r#"variable "api_key" {
default = <<EOF
heredoc-secret-value
EOF
}
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(value_of(&pairs, "api_key"), Some("heredoc-secret-value"));
assert_eq!(
line_of(&pairs, "api_key"),
Some(3),
"heredoc value must map to the content line, not the marker line"
);
}
#[test]
fn inline_default_equals_without_space_extracts_variable_context() {
let text = r#"variable "api_key" { default="nospace-secret-value" }
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(value_of(&pairs, "api_key"), Some("nospace-secret-value"));
assert_eq!(line_of(&pairs, "api_key"), Some(1));
}
#[test]
fn keyword_named_assignment_is_not_dropped_as_block_header() {
let text = r#"provider = "provider-secret-value"
"#;
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(value_of(&pairs, "provider"), Some("provider-secret-value"));
assert_eq!(line_of(&pairs, "provider"), Some(1));
}
#[test]
fn long_heredoc_inside_default_map_does_not_end_variable_scan() {
let heredoc_body = (0..24)
.map(|idx| format!("certificate-line-{idx}"))
.collect::<Vec<_>>()
.join("\n");
let text = format!(
"variable \"api_credentials\" {{\n default = {{\n cert = <<EOF\n{heredoc_body}\nEOF\n password = \"after-long-heredoc-secret\"\n }}\n}}\n"
);
let pairs = hcl_contract::parse_hcl(&text);
assert_eq!(
value_of(&pairs, "api_credentials.password"),
Some("after-long-heredoc-secret"),
"heredoc payload lines must not consume the variable-block structural lookahead budget"
);
assert_eq!(
value_of(&pairs, "password"),
None,
"post-heredoc map entries must keep variable default-map context"
);
}
#[test]
fn single_line_stripper_never_diverges_from_the_one_owner() {
let cases = [
r#"key = "value""#,
"key = 1 # trailing hash",
"key = 2 // trailing slashes",
r#"key = "http://not-a-comment.example""#,
r#"key = "a # b // c""#,
"a = 1 /* mid */ b = 2",
"/* leading */ c = 3",
"d = 4 /* unterminated block to end of line",
r#"e = "quote holding /* not a comment */ inside""#,
"plain line, no comment at all",
"",
];
for case in cases {
let mut in_block = false;
let owner = hcl_contract::strip_hcl_comments(case, &mut in_block);
assert_eq!(
hcl_contract::strip_hcl_line_comment(case),
owner,
"single-line stripper diverged from strip_hcl_comments on {case:?}"
);
}
}
#[test]
fn strip_hcl_line_comment_preserves_code_after_inline_block_comment() {
let stripped = hcl_contract::strip_hcl_line_comment(r#"a = "drop" /* note */ b = "keep""#);
assert!(
stripped.contains(r#"b = "keep""#),
"inline block comment truncated the line, losing trailing code: {stripped:?}"
);
assert!(
!stripped.contains("note"),
"block comment interior leaked into stripped code: {stripped:?}"
);
}
#[test]
fn strip_hcl_line_comment_still_strips_line_comments_but_not_in_strings() {
assert_eq!(
hcl_contract::strip_hcl_line_comment("k = 1 # rotate me").trim(),
"k = 1"
);
assert_eq!(
hcl_contract::strip_hcl_line_comment("k = 2 // rotate me").trim(),
"k = 2"
);
assert_eq!(
hcl_contract::strip_hcl_line_comment(r#"url = "https://example.com/path""#).trim(),
r#"url = "https://example.com/path""#
);
}
#[test]
fn parse_hcl_extracts_value_before_inline_block_comment() {
let pairs =
hcl_contract::parse_hcl(r#"api_token = "TOKEN_abc123def456ghi789" /* rotate quarterly */"#);
assert_eq!(
value_of(&pairs, "api_token"),
Some("TOKEN_abc123def456ghi789"),
"value before an inline block comment was dropped: {pairs:?}"
);
}
#[test]
fn parse_hcl_resumes_after_multiline_block_comment() {
let text = "/* opening\n spanning\n three lines */\napi_key = \"KEY_zzz999yyy888www\"\n";
let pairs = hcl_contract::parse_hcl(text);
assert_eq!(
value_of(&pairs, "api_key"),
Some("KEY_zzz999yyy888www"),
"assignment after a multi-line block comment was dropped: {pairs:?}"
);
}
#[test]
fn parse_hcl_keeps_value_before_trailing_line_comment() {
for line in [
"token = \"KEEPVAL_aa11bb22cc\" # trailing hash comment",
"token = \"KEEPVAL_aa11bb22cc\" // trailing slash comment",
] {
let pairs = hcl_contract::parse_hcl(line);
assert_eq!(
value_of(&pairs, "token"),
Some("KEEPVAL_aa11bb22cc"),
"value dropped by a trailing line comment in {line:?}: {pairs:?}"
);
}
}
mod hcl_robustness_fuzz {
use super::hcl_contract;
use proptest::prelude::*;
const HCL_ALPHABET: &[u8] = b"#/*\"'`\\=<>{}-\n abv12";
proptest! {
#![proptest_config(ProptestConfig::with_cases(3000))]
#[test]
fn parse_hcl_is_total_and_deterministic_on_structural_fuzz(
bytes in prop::collection::vec(
(0usize..HCL_ALPHABET.len()).prop_map(|i| HCL_ALPHABET[i]),
0..240usize,
),
) {
let input = String::from_utf8(bytes).expect("HCL_ALPHABET is ASCII");
let pairs = hcl_contract::parse_hcl(&input);
prop_assert!(
pairs.iter().all(|p| p.line >= 1),
"every extracted pair must carry a 1-based line number: {pairs:?}"
);
let again = hcl_contract::parse_hcl(&input);
prop_assert_eq!(
again.len(),
pairs.len(),
"parse_hcl was not deterministic for the same input"
);
}
}
}