Skip to content
This repository was archived by the owner on Mar 17, 2026. It is now read-only.

fix(engines): resolve format variables and multi variables#87

Merged
jyecusch merged 11 commits intomainfrom
resolve-format-variables
Oct 2, 2025
Merged

fix(engines): resolve format variables and multi variables#87
jyecusch merged 11 commits intomainfrom
resolve-format-variables

Conversation

@HomelessDinosaur
Copy link
Copy Markdown
Member

@HomelessDinosaur HomelessDinosaur commented Sep 25, 2025

Closes NIT-339

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 25, 2025

📝 Walkthrough

Walkthrough

Replaces inline string token handling with a dedicated token-resolution flow: new helpers resolveTokenValue, resolveStringInterpolation, and resolveToken, and token utilities TokenMatch, findAllTokens, and isOnlyToken. resolveToken validates and resolves sources infra, self, and var (including multi-path self lookups via cdktf.Fn_Lookup). Multi-token strings are interpolated right-to-left with type checks; a single-token string uses direct token resolution. resolveValue delegates string cases to this flow; maps and arrays remain recursively resolved and only non-nil results are written to modules. Adds tighter error messages, unit tests, and Makefile test targets.

Possibly related PRs

Suggested labels

released

Suggested reviewers

  • jyecusch
  • davemooreuws
  • tjholm

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive The description only references the issue “Closes NIT-339” without explaining how the pull request addresses format or multi-variable resolution, making it too generic to convey the changes. Please add a brief summary of the specific changes and their impact so reviewers understand what is being implemented and fixed.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly indicates that the engines module has been updated to resolve both single-format variables and multiple-token variables, which aligns with the core changes introducing dedicated token-resolution functions and multi-token interpolation in Terraform support. It is concise, clear, and focused on the main improvement.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a7987d and b8d30bd.

📒 Files selected for processing (2)
  • engines/terraform/resolve.go (2 hunks)
  • engines/terraform/token.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
engines/terraform/resolve.go (2)
engines/terraform/deployment.go (1)
  • TerraformDeployment (16-28)
engines/terraform/terraform.go (1)
  • SpecReference (46-49)
🔇 Additional comments (1)
engines/terraform/token.go (1)

45-46: Guard against nil slices when matching tokens

allTokensPattern.FindString(input) returns an empty string (the zero value) when no token is found, so trimming it is fine. No changes needed.

@davemooreuws davemooreuws changed the title fix: resolve format variables and multi variables fix(engines): resolve format variables and multi variables Sep 25, 2025
tjholm
tjholm previously requested changes Sep 28, 2025
Copy link
Copy Markdown
Member

@tjholm tjholm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking as request changes as I submitted the last review item as just a comment.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8d30bd and a0d13ac.

📒 Files selected for processing (2)
  • engines/terraform/resolve.go (2 hunks)
  • engines/terraform/token.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
engines/terraform/resolve.go (3)
engines/terraform/deployment.go (1)
  • TerraformDeployment (16-28)
engines/terraform/terraform.go (1)
  • SpecReference (45-48)
engines/terraform/platform.go (1)
  • ResourceBlueprint (233-238)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0d13ac and 5154e9c.

📒 Files selected for processing (1)
  • engines/terraform/resolve.go (2 hunks)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
engines/terraform/resolve.go (1)

136-156: Byte-offset slicing can panic on multi-byte UTF-8 characters.

token.Start and token.End are byte offsets. If the input string contains multi-byte UTF-8 characters before a token, slicing by byte index can split a rune and cause a panic or corruption.

Convert to rune-aware slicing:

 	result := input
+	runes := []rune(result)
 	for i := len(tokens) - 1; i >= 0; i-- {
 		token := tokens[i]
 
 		specRef, err := SpecReferenceFromToken(token.Token)
 		if err != nil {
 			continue
 		}
 
 		tokenValue, err := td.resolveToken(intentName, specRef)
 		if err != nil {
 			return nil, err
 		}
 
 		// Convert token to string representation for concatenation
 		replacement := cdktf.Token_AsString(tokenValue, nil)
 		if replacement == nil {
 			return nil, fmt.Errorf("failed to convert token %s to string", token.Token)
 		}
 
-		result = result[:token.Start] + *replacement + result[token.End:]
+		// Convert byte offsets to rune offsets
+		runeStart := len([]rune(input[:token.Start]))
+		runeEnd := len([]rune(input[:token.End]))
+		runes = append(append(runes[:runeStart], []rune(*replacement)...), runes[runeEnd:]...)
 	}
+	result = string(runes)
 
 	return result, nil
 }
🧹 Nitpick comments (3)
engines/Makefile (1)

14-15: Verify gotestsum availability before running test-pretty.

The target assumes gotest.tools/gotestsum is installed. Consider checking or documenting the prerequisite.

 test-pretty:
+	@which gotestsum > /dev/null || (echo "gotestsum not found. Install with: go install gotest.tools/gotestsum@latest" && exit 1)
 	@go tool gotest.tools/gotestsum --format=pkgname ./terraform/...
engines/terraform/resolve_test.go (1)

1-200: Expand test coverage for infra/var sources and multi-token substitution.

The tests validate self-references and token parsing but don't cover:

  • resolveToken with "infra" source (success/error paths)
  • resolveToken with "var" source (success/error paths)
  • resolveTokenString with multiple tokens in a single string (e.g., "prefix-${infra.a.b}-middle-${var.c}-suffix")

Consider adding tests like:

func TestResolveToken_InfraReference(t *testing.T) {
	// Test infra resource resolution with valid/missing attributes
}

func TestResolveToken_VarReference(t *testing.T) {
	// Test platform variable resolution
}

func TestResolveTokenString_MultipleTokens(t *testing.T) {
	// Test string with multiple embedded tokens
}
engines/terraform/resolve.go (1)

149-153: Token_AsString can fail for complex types.

cdktf.Token_AsString returns nil for values that cannot be represented as strings (e.g., deeply nested maps). The error message could be more actionable.

 		replacement := cdktf.Token_AsString(tokenValue, nil)
 		if replacement == nil {
-			return nil, fmt.Errorf("failed to convert token %s to string", token.Token)
+			return nil, fmt.Errorf("token %s resolved to a type that cannot be embedded in a string (only single tokens may resolve to complex types)", token.Token)
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cacbd1a and 3b2f583.

📒 Files selected for processing (3)
  • engines/Makefile (1 hunks)
  • engines/terraform/resolve.go (3 hunks)
  • engines/terraform/resolve_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
engines/terraform/resolve_test.go (3)
engines/terraform/terraform.go (1)
  • SpecReference (45-48)
engines/terraform/resolve.go (1)
  • SpecReferenceFromToken (11-26)
engines/terraform/deployment.go (1)
  • TerraformDeployment (16-28)
engines/terraform/resolve.go (3)
engines/terraform/deployment.go (1)
  • TerraformDeployment (16-28)
engines/terraform/terraform.go (1)
  • SpecReference (45-48)
engines/terraform/platform.go (1)
  • ResourceBlueprint (233-238)
🪛 checkmake (0.2.2)
engines/Makefile

[warning] 1-1: Missing required phony target "all"

(minphony)


[warning] 1-1: Missing required phony target "clean"

(minphony)


[warning] 1-1: Missing required phony target "test"

(minphony)

🔇 Additional comments (10)
engines/terraform/resolve_test.go (3)

11-96: LGTM! Comprehensive token parsing validation.

The test covers valid infra/self/var references, nested paths, and multiple error cases with clear assertions.


98-180: LGTM! Self-reference resolution properly validated.

Tests cover error cases (missing variable name, non-existent variable) and success cases (single and nested paths) with proper setup.


182-200: LGTM! Unknown source error handling verified.

engines/terraform/resolve.go (7)

11-26: LGTM! Token parsing with proper validation.

The function correctly extracts token contents and validates minimum path requirements.


28-58: LGTM! Proper delegation to resolveTokenString.

String values now delegate to the dedicated token resolution flow, while maintaining recursive traversal for maps and arrays.


60-80: LGTM! Infra resolution with multi-path support and validation.

The code now:

  • Validates minimum path length
  • Joins all path segments beyond the resource name
  • Returns descriptive errors for missing attributes

This addresses past review feedback.


81-98: LGTM! Self-reference resolution with multi-path support.

The code validates variable names and handles nested paths via cdktf.Fn_Lookup, addressing past review feedback about multi-path cases.


100-117: LGTM! Var resolution with validation.

Platform variable resolution includes proper validation and descriptive errors.


119-134: LGTM! Single-token optimization is sound.

When a string contains only one token, returning the resolved value directly (without string conversion) preserves type information.


161-173: LGTM! Nil check prevents writing unresolved values.

The guard on line 167 prevents writing nil to modules, addressing the NIT-339 panic.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b2f583 and 0a59309.

📒 Files selected for processing (1)
  • engines/Makefile (1 hunks)
🧰 Additional context used
🪛 checkmake (0.2.2)
engines/Makefile

[warning] 2-2: Missing required phony target "all"

(minphony)


[warning] 2-2: Missing required phony target "clean"

(minphony)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
engines/terraform/resolve.go (2)

11-26: Improve error messages with examples.

The generic "invalid reference format" doesn't guide users on what's expected. Consider adding examples in the error messages.

Apply this diff:

 func SpecReferenceFromToken(token string) (*SpecReference, error) {
 	contents, ok := extractTokenContents(token)
 	if !ok {
-		return nil, fmt.Errorf("invalid reference format")
+		return nil, fmt.Errorf("invalid reference format: token must be in the form ${source.path} (e.g., ${infra.db.outputs}, ${self.my_var})")
 	}
 
 	parts := strings.Split(contents, ".")
 	if len(parts) < 2 {
-		return nil, fmt.Errorf("invalid reference format")
+		return nil, fmt.Errorf("invalid reference format: token must have at least a source and one path component (e.g., ${infra.db}, ${self.my_var})")
 	}
 
 	return &SpecReference{
 		Source: parts[0],
 		Path:   parts[1:],
 	}, nil
 }

118-135: LGTM! Consider logging malformed token attempts.

The routing logic correctly preserves types for single-token values and delegates to string interpolation for multiple tokens. Line 128 silently ignores malformed tokens—this may be intentional for backward compatibility, but consider logging a warning to help debug configuration issues.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a59309 and 7468488.

📒 Files selected for processing (1)
  • engines/terraform/resolve.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
engines/terraform/resolve.go (4)
engines/terraform/deployment.go (1)
  • TerraformDeployment (16-28)
engines/terraform/terraform.go (1)
  • SpecReference (45-48)
engines/terraform/token.go (1)
  • TokenMatch (8-13)
engines/terraform/platform.go (1)
  • ResourceBlueprint (233-238)
🔇 Additional comments (3)
engines/terraform/resolve.go (3)

28-58: LGTM!

Clean refactor that properly delegates string token resolution to the new helper while maintaining recursive resolution for maps and arrays.


163-175: LGTM! Nil check prevents panic.

The nil check (line 169) correctly prevents setting nil values on modules, addressing the panic mentioned in NIT-339 per the past review comment.


99-111: Consistent multi-path handling for var source
The var case only uses specRef.Path[0] and ignores any additional segments, unlike self which supports nested lookup via Fn_Lookup. If your platform variables may be maps/objects (e.g. ${var.config.endpoint}), choose one of:

  • Implement multi-path resolution for var (as in lines 93–96)
  • Or reject/throw an error when len(specRef.Path) > 1 to avoid silent drops

Please confirm the intended behavior for nested var tokens and update accordingly.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7468488 and bf39d6a.

📒 Files selected for processing (3)
  • engines/terraform/resolve.go (3 hunks)
  • engines/terraform/resolve_test.go (1 hunks)
  • engines/terraform/token.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • engines/terraform/resolve_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
engines/terraform/resolve.go (4)
engines/terraform/deployment.go (1)
  • TerraformDeployment (16-28)
engines/terraform/terraform.go (1)
  • SpecReference (45-48)
engines/terraform/token.go (1)
  • TokenMatch (8-13)
engines/terraform/platform.go (1)
  • ResourceBlueprint (233-238)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
engines/terraform/resolve.go (2)

14-27: Consider more specific error messages for better developer experience.

The validation logic is sound, but generic "invalid reference format" messages don't guide users toward fixing their tokens. Past reviewers suggested context-specific messages like "'${var.}' doesn't contain a valid variable reference" or "infra reference requires at least 2 path components."

Consider something like:

 func SpecReferenceFromToken(token string) (*SpecReference, error) {
 	contents, ok := extractTokenContents(token)
 	if !ok {
-		return nil, fmt.Errorf("invalid reference format")
+		return nil, fmt.Errorf("invalid token format: %s", token)
 	}
 
 	parts := strings.Split(contents, ".")
 	if len(parts) < 2 {
-		return nil, fmt.Errorf("invalid reference format")
+		return nil, fmt.Errorf("reference '%s' must include a source and at least one path component (e.g., ${var.name})", token)
 	}
 
 	// Validate that all path components are non-empty
 	for _, part := range parts[1:] {
 		if part == "" {
-			return nil, fmt.Errorf("invalid reference format")
+			return nil, fmt.Errorf("reference '%s' contains empty path components", token)
 		}
 	}

106-122: Nested var lookups now work, but error message needs refinement.

The Fn_Lookup implementation (lines 118-121) correctly handles ${var.config.endpoint} and addresses prior concerns. However, line 108 formats specRef.Source (always "var") instead of providing useful context.

Consider:

 	if len(specRef.Path) < 1 {
-		return nil, fmt.Errorf("var `%s` doesn't contain a valid variable reference", specRef.Source)
+		return nil, fmt.Errorf("var reference must specify a variable name (e.g., ${var.my_var})")
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf39d6a and 0c95d0a.

📒 Files selected for processing (2)
  • engines/terraform/resolve.go (3 hunks)
  • engines/terraform/resolve_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • engines/terraform/resolve_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
engines/terraform/resolve.go (4)
engines/terraform/deployment.go (1)
  • TerraformDeployment (16-28)
engines/terraform/terraform.go (1)
  • SpecReference (45-48)
engines/terraform/token.go (1)
  • TokenMatch (8-13)
engines/terraform/platform.go (1)
  • ResourceBlueprint (233-238)
🔇 Additional comments (5)
engines/terraform/resolve.go (5)

35-65: LGTM!

The delegation to resolveTokenValue for strings cleanly separates concerns, and the recursive handling of maps and arrays is correct.


67-86: Excellent fix for nested infra paths and missing attributes.

The change from specRef.Path[1] to strings.Join(specRef.Path[1:], ".") (line 75) correctly handles multi-level lookups like ${infra.db.outputs.endpoint}, and returning an error for missing attributes (lines 83-85) prevents silent failures. Both address prior critical concerns.


88-104: LGTM! Nested self lookups now supported.

The addition of Fn_Lookup for multi-segment paths (lines 100-103) correctly resolves references like ${self.config.endpoint}, addressing the past concern about multi-path cases.


129-172: LGTM! Clean separation of single-token vs multi-token handling.

The logic correctly preserves native types for single-token strings (lines 136-142) while converting multi-token strings via interpolation (line 145). The error message at lines 165-166 is clear and actionable when a non-string-compatible value appears in interpolation context.


174-186: LGTM! Nil check prevents panics.

The nil guard (line 180) before setting module properties correctly prevents the panic described in NIT-339.

@jyecusch jyecusch merged commit ad3ca7d into main Oct 2, 2025
3 checks passed
@jyecusch jyecusch deleted the resolve-format-variables branch October 2, 2025 03:35
@nitric-bot
Copy link
Copy Markdown

🎉 This PR is included in version 0.1.13 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants