Merge pull request #387 from hashicorp/hcldec-add-validatefuncspec
hcldec: add ValidateSpec
This commit is contained in:
commit
919ba77aeb
@ -1565,6 +1565,52 @@ func (s *TransformFuncSpec) sourceRange(content *hcl.BodyContent, blockLabels []
|
||||
return s.Wrapped.sourceRange(content, blockLabels)
|
||||
}
|
||||
|
||||
// ValidateFuncSpec is a spec that allows for extended
|
||||
// developer-defined validation. The validation function receives the
|
||||
// result of the wrapped spec.
|
||||
//
|
||||
// The Subject field of the returned Diagnostic is optional. If not
|
||||
// specified, it is automatically populated with the range covered by
|
||||
// the wrapped spec.
|
||||
//
|
||||
type ValidateSpec struct {
|
||||
Wrapped Spec
|
||||
Func func(value cty.Value) hcl.Diagnostics
|
||||
}
|
||||
|
||||
func (s *ValidateSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
cb(s.Wrapped)
|
||||
}
|
||||
|
||||
func (s *ValidateSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx)
|
||||
if diags.HasErrors() {
|
||||
// We won't try to run our function in this case, because it'll probably
|
||||
// generate confusing additional errors that will distract from the
|
||||
// root cause.
|
||||
return cty.UnknownVal(s.impliedType()), diags
|
||||
}
|
||||
|
||||
validateDiags := s.Func(wrappedVal)
|
||||
// Auto-populate the Subject fields if they weren't set.
|
||||
for i := range validateDiags {
|
||||
if validateDiags[i].Subject == nil {
|
||||
validateDiags[i].Subject = s.sourceRange(content, blockLabels).Ptr()
|
||||
}
|
||||
}
|
||||
|
||||
diags = append(diags, validateDiags...)
|
||||
return wrappedVal, diags
|
||||
}
|
||||
|
||||
func (s *ValidateSpec) impliedType() cty.Type {
|
||||
return s.Wrapped.impliedType()
|
||||
}
|
||||
|
||||
func (s *ValidateSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
return s.Wrapped.sourceRange(content, blockLabels)
|
||||
}
|
||||
|
||||
// noopSpec is a placeholder spec that does nothing, used in situations where
|
||||
// a non-nil placeholder spec is required. It is not exported because there is
|
||||
// no reason to use it directly; it is always an implementation detail only.
|
||||
|
@ -1,6 +1,7 @@
|
||||
package hcldec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@ -26,6 +27,7 @@ var _ Spec = (*BlockLabelSpec)(nil)
|
||||
var _ Spec = (*DefaultSpec)(nil)
|
||||
var _ Spec = (*TransformExprSpec)(nil)
|
||||
var _ Spec = (*TransformFuncSpec)(nil)
|
||||
var _ Spec = (*ValidateSpec)(nil)
|
||||
|
||||
var _ attrSpec = (*AttrSpec)(nil)
|
||||
var _ attrSpec = (*DefaultSpec)(nil)
|
||||
@ -139,3 +141,69 @@ bar = barval
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateFuncSpec(t *testing.T) {
|
||||
config := `
|
||||
foo = "invalid"
|
||||
`
|
||||
f, diags := hclsyntax.ParseConfig([]byte(config), "", hcl.Pos{Line: 1, Column: 1})
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Error())
|
||||
}
|
||||
|
||||
expectRange := map[string]*hcl.Range{
|
||||
"without_range": nil,
|
||||
"with_range": &hcl.Range{
|
||||
Filename: "foobar",
|
||||
Start: hcl.Pos{Line: 99, Column: 99},
|
||||
End: hcl.Pos{Line: 999, Column: 999},
|
||||
},
|
||||
}
|
||||
|
||||
for name := range expectRange {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
spec := &ValidateSpec{
|
||||
Wrapped: &AttrSpec{
|
||||
Name: "foo",
|
||||
Type: cty.String,
|
||||
},
|
||||
Func: func(value cty.Value) hcl.Diagnostics {
|
||||
if value.AsString() != "invalid" {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "incorrect value",
|
||||
Detail: fmt.Sprintf("invalid value passed in: %s", value.GoString()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "OK",
|
||||
Detail: "validation called correctly",
|
||||
Subject: expectRange[name],
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_, diags = Decode(f.Body, spec, nil)
|
||||
if len(diags) != 1 ||
|
||||
diags[0].Severity != hcl.DiagWarning ||
|
||||
diags[0].Summary != "OK" ||
|
||||
diags[0].Detail != "validation called correctly" {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags.Error())
|
||||
}
|
||||
|
||||
if expectRange[name] == nil && diags[0].Subject == nil {
|
||||
t.Fatal("returned diagnostic subject missing")
|
||||
}
|
||||
|
||||
if expectRange[name] != nil && !reflect.DeepEqual(expectRange[name], diags[0].Subject) {
|
||||
t.Fatalf("expected range %s, got range %s", expectRange[name], diags[0].Subject)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user