-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #991 from Juniper/990-introduce-custom-type-string…
…withaltvalues Introduce custom type `StringWithAltValues`
- Loading branch information
Showing
6 changed files
with
280 additions
and
4 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,69 @@ | ||
package customtypes | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes" | ||
"github.com/hashicorp/terraform-plugin-go/tftypes" | ||
) | ||
|
||
var ( | ||
_ basetypes.StringTypable = (*StringWithAltValuesType)(nil) | ||
_ attr.Type = (*StringWithAltValuesType)(nil) | ||
) | ||
|
||
type StringWithAltValuesType struct { | ||
basetypes.StringType | ||
} | ||
|
||
// String returns a human readable string of the type name. | ||
func (t StringWithAltValuesType) String() string { | ||
return "customtypes.StringWithAltValues" | ||
} | ||
|
||
// ValueType returns the Value type. | ||
func (t StringWithAltValuesType) ValueType(_ context.Context) attr.Value { | ||
return StringWithAltValues{} | ||
} | ||
|
||
// Equal returns true if the given type is equivalent. | ||
func (t StringWithAltValuesType) Equal(o attr.Type) bool { | ||
other, ok := o.(StringWithAltValuesType) | ||
|
||
if !ok { | ||
return false | ||
} | ||
|
||
return t.StringType.Equal(other.StringType) | ||
} | ||
|
||
// ValueFromString returns a StringValuable type given a StringValue. | ||
func (t StringWithAltValuesType) ValueFromString(_ context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) { | ||
return StringWithAltValues{ | ||
StringValue: in, | ||
}, nil | ||
} | ||
|
||
// ValueFromTerraform returns a Value given a tftypes.Value. This is meant to convert the tftypes.Value into a more convenient Go type | ||
// for the provider to consume the data with. | ||
func (t StringWithAltValuesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { | ||
attrValue, err := t.StringType.ValueFromTerraform(ctx, in) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
stringValue, ok := attrValue.(basetypes.StringValue) | ||
if !ok { | ||
return nil, fmt.Errorf("unexpected value type of %T", attrValue) | ||
} | ||
|
||
stringValuable, diags := t.ValueFromString(ctx, stringValue) | ||
if diags.HasError() { | ||
return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags) | ||
} | ||
|
||
return stringValuable, nil | ||
} |
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,56 @@ | ||
package customtypes_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
customtypes "github.com/Juniper/terraform-provider-apstra/apstra/custom_types" | ||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-go/tftypes" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestStringWithAltValuesType_ValueFromTerraform(t *testing.T) { | ||
t.Parallel() | ||
|
||
testCases := map[string]struct { | ||
in tftypes.Value | ||
expectation attr.Value | ||
expectedErr string | ||
}{ | ||
"true": { | ||
in: tftypes.NewValue(tftypes.String, "foo"), | ||
expectation: customtypes.NewStringWithAltValuesValue("foo"), | ||
}, | ||
"unknown": { | ||
in: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), | ||
expectation: customtypes.NewStringWithAltValuesUnknown(), | ||
}, | ||
"null": { | ||
in: tftypes.NewValue(tftypes.String, nil), | ||
expectation: customtypes.NewStringWithAltValuesNull(), | ||
}, | ||
"wrongType": { | ||
in: tftypes.NewValue(tftypes.Number, 123), | ||
expectedErr: "can't unmarshal tftypes.Number into *string, expected string", | ||
}, | ||
} | ||
|
||
for tName, tCase := range testCases { | ||
t.Run(tName, func(t *testing.T) { | ||
t.Parallel() | ||
ctx := context.Background() | ||
|
||
got, err := customtypes.StringWithAltValuesType{}.ValueFromTerraform(ctx, tCase.in) | ||
if tCase.expectedErr == "" { | ||
require.NoError(t, err) | ||
} else { | ||
require.Error(t, err) | ||
require.Equal(t, tCase.expectedErr, err.Error()) | ||
return | ||
} | ||
|
||
require.Truef(t, got.Equal(tCase.expectation), "values not equal %s, %s", tCase.expectation, got) | ||
}) | ||
} | ||
} |
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,98 @@ | ||
package customtypes | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes" | ||
) | ||
|
||
var ( | ||
_ basetypes.StringValuable = (*StringWithAltValues)(nil) | ||
_ basetypes.StringValuableWithSemanticEquals = (*StringWithAltValues)(nil) | ||
) | ||
|
||
type StringWithAltValues struct { | ||
basetypes.StringValue | ||
altValues []attr.Value | ||
} | ||
|
||
func (v StringWithAltValues) Type(_ context.Context) attr.Type { | ||
return StringWithAltValuesType{} | ||
} | ||
|
||
func (v StringWithAltValues) Equal(o attr.Value) bool { | ||
other, ok := o.(StringWithAltValues) | ||
if !ok { | ||
return false | ||
} | ||
|
||
return v.StringValue.Equal(other.StringValue) | ||
} | ||
|
||
// StringSemanticEquals implements the semantic equality check. According to this | ||
// (https://discuss.hashicorp.com/t/can-semantic-equality-check-in-custom-types-be-asymmetrical/60644/2?u=hqnvylrx) | ||
// semantic equality checks on custom types are always implementeed as oldValue.SemanticEquals(ctx, newValue) | ||
func (v StringWithAltValues) StringSemanticEquals(_ context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) { | ||
var diags diag.Diagnostics | ||
|
||
newValue, ok := newValuable.(StringWithAltValues) | ||
if !ok { | ||
diags.AddError( | ||
"Semantic Equality Check Error", | ||
"An unexpected value type was received while performing semantic equality checks. "+ | ||
"Please report this to the provider developers.\n\n"+ | ||
"Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+ | ||
"Got Value Type: "+fmt.Sprintf("%T", newValuable), | ||
) | ||
|
||
return false, diags | ||
} | ||
|
||
// check new value against our "main" value | ||
if v.Equal(newValue) { | ||
return true, diags | ||
} | ||
|
||
// check new value against our "alt" values | ||
for _, a := range v.altValues { | ||
if a.Equal(newValue) { | ||
return true, diags | ||
} | ||
} | ||
|
||
// check old value against new "alt" values | ||
for _, a := range newValue.altValues { | ||
if a.Equal(v) { | ||
return true, diags | ||
} | ||
} | ||
|
||
return false, diags | ||
} | ||
|
||
func NewStringWithAltValuesNull() StringWithAltValues { | ||
return StringWithAltValues{ | ||
StringValue: basetypes.NewStringNull(), | ||
} | ||
} | ||
|
||
func NewStringWithAltValuesUnknown() StringWithAltValues { | ||
return StringWithAltValues{ | ||
StringValue: basetypes.NewStringUnknown(), | ||
} | ||
} | ||
|
||
func NewStringWithAltValuesValue(value string, alt ...string) StringWithAltValues { | ||
altValues := make([]attr.Value, len(alt)) | ||
for i, a := range alt { | ||
altValues[i] = StringWithAltValues{StringValue: basetypes.NewStringValue(a)} | ||
} | ||
|
||
return StringWithAltValues{ | ||
StringValue: basetypes.NewStringValue(value), | ||
altValues: altValues, | ||
} | ||
} |
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,56 @@ | ||
package customtypes_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
customtypes "github.com/Juniper/terraform-provider-apstra/apstra/custom_types" | ||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestStringWithAltValues_StringSemanticEquals(t *testing.T) { | ||
t.Parallel() | ||
|
||
testCases := map[string]struct { | ||
currentValue customtypes.StringWithAltValues | ||
givenValue basetypes.StringValuable | ||
expectedMatch bool | ||
}{ | ||
"equal - no alt values": { | ||
currentValue: customtypes.NewStringWithAltValuesValue("foo"), | ||
givenValue: customtypes.NewStringWithAltValuesValue("foo"), | ||
expectedMatch: true, | ||
}, | ||
"equal - with alt values": { | ||
currentValue: customtypes.NewStringWithAltValuesValue("foo", "bar", "baz"), | ||
givenValue: customtypes.NewStringWithAltValuesValue("foo"), | ||
expectedMatch: true, | ||
}, | ||
"semantically equal - given matches an alt value": { | ||
currentValue: customtypes.NewStringWithAltValuesValue("foo", "bar", "baz", "bang"), | ||
givenValue: customtypes.NewStringWithAltValuesValue("baz"), | ||
expectedMatch: true, | ||
}, | ||
"semantically equal - current matches an alt value": { | ||
currentValue: customtypes.NewStringWithAltValuesValue("baz"), | ||
givenValue: customtypes.NewStringWithAltValuesValue("foo", "bar", "baz", "bang"), | ||
expectedMatch: true, | ||
}, | ||
"not equal": { | ||
currentValue: customtypes.NewStringWithAltValuesValue("foo", "bar", "baz", "bang"), | ||
givenValue: customtypes.NewStringWithAltValuesValue("FOO"), | ||
expectedMatch: false, | ||
}, | ||
} | ||
|
||
for tName, tCase := range testCases { | ||
t.Run(tName, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
match, diags := tCase.currentValue.StringSemanticEquals(context.Background(), tCase.givenValue) | ||
require.Equalf(t, tCase.expectedMatch, match, "Expected StringSemanticEquals to return: %t, but got: %t", tCase.expectedMatch, match) | ||
require.Nil(t, diags) | ||
}) | ||
} | ||
} |