From d29499f62643314b5e834a420b04603d8278ab0c Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Fri, 4 Nov 2022 11:04:27 +0100 Subject: [PATCH 1/3] don't insert default values into null objects --- ext/typeexpr/defaults.go | 9 +- ext/typeexpr/defaults_test.go | 338 ++++++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+), 2 deletions(-) diff --git a/ext/typeexpr/defaults.go b/ext/typeexpr/defaults.go index 4e960579..b30f0a64 100644 --- a/ext/typeexpr/defaults.go +++ b/ext/typeexpr/defaults.go @@ -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 } diff --git a/ext/typeexpr/defaults_test.go b/ext/typeexpr/defaults_test.go index a4da6bb6..49bf4aa3 100644 --- a/ext/typeexpr/defaults_test.go +++ b/ext/typeexpr/defaults_test.go @@ -491,6 +491,344 @@ 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": 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"), + }), + 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"), + }), + }), + }, + //"list of objects with nested optional list with empty default": { + // defaults: &Defaults{ + // Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "name": cty.String, + // "optional_list": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "string": cty.String, + // "optional_string": cty.String, + // }, []string{"optional_string"})), + // }, []string{"optional_list"})), + // Children: map[string]*Defaults{ + // "": { + // Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "name": cty.String, + // "optional_list": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "string": cty.String, + // "optional_string": cty.String, + // }, []string{"optional_string"})), + // }, []string{"optional_list"}), + // DefaultValues: map[string]cty.Value{ + // "optional_list": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + // "string": cty.String, + // "optional_string": cty.String, + // })), + // }, + // }, + // }, + // }, + // value: cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "name": cty.StringVal("abc"), + // "optional_list": cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "optional_string": cty.NullVal(cty.String), + // "string": cty.StringVal("child"), + // }), + // }), + // }), + // cty.ObjectVal(map[string]cty.Value{ + // "name": cty.StringVal("def"), + // "optional_list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ + // "optional_string": cty.String, + // "string": cty.String, + // }))), + // }), + // }), + // want: cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "name": cty.StringVal("abc"), + // "optional_list": cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "optional_string": cty.NullVal(cty.String), + // "string": cty.StringVal("child"), + // }), + // }), + // }), + // cty.ObjectVal(map[string]cty.Value{ + // "name": cty.StringVal("def"), + // "optional_list": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + // "optional_string": cty.String, + // "string": cty.String, + // })), + // }), + // }), + //}, + //"list of objects with nested optional list with default": { + // defaults: &Defaults{ + // Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "name": cty.String, + // "optional_list": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "string": cty.String, + // "optional_string": cty.String, + // }, []string{"optional_string"})), + // }, []string{"optional_list"})), + // Children: map[string]*Defaults{ + // "": { + // Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "name": cty.String, + // "optional_list": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "string": cty.String, + // "optional_string": cty.String, + // }, []string{"optional_string"})), + // }, []string{"optional_list"}), + // DefaultValues: map[string]cty.Value{ + // "optional_list": cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "string": cty.StringVal("default"), + // "optional_string": cty.NullVal(cty.String), + // }), + // }), + // }, + // }, + // }, + // }, + // value: cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "name": cty.StringVal("abc"), + // "optional_list": cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "optional_string": cty.NullVal(cty.String), + // "string": cty.StringVal("child"), + // }), + // }), + // }), + // cty.ObjectVal(map[string]cty.Value{ + // "name": cty.StringVal("def"), + // "optional_list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ + // "optional_string": cty.String, + // "string": cty.String, + // }))), + // }), + // }), + // want: cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "name": cty.StringVal("abc"), + // "optional_list": cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "optional_string": cty.NullVal(cty.String), + // "string": cty.StringVal("child"), + // }), + // }), + // }), + // cty.ObjectVal(map[string]cty.Value{ + // "name": cty.StringVal("def"), + // "optional_list": cty.ListVal([]cty.Value{ + // cty.ObjectVal(map[string]cty.Value{ + // "optional_string": cty.NullVal(cty.String), + // "string": cty.StringVal("default"), + // }), + // }), + // }), + // }), + //}, + //"map with nested empty objects with inner defaults": { + // defaults: &Defaults{ + // Type: cty.Map(cty.Object(map[string]cty.Type{ + // "rules": cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "backend": cty.String, + // "url_redirect": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "destination": cty.String, + // "preserve_path": cty.Bool, + // }, []string{"preserve_path"}), + // }, []string{"backend", "url_redirect"})), + // })), + // Children: map[string]*Defaults{ + // "": { + // Type: cty.Object(map[string]cty.Type{ + // "rules": cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "backend": cty.String, + // "url_redirect": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "destination": cty.String, + // "preserve_path": cty.Bool, + // }, []string{"preserve_path"}), + // }, []string{"backend", "url_redirect"})), + // }), + // Children: map[string]*Defaults{ + // "rules": { + // Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "backend": cty.String, + // "url_redirect": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "destination": cty.String, + // "preserve_path": cty.Bool, + // }, []string{"preserve_path"}), + // }, []string{"backend", "url_redirect"})), + // Children: map[string]*Defaults{ + // "": { + // Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "backend": cty.String, + // "url_redirect": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "destination": cty.String, + // "preserve_path": cty.Bool, + // }, []string{"preserve_path"}), + // }, []string{"backend", "url_redirect"}), + // Children: map[string]*Defaults{ + // "url_redirect": { + // Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + // "destination": cty.String, + // "preserve_path": cty.Bool, + // }, []string{"preserve_path"}), + // DefaultValues: map[string]cty.Value{ + // "preserve_path": cty.True, + // }, + // }, + // }, + // }, + // }, + // }, + // }, + // }, + // }, + // }, + // value: cty.MapVal(map[string]cty.Value{ + // "one": cty.ObjectVal(map[string]cty.Value{ + // "rules": cty.MapVal(map[string]cty.Value{ + // "/": cty.ObjectVal(map[string]cty.Value{ + // "backend": cty.StringVal("testing"), + // "url_redirect": cty.NullVal(cty.Object(map[string]cty.Type{ + // "destination": cty.String, + // "preserve_path": cty.Bool, + // })), + // }), + // }), + // }), + // "two": cty.ObjectVal(map[string]cty.Value{ + // "rules": cty.MapVal(map[string]cty.Value{ + // "/": cty.ObjectVal(map[string]cty.Value{ + // "backend": cty.NullVal(cty.String), + // "url_redirect": cty.ObjectVal(map[string]cty.Value{ + // "destination": cty.NullVal(cty.String), + // "preserve_path": cty.False, + // }), + // }), + // }), + // }), + // }), + // want: cty.MapVal(map[string]cty.Value{ + // "one": cty.ObjectVal(map[string]cty.Value{ + // "rules": cty.MapVal(map[string]cty.Value{ + // "/": cty.ObjectVal(map[string]cty.Value{ + // "backend": cty.StringVal("testing"), + // "url_redirect": cty.NullVal(cty.Object(map[string]cty.Type{ + // "destination": cty.String, + // "preserve_path": cty.Bool, + // })), + // }), + // }), + // }), + // "two": cty.ObjectVal(map[string]cty.Value{ + // "rules": cty.MapVal(map[string]cty.Value{ + // "/": cty.ObjectVal(map[string]cty.Value{ + // "backend": cty.NullVal(cty.String), + // "url_redirect": cty.ObjectVal(map[string]cty.Value{ + // "destination": cty.NullVal(cty.String), + // "preserve_path": cty.False, + // }), + // }), + // }), + // }), + // }), + //}, } for name, tc := range testCases { From c7cfb6afb235cc2be4b9ba58ebd0b265a9305111 Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Fri, 4 Nov 2022 11:09:41 +0100 Subject: [PATCH 2/3] remove previous complex test cases --- ext/typeexpr/defaults_test.go | 239 ---------------------------------- 1 file changed, 239 deletions(-) diff --git a/ext/typeexpr/defaults_test.go b/ext/typeexpr/defaults_test.go index 49bf4aa3..7ca6161e 100644 --- a/ext/typeexpr/defaults_test.go +++ b/ext/typeexpr/defaults_test.go @@ -590,245 +590,6 @@ func TestDefaults_Apply(t *testing.T) { }), }), }, - //"list of objects with nested optional list with empty default": { - // defaults: &Defaults{ - // Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "name": cty.String, - // "optional_list": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "string": cty.String, - // "optional_string": cty.String, - // }, []string{"optional_string"})), - // }, []string{"optional_list"})), - // Children: map[string]*Defaults{ - // "": { - // Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "name": cty.String, - // "optional_list": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "string": cty.String, - // "optional_string": cty.String, - // }, []string{"optional_string"})), - // }, []string{"optional_list"}), - // DefaultValues: map[string]cty.Value{ - // "optional_list": cty.ListValEmpty(cty.Object(map[string]cty.Type{ - // "string": cty.String, - // "optional_string": cty.String, - // })), - // }, - // }, - // }, - // }, - // value: cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "name": cty.StringVal("abc"), - // "optional_list": cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "optional_string": cty.NullVal(cty.String), - // "string": cty.StringVal("child"), - // }), - // }), - // }), - // cty.ObjectVal(map[string]cty.Value{ - // "name": cty.StringVal("def"), - // "optional_list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ - // "optional_string": cty.String, - // "string": cty.String, - // }))), - // }), - // }), - // want: cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "name": cty.StringVal("abc"), - // "optional_list": cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "optional_string": cty.NullVal(cty.String), - // "string": cty.StringVal("child"), - // }), - // }), - // }), - // cty.ObjectVal(map[string]cty.Value{ - // "name": cty.StringVal("def"), - // "optional_list": cty.ListValEmpty(cty.Object(map[string]cty.Type{ - // "optional_string": cty.String, - // "string": cty.String, - // })), - // }), - // }), - //}, - //"list of objects with nested optional list with default": { - // defaults: &Defaults{ - // Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "name": cty.String, - // "optional_list": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "string": cty.String, - // "optional_string": cty.String, - // }, []string{"optional_string"})), - // }, []string{"optional_list"})), - // Children: map[string]*Defaults{ - // "": { - // Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "name": cty.String, - // "optional_list": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "string": cty.String, - // "optional_string": cty.String, - // }, []string{"optional_string"})), - // }, []string{"optional_list"}), - // DefaultValues: map[string]cty.Value{ - // "optional_list": cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "string": cty.StringVal("default"), - // "optional_string": cty.NullVal(cty.String), - // }), - // }), - // }, - // }, - // }, - // }, - // value: cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "name": cty.StringVal("abc"), - // "optional_list": cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "optional_string": cty.NullVal(cty.String), - // "string": cty.StringVal("child"), - // }), - // }), - // }), - // cty.ObjectVal(map[string]cty.Value{ - // "name": cty.StringVal("def"), - // "optional_list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ - // "optional_string": cty.String, - // "string": cty.String, - // }))), - // }), - // }), - // want: cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "name": cty.StringVal("abc"), - // "optional_list": cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "optional_string": cty.NullVal(cty.String), - // "string": cty.StringVal("child"), - // }), - // }), - // }), - // cty.ObjectVal(map[string]cty.Value{ - // "name": cty.StringVal("def"), - // "optional_list": cty.ListVal([]cty.Value{ - // cty.ObjectVal(map[string]cty.Value{ - // "optional_string": cty.NullVal(cty.String), - // "string": cty.StringVal("default"), - // }), - // }), - // }), - // }), - //}, - //"map with nested empty objects with inner defaults": { - // defaults: &Defaults{ - // Type: cty.Map(cty.Object(map[string]cty.Type{ - // "rules": cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "backend": cty.String, - // "url_redirect": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "destination": cty.String, - // "preserve_path": cty.Bool, - // }, []string{"preserve_path"}), - // }, []string{"backend", "url_redirect"})), - // })), - // Children: map[string]*Defaults{ - // "": { - // Type: cty.Object(map[string]cty.Type{ - // "rules": cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "backend": cty.String, - // "url_redirect": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "destination": cty.String, - // "preserve_path": cty.Bool, - // }, []string{"preserve_path"}), - // }, []string{"backend", "url_redirect"})), - // }), - // Children: map[string]*Defaults{ - // "rules": { - // Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "backend": cty.String, - // "url_redirect": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "destination": cty.String, - // "preserve_path": cty.Bool, - // }, []string{"preserve_path"}), - // }, []string{"backend", "url_redirect"})), - // Children: map[string]*Defaults{ - // "": { - // Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "backend": cty.String, - // "url_redirect": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "destination": cty.String, - // "preserve_path": cty.Bool, - // }, []string{"preserve_path"}), - // }, []string{"backend", "url_redirect"}), - // Children: map[string]*Defaults{ - // "url_redirect": { - // Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ - // "destination": cty.String, - // "preserve_path": cty.Bool, - // }, []string{"preserve_path"}), - // DefaultValues: map[string]cty.Value{ - // "preserve_path": cty.True, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // value: cty.MapVal(map[string]cty.Value{ - // "one": cty.ObjectVal(map[string]cty.Value{ - // "rules": cty.MapVal(map[string]cty.Value{ - // "/": cty.ObjectVal(map[string]cty.Value{ - // "backend": cty.StringVal("testing"), - // "url_redirect": cty.NullVal(cty.Object(map[string]cty.Type{ - // "destination": cty.String, - // "preserve_path": cty.Bool, - // })), - // }), - // }), - // }), - // "two": cty.ObjectVal(map[string]cty.Value{ - // "rules": cty.MapVal(map[string]cty.Value{ - // "/": cty.ObjectVal(map[string]cty.Value{ - // "backend": cty.NullVal(cty.String), - // "url_redirect": cty.ObjectVal(map[string]cty.Value{ - // "destination": cty.NullVal(cty.String), - // "preserve_path": cty.False, - // }), - // }), - // }), - // }), - // }), - // want: cty.MapVal(map[string]cty.Value{ - // "one": cty.ObjectVal(map[string]cty.Value{ - // "rules": cty.MapVal(map[string]cty.Value{ - // "/": cty.ObjectVal(map[string]cty.Value{ - // "backend": cty.StringVal("testing"), - // "url_redirect": cty.NullVal(cty.Object(map[string]cty.Type{ - // "destination": cty.String, - // "preserve_path": cty.Bool, - // })), - // }), - // }), - // }), - // "two": cty.ObjectVal(map[string]cty.Value{ - // "rules": cty.MapVal(map[string]cty.Value{ - // "/": cty.ObjectVal(map[string]cty.Value{ - // "backend": cty.NullVal(cty.String), - // "url_redirect": cty.ObjectVal(map[string]cty.Value{ - // "destination": cty.NullVal(cty.String), - // "preserve_path": cty.False, - // }), - // }), - // }), - // }), - // }), - //}, } for name, tc := range testCases { From 233ef620b0a8db817855263a0d0834cffa34f3fa Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Fri, 4 Nov 2022 14:41:42 +0100 Subject: [PATCH 3/3] add test cases that verify behaviour from the forums --- ext/typeexpr/defaults_test.go | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/ext/typeexpr/defaults_test.go b/ext/typeexpr/defaults_test.go index 7ca6161e..d8b583e5 100644 --- a/ext/typeexpr/defaults_test.go +++ b/ext/typeexpr/defaults_test.go @@ -539,6 +539,7 @@ func TestDefaults_Apply(t *testing.T) { }, 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, @@ -581,6 +582,7 @@ func TestDefaults_Apply(t *testing.T) { }, 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"), @@ -590,6 +592,63 @@ func TestDefaults_Apply(t *testing.T) { }), }), }, + // 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 {