Skip to content

Commit

Permalink
add support for case-insensitive collision detection
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismarget-j committed Sep 20, 2023
1 parent d5ea44f commit fcb5d9c
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 16 deletions.
46 changes: 37 additions & 9 deletions apstra/apstra_validator/attribute_conflict.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ var _ CollectionValidator = attributeConflictValidator{}
//
// If keyAttrs is empty, then values across all attributes are evaluated.
type attributeConflictValidator struct {
keyAttrs []string
keyAttrs []string
caseInsensitive bool
}

func (o attributeConflictValidator) Description(_ context.Context) string {
Expand Down Expand Up @@ -107,12 +108,6 @@ func (o attributeConflictValidator) ValidateSet(ctx context.Context, req validat
}
}

func UniqueValueCombinationsAt(attrNames ...string) CollectionValidator {
return attributeConflictValidator{
keyAttrs: attrNames,
}
}

type attributeConflictValidateElementRequest struct {
elementValue attr.Value
elementPath path.Path
Expand Down Expand Up @@ -170,7 +165,14 @@ func (o *attributeConflictValidator) validateElement(ctx context.Context, req at
return // cannot validate when attribute is unknown
}

keyValuesMap[attrName] = base64.StdEncoding.EncodeToString([]byte(attrValue.String()))
var valueToCompare string // a configured value we're checking for unique-ness
if o.caseInsensitive {
valueToCompare = strings.ToLower(attrValue.String())
} else {
valueToCompare = attrValue.String()
}

keyValuesMap[attrName] = base64.StdEncoding.EncodeToString([]byte(valueToCompare))
if len(keyValuesMap) < len(keyAttributeNames) {
continue // keep going until we fill keyValuesMap
}
Expand All @@ -185,14 +187,40 @@ func (o *attributeConflictValidator) validateElement(ctx context.Context, req at
}

if req.foundKeyValueCombinations[sb.String()] { // seen this value before?
var detailedError string
switch len(o.keyAttrs) {
case 0:
detailedError = fmt.Sprintf("Two objects cannot use the same value "+
"combination across all attributes (case sensitive: %t", o.caseInsensitive)
case 1:
detailedError = fmt.Sprintf("Two objects cannot use the same value for "+
"'%s' (case sensitive: %t)", o.keyAttrs[0], o.caseInsensitive)
default:
detailedError = fmt.Sprintf("Two objects cannot use the same value "+
"combination for these attributes: ['%s'] (case sensitive: %t)",
strings.Join(o.keyAttrs, "', '"), o.caseInsensitive)
}
resp.Diagnostics.AddAttributeError(
req.elementPath,
fmt.Sprintf("%s collision", o.keyAttrs),
fmt.Sprintf("Two objects cannot use the same %s", o.keyAttrs),
detailedError,
)
} else {
req.foundKeyValueCombinations[sb.String()] = true // log the name for future collision checks
}
break // all of the the required attribute have been found; move on to the next set member
}
}

func UniqueValueCombinationsAt(attrNames ...string) CollectionValidator {
return attributeConflictValidator{
keyAttrs: attrNames,
}
}

func UniqueInsensitiveValueCombinationsAt(attrNames ...string) CollectionValidator {
return attributeConflictValidator{
keyAttrs: attrNames,
caseInsensitive: true,
}
}
82 changes: 75 additions & 7 deletions apstra/apstra_validator/attribute_conflict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ func TestAttributeConflictValidator(t *testing.T) {
ctx := context.Background()

type testCase struct {
keyAttrNames []string
attrTypes map[string]attr.Type
attrValues map[string]attr.Value
expectError bool
keyAttrNames []string
attrTypes map[string]attr.Type
attrValues map[string]attr.Value
expectError bool
caseInsensitive bool
}

attrValueSlice := func(in map[string]attr.Value) []attr.Value {
Expand Down Expand Up @@ -391,6 +392,58 @@ func TestAttributeConflictValidator(t *testing.T) {
},
expectError: true,
},
"one_key_no_collision_case_sensitive": {
keyAttrNames: []string{"key1"},
attrTypes: map[string]attr.Type{
"key1": types.StringType,
},
attrValues: map[string]attr.Value{
"one": types.ObjectValueMust(
map[string]attr.Type{
"key1": types.StringType,
},
map[string]attr.Value{
"key1": types.StringValue("foo"),
},
),
"two": types.ObjectValueMust(
map[string]attr.Type{
"key1": types.StringType,
},
map[string]attr.Value{
"key1": types.StringValue("FOO"),
},
),
},
expectError: false,
caseInsensitive: false,
},
"one_key_collision_case_insensitive": {
keyAttrNames: []string{"key1"},
attrTypes: map[string]attr.Type{
"key1": types.StringType,
},
attrValues: map[string]attr.Value{
"one": types.ObjectValueMust(
map[string]attr.Type{
"key1": types.StringType,
},
map[string]attr.Value{
"key1": types.StringValue("foo"),
},
),
"two": types.ObjectValueMust(
map[string]attr.Type{
"key1": types.StringType,
},
map[string]attr.Value{
"key1": types.StringValue("FOO"),
},
),
},
expectError: true,
caseInsensitive: true,
},
}

// test list validation
Expand All @@ -404,7 +457,12 @@ func TestAttributeConflictValidator(t *testing.T) {
ConfigValue: types.ListValueMust(types.ObjectType{AttrTypes: tCase.attrTypes}, attrValueSlice(tCase.attrValues)),
}
response := validator.ListResponse{}
v := UniqueValueCombinationsAt(tCase.keyAttrNames...)
var v CollectionValidator
if tCase.caseInsensitive {
v = UniqueInsensitiveValueCombinationsAt(tCase.keyAttrNames...)
} else {
v = UniqueValueCombinationsAt(tCase.keyAttrNames...)
}
v.ValidateList(ctx, request, &response)

if !response.Diagnostics.HasError() && tCase.expectError {
Expand Down Expand Up @@ -436,7 +494,12 @@ func TestAttributeConflictValidator(t *testing.T) {
ConfigValue: types.MapValueMust(types.ObjectType{AttrTypes: tCase.attrTypes}, tCase.attrValues),
}
response := validator.MapResponse{}
v := UniqueValueCombinationsAt(tCase.keyAttrNames...)
var v CollectionValidator
if tCase.caseInsensitive {
v = UniqueInsensitiveValueCombinationsAt(tCase.keyAttrNames...)
} else {
v = UniqueValueCombinationsAt(tCase.keyAttrNames...)
}
v.ValidateMap(ctx, request, &response)

if !response.Diagnostics.HasError() && tCase.expectError {
Expand Down Expand Up @@ -468,7 +531,12 @@ func TestAttributeConflictValidator(t *testing.T) {
ConfigValue: types.SetValueMust(types.ObjectType{AttrTypes: tCase.attrTypes}, attrValueSlice(tCase.attrValues)),
}
response := validator.SetResponse{}
v := UniqueValueCombinationsAt(tCase.keyAttrNames...)
var v CollectionValidator
if tCase.caseInsensitive {
v = UniqueInsensitiveValueCombinationsAt(tCase.keyAttrNames...)
} else {
v = UniqueValueCombinationsAt(tCase.keyAttrNames...)
}
v.ValidateSet(ctx, request, &response)

if !response.Diagnostics.HasError() && tCase.expectError {
Expand Down

0 comments on commit fcb5d9c

Please sign in to comment.