Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/fwschema: Add NestedAttributeObject and NestedBlockObject in…
…terfaces Reference: #132 Reference: #508 Reference: #532 Since the initial version of the framework, it has treated nested attributes and blocks as a singular entity with some internal gynastics to handle the one (single; object) or two (list, map, set to object) actual types that make up the nested attribute or block. Having these schema abstractions as single entities caused developer confusion when attempting to extract data or create paths. Other desirable features for the framework are the ability to customize the types, define validators, and define plan modifiers of nested attributes and blocks. This type customization, validation, or plan modification may need to be on the nested object to simplify provider implementations. Exposing these options with a flat abstraction could be confusing. This change introduces the concept of a nested attribute object and a nested block object into the `internal/fwschema` package and makes the necessary implementation changes to `tfsdk.Attribute` and `tfsdk.Block`. While the `tfsdk` package schema handling does not expose the new potential enhancements, the upcoming `datasource`, `provider`, and `resource` schema handling will. The existing unit testing shows no regressions and the new unit tests with validation show it should be possible to implement nested object validation (and when implemented in the near future, plan modification). This changeset additionally migrates more `tfsdk.Schema` logic into the `internal/fwschema` package in further preparation for creating additional schema implementations that will benefit from the existing schema logic.
- Loading branch information
Showing
34 changed files
with
1,795 additions
and
476 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package fwschema | ||
|
||
import "errors" | ||
|
||
var ( | ||
// ErrPathInsideAtomicAttribute is used with AttributeAtPath is called | ||
// on a path that doesn't have a schema associated with it, because | ||
// it's an element, attribute, or block of a complex type, not a nested | ||
// attribute. | ||
ErrPathInsideAtomicAttribute = errors.New("path leads to element, attribute, or block of a schema.Attribute that has no schema associated with it") | ||
|
||
// ErrPathIsBlock is used with AttributeAtPath is called on a path is a | ||
// block, not an attribute. Use blockAtPath on the path instead. | ||
ErrPathIsBlock = errors.New("path leads to block, not an attribute") | ||
) |
15 changes: 15 additions & 0 deletions
15
internal/fwschema/fwxschema/nested_attribute_object_validation.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package fwxschema | ||
|
||
import ( | ||
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema" | ||
"github.com/hashicorp/terraform-plugin-framework/schema/validator" | ||
) | ||
|
||
// NestedAttributeObjectWithValidators is an optional interface on | ||
// NestedAttributeObject which enables Object validation support. | ||
type NestedAttributeObjectWithValidators interface { | ||
fwschema.NestedAttributeObject | ||
|
||
// ObjectValidators should return a list of Object validators. | ||
ObjectValidators() []validator.Object | ||
} |
15 changes: 15 additions & 0 deletions
15
internal/fwschema/fwxschema/nested_block_object_validators.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package fwxschema | ||
|
||
import ( | ||
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema" | ||
"github.com/hashicorp/terraform-plugin-framework/schema/validator" | ||
) | ||
|
||
// NestedBlockObjectWithValidators is an optional interface on | ||
// NestedBlockObject which enables Object validation support. | ||
type NestedBlockObjectWithValidators interface { | ||
fwschema.NestedBlockObject | ||
|
||
// ObjectValidators should return a list of Object validators. | ||
ObjectValidators() []validator.Object | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package fwschema | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-go/tftypes" | ||
) | ||
|
||
// NestedAttributeObject represents the Object inside a NestedAttribute. | ||
// Refer to the fwxschema package for validation and plan modification | ||
// extensions to this interface. | ||
type NestedAttributeObject interface { | ||
tftypes.AttributePathStepper | ||
|
||
// Equal should return true if given NestedAttributeObject is equivalent. | ||
Equal(NestedAttributeObject) bool | ||
|
||
// GetAttributes should return the nested attributes of an attribute. | ||
GetAttributes() UnderlyingAttributes | ||
|
||
// Type should return the framework type of the object. | ||
Type() types.ObjectTypable | ||
} | ||
|
||
// NestedAttributeObjectApplyTerraform5AttributePathStep is a helper function | ||
// to perform base tftypes.AttributePathStepper handling using the | ||
// GetAttributes method. NestedAttributeObject implementations should still | ||
// include custom type functionality in addition to using this helper. | ||
func NestedAttributeObjectApplyTerraform5AttributePathStep(o NestedAttributeObject, step tftypes.AttributePathStep) (any, error) { | ||
name, ok := step.(tftypes.AttributeName) | ||
|
||
if !ok { | ||
return nil, fmt.Errorf("cannot apply AttributePathStep %T to NestedAttributeObject", step) | ||
} | ||
|
||
attribute, ok := o.GetAttributes()[string(name)] | ||
|
||
if ok { | ||
return attribute, nil | ||
} | ||
|
||
return nil, fmt.Errorf("no attribute %q on NestedAttributeObject", name) | ||
} | ||
|
||
// NestedAttributeObjectEqual is a helper function to perform base equality testing | ||
// on two NestedAttributeObject. NestedAttributeObject implementations should still | ||
// compare the concrete types and other custom functionality in addition to | ||
// using this helper. | ||
func NestedAttributeObjectEqual(a, b NestedAttributeObject) bool { | ||
if !a.Type().Equal(b.Type()) { | ||
return false | ||
} | ||
|
||
if len(a.GetAttributes()) != len(b.GetAttributes()) { | ||
return false | ||
} | ||
|
||
for name, aAttribute := range a.GetAttributes() { | ||
bAttribute, ok := b.GetAttributes()[name] | ||
|
||
if !ok { | ||
return false | ||
} | ||
|
||
if !aAttribute.Equal(bAttribute) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
// NestedAttributeObjectType is a helper function to perform base type handling | ||
// using the GetAttributes and GetBlocks methods. NestedAttributeObject | ||
// implementations should still include custom type functionality in addition | ||
// to using this helper. | ||
func NestedAttributeObjectType(o NestedAttributeObject) types.ObjectTypable { | ||
attrTypes := make(map[string]attr.Type, len(o.GetAttributes())) | ||
|
||
for name, attribute := range o.GetAttributes() { | ||
attrTypes[name] = attribute.GetType() | ||
} | ||
|
||
return types.ObjectType{ | ||
AttrTypes: attrTypes, | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package fwschema | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-go/tftypes" | ||
) | ||
|
||
// NestedBlockObject represents the Object inside a Block. | ||
// Refer to the fwxschema package for validation and plan modification | ||
// extensions to this interface. | ||
type NestedBlockObject interface { | ||
tftypes.AttributePathStepper | ||
|
||
// Equal should return true if given NestedBlockObject is equivalent. | ||
Equal(NestedBlockObject) bool | ||
|
||
// GetAttributes should return the nested attributes of the object. | ||
GetAttributes() UnderlyingAttributes | ||
|
||
// GetBlocks should return the nested attributes of the object. | ||
GetBlocks() map[string]Block | ||
|
||
// Type should return the framework type of the object. | ||
Type() types.ObjectTypable | ||
} | ||
|
||
// NestedBlockObjectApplyTerraform5AttributePathStep is a helper function to | ||
// perform base tftypes.AttributePathStepper handling using the GetAttributes | ||
// and GetBlocks methods. NestedBlockObject implementations should still | ||
// include custom type functionality in addition to using this helper. | ||
func NestedBlockObjectApplyTerraform5AttributePathStep(o NestedBlockObject, step tftypes.AttributePathStep) (any, error) { | ||
name, ok := step.(tftypes.AttributeName) | ||
|
||
if !ok { | ||
return nil, fmt.Errorf("cannot apply AttributePathStep %T to NestedBlockObject", step) | ||
} | ||
|
||
attribute, ok := o.GetAttributes()[string(name)] | ||
|
||
if ok { | ||
return attribute, nil | ||
} | ||
|
||
block, ok := o.GetBlocks()[string(name)] | ||
|
||
if ok { | ||
return block, nil | ||
} | ||
|
||
return nil, fmt.Errorf("no attribute or block %q on NestedBlockObject", name) | ||
} | ||
|
||
// NestedBlockObjectEqual is a helper function to perform base equality testing | ||
// on two NestedBlockObject. NestedBlockObject implementations should still | ||
// compare the concrete types and other custom functionality in addition to | ||
// using this helper. | ||
func NestedBlockObjectEqual(a, b NestedBlockObject) bool { | ||
if !a.Type().Equal(b.Type()) { | ||
return false | ||
} | ||
|
||
if len(a.GetAttributes()) != len(b.GetAttributes()) { | ||
return false | ||
} | ||
|
||
for name, aAttribute := range a.GetAttributes() { | ||
bAttribute, ok := b.GetAttributes()[name] | ||
|
||
if !ok { | ||
return false | ||
} | ||
|
||
if !aAttribute.Equal(bAttribute) { | ||
return false | ||
} | ||
} | ||
|
||
if len(a.GetBlocks()) != len(b.GetBlocks()) { | ||
return false | ||
} | ||
|
||
for name, aBlock := range a.GetBlocks() { | ||
bBlock, ok := b.GetBlocks()[name] | ||
|
||
if !ok { | ||
return false | ||
} | ||
|
||
if !aBlock.Equal(bBlock) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
// NestedBlockObjectType is a helper function to perform base type handling | ||
// using the GetAttributes and GetBlocks methods. NestedBlockObject | ||
// implementations should still include custom type functionality in addition | ||
// to using this helper. | ||
func NestedBlockObjectType(o NestedBlockObject) types.ObjectTypable { | ||
attrTypes := make(map[string]attr.Type, len(o.GetAttributes())+len(o.GetBlocks())) | ||
|
||
for name, attribute := range o.GetAttributes() { | ||
attrTypes[name] = attribute.GetType() | ||
} | ||
|
||
for name, block := range o.GetBlocks() { | ||
attrTypes[name] = block.Type() | ||
} | ||
|
||
return types.ObjectType{ | ||
AttrTypes: attrTypes, | ||
} | ||
} |
Oops, something went wrong.