Skip to content

Commit 0dc8880

Browse files
authored
Improve error messages for unexpected target types (#247)
1 parent 8f09608 commit 0dc8880

File tree

3 files changed

+69
-58
lines changed

3 files changed

+69
-58
lines changed

helper/runner.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,26 @@ var errRefTy = reflect.TypeOf((*error)(nil)).Elem()
203203
// EvaluateExpr returns a value of the passed expression.
204204
// Note that some features are limited
205205
func (r *Runner) EvaluateExpr(expr hcl.Expression, target interface{}, opts *tflint.EvaluateExprOption) error {
206-
var callback bool
207206
rval := reflect.ValueOf(target)
208207
rty := rval.Type()
209-
// Callback must meet the following requirements:
210-
// - It must be a function
211-
// - It must take an argument
212-
// - It must return an error
213-
if rty.Kind() == reflect.Func && rty.NumIn() == 1 && rty.NumOut() == 1 && rty.Out(0).Implements(errRefTy) {
208+
209+
var callback bool
210+
switch rty.Kind() {
211+
case reflect.Func:
212+
// Callback must meet the following requirements:
213+
// - It must be a function
214+
// - It must take an argument
215+
// - It must return an error
216+
if !(rty.NumIn() == 1 && rty.NumOut() == 1 && rty.Out(0).Implements(errRefTy)) {
217+
panic(`callback must be of type "func (v T) error"`)
218+
}
214219
callback = true
215220
target = reflect.New(rty.In(0)).Interface()
221+
222+
case reflect.Pointer:
223+
// ok
224+
default:
225+
panic("target value is not a pointer or function")
216226
}
217227

218228
err := r.evaluateExpr(expr, target, opts)
@@ -246,19 +256,19 @@ func (r *Runner) evaluateExpr(expr hcl.Expression, target interface{}, opts *tfl
246256
ty = *opts.WantType
247257
} else {
248258
switch target.(type) {
249-
case *string, string:
259+
case *string:
250260
ty = cty.String
251-
case *int, int:
261+
case *int:
252262
ty = cty.Number
253-
case *[]string, []string:
263+
case *[]string:
254264
ty = cty.List(cty.String)
255-
case *[]int, []int:
265+
case *[]int:
256266
ty = cty.List(cty.Number)
257-
case *map[string]string, map[string]string:
267+
case *map[string]string:
258268
ty = cty.Map(cty.String)
259-
case *map[string]int, map[string]int:
269+
case *map[string]int:
260270
ty = cty.Map(cty.Number)
261-
case cty.Value, *cty.Value:
271+
case *cty.Value:
262272
ty = cty.DynamicPseudoType
263273
default:
264274
return fmt.Errorf("unsupported target type: %T", target)

plugin/plugin2host/client.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,26 @@ var errRefTy = reflect.TypeOf((*error)(nil)).Elem()
281281
// Passing a callback function instead of a value as the target will invoke the callback,
282282
// passing the evaluated value to the argument.
283283
func (c *GRPCClient) EvaluateExpr(expr hcl.Expression, target interface{}, opts *tflint.EvaluateExprOption) error {
284-
var callback bool
285284
rval := reflect.ValueOf(target)
286285
rty := rval.Type()
287-
// Callback must meet the following requirements:
288-
// - It must be a function
289-
// - It must take an argument
290-
// - It must return an error
291-
if rty.Kind() == reflect.Func && rty.NumIn() == 1 && rty.NumOut() == 1 && rty.Out(0).Implements(errRefTy) {
286+
287+
var callback bool
288+
switch rty.Kind() {
289+
case reflect.Func:
290+
// Callback must meet the following requirements:
291+
// - It must be a function
292+
// - It must take an argument
293+
// - It must return an error
294+
if !(rty.NumIn() == 1 && rty.NumOut() == 1 && rty.Out(0).Implements(errRefTy)) {
295+
panic(`callback must be of type "func (v T) error"`)
296+
}
292297
callback = true
293298
target = reflect.New(rty.In(0)).Interface()
299+
300+
case reflect.Pointer:
301+
// ok
302+
default:
303+
panic("target value is not a pointer or function")
294304
}
295305

296306
err := c.evaluateExpr(expr, target, opts)
@@ -324,19 +334,19 @@ func (c *GRPCClient) evaluateExpr(expr hcl.Expression, target interface{}, opts
324334
ty = *opts.WantType
325335
} else {
326336
switch target.(type) {
327-
case *string, string:
337+
case *string:
328338
ty = cty.String
329-
case *int, int:
339+
case *int:
330340
ty = cty.Number
331-
case *[]string, []string:
341+
case *[]string:
332342
ty = cty.List(cty.String)
333-
case *[]int, []int:
343+
case *[]int:
334344
ty = cty.List(cty.Number)
335-
case *map[string]string, map[string]string:
345+
case *map[string]string:
336346
ty = cty.Map(cty.String)
337-
case *map[string]int, map[string]int:
347+
case *map[string]int:
338348
ty = cty.Map(cty.Number)
339-
case cty.Value, *cty.Value:
349+
case *cty.Value:
340350
ty = cty.DynamicPseudoType
341351
default:
342352
panic(fmt.Sprintf("unsupported target type: %T", target))

tflint/interface.go

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -159,18 +159,14 @@ type Runner interface {
159159
// See the hclext.DecodeBody documentation and examples for more details.
160160
DecodeRuleConfig(ruleName string, ret interface{}) error
161161

162-
// EvaluateExpr evaluates the given expression and assigns the value to the Go value target.
163-
// The target must be a pointer. Otherwise, it will cause a panic.
162+
// EvaluateExpr evaluates an expression and assigns its value to a Go value target,
163+
// which must be a pointer or a function. Any other type of target will trigger a panic.
164164
//
165-
// If the value cannot be assigned to the target, it returns an error.
166-
// There are particularly examples such as:
165+
// For pointers, if the expression value cannot be assigned to the target, an error is returned.
166+
// Some examples of this include unknown values (like variables without defaults or
167+
// aws_instance.foo.arn), null values, and sensitive values (for variables with sensitive = true).
167168
//
168-
// 1. Unknown value (e.g. variables without defaults, `aws_instance.foo.arn`)
169-
// 2. NULL
170-
// 3. Sensitive value (variables with `sensitive = true`)
171-
//
172-
// However, if the target is cty.Value, these errors will not be returned.
173-
// These errors can be handled with errors.Is().
169+
// These errors be handled with errors.Is():
174170
//
175171
// ```
176172
// var val string
@@ -192,29 +188,11 @@ type Runner interface {
192188
// }
193189
// ```
194190
//
195-
// The following are the types that can be passed as the target:
196-
//
197-
// 1. string
198-
// 2. int
199-
// 3. []string
200-
// 4. []int
201-
// 5. map[string]string
202-
// 6. map[string]int
203-
// 7. cty.Value
204-
// 8. func (v T) error
205-
//
206-
// Passing any other type will cause a panic. If you pass a function, the assigned value
207-
// will be used as an argument to execute the function. In this case, if a value cannot be
208-
// assigned to the argument type, instead of returning an error, the execution is skipped.
209-
// This is useful when it is always acceptable to ignore exceptional values.
210-
//
211-
// ```
212-
// runner.EvaluateExpr(expr, func (val string) error {
213-
// // Test value
214-
// }, nil)
215-
// ```
191+
// However, if the target is cty.Value, these errors will not be returned.
216192
//
217-
// Besides this, you can pass a structure. In that case, you need to explicitly pass wantType.
193+
// Here are the types that can be passed as the target: string, int, []string, []int,
194+
// map[string]string, map[string]int, and cty.Value. Passing any other type will
195+
// result in a panic, but you can make an exception by passing wantType as an option.
218196
//
219197
// ```
220198
// type complexVal struct {
@@ -230,6 +208,19 @@ type Runner interface {
230208
// var complexVals []complexVal
231209
// runner.EvaluateExpr(expr, &compleVals, &tflint.EvaluateExprOption{WantType: &wantType})
232210
// ```
211+
//
212+
// For functions (callbacks), the assigned value is used as an argument to execute
213+
// the function. If a value cannot be assigned to the argument type, the execution
214+
// is skipped instead of returning an error. This is useful when it's always acceptable
215+
// to ignore exceptional values.
216+
//
217+
// Here's an example of how you can pass a function to EvaluateExpr:
218+
//
219+
// ```
220+
// runner.EvaluateExpr(expr, func (val string) error {
221+
// // Test value
222+
// }, nil)
223+
// ```
233224
EvaluateExpr(expr hcl.Expression, target interface{}, option *EvaluateExprOption) error
234225

235226
// EmitIssue sends an issue to TFLint. You need to pass the message of the issue and the range.

0 commit comments

Comments
 (0)