Skip to content

Commit 8f22300

Browse files
authored
generator: add tests and improve error handling (#992)
1 parent 12bc504 commit 8f22300

File tree

2 files changed

+378
-3
lines changed

2 files changed

+378
-3
lines changed

rules/models/generator/main.go

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ func main() {
113113
continue
114114
}
115115
if len(vars) > 1 {
116-
fmt.Fprintf(os.Stderr, "Error: `%s.%s` expression references multiple variables, only one shape allowed\n", mapping.Resource, attribute)
116+
varNames := make([]string, len(vars))
117+
for i, v := range vars {
118+
varNames[i] = v.RootName()
119+
}
120+
fmt.Fprintf(os.Stderr, "Error: `%s.%s` expression references %d variables (%v), expected exactly 1\n",
121+
mapping.Resource, attribute, len(vars), varNames)
117122
os.Exit(1)
118123
}
119124
shapeName := vars[0].RootName()
@@ -129,12 +134,15 @@ func main() {
129134

130135
// Populate the eval context with this shape's enum values
131136
if enumValues, ok := model["enum"].([]string); ok {
132-
evalCtx.Variables[shapeName] = stringsToCtyList(enumValues)
137+
// Use fresh Variables map for each evaluation to avoid pollution
138+
evalCtx.Variables = map[string]cty.Value{
139+
shapeName: stringsToCtyList(enumValues),
140+
}
133141

134142
// Evaluate the expression to get transformed enum values
135143
result, diags := value.Expr.Value(evalCtx)
136144
if !diags.HasErrors() && result.Type().IsListType() {
137-
var transformedEnums []string
145+
transformedEnums := make([]string, 0, result.LengthInt())
138146
for it := result.ElementIterator(); it.Next(); {
139147
_, val := it.Element()
140148
transformedEnums = append(transformedEnums, val.AsString())
@@ -341,13 +349,45 @@ func convertSmithyShape(rawShape map[string]interface{}) map[string]interface{}
341349

342350
// stringsToCtyList converts a slice of strings to a cty list value
343351
func stringsToCtyList(values []string) cty.Value {
352+
if len(values) == 0 {
353+
return cty.ListValEmpty(cty.String)
354+
}
344355
ctyValues := make([]cty.Value, 0, len(values))
345356
for _, v := range values {
346357
ctyValues = append(ctyValues, cty.StringVal(v))
347358
}
348359
return cty.ListVal(ctyValues)
349360
}
350361

362+
// HCL Transform System
363+
// ====================
364+
//
365+
// The generator supports HCL function-based transforms for enum values in mapping files.
366+
// This allows enum values from Smithy models to be transformed to match Terraform provider
367+
// expectations without hardcoding transformation logic in Go.
368+
//
369+
// Available Functions:
370+
// - uppercase(list) - Converts all strings in list to uppercase
371+
// - replace(list, old, new) - Replaces substring in all strings
372+
//
373+
// Usage Examples in mappings/*.hcl:
374+
// compression_type = uppercase(CompressionTypeValue)
375+
// -> ["gzip", "none"] becomes ["GZIP", "NONE"]
376+
//
377+
// encryption_mode = uppercase(replace(EncryptionModeValue, "-", "_"))
378+
// -> ["sse-kms", "sse-s3"] becomes ["SSE_KMS", "SSE_S3"]
379+
//
380+
// Adding New Transform Functions:
381+
// 1. Add function to buildEvalContext() Functions map
382+
// 2. Wrap stdlib function with makeListTransformFunction()
383+
// 3. Example: "lowercase": makeListTransformFunction(stdlib.LowerFunc)
384+
//
385+
// Technical Details:
386+
// - Transform functions operate on lists of strings (enum values)
387+
// - Each expression must reference exactly one shape variable
388+
// - Functions can be composed: uppercase(replace(x, "-", "_"))
389+
// - HCL's native expression evaluation handles all transforms
390+
351391
// buildEvalContext creates an HCL evaluation context with transform functions
352392
func buildEvalContext() *hcl.EvalContext {
353393
return &hcl.EvalContext{
@@ -377,6 +417,11 @@ func makeListTransformFunction(strFunc function.Function) function.Function {
377417
return cty.NilVal, fmt.Errorf("first argument must be a list")
378418
}
379419

420+
// Handle empty list
421+
if args[0].LengthInt() == 0 {
422+
return cty.ListValEmpty(cty.String), nil
423+
}
424+
380425
var results []cty.Value
381426
for it := args[0].ElementIterator(); it.Next(); {
382427
_, val := it.Element()

0 commit comments

Comments
 (0)