@@ -8,42 +8,267 @@ import "C"
8
8
import (
9
9
"encoding/json"
10
10
"fmt"
11
- "strings "
12
-
11
+ "github.com/hashicorp/hcl/v2 "
12
+ "github.com/hashicorp/hcl/v2/ext/tryfunc"
13
13
"github.com/hashicorp/hcl/v2/hclparse"
14
+ "github.com/hashicorp/hcl/v2/hclsyntax"
15
+ "github.com/zclconf/go-cty/cty"
16
+ "github.com/zclconf/go-cty/cty/convert"
17
+ "github.com/zclconf/go-cty/cty/function"
18
+ "github.com/zclconf/go-cty/cty/function/stdlib"
19
+ "strings"
14
20
)
15
21
16
22
//export Parse
17
23
func Parse (a * C.char ) (resp C.parseResponse ) {
18
- defer func () {
19
- if err := recover (); err != nil {
24
+ defer func () {
25
+ if err := recover (); err != nil {
20
26
retValue := fmt .Sprintf ("panic HCL: %v" , err )
21
27
resp = C.parseResponse {nil , C .CString (retValue )}
22
- }
23
- }()
28
+ }
29
+ }()
24
30
25
31
input := C .GoString (a )
26
32
hclFile , diags := hclparse .NewParser ().ParseHCL ([]byte (input ), "tmp.hcl" )
27
33
if diags .HasErrors () {
28
- errors := make ([]string , 0 , len (diags ))
29
- for _ , diag := range diags {
30
- errors = append (errors , diag .Error ())
31
- }
32
-
33
- return C.parseResponse {nil , C .CString (fmt .Sprintf ("invalid HCL: %s" , strings .Join (errors , ", " )))}
34
+ return C.parseResponse {nil , C .CString (diagErrorsToString (diags , "invalid HCL: %s" ))}
34
35
}
35
36
hclMap , err := convertFile (hclFile )
36
37
if err != nil {
37
38
return C.parseResponse {nil , C .CString (fmt .Sprintf ("cannot convert HCL to Go map representation: %s" , err ))}
38
39
}
39
40
hclInJson , err := json .Marshal (hclMap )
40
41
if err != nil {
41
- return C.parseResponse {nil , C .CString (fmt .Sprintf ("cannot Go map representation to JSON: %s" , err ))}
42
+ return C.parseResponse {nil , C .CString (fmt .Sprintf ("cannot convert Go map representation to JSON: %s" , err ))}
42
43
}
43
44
resp = C.parseResponse {C .CString (string (hclInJson )), nil }
44
45
45
46
return
46
47
}
47
48
48
- func main () {
49
+ //export ParseAttributes
50
+ func ParseAttributes (a * C.char ) (resp C.parseResponse ) {
51
+ defer func () {
52
+ if err := recover (); err != nil {
53
+ retValue := fmt .Sprintf ("panic HCL: %v" , err )
54
+ resp = C.parseResponse {nil , C .CString (retValue )}
55
+ }
56
+ }()
57
+
58
+ input := C .GoString (a )
59
+ hclFile , parseDiags := hclsyntax .ParseConfig ([]byte (input ), "tmp.hcl" , hcl .InitialPos )
60
+ if parseDiags .HasErrors () {
61
+ return C.parseResponse {nil , C .CString (diagErrorsToString (parseDiags , "invalid HCL: %s" ))}
62
+ }
63
+
64
+ var diags hcl.Diagnostics
65
+ hclMap := make (jsonObj )
66
+ c := converter {}
67
+
68
+ attrs , attrsDiags := hclFile .Body .JustAttributes ()
69
+ diags = diags .Extend (attrsDiags )
70
+
71
+ for _ , attr := range attrs {
72
+ _ , valueDiags := attr .Expr .Value (nil )
73
+ diags = diags .Extend (valueDiags )
74
+ if valueDiags .HasErrors () {
75
+ continue
76
+ }
77
+
78
+ value , err := c .convertExpression (attr .Expr .(hclsyntax.Expression ))
79
+ if err != nil {
80
+ diags .Append (& hcl.Diagnostic {
81
+ Severity : hcl .DiagError ,
82
+ Summary : "Error processing variable value" ,
83
+ Detail : fmt .Sprintf ("Cannot convert HCL to Go map representation: %s." , err ),
84
+ Subject : attr .NameRange .Ptr (),
85
+ })
86
+ continue
87
+ }
88
+
89
+ hclMap [attr .Name ] = value
90
+ }
91
+
92
+ hclInJson , err := json .Marshal (hclMap )
93
+ if err != nil {
94
+ diags .Append (& hcl.Diagnostic {
95
+ Severity : hcl .DiagError ,
96
+ Summary : "Error preparing JSON result" ,
97
+ Detail : fmt .Sprintf ("Cannot convert Go map representation to JSON: %s." , err ),
98
+ })
99
+ return C.parseResponse {nil , C .CString (diagErrorsToString (diags , "" ))}
100
+ }
101
+ if diags .HasErrors () {
102
+ resp = C.parseResponse {C .CString (string (hclInJson )), C .CString (diagErrorsToString (diags , "" ))}
103
+ } else {
104
+ resp = C.parseResponse {C .CString (string (hclInJson )), nil }
105
+ }
106
+
107
+ return
108
+ }
109
+
110
+ //export EvalValidationRule
111
+ func EvalValidationRule (c * C.char , e * C.char , n * C.char , v * C.char ) (resp * C.char ) {
112
+ defer func () {
113
+ if err := recover (); err != nil {
114
+ retValue := fmt .Sprintf ("panic HCL: %v" , err )
115
+ resp = C .CString (retValue )
116
+ }
117
+ }()
118
+
119
+ condition := C .GoString (c )
120
+ errorMsg := C .GoString (e )
121
+ varName := C .GoString (n )
122
+ varValue := C .GoString (v )
123
+
124
+ // First evaluate variable value to get its cty representation
125
+
126
+ varValueCty , diags := expressionValue (varValue , nil )
127
+ if diags .HasErrors () {
128
+ if containsError (diags , "Variables not allowed" ) {
129
+ // Try again to handle the case when a string value was provided without enclosing quotes
130
+ varValueCty , diags = expressionValue (fmt .Sprintf ("%q" , varValue ), nil )
131
+ }
132
+ }
133
+ if diags .HasErrors () {
134
+ return C .CString (diagErrorsToString (diags , "cannot process variable value: %s" ))
135
+ }
136
+
137
+ // Now evaluate the condition
138
+
139
+ hclCtx := & hcl.EvalContext {
140
+ Variables : map [string ]cty.Value {
141
+ "var" : cty .ObjectVal (map [string ]cty.Value {
142
+ varName : varValueCty ,
143
+ }),
144
+ },
145
+ Functions : knownFunctions ,
146
+ }
147
+ conditionCty , diags := expressionValue (condition , hclCtx )
148
+ if diags .HasErrors () {
149
+ return C .CString (diagErrorsToString (diags , "cannot process condition expression: %s" ))
150
+ }
151
+
152
+ if conditionCty .IsNull () {
153
+ return C .CString ("condition expression result is null" )
154
+ }
155
+
156
+ conditionCty , err := convert .Convert (conditionCty , cty .Bool )
157
+ if err != nil {
158
+ return C .CString ("condition expression result must be bool" )
159
+ }
160
+
161
+ if conditionCty .True () {
162
+ return nil
163
+ }
164
+
165
+ // Finally evaluate the error message expression
166
+
167
+ var errorMsgValue = "cannot process error message expression"
168
+ errorMsgCty , diags := expressionValue (errorMsg , hclCtx )
169
+ if diags .HasErrors () {
170
+ errorMsgCty , diags = expressionValue (fmt .Sprintf ("%q" , errorMsg ), hclCtx )
171
+ }
172
+ if ! diags .HasErrors () && ! errorMsgCty .IsNull () {
173
+ errorMsgCty , err = convert .Convert (errorMsgCty , cty .String )
174
+ if err == nil {
175
+ errorMsgValue = errorMsgCty .AsString ()
176
+ }
177
+ }
178
+ return C .CString (errorMsgValue )
179
+ }
180
+
181
+ func diagErrorsToString (diags hcl.Diagnostics , format string ) string {
182
+ diagErrs := diags .Errs ()
183
+ errors := make ([]string , 0 , len (diagErrs ))
184
+ for _ , err := range diagErrs {
185
+ errors = append (errors , err .Error ())
186
+ }
187
+ if format == "" {
188
+ return strings .Join (errors , ", " )
189
+ }
190
+ return fmt .Sprintf (format , strings .Join (errors , ", " ))
191
+ }
192
+
193
+ func containsError (diags hcl.Diagnostics , e string ) bool {
194
+ for _ , err := range diags .Errs () {
195
+ if strings .Contains (err .Error (), e ) {
196
+ return true
197
+ }
198
+ }
199
+ return false
49
200
}
201
+
202
+ func expressionValue (in string , ctx * hcl.EvalContext ) (cty.Value , hcl.Diagnostics ) {
203
+ var diags hcl.Diagnostics
204
+
205
+ expr , diags := hclsyntax .ParseExpression ([]byte (in ), "tmp.hcl" , hcl .InitialPos )
206
+ if diags .HasErrors () {
207
+ return cty .NilVal , diags
208
+ }
209
+
210
+ val , diags := expr .Value (ctx )
211
+ if diags .HasErrors () {
212
+ return cty .NilVal , diags
213
+ }
214
+
215
+ return val , diags
216
+ }
217
+
218
+ var knownFunctions = map [string ]function.Function {
219
+ "abs" : stdlib .AbsoluteFunc ,
220
+ "can" : tryfunc .CanFunc ,
221
+ "ceil" : stdlib .CeilFunc ,
222
+ "chomp" : stdlib .ChompFunc ,
223
+ "coalescelist" : stdlib .CoalesceListFunc ,
224
+ "compact" : stdlib .CompactFunc ,
225
+ "concat" : stdlib .ConcatFunc ,
226
+ "contains" : stdlib .ContainsFunc ,
227
+ "csvdecode" : stdlib .CSVDecodeFunc ,
228
+ "distinct" : stdlib .DistinctFunc ,
229
+ "element" : stdlib .ElementFunc ,
230
+ "chunklist" : stdlib .ChunklistFunc ,
231
+ "flatten" : stdlib .FlattenFunc ,
232
+ "floor" : stdlib .FloorFunc ,
233
+ "format" : stdlib .FormatFunc ,
234
+ "formatdate" : stdlib .FormatDateFunc ,
235
+ "formatlist" : stdlib .FormatListFunc ,
236
+ "indent" : stdlib .IndentFunc ,
237
+ "join" : stdlib .JoinFunc ,
238
+ "jsondecode" : stdlib .JSONDecodeFunc ,
239
+ "jsonencode" : stdlib .JSONEncodeFunc ,
240
+ "keys" : stdlib .KeysFunc ,
241
+ "log" : stdlib .LogFunc ,
242
+ "lower" : stdlib .LowerFunc ,
243
+ "max" : stdlib .MaxFunc ,
244
+ "merge" : stdlib .MergeFunc ,
245
+ "min" : stdlib .MinFunc ,
246
+ "parseint" : stdlib .ParseIntFunc ,
247
+ "pow" : stdlib .PowFunc ,
248
+ "range" : stdlib .RangeFunc ,
249
+ "regex" : stdlib .RegexFunc ,
250
+ "regexall" : stdlib .RegexAllFunc ,
251
+ "reverse" : stdlib .ReverseListFunc ,
252
+ "setintersection" : stdlib .SetIntersectionFunc ,
253
+ "setproduct" : stdlib .SetProductFunc ,
254
+ "setsubtract" : stdlib .SetSubtractFunc ,
255
+ "setunion" : stdlib .SetUnionFunc ,
256
+ "signum" : stdlib .SignumFunc ,
257
+ "slice" : stdlib .SliceFunc ,
258
+ "sort" : stdlib .SortFunc ,
259
+ "split" : stdlib .SplitFunc ,
260
+ "strrev" : stdlib .ReverseFunc ,
261
+ "substr" : stdlib .SubstrFunc ,
262
+ "timeadd" : stdlib .TimeAddFunc ,
263
+ "title" : stdlib .TitleFunc ,
264
+ "trim" : stdlib .TrimFunc ,
265
+ "trimprefix" : stdlib .TrimPrefixFunc ,
266
+ "trimspace" : stdlib .TrimSpaceFunc ,
267
+ "trimsuffix" : stdlib .TrimSuffixFunc ,
268
+ "try" : tryfunc .TryFunc ,
269
+ "upper" : stdlib .UpperFunc ,
270
+ "values" : stdlib .ValuesFunc ,
271
+ "zipmap" : stdlib .ZipmapFunc ,
272
+ }
273
+
274
+ func main () {}
0 commit comments