-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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 #23568 from hashicorp/f/typed-sdk-consistent-encoding
internal/sdk: resource decoding/encoding now parses the struct tags consistently
- Loading branch information
Showing
5 changed files
with
222 additions
and
37 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,55 @@ | ||
package sdk | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
) | ||
|
||
type decodedStructTags struct { | ||
// hclPath defines the path to this field used for this in the Schema for this Resource | ||
hclPath string | ||
|
||
// removedInNextMajorVersion specifies whether this field is deprecated and should not | ||
// be set into the state in the next major version of the Provider | ||
removedInNextMajorVersion bool | ||
} | ||
|
||
// parseStructTags parses the struct tags defined in input into a decodedStructTags object | ||
// which allows for the consistent parsing of struct tags across the Typed SDK. | ||
func parseStructTags(input reflect.StructTag) (*decodedStructTags, error) { | ||
tag, ok := input.Lookup("tfschema") | ||
if !ok { | ||
// doesn't exist - ignore it? | ||
return nil, nil | ||
} | ||
if tag == "" { | ||
return nil, fmt.Errorf("the `tfschema` struct tag was defined but empty") | ||
} | ||
|
||
components := strings.Split(tag, ",") | ||
output := &decodedStructTags{ | ||
// NOTE: `hclPath` has to be the first item in the struct tag | ||
hclPath: strings.TrimSpace(components[0]), | ||
removedInNextMajorVersion: false, | ||
} | ||
if output.hclPath == "" { | ||
return nil, fmt.Errorf("hclPath was empty") | ||
} | ||
|
||
if len(components) > 1 { | ||
// remove the hcl field name since it's been parsed | ||
components = components[1:] | ||
for _, item := range components { | ||
item = strings.TrimSpace(item) // allowing for both `foo,bar` and `foo, bar` in struct tags | ||
if strings.EqualFold(item, "removedInNextMajorVersion") { | ||
output.removedInNextMajorVersion = true | ||
continue | ||
} | ||
|
||
return nil, fmt.Errorf("internal-error: the struct-tag %q is not implemented - struct tags are %q", item, tag) | ||
} | ||
} | ||
|
||
return output, 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,111 @@ | ||
package sdk | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/hashicorp/go-azure-helpers/lang/pointer" | ||
) | ||
|
||
func TestParseStructTags_Empty(t *testing.T) { | ||
actual, err := parseStructTags("") | ||
if err != nil { | ||
t.Fatalf("unexpected error %q", err.Error()) | ||
} | ||
|
||
if actual != nil { | ||
t.Fatalf("expected actual to be nil but got %+v", *actual) | ||
} | ||
} | ||
|
||
func TestParseStructTags_WithValue(t *testing.T) { | ||
testData := []struct { | ||
input reflect.StructTag | ||
expected *decodedStructTags | ||
error *string | ||
}{ | ||
{ | ||
// empty hclPath | ||
input: `tfschema:""`, | ||
expected: nil, | ||
error: pointer.To("the `tfschema` struct tag was defined but empty"), | ||
}, | ||
{ | ||
// valid, no removedInNextMajorVersion | ||
input: `tfschema:"hello"`, | ||
expected: &decodedStructTags{ | ||
hclPath: "hello", | ||
removedInNextMajorVersion: false, | ||
}, | ||
}, | ||
{ | ||
// valid, with removedInNextMajorVersion | ||
input: `tfschema:"hello,removedInNextMajorVersion"`, | ||
expected: &decodedStructTags{ | ||
hclPath: "hello", | ||
removedInNextMajorVersion: true, | ||
}, | ||
}, | ||
{ | ||
// valid, with removedInNextMajorVersion and a space before the comma | ||
input: `tfschema:"hello, removedInNextMajorVersion"`, | ||
expected: &decodedStructTags{ | ||
hclPath: "hello", | ||
removedInNextMajorVersion: true, | ||
}, | ||
}, | ||
{ | ||
// valid, with removedInNextMajorVersion and a space after the comma | ||
// | ||
// This would be caught in PR review, but would be a confusing error/experience | ||
// during development so it's worth being lenient here since it's non-impactful | ||
input: `tfschema:"hello ,removedInNextMajorVersion"`, | ||
expected: &decodedStructTags{ | ||
hclPath: "hello", | ||
removedInNextMajorVersion: true, | ||
}, | ||
}, | ||
{ | ||
// valid, with removedInNextMajorVersion and a space either side | ||
// | ||
// This would be caught in PR review, but would be a confusing error/experience | ||
// during development so it's worth being lenient here since it's non-impactful | ||
input: `tfschema:"hello , removedInNextMajorVersion"`, | ||
expected: &decodedStructTags{ | ||
hclPath: "hello", | ||
removedInNextMajorVersion: true, | ||
}, | ||
}, | ||
{ | ||
// invalid, unknown struct tags | ||
input: `tfschema:"hello,world"`, | ||
expected: nil, | ||
error: pointer.To(`internal-error: the struct-tag "world" is not implemented - struct tags are "hello,world"`), | ||
}, | ||
} | ||
for i, data := range testData { | ||
t.Logf("Index %d - Input %q", i, data.input) | ||
actual, err := parseStructTags(data.input) | ||
if err != nil { | ||
if data.error != nil { | ||
if err.Error() == *data.error { | ||
continue | ||
} | ||
|
||
t.Fatalf("expected the error %q but got %q", *data.error, err.Error()) | ||
} | ||
|
||
t.Fatalf("unexpected error %q", err.Error()) | ||
} | ||
if data.error != nil { | ||
t.Fatalf("expected the error %q but didn't get one", *data.error) | ||
} | ||
|
||
if actual == nil { | ||
t.Fatalf("expected actual to have a value but got nil") | ||
} | ||
if !reflect.DeepEqual(*data.expected, *actual) { | ||
t.Fatalf("expected [%+v] and actual [%+v] didn't match", *data.expected, *actual) | ||
} | ||
} | ||
} |
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