helper/schema: Better mimic some undocumented behaviors in Core schema

Since the SDK's schema system conflates attributes and nested blocks, it's
possible to state some nonsensical schema situations such as:

- A nested block is both optional but has MinItems > 0
- A nested block is entirely computed but has MinItems or MaxItems set

Both of these weird situations are handled here in the same way that the
existing helper/schema validation code would've handled them: by
effectively disabling the MinItems/MaxItems checks where they would've
been ignored before.

the MinItems/MaxItems
This commit is contained in:
Martin Atkins 2018-11-26 15:44:27 -08:00
parent 37da625ee9
commit 3d54af9c09
2 changed files with 98 additions and 0 deletions

View File

@ -125,6 +125,20 @@ func (s *Schema) coreConfigSchemaBlock() *configschema.NestedBlock {
// blocks, but we can fake it by requiring at least one item.
ret.MinItems = 1
}
if s.Optional && s.MinItems > 0 {
// Historically helper/schema would ignore MinItems if Optional were
// set, so we must mimic this behavior here to ensure that providers
// relying on that undocumented behavior can continue to operate as
// they did before.
ret.MinItems = 0
}
if s.Computed && !s.Optional {
// MinItems/MaxItems are meaningless for computed nested blocks, since
// they are never set by the user anyway. This ensures that we'll never
// generate weird errors about them.
ret.MinItems = 0
ret.MaxItems = 0
}
return ret
}

View File

@ -224,6 +224,90 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
},
}),
},
"sub-resource collections minitems+optional": {
// This particular case is an odd one where the provider gives
// conflicting information about whether a sub-resource is required,
// by marking it as optional but also requiring one item.
// Historically the optional-ness "won" here, and so we must
// honor that for compatibility with providers that relied on this
// undocumented interaction.
map[string]*Schema{
"list": {
Type: TypeList,
Optional: true,
Elem: &Resource{
Schema: map[string]*Schema{},
},
MinItems: 1,
MaxItems: 1,
},
"set": {
Type: TypeSet,
Optional: true,
Elem: &Resource{
Schema: map[string]*Schema{},
},
MinItems: 1,
MaxItems: 1,
},
},
testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{},
BlockTypes: map[string]*configschema.NestedBlock{
"list": {
Nesting: configschema.NestingList,
Block: configschema.Block{},
MinItems: 0,
MaxItems: 1,
},
"set": {
Nesting: configschema.NestingSet,
Block: configschema.Block{},
MinItems: 0,
MaxItems: 1,
},
},
}),
},
"sub-resource collections minitems+computed": {
map[string]*Schema{
"list": {
Type: TypeList,
Computed: true,
Elem: &Resource{
Schema: map[string]*Schema{},
},
MinItems: 1,
MaxItems: 1,
},
"set": {
Type: TypeSet,
Computed: true,
Elem: &Resource{
Schema: map[string]*Schema{},
},
MinItems: 1,
MaxItems: 1,
},
},
testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{},
BlockTypes: map[string]*configschema.NestedBlock{
"list": {
Nesting: configschema.NestingList,
Block: configschema.Block{},
MinItems: 0,
MaxItems: 0,
},
"set": {
Nesting: configschema.NestingSet,
Block: configschema.Block{},
MinItems: 0,
MaxItems: 0,
},
},
}),
},
"nested attributes and blocks": {
map[string]*Schema{
"foo": {