Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20230330-105617.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: 'datasource/schema: Raise validation errors if attempting to use attribute names
with leading numerics (0-9), which are invalid in the Terraform configuration language'
time: 2023-03-30T10:56:17.789569-04:00
custom:
Issue: "705"
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20230330-105618.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: 'provider/schema: Raise validation errors if attempting to use attribute names
with leading numerics (0-9), which are invalid in the Terraform configuration language'
time: 2023-03-30T10:56:18.789569-04:00
custom:
Issue: "705"
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20230330-105619.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: 'resource/schema: Raise validation errors if attempting to use attribute names
with leading numerics (0-9), which are invalid in the Terraform configuration language'
time: 2023-03-30T10:56:19.789569-04:00
custom:
Issue: "705"
22 changes: 17 additions & 5 deletions internal/fwschema/attribute_name_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ package fwschema
import (
"fmt"
"regexp"
"strings"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
)

// NumericPrefixRegex is a regular expression which matches whether a string
// begins with a numeric (0-9).
var NumericPrefixRegex = regexp.MustCompile(`^[0-9]`)

// ReservedProviderAttributeNames contains the list of root attribute names
// which should not be included in provider-defined provider schemas since
// they require practitioners to implement special syntax in their
Expand Down Expand Up @@ -51,11 +56,8 @@ var ReservedResourceAttributeNames = []string{
// including them in attribute names. Introducing them could cause practitioner
// confusion.
//
// TODO: Validate leading numeric characters
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/705
//
// [identifiers]: https://developer.hashicorp.com/terraform/language/syntax/configuration#identifiers
var ValidAttributeNameRegex = regexp.MustCompile("^[a-z0-9_]+$")
var ValidAttributeNameRegex = regexp.MustCompile("^[a-z_][a-z0-9_]*$")

// IsReservedProviderAttributeName returns an error diagnostic if the given
// attribute path represents a root attribute name in
Expand Down Expand Up @@ -134,6 +136,16 @@ func IsValidAttributeName(name string, attributePath path.Path) diag.Diagnostics
return diags
}

var message strings.Builder

message.WriteString("Names must ")

if NumericPrefixRegex.MatchString(name) {
message.WriteString("begin with a lowercase alphabet character (a-z) or underscore (_) and must ")
}

message.WriteString("only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).")

// The diagnostic path is intentionally omitted as it is invalid in this
// context. Diagnostic paths are intended to be mapped to actual data,
// while this path information must be synthesized.
Expand All @@ -142,7 +154,7 @@ func IsValidAttributeName(name string, attributePath path.Path) diag.Diagnostics
"When validating the schema, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("%q at schema path %q is an invalid attribute/block name. ", name, attributePath)+
"Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).",
message.String(),
)

return diags
Expand Down
125 changes: 125 additions & 0 deletions internal/fwschema/attribute_name_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,128 @@ func TestIsReservedResourceAttributeName(t *testing.T) {
})
}
}

func TestIsValidAttributeName(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
name string
expected diag.Diagnostics
}{
"empty": {
name: "",
expected: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Attribute/Block Name",
"When validating the schema, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"\"\" at schema path \"\" is an invalid attribute/block name. "+
"Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).",
),
},
},
"ascii-lowercase-alphabet": {
name: "test",
expected: nil,
},
"ascii-lowercase-alphabet-leading-underscore": {
name: "_test",
expected: nil,
},
"ascii-lowercase-alphabet-middle-hyphens": {
name: "test-me",
expected: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Attribute/Block Name",
"When validating the schema, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"\"test-me\" at schema path \"test-me\" is an invalid attribute/block name. "+
"Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).",
),
},
},
"ascii-lowercase-alphabet-middle-underscore": {
name: "test_me",
expected: nil,
},
"ascii-lowercase-alphanumeric": {
name: "test123",
expected: nil,
},
"ascii-lowercase-alphanumeric-leading-numeric": {
name: "123test",
expected: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Attribute/Block Name",
"When validating the schema, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"\"123test\" at schema path \"123test\" is an invalid attribute/block name. "+
"Names must begin with a lowercase alphabet character (a-z) or underscore (_) and "+
"must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).",
),
},
},
"ascii-uppercase-alphabet": {
name: "TEST",
expected: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Attribute/Block Name",
"When validating the schema, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"\"TEST\" at schema path \"TEST\" is an invalid attribute/block name. "+
"Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).",
),
},
},
"ascii-uppercase-alphanumeric": {
name: "TEST123",
expected: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Attribute/Block Name",
"When validating the schema, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"\"TEST123\" at schema path \"TEST123\" is an invalid attribute/block name. "+
"Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).",
),
},
},
"invalid-bytes": {
name: "\xff\xff",
expected: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Attribute/Block Name",
"When validating the schema, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"\"\\xff\\xff\" at schema path \"\\xff\\xff\" is an invalid attribute/block name. "+
"Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).",
),
},
},
"unicode": {
name: `tést`, // t, latin small letter e with acute (00e9), s, t
expected: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Attribute/Block Name",
"When validating the schema, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"\"tést\" at schema path \"tést\" is an invalid attribute/block name. "+
"Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).",
),
},
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

got := fwschema.IsValidAttributeName(testCase.name, path.Root(testCase.name))

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}