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

Allow ignoring undefined attributes during unmarshalling #213

Merged
merged 4 commits into from Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions .changelog/213.txt
@@ -0,0 +1,7 @@
```release-note:enhancement
tfprotov5/state: Added `UnmarshalWithOpts` func to facilitate configurable behaviour during unmarshalling
```

```release-note:enhancement
tfprotov6/state: Added `UnmarshalWithOpts` func to facilitate configurable behaviour during unmarshalling
```
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
19 changes: 19 additions & 0 deletions tfprotov5/state.go
Expand Up @@ -77,3 +77,22 @@ func (s RawState) Unmarshal(typ tftypes.Type) (tftypes.Value, error) {
}
return tftypes.Value{}, ErrUnknownRawStateType
}

// UnmarshalOpts contains options that can be used to modify the behaviour when
// unmarshalling. Currently, this only contains a struct for opts for JSON but
// could have a field for Flatmap in the future.
type UnmarshalOpts struct {
ValueFromJSONOpts tftypes.ValueFromJSONOpts
}

// UnmarshalWithOpts is identical to Unmarshal but also accepts a tftypes.UnmarshalOpts which contains
// options that can be used to modify the behaviour when unmarshalling JSON or Flatmap.
func (s RawState) UnmarshalWithOpts(typ tftypes.Type, opts UnmarshalOpts) (tftypes.Value, error) {
if s.JSON != nil {
return tftypes.ValueFromJSONWithOpts(s.JSON, typ, opts.ValueFromJSONOpts) //nolint:staticcheck
}
if s.Flatmap != nil {
return tftypes.Value{}, fmt.Errorf("flatmap states cannot be unmarshaled, only states written by Terraform 0.12 and higher can be unmarshaled")
}
return tftypes.Value{}, ErrUnknownRawStateType
}
83 changes: 83 additions & 0 deletions tfprotov5/state_test.go
@@ -0,0 +1,83 @@
package tfprotov5_test

import (
"math/big"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestRawStateUnmarshalWithOpts(t *testing.T) {
t.Parallel()
type testCase struct {
rawState tfprotov5.RawState
value tftypes.Value
typ tftypes.Type
opts tfprotov5.UnmarshalOpts
}
tests := map[string]testCase{
"object-of-bool-number": {
rawState: tfprotov5.RawState{
JSON: []byte(`{"bool":true,"number":0}`),
},
value: tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool": tftypes.Bool,
"number": tftypes.Number,
},
}, map[string]tftypes.Value{
"bool": tftypes.NewValue(tftypes.Bool, true),
"number": tftypes.NewValue(tftypes.Number, big.NewFloat(0)),
}),
typ: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool": tftypes.Bool,
"number": tftypes.Number,
},
},
},
"object-with-missing-attribute": {
rawState: tfprotov5.RawState{
JSON: []byte(`{"bool":true,"number":0,"unknown":"whatever"}`),
},
value: tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool": tftypes.Bool,
"number": tftypes.Number,
},
}, map[string]tftypes.Value{
"bool": tftypes.NewValue(tftypes.Bool, true),
"number": tftypes.NewValue(tftypes.Number, big.NewFloat(0)),
}),
typ: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool": tftypes.Bool,
"number": tftypes.Number,
},
},
opts: tfprotov5.UnmarshalOpts{
ValueFromJSONOpts: tftypes.ValueFromJSONOpts{
IgnoreUndefinedAttributes: true,
},
},
},
}
for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
t.Parallel()

val, err := test.rawState.UnmarshalWithOpts(test.typ, test.opts)
if err != nil {
t.Fatalf("unexpected error unmarshaling: %s", err)
}

if diff := cmp.Diff(test.value, val); diff != "" {
t.Errorf("Unexpected results (-wanted +got): %s", diff)
}
})
}
}
19 changes: 19 additions & 0 deletions tfprotov6/state.go
Expand Up @@ -77,3 +77,22 @@ func (s RawState) Unmarshal(typ tftypes.Type) (tftypes.Value, error) {
}
return tftypes.Value{}, ErrUnknownRawStateType
}

// UnmarshalOpts contains options that can be used to modify the behaviour when
// unmarshalling. Currently, this only contains a struct for opts for JSON but
// could have a field for Flatmap in the future.
type UnmarshalOpts struct {
ValueFromJSONOpts tftypes.ValueFromJSONOpts
}

// UnmarshalWithOpts is identical to Unmarshal but also accepts a tftypes.UnmarshalOpts which contains
// options that can be used to modify the behaviour when unmarshalling JSON or Flatmap.
func (s RawState) UnmarshalWithOpts(typ tftypes.Type, opts UnmarshalOpts) (tftypes.Value, error) {
if s.JSON != nil {
return tftypes.ValueFromJSONWithOpts(s.JSON, typ, opts.ValueFromJSONOpts) //nolint:staticcheck
}
if s.Flatmap != nil {
return tftypes.Value{}, fmt.Errorf("flatmap states cannot be unmarshaled, only states written by Terraform 0.12 and higher can be unmarshaled")
}
return tftypes.Value{}, ErrUnknownRawStateType
}
83 changes: 83 additions & 0 deletions tfprotov6/state_test.go
@@ -0,0 +1,83 @@
package tfprotov6_test

import (
"math/big"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestRawStateUnmarshalWithOpts(t *testing.T) {
t.Parallel()
type testCase struct {
rawState tfprotov6.RawState
value tftypes.Value
typ tftypes.Type
opts tfprotov6.UnmarshalOpts
}
tests := map[string]testCase{
"object-of-bool-number": {
rawState: tfprotov6.RawState{
JSON: []byte(`{"bool":true,"number":0}`),
},
value: tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool": tftypes.Bool,
"number": tftypes.Number,
},
}, map[string]tftypes.Value{
"bool": tftypes.NewValue(tftypes.Bool, true),
"number": tftypes.NewValue(tftypes.Number, big.NewFloat(0)),
}),
typ: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool": tftypes.Bool,
"number": tftypes.Number,
},
},
},
"object-with-missing-attribute": {
rawState: tfprotov6.RawState{
JSON: []byte(`{"bool":true,"number":0,"unknown":"whatever"}`),
},
value: tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool": tftypes.Bool,
"number": tftypes.Number,
},
}, map[string]tftypes.Value{
"bool": tftypes.NewValue(tftypes.Bool, true),
"number": tftypes.NewValue(tftypes.Number, big.NewFloat(0)),
}),
typ: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool": tftypes.Bool,
"number": tftypes.Number,
},
},
opts: tfprotov6.UnmarshalOpts{
ValueFromJSONOpts: tftypes.ValueFromJSONOpts{
IgnoreUndefinedAttributes: true,
},
},
},
}
for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
t.Parallel()

val, err := test.rawState.UnmarshalWithOpts(test.typ, test.opts)
if err != nil {
t.Fatalf("unexpected error unmarshaling: %s", err)
}

if diff := cmp.Diff(test.value, val); diff != "" {
t.Errorf("Unexpected results (-wanted +got): %s", diff)
}
})
}
}