Skip to content

Commit

Permalink
diag: Add WithPath function and remove AttributeErrorDiagnostic and A…
Browse files Browse the repository at this point in the history
…ttributeWarningDiagnostic types (#219)

Reference: #169

Instead of embedding path information directly in diagnostic types, it can instead be handled as a wrapper. This encourages a separation of concerns and simplifies implementations. Future enhancements, such as type checking wrapped diagnostics in #218, can enable advanced use cases.
  • Loading branch information
bflad authored Nov 3, 2021
1 parent 0886682 commit 16188d2
Show file tree
Hide file tree
Showing 23 changed files with 165 additions and 253 deletions.
7 changes: 7 additions & 0 deletions .changelog/219.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:breaking-change
diag: The `AttributeErrorDiagnostic` and `AttributeWarningDiagnostic` types have been removed. Any usage can be replaced with `DiagnosticWithPath`.
```

```release-note:enhancement
diag: Added `WithPath()` function to wrap or overwrite diagnostic path information.
```
40 changes: 4 additions & 36 deletions diag/attribute_error_diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,10 @@ import (
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

var _ DiagnosticWithPath = AttributeErrorDiagnostic{}

// AttributeErrorDiagnostic is a generic attribute diagnostic with error severity.
type AttributeErrorDiagnostic struct {
ErrorDiagnostic

path *tftypes.AttributePath
}

// Equal returns true if the other diagnostic is wholly equivalent.
func (d AttributeErrorDiagnostic) Equal(other Diagnostic) bool {
aed, ok := other.(AttributeErrorDiagnostic)

if !ok {
return false
}

if !aed.Path().Equal(d.Path()) {
return false
}

return aed.ErrorDiagnostic.Equal(d.ErrorDiagnostic)
}

// Path returns the diagnostic path.
func (d AttributeErrorDiagnostic) Path() *tftypes.AttributePath {
return d.path
}

// NewAttributeErrorDiagnostic returns a new error severity diagnostic with the given summary, detail, and path.
func NewAttributeErrorDiagnostic(path *tftypes.AttributePath, summary string, detail string) AttributeErrorDiagnostic {
return AttributeErrorDiagnostic{
ErrorDiagnostic: ErrorDiagnostic{
detail: detail,
summary: summary,
},
path: path,
func NewAttributeErrorDiagnostic(path *tftypes.AttributePath, summary string, detail string) DiagnosticWithPath {
return withPath{
Diagnostic: NewErrorDiagnostic(summary, detail),
path: path,
}
}
40 changes: 4 additions & 36 deletions diag/attribute_warning_diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,10 @@ import (
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

var _ DiagnosticWithPath = AttributeWarningDiagnostic{}

// AttributeErrorDiagnostic is a generic attribute diagnostic with warning severity.
type AttributeWarningDiagnostic struct {
WarningDiagnostic

path *tftypes.AttributePath
}

// Equal returns true if the other diagnostic is wholly equivalent.
func (d AttributeWarningDiagnostic) Equal(other Diagnostic) bool {
awd, ok := other.(AttributeWarningDiagnostic)

if !ok {
return false
}

if !awd.Path().Equal(d.Path()) {
return false
}

return awd.WarningDiagnostic.Equal(d.WarningDiagnostic)
}

// Path returns the diagnostic path.
func (d AttributeWarningDiagnostic) Path() *tftypes.AttributePath {
return d.path
}

// NewAttributeWarningDiagnostic returns a new warning severity diagnostic with the given summary, detail, and path.
func NewAttributeWarningDiagnostic(path *tftypes.AttributePath, summary string, detail string) AttributeWarningDiagnostic {
return AttributeWarningDiagnostic{
WarningDiagnostic: WarningDiagnostic{
detail: detail,
summary: summary,
},
path: path,
func NewAttributeWarningDiagnostic(path *tftypes.AttributePath, summary string, detail string) DiagnosticWithPath {
return withPath{
Diagnostic: NewWarningDiagnostic(summary, detail),
path: path,
}
}
3 changes: 3 additions & 0 deletions diag/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
//
// See the ErrorDiagnostic and WarningDiagnostic concrete types for generic
// implementations.
//
// To add path information to an existing diagnostic, see the WithPath()
// function.
type Diagnostic interface {
// Severity returns the desired level of feedback for the diagnostic.
Severity() Severity
Expand Down
54 changes: 54 additions & 0 deletions diag/with_path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package diag

import (
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

var _ DiagnosticWithPath = withPath{}

// withPath wraps a diagnostic with path information.
type withPath struct {
Diagnostic

path *tftypes.AttributePath
}

// Equal returns true if the other diagnostic is wholly equivalent.
func (d withPath) Equal(other Diagnostic) bool {
o, ok := other.(withPath)

if !ok {
return false
}

if !d.Path().Equal(o.Path()) {
return false
}

if d.Diagnostic == nil {
return d.Diagnostic == o.Diagnostic
}

return d.Diagnostic.Equal(o.Diagnostic)
}

// Path returns the diagnostic path.
func (d withPath) Path() *tftypes.AttributePath {
return d.path
}

// WithPath wraps a diagnostic with path information or overwrites the path.
func WithPath(path *tftypes.AttributePath, d Diagnostic) DiagnosticWithPath {
wp, ok := d.(withPath)

if !ok {
return withPath{
Diagnostic: d,
path: path,
}
}

wp.path = path

return wp
}
24 changes: 4 additions & 20 deletions internal/reflect/diags.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,31 @@ import (
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func toTerraform5ValueErrorDiag(err error, path *tftypes.AttributePath) diag.AttributeErrorDiagnostic {
func toTerraform5ValueErrorDiag(err error, path *tftypes.AttributePath) diag.DiagnosticWithPath {
return diag.NewAttributeErrorDiagnostic(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert into a Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
}

func toTerraformValueErrorDiag(err error, path *tftypes.AttributePath) diag.AttributeErrorDiagnostic {
func toTerraformValueErrorDiag(err error, path *tftypes.AttributePath) diag.DiagnosticWithPath {
return diag.NewAttributeErrorDiagnostic(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert the Attribute value into a Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
}

func validateValueErrorDiag(err error, path *tftypes.AttributePath) diag.AttributeErrorDiagnostic {
func validateValueErrorDiag(err error, path *tftypes.AttributePath) diag.DiagnosticWithPath {
return diag.NewAttributeErrorDiagnostic(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to validate the Terraform value type. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
}

func valueFromTerraformErrorDiag(err error, path *tftypes.AttributePath) diag.AttributeErrorDiagnostic {
func valueFromTerraformErrorDiag(err error, path *tftypes.AttributePath) diag.DiagnosticWithPath {
return diag.NewAttributeErrorDiagnostic(
path,
"Value Conversion Error",
Expand All @@ -44,7 +44,6 @@ func valueFromTerraformErrorDiag(err error, path *tftypes.AttributePath) diag.At
type DiagIntoIncompatibleType struct {
Val tftypes.Value
TargetType reflect.Type
AttrPath *tftypes.AttributePath
Err error
}

Expand All @@ -71,24 +70,16 @@ func (d DiagIntoIncompatibleType) Equal(o diag.Diagnostic) bool {
if d.TargetType != od.TargetType {
return false
}
if !d.AttrPath.Equal(od.AttrPath) {
return false
}
if d.Err.Error() != od.Err.Error() {
return false
}
return true
}

func (d DiagIntoIncompatibleType) Path() *tftypes.AttributePath {
return d.AttrPath
}

type DiagNewAttributeValueIntoWrongType struct {
ValType reflect.Type
TargetType reflect.Type
SchemaType attr.Type
AttrPath *tftypes.AttributePath
}

func (d DiagNewAttributeValueIntoWrongType) Severity() diag.Severity {
Expand Down Expand Up @@ -117,12 +108,5 @@ func (d DiagNewAttributeValueIntoWrongType) Equal(o diag.Diagnostic) bool {
if !d.SchemaType.Equal(od.SchemaType) {
return false
}
if !d.AttrPath.Equal(od.AttrPath) {
return false
}
return true
}

func (d DiagNewAttributeValueIntoWrongType) Path() *tftypes.AttributePath {
return d.AttrPath
}
5 changes: 2 additions & 3 deletions internal/reflect/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,11 @@ func NewAttributeValue(ctx context.Context, typ attr.Type, val tftypes.Value, ta
return target, append(diags, valueFromTerraformErrorDiag(err, path))
}
if reflect.TypeOf(res) != target.Type() {
diags.Append(DiagNewAttributeValueIntoWrongType{
diags.Append(diag.WithPath(path, DiagNewAttributeValueIntoWrongType{
ValType: reflect.TypeOf(res),
TargetType: target.Type(),
SchemaType: typ,
AttrPath: path,
})
}))
return target, diags
}
return reflect.ValueOf(res), diags
Expand Down
20 changes: 8 additions & 12 deletions internal/reflect/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,28 @@ func Map(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.V

// this only works with maps, so check that out first
if underlyingValue.Kind() != reflect.Map {
diags.Append(DiagIntoIncompatibleType{
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: val,
TargetType: target.Type(),
AttrPath: path,
Err: fmt.Errorf("expected a map type, got %s", target.Type()),
})
}))
return target, diags
}
if !val.Type().Is(tftypes.Map{}) {
diags.Append(DiagIntoIncompatibleType{
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: val,
TargetType: target.Type(),
AttrPath: path,
Err: fmt.Errorf("cannot reflect %s into a map, must be a map", val.Type().String()),
})
}))
return target, diags
}
elemTyper, ok := typ.(attr.TypeWithElementType)
if !ok {
diags.Append(DiagIntoIncompatibleType{
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: val,
TargetType: target.Type(),
AttrPath: path,
Err: fmt.Errorf("cannot reflect map using type information provided by %T, %T must be an attr.TypeWithElementType", typ, typ),
})
}))
return target, diags
}

Expand All @@ -51,12 +48,11 @@ func Map(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.V
values := map[string]tftypes.Value{}
err := val.As(&values)
if err != nil {
diags.Append(DiagIntoIncompatibleType{
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: val,
TargetType: target.Type(),
AttrPath: path,
Err: err,
})
}))
return target, diags
}

Expand Down
5 changes: 2 additions & 3 deletions internal/reflect/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflec
result := big.NewFloat(0)
err := val.As(&result)
if err != nil {
diags.Append(DiagIntoIncompatibleType{
AttrPath: path,
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Err: err,
TargetType: target.Type(),
Val: val,
})
}))
return target, diags
}
roundingError := fmt.Errorf("cannot store %s in %s", result.String(), target.Type())
Expand Down
5 changes: 2 additions & 3 deletions internal/reflect/pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ func Pointer(ctx context.Context, typ attr.Type, val tftypes.Value, target refle
var diags diag.Diagnostics

if target.Kind() != reflect.Ptr {
diags.Append(DiagIntoIncompatibleType{
diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{
Val: val,
TargetType: target.Type(),
AttrPath: path,
Err: fmt.Errorf("cannot dereference pointer, not a pointer, is a %s (%s)", target.Type(), target.Kind()),
})
}))
return target, diags
}
// we may have gotten a nil pointer, so we need to create our own that
Expand Down
5 changes: 2 additions & 3 deletions internal/reflect/pointer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ func TestPointer_notAPointer(t *testing.T) {

var s string
expectedDiags := diag.Diagnostics{
refl.DiagIntoIncompatibleType{
diag.WithPath(tftypes.NewAttributePath(), refl.DiagIntoIncompatibleType{
Val: tftypes.NewValue(tftypes.String, "hello"),
TargetType: reflect.TypeOf(s),
AttrPath: tftypes.NewAttributePath(),
Err: fmt.Errorf("cannot dereference pointer, not a pointer, is a %s (%s)", reflect.TypeOf(s), reflect.TypeOf(s).Kind()),
},
}),
}

_, diags := refl.Pointer(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath())
Expand Down
Loading

0 comments on commit 16188d2

Please sign in to comment.