Skip to content

Commit

Permalink
attr: Add ValueType method to Type interface (#497)
Browse files Browse the repository at this point in the history
Reference: #496

This will allow the internal reflection package to return better error diagnostics.
  • Loading branch information
bflad committed Sep 23, 2022
1 parent 123c67f commit f24aeb9
Show file tree
Hide file tree
Showing 16 changed files with 163 additions and 78 deletions.
7 changes: 7 additions & 0 deletions .changelog/497.txt
@@ -0,0 +1,7 @@
```release-note:breaking-change
attr: The `Type` interface now requires the `ValueType` method, which is used for enhancing error diagnostics from the framework
```

```release-note:enhancement
internal/reflect: Added `attr.Value` type suggestions to error diagnostics
```
6 changes: 6 additions & 0 deletions attr/type.go
Expand Up @@ -25,6 +25,12 @@ type Type interface {
// for the provider to consume the data with.
ValueFromTerraform(context.Context, tftypes.Value) (Value, error)

// ValueType should return the attr.Value type returned by
// ValueFromTerraform. The returned attr.Value can be any null, unknown,
// or known value for the type, as this is intended for type detection
// and improving error diagnostics.
ValueType(context.Context) Value

// Equal must return true if the Type is considered semantically equal
// to the Type passed as an argument.
Equal(Type) bool
Expand Down
74 changes: 37 additions & 37 deletions internal/fwschemadata/data_get_at_path_test.go

Large diffs are not rendered by default.

74 changes: 37 additions & 37 deletions internal/fwschemadata/data_get_test.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions internal/reflect/build_value_test.go
Expand Up @@ -29,7 +29,7 @@ func TestBuildValue(t *testing.T) {
"Value Conversion Error",
"An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Received null value, however the target type cannot handle null values. Use the corresponding `types` package type, a pointer type or a custom type that handles null values.\n\n"+
"Path: id\nTarget Type: string\nSuggested `types` Type: types.StringType\nSuggested Pointer Type: *string",
"Path: id\nTarget Type: string\nSuggested `types` Type: types.String\nSuggested Pointer Type: *string",
),
},
},
Expand All @@ -41,7 +41,7 @@ func TestBuildValue(t *testing.T) {
"Value Conversion Error",
"An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Received unknown value, however the target type cannot handle unknown values. Use the corresponding `types` package type or a custom type that handles unknown values.\n\n"+
"Path: id\nTarget Type: string\nSuggested Type: types.StringType",
"Path: id\nTarget Type: string\nSuggested Type: types.String",
),
},
},
Expand Down
4 changes: 2 additions & 2 deletions internal/reflect/into.go
Expand Up @@ -116,7 +116,7 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re
"Value Conversion Error",
"An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Received unknown value, however the target type cannot handle unknown values. Use the corresponding `types` package type or a custom type that handles unknown values.\n\n"+
fmt.Sprintf("Path: %s\nTarget Type: %s\nSuggested Type: %s", path.String(), target.Type(), typ.String()),
fmt.Sprintf("Path: %s\nTarget Type: %s\nSuggested Type: %s", path.String(), target.Type(), reflect.TypeOf(typ.ValueType(ctx))),
)
return target, diags
}
Expand All @@ -140,7 +140,7 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re
"Value Conversion Error",
"An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Received null value, however the target type cannot handle null values. Use the corresponding `types` package type, a pointer type or a custom type that handles null values.\n\n"+
fmt.Sprintf("Path: %s\nTarget Type: %s\nSuggested `types` Type: %s\nSuggested Pointer Type: *%s", path.String(), target.Type(), typ.String(), target.Type()),
fmt.Sprintf("Path: %s\nTarget Type: %s\nSuggested `types` Type: %s\nSuggested Pointer Type: *%s", path.String(), target.Type(), reflect.TypeOf(typ.ValueType(ctx)), target.Type()),
)

return target, diags
Expand Down
5 changes: 5 additions & 0 deletions internal/testing/types/bool.go
Expand Up @@ -59,6 +59,11 @@ func (t BoolType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (att
return Bool{Bool: types.Bool{Value: b}, CreatedBy: t}, nil
}

// ValueType returns the Value type.
func (t BoolType) ValueType(_ context.Context) attr.Value {
return Bool{}
}

type Bool struct {
types.Bool

Expand Down
5 changes: 5 additions & 0 deletions internal/testing/types/invalid.go
Expand Up @@ -38,6 +38,11 @@ func (t InvalidType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (
return nil, fmt.Errorf("intentional ValueFromTerraform error")
}

// ValueType returns the Value type.
func (t InvalidType) ValueType(_ context.Context) attr.Value {
return Invalid{}
}

// Invalid is an attr.Value that returns errors for methods than can return errors.
type Invalid struct{}

Expand Down
5 changes: 5 additions & 0 deletions internal/testing/types/number.go
Expand Up @@ -63,6 +63,11 @@ func (t NumberType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (a
}, nil
}

// ValueType returns the Value type.
func (t NumberType) ValueType(_ context.Context) attr.Value {
return Number{}
}

type Number struct {
types.Number

Expand Down
5 changes: 5 additions & 0 deletions internal/testing/types/string.go
Expand Up @@ -62,6 +62,11 @@ func (t StringType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (a
}, nil
}

// ValueType returns the Value type.
func (t StringType) ValueType(_ context.Context) attr.Value {
return String{}
}

type String struct {
InternalString types.String

Expand Down
7 changes: 7 additions & 0 deletions types/list.go
Expand Up @@ -163,6 +163,13 @@ func (l ListType) Validate(ctx context.Context, in tftypes.Value, path path.Path
return diags
}

// ValueType returns the Value type.
func (t ListType) ValueType(_ context.Context) attr.Value {
return List{
ElemType: t.ElemType,
}
}

// List represents a list of attr.Values, all of the same type, indicated
// by ElemType.
type List struct {
Expand Down
7 changes: 7 additions & 0 deletions types/map.go
Expand Up @@ -167,6 +167,13 @@ func (m MapType) Validate(ctx context.Context, in tftypes.Value, path path.Path)
return diags
}

// ValueType returns the Value type.
func (t MapType) ValueType(_ context.Context) attr.Value {
return Map{
ElemType: t.ElemType,
}
}

// Map represents a map of attr.Values, all of the same type, indicated by
// ElemType. Keys for the map will always be strings.
type Map struct {
Expand Down
7 changes: 7 additions & 0 deletions types/object.go
Expand Up @@ -145,6 +145,13 @@ func (o ObjectType) String() string {
return res.String()
}

// ValueType returns the Value type.
func (t ObjectType) ValueType(_ context.Context) attr.Value {
return Object{
AttrTypes: t.AttrTypes,
}
}

// Object represents an object
type Object struct {
// Unknown will be set to true if the entire object is an unknown value.
Expand Down
19 changes: 19 additions & 0 deletions types/primitive.go
Expand Up @@ -92,6 +92,25 @@ func (p primitive) ValueFromTerraform(ctx context.Context, in tftypes.Value) (at
}
}

// ValueType returns the Value type.
func (p primitive) ValueType(_ context.Context) attr.Value {
// These Value do not need to be valid.
switch p {
case BoolType:
return Bool{}
case Float64Type:
return Float64{}
case Int64Type:
return Int64{}
case NumberType:
return Number{}
case StringType:
return String{}
default:
panic(fmt.Sprintf("unknown primitive %d", p))
}
}

// Equal returns true if `o` is also a primitive, and is the same type of
// primitive as `p`.
func (p primitive) Equal(o attr.Type) bool {
Expand Down
5 changes: 5 additions & 0 deletions types/primitive_test.go
Expand Up @@ -90,6 +90,11 @@ func (t testAttributeType) String() string {
panic("not implemented")
}

// ValueType returns the Value type.
func (t testAttributeType) ValueType(_ context.Context) attr.Value {
panic("not implemented")
}

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

Expand Down
7 changes: 7 additions & 0 deletions types/set.go
Expand Up @@ -195,6 +195,13 @@ func (st SetType) Validate(ctx context.Context, in tftypes.Value, path path.Path
return diags
}

// ValueType returns the Value type.
func (t SetType) ValueType(_ context.Context) attr.Value {
return Set{
ElemType: t.ElemType,
}
}

// Set represents a set of attr.Value, all of the same type,
// indicated by ElemType.
type Set struct {
Expand Down

0 comments on commit f24aeb9

Please sign in to comment.