Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

schema: Introduce DefaultValue for AttributeSchema #327

Merged
merged 2 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 31 additions & 20 deletions decoder/internal/schemahelper/dependent_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
)

type blockSchema struct {
Expand Down Expand Up @@ -84,31 +85,41 @@ func dependencyKeysFromBlock(block *hcl.Block, blockSchema blockSchema) schema.D

for name, attrSchema := range blockSchema.Body.Attributes {
if attrSchema.IsDepKey {
var value cty.Value
attr, ok := content.Attributes[name]
if !ok {
// dependent attribute not present
continue
}

st, ok := attr.Expr.(*hclsyntax.ScopeTraversalExpr)
if ok {
addr, err := lang.TraversalToAddress(st.AsTraversal())
if err != nil {
// skip unparsable traversal
st, ok := attr.Expr.(*hclsyntax.ScopeTraversalExpr)
if ok {
addr, err := lang.TraversalToAddress(st.AsTraversal())
if err != nil {
// skip unparsable traversal
continue
}
dk.Attributes = append(dk.Attributes, schema.AttributeDependent{
Name: name,
Expr: schema.ExpressionValue{
Address: addr,
},
})
continue
}
dk.Attributes = append(dk.Attributes, schema.AttributeDependent{
Name: name,
Expr: schema.ExpressionValue{
Address: addr,
},
})
continue
}

value, diags := attr.Expr.Value(nil)
if len(diags) > 0 && value.IsNull() {
// skip attribute if we can't get the value
var diags hcl.Diagnostics
value, diags = attr.Expr.Value(nil)
if len(diags) > 0 && value.IsNull() {
// skip attribute if we can't get the value
continue
}
} else if attrSchema.DefaultValue != nil {
defaultValue, ok := attrSchema.DefaultValue.(schema.DefaultValue)
if !ok {
// TODO: DefaultKeyword
// TODO: DefaultTypeDeclaration
continue
}
value = defaultValue.Value
} else {
// dependent attribute not present
continue
}

Expand Down
112 changes: 110 additions & 2 deletions decoder/internal/schemahelper/dependent_body_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
testCases := []struct {
name string
attributes hclsyntax.Attributes
schema blockSchema
expectedSchema *schema.BodySchema
}{
{
Expand All @@ -291,6 +292,7 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
},
},
},
testSchemaWithAttributes,
&schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"depval_attr": {Constraint: schema.LiteralType{Type: cty.String}},
Expand All @@ -307,6 +309,7 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
},
},
},
testSchemaWithAttributes,
&schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"number_found": {Constraint: schema.LiteralType{Type: cty.Number}},
Expand All @@ -326,6 +329,7 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
},
},
},
testSchemaWithAttributes,
&schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"refbar": {Constraint: schema.LiteralType{Type: cty.Number}},
Expand All @@ -348,6 +352,7 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
},
},
},
testSchemaWithAttributes,
&schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"sortedattr": {Constraint: schema.LiteralType{Type: cty.String}},
Expand All @@ -370,12 +375,40 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
},
},
},
testSchemaWithAttributes,
&schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"unsortedattr": {Constraint: schema.LiteralType{Type: cty.String}},
},
},
},
{
"attribute with default value only",
map[string]*hclsyntax.Attribute{},
testSchemaWithAttributesWithDefaultValue,
&schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"defaultattr": {Constraint: schema.LiteralType{Type: cty.String}},
},
},
},
{
"attribute with default value and explicit value",
map[string]*hclsyntax.Attribute{
"depattr": {
Name: "depattr",
Expr: &hclsyntax.LiteralValueExpr{
Val: cty.StringVal("pumpkin"),
},
},
},
testSchemaWithAttributesWithDefaultValue,
&schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"combinedattr": {Constraint: schema.LiteralType{Type: cty.String}},
},
},
},
}

for i, tc := range testCases {
Expand All @@ -385,9 +418,10 @@ func TestBodySchema_DependentBodySchema_attributes(t *testing.T) {
Attributes: tc.attributes,
},
}
bodySchema, _, ok := testSchemaWithAttributes.DependentBodySchema(block)
bodySchema, _, ok := tc.schema.DependentBodySchema(block)
if !ok {
t.Fatal("expected to find body schema for 'depattr' attribute")
t.Fatalf("expected to find body schema for given block with %d attributes",
len(tc.attributes))
}
if diff := cmp.Diff(tc.expectedSchema, bodySchema, ctydebug.CmpOptions); diff != "" {
t.Fatalf("unexpected body schema: %s", diff)
Expand Down Expand Up @@ -619,3 +653,77 @@ var testSchemaWithAttributes = NewBlockSchema(&schema.BlockSchema{
},
},
})

var testSchemaWithAttributesWithDefaultValue = NewBlockSchema(&schema.BlockSchema{
Labels: []*schema.LabelSchema{
{
Name: "type",
},
{
Name: "name",
},
},
Body: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"depattr": {
Constraint: schema.LiteralType{Type: cty.String},
IsDepKey: true,
},
"depdefault": {
Constraint: schema.LiteralType{Type: cty.String},
IsDepKey: true,
DefaultValue: schema.DefaultValue{Value: cty.StringVal("foobar")},
},
},
},
DependentBody: map[schema.SchemaKey]*schema.BodySchema{
schema.NewSchemaKey(schema.DependencyKeys{
Attributes: []schema.AttributeDependent{
{
Name: "depattr",
Expr: schema.ExpressionValue{
Static: cty.StringVal("dep-val"),
},
},
},
}): {
Attributes: map[string]*schema.AttributeSchema{
"depval_attr": {Constraint: schema.LiteralType{Type: cty.String}},
},
},
schema.NewSchemaKey(schema.DependencyKeys{
Attributes: []schema.AttributeDependent{
{
Name: "depdefault",
Expr: schema.ExpressionValue{
Static: cty.StringVal("foobar"),
},
},
},
}): {
Attributes: map[string]*schema.AttributeSchema{
"defaultattr": {Constraint: schema.LiteralType{Type: cty.String}},
},
},
schema.NewSchemaKey(schema.DependencyKeys{
Attributes: []schema.AttributeDependent{
{
Name: "depattr",
Expr: schema.ExpressionValue{
Static: cty.StringVal("pumpkin"),
},
},
{
Name: "depdefault",
Expr: schema.ExpressionValue{
Static: cty.StringVal("foobar"),
},
},
},
}): {
Attributes: map[string]*schema.AttributeSchema{
"combinedattr": {Constraint: schema.LiteralType{Type: cty.String}},
},
},
},
})
6 changes: 6 additions & 0 deletions schema/attribute_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ type AttributeSchema struct {
// expressions are expected for the attribute
Constraint Constraint

// DefaultValue represents default value which applies
// if the attribute is not declared (e.g. when looking up
// attribute-dependent body).
DefaultValue Default

// IsDepKey describes whether to use this attribute (and its value)
// as key when looking up dependent schema
IsDepKey bool
Expand Down Expand Up @@ -144,6 +149,7 @@ func (as *AttributeSchema) Copy() *AttributeSchema {
IsComputed: as.IsComputed,
IsSensitive: as.IsSensitive,
IsDepKey: as.IsDepKey,
DefaultValue: as.DefaultValue,
Description: as.Description,
Address: as.Address.Copy(),
OriginForTarget: as.OriginForTarget.Copy(),
Expand Down
25 changes: 25 additions & 0 deletions schema/default_value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package schema

import "github.com/zclconf/go-cty/cty"

type defaultSigil struct{}

type Default interface {
isDefaultImpl() defaultSigil
}

type DefaultValue struct {
Value cty.Value
}

func (dv DefaultValue) isDefaultImpl() defaultSigil {
return defaultSigil{}
}

// TODO: DefaultKeyword
// TODO: DefaultTypeDeclaration
// TODO: defaults dependent on other attributes
// TODO: defaults dependent on env variables