configs/configschema: add new NestedType to attribute
This commit adds a new field, NestedType, to the Attribute schema, and extends the current Attribute decoderSpec to account for the new type. The codepaths are mostly unused and included in a separate commit to verify that the included changes do not impact any other tests yet.
This commit is contained in:
parent
f3a057eb35
commit
3ad720e9dc
|
@ -6,6 +6,7 @@ import (
|
|||
"unsafe"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
var mapLabelNames = []string{"key"}
|
||||
|
@ -183,9 +184,48 @@ func (b *Block) DecoderSpec() hcldec.Spec {
|
|||
}
|
||||
|
||||
func (a *Attribute) decoderSpec(name string) hcldec.Spec {
|
||||
return &hcldec.AttrSpec{
|
||||
Name: name,
|
||||
Type: a.Type,
|
||||
Required: a.Required,
|
||||
ret := &hcldec.AttrSpec{Name: name}
|
||||
if a == nil {
|
||||
return ret
|
||||
}
|
||||
|
||||
if a.NestedType != nil {
|
||||
// FIXME: a panic() is a bad UX. Fix this, probably by extending
|
||||
// InternalValidate() to check Attribute schemas as well and calling it
|
||||
// when we get the schema from the provider in Context().
|
||||
if a.Type != cty.NilType {
|
||||
panic("Invalid attribute schema: NestedType and Type cannot both be set. This is a bug in the provider.")
|
||||
}
|
||||
|
||||
var optAttrs []string
|
||||
optAttrs = listOptionalAttrsFromObject(a.NestedType, optAttrs)
|
||||
ty := a.NestedType.ImpliedType()
|
||||
|
||||
switch a.NestedType.Nesting {
|
||||
case NestingList:
|
||||
ret.Type = cty.List(ty)
|
||||
case NestingSet:
|
||||
ret.Type = cty.Set(ty)
|
||||
case NestingMap:
|
||||
ret.Type = cty.Map(ty)
|
||||
default: // NestingSingle, NestingGroup, or no NestingMode
|
||||
ret.Type = ty
|
||||
}
|
||||
ret.Required = a.NestedType.MinItems > 0
|
||||
return ret
|
||||
}
|
||||
|
||||
ret.Type = a.Type
|
||||
ret.Required = a.Required
|
||||
return ret
|
||||
}
|
||||
|
||||
func listOptionalAttrsFromObject(o *Object, optAttrs []string) []string {
|
||||
for name, attr := range o.Attributes {
|
||||
if attr.Optional == true {
|
||||
optAttrs = append(optAttrs, name)
|
||||
}
|
||||
}
|
||||
|
||||
return optAttrs
|
||||
}
|
||||
|
|
|
@ -426,3 +426,363 @@ func TestBlockDecoderSpec(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttributeDecoderSpec(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
Schema *Attribute
|
||||
TestBody hcl.Body
|
||||
Want cty.Value
|
||||
DiagCount int
|
||||
}{
|
||||
"empty": {
|
||||
&Attribute{},
|
||||
hcl.EmptyBody(),
|
||||
cty.NilVal,
|
||||
0,
|
||||
},
|
||||
"nil": {
|
||||
nil,
|
||||
hcl.EmptyBody(),
|
||||
cty.NilVal,
|
||||
0,
|
||||
},
|
||||
"optional string (null)": {
|
||||
&Attribute{
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
hcl.EmptyBody(),
|
||||
cty.NullVal(cty.String),
|
||||
0,
|
||||
},
|
||||
"optional string": {
|
||||
&Attribute{
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.StringVal("bar")),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.StringVal("bar"),
|
||||
0,
|
||||
},
|
||||
"NestedType with required string": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.StringVal("bar"),
|
||||
})),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.StringVal("bar"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
"NestedType with optional attributes": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
"bar": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.StringVal("bar"),
|
||||
})),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.StringVal("bar"),
|
||||
"bar": cty.NullVal(cty.String),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
"NestedType with missing required string": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.EmptyObjectVal),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.UnknownVal(cty.Object(map[string]cty.Type{
|
||||
"foo": cty.String,
|
||||
})),
|
||||
1,
|
||||
},
|
||||
// NestedModes
|
||||
"NestedType NestingModeList valid": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingList,
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.StringVal("bar"),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.StringVal("baz"),
|
||||
}),
|
||||
})),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("bar")}),
|
||||
cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("baz")}),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
"NestedType NestingModeList invalid": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingList,
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.ListVal([]cty.Value{cty.StringVal("bar"), cty.StringVal("baz")}),
|
||||
})})),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{"foo": cty.String}))),
|
||||
1,
|
||||
},
|
||||
"NestedType NestingModeSet valid": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingSet,
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.StringVal("bar"),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.StringVal("baz"),
|
||||
}),
|
||||
})),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("bar")}),
|
||||
cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("baz")}),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
"NestedType NestingModeSet invalid": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingSet,
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.ListVal([]cty.Value{cty.StringVal("bar"), cty.StringVal("baz")}),
|
||||
})})),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{"foo": cty.String}))),
|
||||
1,
|
||||
},
|
||||
"NestedType NestingModeMap valid": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingMap,
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
|
||||
"one": cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.StringVal("bar"),
|
||||
}),
|
||||
"two": cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.StringVal("baz"),
|
||||
}),
|
||||
})),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"one": cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("bar")}),
|
||||
"two": cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("baz")}),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
"NestedType NestingModeMap invalid": {
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingMap,
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"attr": {
|
||||
Name: "attr",
|
||||
Expr: hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
|
||||
"one": cty.ObjectVal(map[string]cty.Value{
|
||||
// "foo" should be a string, not a list
|
||||
"foo": cty.ListVal([]cty.Value{cty.StringVal("bar"), cty.StringVal("baz")}),
|
||||
}),
|
||||
})),
|
||||
},
|
||||
},
|
||||
}),
|
||||
cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{"foo": cty.String}))),
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
spec := test.Schema.decoderSpec("attr")
|
||||
got, diags := hcldec.Decode(test.TestBody, spec, nil)
|
||||
if len(diags) != test.DiagCount {
|
||||
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
|
||||
for _, diag := range diags {
|
||||
t.Logf("- %s", diag.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Logf("[INFO] implied schema is %s", spew.Sdump(hcldec.ImpliedSchema(spec)))
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", dump.Value(got), dump.Value(test.Want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestAttributeDecodeSpec_panic is a temporary test which verifies that
|
||||
// decoderSpec panics when an invalid Attribute schema is encountered. It will
|
||||
// be removed when InternalValidate() is extended to validate Attribute specs
|
||||
// (and is used). See the #FIXME in decoderSpec.
|
||||
func TestAttributeDecoderSpec_panic(t *testing.T) {
|
||||
attrS := &Attribute{
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"nested_attribute": cty.String,
|
||||
}),
|
||||
NestedType: &Object{},
|
||||
Optional: true,
|
||||
}
|
||||
|
||||
defer func() { recover() }()
|
||||
attrS.decoderSpec("attr")
|
||||
t.Errorf("expected panic")
|
||||
}
|
||||
|
|
|
@ -26,6 +26,18 @@ func (b *Block) EmptyValue() cty.Value {
|
|||
// the value that would be returned if there were no definition of the attribute
|
||||
// at all, ignoring any required constraint.
|
||||
func (a *Attribute) EmptyValue() cty.Value {
|
||||
if a.NestedType != nil {
|
||||
switch a.NestedType.Nesting {
|
||||
case NestingList:
|
||||
return cty.NullVal(cty.List(a.NestedType.ImpliedType()))
|
||||
case NestingSet:
|
||||
return cty.NullVal(cty.Set(a.NestedType.ImpliedType()))
|
||||
case NestingMap:
|
||||
return cty.NullVal(cty.Map(a.NestedType.ImpliedType()))
|
||||
default: // NestingSingle, NestingGroup, or no NestingMode
|
||||
return cty.NullVal(a.NestedType.ImpliedType())
|
||||
}
|
||||
}
|
||||
return cty.NullVal(a.Type)
|
||||
}
|
||||
|
||||
|
|
|
@ -168,3 +168,116 @@ func TestBlockEmptyValue(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute EmptyValue() is well covered by the Block tests above; these tests
|
||||
// focus on the behavior with NestedType field inside an Attribute
|
||||
func TestAttributeEmptyValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
Schema *Attribute
|
||||
Want cty.Value
|
||||
}{
|
||||
{
|
||||
&Attribute{},
|
||||
cty.NilVal,
|
||||
},
|
||||
{
|
||||
&Attribute{
|
||||
Type: cty.String,
|
||||
},
|
||||
cty.NullVal(cty.String),
|
||||
},
|
||||
{
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
// no Nesting set should behave the same as NestingSingle
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
})),
|
||||
},
|
||||
{
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingSingle,
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
})),
|
||||
},
|
||||
{
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingGroup, // functionally equivalent to NestingSingle in a NestedType
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
})),
|
||||
},
|
||||
{
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingList,
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.List(
|
||||
cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
}),
|
||||
)),
|
||||
},
|
||||
{
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingMap,
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.Map(
|
||||
cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
}),
|
||||
)),
|
||||
},
|
||||
{
|
||||
&Attribute{
|
||||
NestedType: &Object{
|
||||
Nesting: NestingSet,
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.Set(
|
||||
cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
}),
|
||||
)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%#v", test.Schema), func(t *testing.T) {
|
||||
got := test.Schema.EmptyValue()
|
||||
if !test.Want.RawEquals(got) {
|
||||
t.Errorf("wrong result\nschema: %s\ngot: %s\nwant: %s", spew.Sdump(test.Schema), dump.Value(got), dump.Value(test.Want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,3 +40,37 @@ func (b *Block) ContainsSensitive() bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ImpliedType returns the cty.Type that would result from decoding a NestedType
|
||||
// Attribute using the receiving block schema.
|
||||
//
|
||||
// ImpliedType always returns a result, even if the given schema is
|
||||
// inconsistent. Code that creates configschema.Object objects should be tested
|
||||
// using the InternalValidate method to detect any inconsistencies that would
|
||||
// cause this method to fall back on defaults and assumptions.
|
||||
func (o *Object) ImpliedType() cty.Type {
|
||||
if o == nil {
|
||||
return cty.EmptyObject
|
||||
}
|
||||
|
||||
attrTys := make(map[string]cty.Type, len(o.Attributes))
|
||||
for name, attrS := range o.Attributes {
|
||||
attrTys[name] = attrS.Type
|
||||
}
|
||||
|
||||
var optAttrs []string
|
||||
optAttrs = listOptionalAttrsFromObject(o, optAttrs)
|
||||
|
||||
return cty.ObjectWithOptionalAttrs(attrTys, optAttrs)
|
||||
}
|
||||
|
||||
// ContainsSensitive returns true if any of the attributes of the receiving
|
||||
// Object are marked as sensitive.
|
||||
func (o *Object) ContainsSensitive() bool {
|
||||
for _, attrS := range o.Attributes {
|
||||
if attrS.Sensitive {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -122,3 +122,59 @@ func TestBlockImpliedType(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectImpliedType(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
Schema *Object
|
||||
Want cty.Type
|
||||
}{
|
||||
"nil": {
|
||||
nil,
|
||||
cty.EmptyObject,
|
||||
},
|
||||
"empty": {
|
||||
&Object{},
|
||||
cty.EmptyObject,
|
||||
},
|
||||
"attributes": {
|
||||
&Object{
|
||||
Attributes: map[string]*Attribute{
|
||||
"optional": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
"required": {
|
||||
Type: cty.Number,
|
||||
Required: true,
|
||||
},
|
||||
"computed": {
|
||||
Type: cty.List(cty.Bool),
|
||||
Computed: true,
|
||||
},
|
||||
"optional_computed": {
|
||||
Type: cty.Map(cty.Bool),
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectWithOptionalAttrs(
|
||||
map[string]cty.Type{
|
||||
"optional": cty.String,
|
||||
"required": cty.Number,
|
||||
"computed": cty.List(cty.Bool),
|
||||
"optional_computed": cty.Map(cty.Bool),
|
||||
},
|
||||
[]string{"optional", "optional_computed"},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := test.Schema.ImpliedType()
|
||||
if !got.Equals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,8 +38,13 @@ type Block struct {
|
|||
// Attribute represents a configuration attribute, within a block.
|
||||
type Attribute struct {
|
||||
// Type is a type specification that the attribute's value must conform to.
|
||||
// It conflicts with NestedType.
|
||||
Type cty.Type
|
||||
|
||||
// NestedType indicates that the attribute is a NestedBlock-style object.
|
||||
// This field conflicts with Type.
|
||||
NestedType *Object
|
||||
|
||||
// Description is an English-language description of the purpose and
|
||||
// usage of the attribute. A description should be concise and use only
|
||||
// one or two sentences, leaving full definition to longer-form
|
||||
|
@ -72,6 +77,25 @@ type Attribute struct {
|
|||
Deprecated bool
|
||||
}
|
||||
|
||||
// Object represents the embedding of a structural object inside an Attribute.
|
||||
type Object struct {
|
||||
// Attributes describes the nested attributes which may appear inside the
|
||||
// Object.
|
||||
Attributes map[string]*Attribute
|
||||
|
||||
// Nesting provides the nesting mode for this Object, which determines how
|
||||
// many instances of the Object are allowed, how many labels it expects, and
|
||||
// how the resulting data will be converted into a data structure.
|
||||
Nesting NestingMode
|
||||
|
||||
// MinItems and MaxItems set, for the NestingList and NestingSet nesting
|
||||
// modes, lower and upper limits on the number of child blocks allowed
|
||||
// of the given type. If both are left at zero, no limit is applied.
|
||||
// These fields are ignored for other nesting modes and must both be left
|
||||
// at zero.
|
||||
MinItems, MaxItems int
|
||||
}
|
||||
|
||||
// NestedBlock represents the embedding of one block within another.
|
||||
type NestedBlock struct {
|
||||
// Block is the description of the block that's nested.
|
||||
|
@ -98,6 +122,8 @@ type NestedBlock struct {
|
|||
// blocks.
|
||||
type NestingMode int
|
||||
|
||||
// Object represents the embedding of a NestedBl
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=NestingMode
|
||||
|
||||
const (
|
||||
|
|
Loading…
Reference in New Issue