Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-jcieslak committed Dec 14, 2023
1 parent 18b0dcc commit c8d1163
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 0 deletions.
78 changes: 78 additions & 0 deletions pkg/resources/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package resources

import (
"fmt"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"reflect"

Check failure on line 8 in pkg/resources/helpers.go

View workflow job for this annotation

GitHub Actions / reviewdog

[golangci] reported by reviewdog 🐶 File is not `gofumpt`-ed (gofumpt) Raw Output: pkg/resources/helpers.go:8: File is not `gofumpt`-ed (gofumpt) "reflect" "strings"
"strings"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
Expand Down Expand Up @@ -142,3 +146,77 @@ func IsDataType() schema.SchemaValidateFunc {
return warnings, errors
}
}

// IsValidIdentifier is a validator that can be used for validating identifiers passed in resources and data sources.
// Typically, we expect passed identifiers to be a variation of sdk.ObjectIdentifier. To use this function, pass it as
// a validation function on identifier field with generic parameter set to the desired sdk.ObjectIdentifier implementation.
func IsValidIdentifier[T sdk.ObjectIdentifier]() schema.SchemaValidateDiagFunc {
return func(value any, path cty.Path) diag.Diagnostics {
var diags diag.Diagnostics

// For now, we won't support sdk.ExternalObjectIdentifiers. The reason behind it is that the functions that parse identifiers are not
// able to differentiate between sdk.ExternalObjectIdentifiers and sdk.DatabaseObjectIdentifier or sdk.SchemaObjectIdentifier,
// because sdk.ExternalObjectIdentifiers has varying parts count (2 or 3).
if _, ok := any(sdk.ExternalObjectIdentifier{}).(T); ok {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Invalid schema identifier type",
Detail: "Identifier validation is not available for sdk.ExternalObjectIdentifier type. This is a provider error please file a report: https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/new/choose",
AttributePath: path,
})
return diags
}

if stringValue, ok := value.(string); ok {
id, err := helpers.DecodeSnowflakeParameterID(stringValue)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Unable to parse the identifier",
Detail: fmt.Sprintf(
"Unable to parse the identifier: %s. Make sure you are using the correct form of the fully qualified name for this field: %s",
stringValue,
getExpectedIdentifierForm[T](nil),
),
AttributePath: path,
})
return diags
}

if _, ok := id.(T); !ok {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Invalid identifier type",
Detail: fmt.Sprintf(
"Expected %s identifier type, but got: %T. The correct form of the fully qualified name for this field is: %s, but was %s",
reflect.TypeOf(new(T)).Elem().Name(),
id,
getExpectedIdentifierForm[T](nil),
getExpectedIdentifierForm(&id),
),
AttributePath: path,
})
}
} else {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Invalid schema identifier type",
Detail: fmt.Sprintf("Expected schema string type, but got: %T. This is a provider error please file a report: https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/new/choose", value),
AttributePath: path,
})
}

return diags
}
}

// getExpectedIdentifierForm will choose the type either from the objectIdentifier parameter if it's present. If it's not,
// then it will create a new identifier based on the generic type parameter T, then it will return the proper structure
// we are expecting for the given sdk.ObjectIdentifier type.
func getExpectedIdentifierForm[T sdk.ObjectIdentifier](objectIdentifier *T) string {
if objectIdentifier != nil {
return (*objectIdentifier).Representation()
}
id := new(T)
return sdk.GetIdentifierRepresentation(*id)
}
94 changes: 94 additions & 0 deletions pkg/resources/helpers_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package resources_test

import (
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/hashicorp/go-cty/cty"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -512,3 +514,95 @@ func TestIsDataType(t *testing.T) {
})
}
}

func TestIsValidIdentifier(t *testing.T) {
accountObjectIdentifierCheck := resources.IsValidIdentifier[sdk.AccountObjectIdentifier]()
databaseObjectIdentifierCheck := resources.IsValidIdentifier[sdk.DatabaseObjectIdentifier]()
schemaObjectIdentifierCheck := resources.IsValidIdentifier[sdk.SchemaObjectIdentifier]()
externalObjectIdentifierCheck := resources.IsValidIdentifier[sdk.ExternalObjectIdentifier]()
tableColumnIdentifierCheck := resources.IsValidIdentifier[sdk.TableColumnIdentifier]()

testCases := []struct {
Name string
Value any
Error string
CheckingFn schema.SchemaValidateDiagFunc
}{
{
Name: "validation: invalid value type",
Value: 123,
Error: "Expected schema string type, but got: int",
CheckingFn: accountObjectIdentifierCheck,
},
{
Name: "validation: invalid identifier representation",
Value: "",
Error: "Unable to parse the identifier: ",
CheckingFn: accountObjectIdentifierCheck,
},
// Invalid form for different checkers (tests getExpectedIdentifierForm function)
{
Name: "validation: incorrect form for account object identifier",
Value: "a.b",
Error: "<name>, but was <database_name>.<name>",
CheckingFn: accountObjectIdentifierCheck,
},
{
Name: "validation: incorrect form for database object identifier",
Value: "a.b.c",
Error: "<database_name>.<name>, but was <database_name>.<schema_name>.<name>",
CheckingFn: databaseObjectIdentifierCheck,
},
{
Name: "validation: incorrect form for schema object identifier",
Value: "a.b.c.d",
Error: "<database_name>.<schema_name>.<name>, but was <database_name>.<schema_name>.<table_name>.<column_name>",
CheckingFn: schemaObjectIdentifierCheck,
},
{
Name: "validation: incorrect form for table column identifier",
Value: "a",
Error: "<database_name>.<schema_name>.<table_name>.<column_name>, but was <name>",
CheckingFn: tableColumnIdentifierCheck,
},
{
Name: "validation: external object identifier is validated",
Value: "a",
Error: "Identifier validation is not available for sdk.ExternalObjectIdentifier type.",
CheckingFn: externalObjectIdentifierCheck,
},
// Valid form for different checkers (tests getExpectedIdentifierForm function)
{
Name: "correct form for account object identifier",
Value: "a",
CheckingFn: accountObjectIdentifierCheck,
},
{
Name: "correct form for database object identifier",
Value: "a.b",
CheckingFn: databaseObjectIdentifierCheck,
},
{
Name: "correct form for schema object identifier",
Value: "a.b.c",
CheckingFn: schemaObjectIdentifierCheck,
},
{
Name: "correct form for table column identifier",
Value: "a.b.c.d",
CheckingFn: tableColumnIdentifierCheck,
},
}

for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
diag := tt.CheckingFn(tt.Value, cty.IndexStringPath("path"))
if tt.Error != "" {
assert.Len(t, diag, 1)
assert.Contains(t, diag[0].Detail, tt.Error)
} else {
assert.Len(t, diag, 0)
}
})
}
}
25 changes: 25 additions & 0 deletions pkg/sdk/identifier_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Identifier interface {
type ObjectIdentifier interface {
Identifier
FullyQualifiedName() string
Representation() string
}

func NewObjectIdentifierFromFullyQualifiedName(fullyQualifiedName string) ObjectIdentifier {
Expand Down Expand Up @@ -80,6 +81,10 @@ func (i ExternalObjectIdentifier) FullyQualifiedName() string {
return fmt.Sprintf(`%v.%v`, i.accountIdentifier.Name(), i.objectIdentifier.FullyQualifiedName())
}

func (i ExternalObjectIdentifier) Representation() string {
return "<account_locator>.<name> or <org_name>.<account_name>.<name>"
}

type AccountIdentifier struct {
organizationName string
accountName string
Expand Down Expand Up @@ -142,6 +147,10 @@ func (i AccountObjectIdentifier) FullyQualifiedName() string {
return fmt.Sprintf(`"%v"`, i.name)
}

func (i AccountObjectIdentifier) Representation() string {
return "<name>"
}

type DatabaseObjectIdentifier struct {
databaseName string
name string
Expand Down Expand Up @@ -177,6 +186,10 @@ func (i DatabaseObjectIdentifier) FullyQualifiedName() string {
return fmt.Sprintf(`"%v"."%v"`, i.databaseName, i.name)
}

func (i DatabaseObjectIdentifier) Representation() string {
return "<database_name>.<name>"
}

type SchemaObjectIdentifier struct {
databaseName string
schemaName string
Expand Down Expand Up @@ -262,6 +275,10 @@ func (i SchemaObjectIdentifier) FullyQualifiedName() string {
return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(args, ", "))
}

func (i SchemaObjectIdentifier) Representation() string {
return "<database_name>.<schema_name>.<name>"
}

type TableColumnIdentifier struct {
databaseName string
schemaName string
Expand Down Expand Up @@ -310,3 +327,11 @@ func (i TableColumnIdentifier) FullyQualifiedName() string {
}
return fmt.Sprintf(`"%v"."%v"."%v"."%v"`, i.databaseName, i.schemaName, i.tableName, i.columnName)
}

func (i TableColumnIdentifier) Representation() string {
return "<database_name>.<schema_name>.<table_name>.<column_name>"
}

func GetIdentifierRepresentation(identifier ObjectIdentifier) string {
return identifier.Representation()
}

0 comments on commit c8d1163

Please sign in to comment.