Skip to content
This repository has been archived by the owner on Dec 30, 2024. It is now read-only.

Commit

Permalink
feat: add typeOf
Browse files Browse the repository at this point in the history
  • Loading branch information
azrod committed Oct 31, 2023
1 parent c00e1b8 commit 45c98c5
Show file tree
Hide file tree
Showing 22 changed files with 1,786 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ linters-settings:
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
- prefix(github.com/github.com/FrangipaneTeam/terraform-plugin-framework-supertypes) # Custom section: groups all imports with the specified Prefix.
- prefix(github.com/FrangipaneTeam/terraform-plugin-framework-supertypes) # Custom section: groups all imports with the specified Prefix.
- blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled.
- dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled.
skip-generated: true
Expand Down
46 changes: 46 additions & 0 deletions attrtypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package supertypes

import (
"context"
"fmt"
"reflect"

"github.com/hashicorp/terraform-plugin-framework/attr"
)

// AttributeTypes returns a map of attribute types for the specified type T.
// T must be a struct and reflection is used to find exported fields of T with the `tfsdk` tag.
func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) {
var t T
val := reflect.ValueOf(t)
typ := val.Type()

if typ.Kind() != reflect.Struct {
return nil, fmt.Errorf("%T has unsupported type: %s", t, typ)
}

attributeTypes := make(map[string]attr.Type)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.PkgPath != "" {
continue // Skip unexported fields.
}
tag := field.Tag.Get(`tfsdk`)
if tag == "-" {
continue // Skip explicitly excluded fields.
}
if tag == "" {
return nil, fmt.Errorf(`%T needs a struct tag for "tfsdk" on %s`, t, field.Name)
}

if v, ok := val.Field(i).Interface().(attr.Value); ok {
attributeTypes[tag] = v.Type(ctx)
}
}

return attributeTypes, nil
}

func AttributeTypesMust[T any](ctx context.Context) map[string]attr.Type {
return Must(AttributeTypes[T](ctx))
}
53 changes: 53 additions & 0 deletions attrtypes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package supertypes_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"

supertypes "github.com/FrangipaneTeam/terraform-plugin-framework-supertypes"
)

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

type struct1 struct{}
type struct2 struct {
Name types.String `tfsdk:"name"`
ID types.Int64 `tfsdk:"id"`
IncludeProperty types.Bool `tfsdk:"include_property"`
}

ctx := context.Background()
got := supertypes.AttributeTypesMust[struct1](ctx)
wanted := map[string]attr.Type{}

if diff := cmp.Diff(got, wanted); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}

_, err := supertypes.AttributeTypes[int](ctx)

if err == nil {
t.Fatalf("expected error")
}

got, err = supertypes.AttributeTypes[struct2](ctx)

if err != nil {
t.Fatalf("unexpected error")
}

wanted = map[string]attr.Type{
"name": types.StringType,
"id": types.Int64Type,
"include_property": types.BoolType,
}

if diff := cmp.Diff(got, wanted); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}
}
68 changes: 68 additions & 0 deletions base.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ package supertypes

import (
// These are all the packages that are used in the templates.
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"

// Used for the templates.
_ "github.com/fatih/color"
_ "github.com/iancoleman/strcase"
)
Expand All @@ -10,4 +17,65 @@ const (
errorTest = "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer:\n\n"
)

func nestedObjectTypeNewObjectPtr[T any](_ context.Context) (*T, diag.Diagnostics) {
var diags diag.Diagnostics

return new(T), diags
}

func nestedObjectTypeNewObjectSlice[T any](_ context.Context, xlen, xcap int) ([]*T, diag.Diagnostics) { //nolint:unparam
var diags diag.Diagnostics

return make([]*T, xlen, xcap), diags
}

func nestedObjectValueSlice[T any](ctx context.Context, val valueWithElements) ([]*T, diag.Diagnostics) {
var diags diag.Diagnostics

elements := val.Elements()
n := len(elements)
slice := make([]*T, n)
for i := 0; i < n; i++ {
ptr, d := nestedObjectValueObjectPtrFromElement[T](ctx, elements[i])
diags.Append(d...)
if diags.HasError() {
return nil, diags
}

slice[i] = ptr
}

return slice, diags
}

func nestedObjectValueMap[T any](ctx context.Context, val valueWithElementsMap) (map[string]*T, diag.Diagnostics) {
var diags diag.Diagnostics

elements := val.Elements()
m := make(map[string]*T)
for e := range elements {
ptr, d := nestedObjectValueObjectPtrFromElement[T](ctx, elements[e])
diags.Append(d...)
if diags.HasError() {
return nil, diags
}

m[e] = ptr
}

return m, diags
}

func nestedObjectValueObjectPtrFromElement[T any](ctx context.Context, val attr.Value) (*T, diag.Diagnostics) {
var diags diag.Diagnostics

ptr := new(T)
diags.Append(val.(ObjectValueOf[T]).ObjectValue.As(ctx, ptr, basetypes.ObjectAsOptions{})...)
if diags.HasError() {
return nil, diags
}

return ptr, diags
}

//go:generate go run template.go
49 changes: 49 additions & 0 deletions errs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package supertypes

import (
"errors"
"fmt"

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

// Must is a generic implementation of the Go Must idiom [1, 2]. It panics if
// the provided error is non-nil and returns x otherwise.
//
// [1]: https://pkg.go.dev/text/template#Must
// [2]: https://pkg.go.dev/regexp#MustCompile
func Must[T any](x T, err error) T {
if err != nil {
panic(err)
}
return x
}

// MustDiag is a generic implementation of the Go Must idiom [1, 2]. It panics if
// the provided Diagnostics has errors and returns x otherwise.
//
// [1]: https://pkg.go.dev/text/template#Must
// [2]: https://pkg.go.dev/regexp#MustCompile
func MustDiag[T any](x T, diags diag.Diagnostics) T {
return Must(x, DiagnosticsError(diags))
}

// DiagnosticsError returns an error containing all Diagnostic with SeverityError
func DiagnosticsError(diags diag.Diagnostics) error {
var errs []error

for _, d := range diags.Errors() {
errs = append(errs, errors.New(DiagnosticString(d)))
}

return errors.Join(errs...)
}

// DiagnosticString formats a Diagnostic
// If there is no `Detail`, only prints summary, otherwise prints both
func DiagnosticString(d diag.Diagnostic) string {
if d.Detail() == "" {
return d.Summary()
}
return fmt.Sprintf("%s\n\n%s", d.Summary(), d.Detail())
}
Loading

0 comments on commit 45c98c5

Please sign in to comment.