@@ -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
343351func 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
352392func 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