github.com/mineiros-io/terradoc@v0.0.9-0.20220711062319-018bd4ae81f5/internal/parsers/hclparser/hcltype.go (about) 1 package hclparser 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/hashicorp/hcl/v2/hclsyntax" 9 "github.com/mineiros-io/terradoc/internal/entities" 10 "github.com/mineiros-io/terradoc/internal/types" 11 "github.com/zclconf/go-cty/cty" 12 "github.com/zclconf/go-cty/cty/function" 13 "github.com/zclconf/go-cty/cty/gocty" 14 ) 15 16 func GetVarTypeFromExpression(expr hcl.Expression) (entities.Type, error) { 17 return getTypeFromExpression(expr, varFunctions()) 18 } 19 20 func GetOutputTypeFromExpression(expr hcl.Expression) (entities.Type, error) { 21 return getTypeFromExpression(expr, outputFunctions()) 22 } 23 24 func getTypeFromExpression(expr hcl.Expression, ctxFunctions map[string]function.Function) (entities.Type, error) { 25 kw := hcl.ExprAsKeyword(expr) 26 27 switch kw { 28 case "string", "number", "bool", "any": 29 tfType, ok := types.TerraformTypes(kw) 30 if !ok { 31 return entities.Type{}, fmt.Errorf("could not get terraform type for %q", kw) 32 } 33 34 return entities.Type{TFType: tfType}, nil 35 case "list", "object", "map", "tuple": 36 // invalid as these types should be function calls 37 return entities.Type{}, fmt.Errorf("type %q needs an argument", kw) 38 } 39 40 // TODO: how to make this decent? 41 if kw != "" && !(strings.HasPrefix(kw, "list") || 42 strings.HasPrefix(kw, "object") || 43 strings.HasPrefix(kw, "map") || 44 strings.HasPrefix(kw, "tuple")) { 45 return entities.Type{}, fmt.Errorf("type %q is invalid", kw) 46 } 47 48 return getComplexType(expr, ctxFunctions) 49 } 50 51 // this function exists to make it possible to parse `type` attribute expressions and `readme_type` 52 // attribute strings in the same way, so they are compatible even though they have different types 53 func getVarTypeFromString(str string, startRange hcl.Pos) (entities.Type, error) { 54 expr, parseDiags := hclsyntax.ParseExpression([]byte(str), "", startRange) 55 if parseDiags.HasErrors() { 56 return entities.Type{}, fmt.Errorf("parsing type string expression: %v", parseDiags.Errs()) 57 } 58 59 return GetVarTypeFromExpression(expr) 60 } 61 62 func getComplexType(expr hcl.Expression, ctxFunctions map[string]function.Function) (entities.Type, error) { 63 got, exprDiags := expr.Value(getEvalContextForExpr(expr, ctxFunctions)) 64 if exprDiags.HasErrors() { 65 return entities.Type{}, fmt.Errorf("getting expression value: %v", exprDiags.Errs()) 66 } 67 var tfType, nestedTFType types.TerraformType 68 69 err := gocty.FromCtyValue(got.GetAttr("type"), &tfType) 70 if err != nil { 71 return entities.Type{}, fmt.Errorf("getting type definition: %v", err) 72 } 73 74 err = gocty.FromCtyValue(got.GetAttr("nestedType"), &nestedTFType) 75 if err != nil { 76 return entities.Type{}, fmt.Errorf("getting nested type definition: %v", err) 77 } 78 79 var typeLabel, nestedTypeLabel string 80 err = gocty.FromCtyValue(got.GetAttr("typeLabel"), &typeLabel) 81 if err != nil { 82 return entities.Type{}, fmt.Errorf("getting type label: %v", err) 83 } 84 85 err = gocty.FromCtyValue(got.GetAttr("nestedTypeLabel"), &nestedTypeLabel) 86 if err != nil { 87 return entities.Type{}, fmt.Errorf("getting nested type label: %v", err) 88 } 89 90 typeDef := entities.Type{ 91 TFType: tfType, 92 Label: typeLabel, 93 } 94 95 if nestedTFType != types.TerraformEmptyType { 96 typeDef.Nested = &entities.Type{ 97 TFType: nestedTFType, 98 Label: nestedTypeLabel, 99 } 100 } 101 102 return typeDef, nil 103 } 104 105 func nestedTypeFunc(tfType types.TerraformType) function.Function { 106 return function.New(&function.Spec{ 107 Params: []function.Parameter{ 108 { 109 Name: "nestedTypeLabel", 110 Type: cty.String, 111 AllowDynamicType: true, 112 }, 113 }, 114 Type: function.StaticReturnType(cty.Object(typeObj())), 115 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 116 var nestedType types.TerraformType 117 var nestedLabel string 118 119 nestedTypeName := args[0].AsString() 120 121 nestedType, ok := types.TerraformTypes(nestedTypeName) 122 if !ok { 123 nestedType = types.TerraformObject 124 nestedLabel = nestedTypeName 125 } 126 127 return cty.ObjectVal(map[string]cty.Value{ 128 "type": cty.NumberIntVal(int64(tfType)), 129 "typeLabel": cty.StringVal(""), // need to pass empty value here so cty doesn't panic 130 "nestedType": cty.NumberIntVal(int64(nestedType)), 131 "nestedTypeLabel": cty.StringVal(nestedLabel), 132 }), nil 133 }, 134 }) 135 } 136 137 func complexTypeFunc(tfType types.TerraformType) function.Function { 138 return function.New(&function.Spec{ 139 Params: []function.Parameter{ 140 { 141 Name: "typeLabel", 142 Type: cty.String, 143 AllowDynamicType: true, 144 }, 145 }, 146 Type: function.StaticReturnType(cty.Object(typeObj())), 147 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 148 typeLabel := args[0].AsString() 149 150 return cty.ObjectVal(map[string]cty.Value{ 151 "type": cty.NumberIntVal(int64(tfType)), 152 "typeLabel": cty.StringVal(typeLabel), 153 // the following empty values need to be set to the attributes 154 // otherwise cty panics 155 "nestedType": cty.NumberIntVal(int64(types.TerraformEmptyType)), 156 "nestedTypeLabel": cty.StringVal(""), // 157 }), nil 158 }, 159 }) 160 } 161 162 func getVariablesMap(expr hcl.Expression) map[string]cty.Value { 163 varMap := make(map[string]cty.Value) 164 for _, variable := range expr.Variables() { 165 name := variable.RootName() 166 167 varMap[name] = cty.StringVal(name) 168 } 169 170 return varMap 171 } 172 173 func getEvalContextForExpr(expr hcl.Expression, ctxFunctions map[string]function.Function) *hcl.EvalContext { 174 return &hcl.EvalContext{ 175 Functions: ctxFunctions, 176 Variables: getVariablesMap(expr), 177 } 178 } 179 180 func varFunctions() map[string]function.Function { 181 return map[string]function.Function{ 182 "object": complexTypeFunc(types.TerraformObject), 183 "map": nestedTypeFunc(types.TerraformMap), 184 "list": nestedTypeFunc(types.TerraformList), 185 "set": nestedTypeFunc(types.TerraformSet), 186 } 187 } 188 189 func outputFunctions() map[string]function.Function { 190 return map[string]function.Function{ 191 "resource": complexTypeFunc(types.TerraformResource), 192 "object": complexTypeFunc(types.TerraformObject), 193 "map": nestedTypeFunc(types.TerraformMap), 194 "list": nestedTypeFunc(types.TerraformList), 195 "set": nestedTypeFunc(types.TerraformSet), 196 } 197 } 198 199 func typeObj() map[string]cty.Type { 200 return map[string]cty.Type{ 201 "type": cty.Number, 202 "typeLabel": cty.String, 203 "nestedType": cty.Number, 204 "nestedTypeLabel": cty.String, 205 } 206 }