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

Don't insert default values into null objects #567

Merged
merged 3 commits into from Nov 7, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions ext/typeexpr/defaults.go
Expand Up @@ -56,8 +56,13 @@ type defaultsTransformer struct {
var _ cty.Transformer = (*defaultsTransformer)(nil)

func (t *defaultsTransformer) Enter(p cty.Path, v cty.Value) (cty.Value, error) {
// Cannot apply defaults to an unknown value
if !v.IsKnown() {
// Cannot apply defaults to an unknown value, and should not apply defaults
// to a null value.
//
// A quick clarification, we should still override null values *inside* the
// object or map with defaults. But if the actual object or map itself is
// null then we skip it.
if !v.IsKnown() || v.IsNull() {
return v, nil
}

Expand Down
158 changes: 158 additions & 0 deletions ext/typeexpr/defaults_test.go
Expand Up @@ -491,6 +491,164 @@ func TestDefaults_Apply(t *testing.T) {
}),
}),
},
"null objects do not get default values inserted": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"required": cty.String,
"optional": cty.String,
}, []string{"optional"}),
DefaultValues: map[string]cty.Value{
"optional": cty.StringVal("optional"),
},
},
value: cty.NullVal(cty.Object(map[string]cty.Type{
"required": cty.String,
"optional": cty.String,
})),
want: cty.NullVal(cty.Object(map[string]cty.Type{
"required": cty.String,
"optional": cty.String,
})),
},
"defaults with unset defaults are still applied (null)": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"required": cty.String,
"optional_object": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"nested_required": cty.String,
"nested_optional": cty.String,
}, []string{"nested_optional"}),
}, []string{"optional_object"}),
DefaultValues: map[string]cty.Value{
"optional_object": cty.ObjectVal(map[string]cty.Value{
"nested_required": cty.StringVal("required"),
"nested_optional": cty.NullVal(cty.String),
}),
},
Children: map[string]*Defaults{
"optional_object": {
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"nested_required": cty.String,
"nested_optional": cty.String,
}, []string{"nested_optional"}),
DefaultValues: map[string]cty.Value{
"nested_optional": cty.StringVal("optional"),
},
},
},
},
value: cty.ObjectVal(map[string]cty.Value{
"required": cty.StringVal("required"),
// optional_object is explicitly set to null for this test case.
"optional_object": cty.NullVal(cty.Object(map[string]cty.Type{
"nested_required": cty.String,
"nested_optional": cty.String,
})),
}),
want: cty.ObjectVal(map[string]cty.Value{
"required": cty.StringVal("required"),
"optional_object": cty.ObjectVal(map[string]cty.Value{
"nested_required": cty.StringVal("required"),
"nested_optional": cty.StringVal("optional"),
}),
}),
},
"defaults with unset defaults are still applied (missing)": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"required": cty.String,
"optional_object": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"nested_required": cty.String,
"nested_optional": cty.String,
}, []string{"nested_optional"}),
}, []string{"optional_object"}),
DefaultValues: map[string]cty.Value{
"optional_object": cty.ObjectVal(map[string]cty.Value{
"nested_required": cty.StringVal("required"),
"nested_optional": cty.NullVal(cty.String),
}),
},
Children: map[string]*Defaults{
"optional_object": {
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"nested_required": cty.String,
"nested_optional": cty.String,
}, []string{"nested_optional"}),
DefaultValues: map[string]cty.Value{
"nested_optional": cty.StringVal("optional"),
},
},
},
},
value: cty.ObjectVal(map[string]cty.Value{
"required": cty.StringVal("required"),
// optional_object is missing but not null for this test case.
}),
want: cty.ObjectVal(map[string]cty.Value{
"required": cty.StringVal("required"),
"optional_object": cty.ObjectVal(map[string]cty.Value{
"nested_required": cty.StringVal("required"),
"nested_optional": cty.StringVal("optional"),
}),
}),
},
// https://discuss.hashicorp.com/t/request-for-feedback-optional-object-type-attributes-with-defaults-in-v1-3-alpha/40550/6?u=alisdair
"all child and nested values are optional with defaults": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"settings": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"setting_one": cty.String,
"setting_two": cty.Number,
}, []string{"setting_one", "setting_two"}),
}, []string{"settings"}),
DefaultValues: map[string]cty.Value{
"settings": cty.EmptyObjectVal,
},
Children: map[string]*Defaults{
"settings": {
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"setting_one": cty.String,
"setting_two": cty.String,
}, []string{"setting_one", "setting_two"}),
DefaultValues: map[string]cty.Value{
"setting_one": cty.StringVal(""),
"setting_two": cty.NumberIntVal(0),
},
},
},
},
value: cty.EmptyObjectVal,
want: cty.ObjectVal(map[string]cty.Value{
"settings": cty.ObjectVal(map[string]cty.Value{
"setting_one": cty.StringVal(""),
"setting_two": cty.NumberIntVal(0),
}),
}),
},
"all nested values are optional with defaults, but direct child has no default": {
defaults: &Defaults{
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"settings": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"setting_one": cty.String,
"setting_two": cty.Number,
}, []string{"setting_one", "setting_two"}),
}, []string{"settings"}),
Children: map[string]*Defaults{
"settings": {
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"setting_one": cty.String,
"setting_two": cty.String,
}, []string{"setting_one", "setting_two"}),
DefaultValues: map[string]cty.Value{
"setting_one": cty.StringVal(""),
"setting_two": cty.NumberIntVal(0),
},
},
},
},
value: cty.EmptyObjectVal,
want: cty.EmptyObjectVal,
},
}

for name, tc := range testCases {
Expand Down