diff --git a/.changelog/110.txt b/.changelog/110.txt new file mode 100644 index 000000000..5b80b1fcd --- /dev/null +++ b/.changelog/110.txt @@ -0,0 +1,7 @@ +```release-note:breaking-change +Most uses of `[]*tfprotov6.Diagnostic` have been replaced with a new `diag.Diagnostics` type. Please update your type signatures, and use one of the `diags.New*` helper functions instead of constructing `*tfprotov6.Diagnostic`s by hand. +``` + +```release-note:feature +Introduced first-class diagnostics (`diag` package). +``` diff --git a/attr/type.go b/attr/type.go index 8cc44320a..db1e6c339 100644 --- a/attr/type.go +++ b/attr/type.go @@ -3,7 +3,7 @@ package attr import ( "context" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -80,9 +80,7 @@ type TypeWithValidate interface { // being used to populate the Type. It is generally used to check the // data format and ensure that it complies with the requirements of the // Type. - // - // TODO: don't use tfprotov6.Diagnostic, use our type - Validate(context.Context, tftypes.Value) []*tfprotov6.Diagnostic + Validate(context.Context, tftypes.Value) diag.Diagnostics } // TypeWithPlaintextDescription extends the Type interface to include a diff --git a/diag/attribute_error_diagnostic.go b/diag/attribute_error_diagnostic.go new file mode 100644 index 000000000..922f1d14e --- /dev/null +++ b/diag/attribute_error_diagnostic.go @@ -0,0 +1,45 @@ +package diag + +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, + } +} diff --git a/diag/attribute_warning_diagnostic.go b/diag/attribute_warning_diagnostic.go new file mode 100644 index 000000000..d58460a17 --- /dev/null +++ b/diag/attribute_warning_diagnostic.go @@ -0,0 +1,45 @@ +package diag + +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, + } +} diff --git a/diag/diagnostic.go b/diag/diagnostic.go new file mode 100644 index 000000000..ded076f8d --- /dev/null +++ b/diag/diagnostic.go @@ -0,0 +1,49 @@ +package diag + +import ( + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Diagnostic is an interface for providing enhanced feedback. +// +// These are typically practitioner facing, however it is possible for +// functionality, such as validation, to use these to change behaviors or +// otherwise have these be manipulated or removed before being presented. +// +// See the ErrorDiagnostic and WarningDiagnostic concrete types for generic +// implementations. +type Diagnostic interface { + // Severity returns the desired level of feedback for the diagnostic. + Severity() Severity + + // Summary is a short description for the diagnostic. + // + // Typically this is implemented as a title, such as "Invalid Resource Name", + // or single line sentence. + Summary() string + + // Detail is a long description for the diagnostic. + // + // This should contain all relevant information about why the diagnostic + // was generated and if applicable, ways to prevent the diagnostic. It + // should generally be written and formatted for human consumption by + // practitioners or provider developers. + Detail() string + + // Equal returns true if the other diagnostic is wholly equivalent. + Equal(Diagnostic) bool +} + +// DiagnosticWithPath is a diagnostic associated with an attribute path. +// +// This attribute information is used to display contextual source configuration +// to practitioners. +type DiagnosticWithPath interface { + Diagnostic + + // Path points to a specific value within an aggregate value. + // + // If present, this enables the display of source configuration context for + // supporting implementations such as Terraform CLI commands. + Path() *tftypes.AttributePath +} diff --git a/diag/diagnostic_test.go b/diag/diagnostic_test.go new file mode 100644 index 000000000..dd754d8c5 --- /dev/null +++ b/diag/diagnostic_test.go @@ -0,0 +1,31 @@ +package diag_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +var _ diag.Diagnostic = invalidSeverityDiagnostic{} + +type invalidSeverityDiagnostic struct{} + +func (d invalidSeverityDiagnostic) Detail() string { + return "detail for invalid severity diagnostic" +} + +func (d invalidSeverityDiagnostic) Equal(other diag.Diagnostic) bool { + isd, ok := other.(invalidSeverityDiagnostic) + + if !ok { + return false + } + + return isd.Summary() == d.Summary() && isd.Detail() == d.Detail() && isd.Severity() == d.Severity() +} + +func (d invalidSeverityDiagnostic) Severity() diag.Severity { + return diag.SeverityInvalid +} + +func (d invalidSeverityDiagnostic) Summary() string { + return "summary for invalid severity diagnostic" +} diff --git a/diag/diagnostics.go b/diag/diagnostics.go new file mode 100644 index 000000000..8ab488faa --- /dev/null +++ b/diag/diagnostics.go @@ -0,0 +1,97 @@ +package diag + +import ( + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Diagnostics represents a collection of diagnostics. +// +// While this collection is ordered, the order is not guaranteed as reliable +// or consistent. +type Diagnostics []Diagnostic + +// AddAttributeError adds a generic attribute error diagnostic to the collection. +func (diags *Diagnostics) AddAttributeError(path *tftypes.AttributePath, summary string, detail string) { + diags.Append(NewAttributeErrorDiagnostic(path, summary, detail)) +} + +// AddAttributeWarning adds a generic attribute warning diagnostic to the collection. +func (diags *Diagnostics) AddAttributeWarning(path *tftypes.AttributePath, summary string, detail string) { + diags.Append(NewAttributeWarningDiagnostic(path, summary, detail)) +} + +// AddError adds a generic error diagnostic to the collection. +func (diags *Diagnostics) AddError(summary string, detail string) { + diags.Append(NewErrorDiagnostic(summary, detail)) +} + +// AddWarning adds a generic warning diagnostic to the collection. +func (diags *Diagnostics) AddWarning(summary string, detail string) { + diags.Append(NewWarningDiagnostic(summary, detail)) +} + +// Append adds non-empty and non-duplicate diagnostics to the collection. +func (diags *Diagnostics) Append(in ...Diagnostic) { + for _, diag := range in { + if diag == nil { + continue + } + + if diags.Contains(diag) { + continue + } + + if diags == nil { + *diags = Diagnostics{diag} + } else { + *diags = append(*diags, diag) + } + } +} + +// Contains returns true if the collection contains an equal Diagnostic. +func (diags Diagnostics) Contains(in Diagnostic) bool { + for _, diag := range diags { + if diag.Equal(in) { + return true + } + } + + return false +} + +// HasError returns true if the collection has an error severity Diagnostic. +func (diags Diagnostics) HasError() bool { + for _, diag := range diags { + if diag.Severity() == SeverityError { + return true + } + } + + return false +} + +// ToTfprotov6Diagnostics converts the diagnostics into the tfprotov6 collection type. +// +// Usage of this method outside the framework is not supported nor considered +// for backwards compatibility promises. +func (diags Diagnostics) ToTfprotov6Diagnostics() []*tfprotov6.Diagnostic { + var results []*tfprotov6.Diagnostic + + for _, diag := range diags { + tfprotov6Diagnostic := &tfprotov6.Diagnostic{ + Detail: diag.Detail(), + Severity: diag.Severity().ToTfprotov6DiagnosticSeverity(), + Summary: diag.Summary(), + } + + if diagWithPath, ok := diag.(DiagnosticWithPath); ok { + tfprotov6Diagnostic.Attribute = diagWithPath.Path() + } + + results = append(results, tfprotov6Diagnostic) + } + + return results +} diff --git a/diag/diagnostics_test.go b/diag/diagnostics_test.go new file mode 100644 index 000000000..e05c91ca2 --- /dev/null +++ b/diag/diagnostics_test.go @@ -0,0 +1,598 @@ +package diag_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestDiagnosticsAddAttributeError(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diags diag.Diagnostics + path *tftypes.AttributePath + summary string + detail string + expected diag.Diagnostics + }{ + "nil-add": { + diags: nil, + path: tftypes.NewAttributePath().WithAttributeName("test"), + summary: "one summary", + detail: "one detail", + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + }, + }, + "add": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + summary: "three summary", + detail: "three detail", + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "three summary", "three detail"), + }, + }, + "duplicate": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + summary: "one summary", + detail: "one detail", + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + tc.diags.AddAttributeError(tc.path, tc.summary, tc.detail) + + if diff := cmp.Diff(tc.diags, tc.expected); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} + +func TestDiagnosticsAddAttributeWarning(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diags diag.Diagnostics + path *tftypes.AttributePath + summary string + detail string + expected diag.Diagnostics + }{ + "nil-add": { + diags: nil, + path: tftypes.NewAttributePath().WithAttributeName("test"), + summary: "one summary", + detail: "one detail", + expected: diag.Diagnostics{ + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + }, + }, + "add": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + summary: "three summary", + detail: "three detail", + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "three summary", "three detail"), + }, + }, + "duplicate": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + summary: "two summary", + detail: "two detail", + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + tc.diags.AddAttributeWarning(tc.path, tc.summary, tc.detail) + + if diff := cmp.Diff(tc.diags, tc.expected); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} + +func TestDiagnosticsAddError(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diags diag.Diagnostics + summary string + detail string + expected diag.Diagnostics + }{ + "nil-add": { + diags: nil, + summary: "one summary", + detail: "one detail", + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + }, + }, + "add": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + summary: "three summary", + detail: "three detail", + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + diag.NewErrorDiagnostic("three summary", "three detail"), + }, + }, + "duplicate": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + summary: "one summary", + detail: "one detail", + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + tc.diags.AddError(tc.summary, tc.detail) + + if diff := cmp.Diff(tc.diags, tc.expected); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} + +func TestDiagnosticsAddWarning(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diags diag.Diagnostics + summary string + detail string + expected diag.Diagnostics + }{ + "nil-add": { + diags: nil, + summary: "one summary", + detail: "one detail", + expected: diag.Diagnostics{ + diag.NewWarningDiagnostic("one summary", "one detail"), + }, + }, + "add": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + summary: "three summary", + detail: "three detail", + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + diag.NewWarningDiagnostic("three summary", "three detail"), + }, + }, + "duplicate": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + summary: "two summary", + detail: "two detail", + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + tc.diags.AddWarning(tc.summary, tc.detail) + + if diff := cmp.Diff(tc.diags, tc.expected); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} + +func TestDiagnosticsAppend(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diags diag.Diagnostics + in diag.Diagnostics + expected diag.Diagnostics + }{ + "nil-append": { + diags: nil, + in: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + }, + "append": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.Diagnostics{ + diag.NewErrorDiagnostic("three summary", "three detail"), + diag.NewWarningDiagnostic("four summary", "four detail"), + }, + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + diag.NewErrorDiagnostic("three summary", "three detail"), + diag.NewWarningDiagnostic("four summary", "four detail"), + }, + }, + "append-less-specific": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("error"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + }, + in: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("error"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + }, + "append-more-specific": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("error"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + }, + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("error"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + }, + }, + "empty-diagnostics": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: nil, + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + }, + "empty-diagnostics-elements": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.Diagnostics{ + nil, + nil, + }, + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + }, + "duplicate": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + expected: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + tc.diags.Append(tc.in...) + + if diff := cmp.Diff(tc.diags, tc.expected); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} + +func TestDiagnosticsContains(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diags diag.Diagnostics + in diag.Diagnostic + expected bool + }{ + "matching-basic": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.NewWarningDiagnostic("two summary", "two detail"), + expected: true, + }, + "matching-attribute-path": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("error"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + }, + in: diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + expected: true, + }, + "nil-diagnostics": { + diags: nil, + in: diag.NewErrorDiagnostic("one summary", "one detail"), + expected: false, + }, + "nil-in": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: nil, + expected: false, + }, + "different-attribute-path": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("error"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + }, + in: diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("different"), "two summary", "two detail"), + expected: false, + }, + "different-detail": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.NewWarningDiagnostic("two summary", "different detail"), + expected: false, + }, + "different-severity": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.NewWarningDiagnostic("one summary", "one detail"), + expected: false, + }, + "different-summary": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.NewWarningDiagnostic("different summary", "two detail"), + expected: false, + }, + "different-type-less-specific": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("error"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + }, + in: diag.NewWarningDiagnostic("two summary", "two detail"), + expected: false, + }, + "different-type-more-specific": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + in: diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + expected: false, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.diags.Contains(tc.in) + + if got != tc.expected { + t.Errorf("Unexpected response: got: %t, wanted: %t", got, tc.expected) + } + }) + } +} + +func TestDiagnosticsHasError(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diags diag.Diagnostics + expected bool + }{ + "matching-basic": { + diags: diag.Diagnostics{ + diag.NewWarningDiagnostic("one summary", "one detail"), + diag.NewErrorDiagnostic("two summary", "two detail"), + }, + expected: true, + }, + "matching-attribute-path": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("error"), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("warning"), "two summary", "two detail"), + }, + expected: true, + }, + "nil-diagnostics": { + diags: nil, + expected: false, + }, + "different-severity": { + diags: diag.Diagnostics{ + diag.NewWarningDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + expected: false, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.diags.HasError() + + if got != tc.expected { + t.Errorf("Unexpected response: got: %t, wanted: %t", got, tc.expected) + } + }) + } +} + +func TestDiagnosticsToTfprotov6Diagnostics(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diags diag.Diagnostics + expected []*tfprotov6.Diagnostic + }{ + "nil": { + diags: nil, + expected: nil, + }, + "Diagnostic-SeverityInvalid": { + diags: diag.Diagnostics{ + invalidSeverityDiagnostic{}, + }, + expected: []*tfprotov6.Diagnostic{ + { + Detail: invalidSeverityDiagnostic{}.Detail(), + Severity: tfprotov6.DiagnosticSeverityInvalid, + Summary: invalidSeverityDiagnostic{}.Summary(), + }, + }, + }, + "Diagnostic": { + diags: diag.Diagnostics{ + diag.NewErrorDiagnostic("one summary", "one detail"), + diag.NewWarningDiagnostic("two summary", "two detail"), + }, + expected: []*tfprotov6.Diagnostic{ + { + Detail: "one detail", + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "one summary", + }, + { + Detail: "two detail", + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "two summary", + }, + }, + }, + "DiagnosticWithPath": { + diags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic(tftypes.NewAttributePath(), "one summary", "one detail"), + diag.NewAttributeWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"), "two summary", "two detail"), + }, + expected: []*tfprotov6.Diagnostic{ + { + Attribute: tftypes.NewAttributePath(), + Detail: "one detail", + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "one summary", + }, + { + Attribute: tftypes.NewAttributePath().WithAttributeName("test"), + Detail: "two detail", + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "two summary", + }, + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.diags.ToTfprotov6Diagnostics() + + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} diff --git a/diag/error_diagnostic.go b/diag/error_diagnostic.go new file mode 100644 index 000000000..9cc169692 --- /dev/null +++ b/diag/error_diagnostic.go @@ -0,0 +1,43 @@ +package diag + +var _ Diagnostic = ErrorDiagnostic{} + +// ErrorDiagnostic is a generic diagnostic with error severity. +type ErrorDiagnostic struct { + detail string + summary string +} + +// Detail returns the diagnostic detail. +func (d ErrorDiagnostic) Detail() string { + return d.detail +} + +// Equal returns true if the other diagnostic is wholly equivalent. +func (d ErrorDiagnostic) Equal(other Diagnostic) bool { + ed, ok := other.(ErrorDiagnostic) + + if !ok { + return false + } + + return ed.Summary() == d.Summary() && ed.Detail() == d.Detail() +} + +// Severity returns the diagnostic severity. +func (d ErrorDiagnostic) Severity() Severity { + return SeverityError +} + +// Summary returns the diagnostic summary. +func (d ErrorDiagnostic) Summary() string { + return d.summary +} + +// NewErrorDiagnostic returns a new error severity diagnostic with the given summary and detail. +func NewErrorDiagnostic(summary string, detail string) ErrorDiagnostic { + return ErrorDiagnostic{ + detail: detail, + summary: summary, + } +} diff --git a/diag/error_diagnostic_test.go b/diag/error_diagnostic_test.go new file mode 100644 index 000000000..5d0d02343 --- /dev/null +++ b/diag/error_diagnostic_test.go @@ -0,0 +1,56 @@ +package diag_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +func TestErrorDiagnosticEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diag diag.ErrorDiagnostic + other diag.Diagnostic + expected bool + }{ + "matching": { + diag: diag.NewErrorDiagnostic("test summary", "test detail"), + other: diag.NewErrorDiagnostic("test summary", "test detail"), + expected: true, + }, + "nil": { + diag: diag.NewErrorDiagnostic("test summary", "test detail"), + other: nil, + expected: false, + }, + "different-detail": { + diag: diag.NewErrorDiagnostic("test summary", "test detail"), + other: diag.NewErrorDiagnostic("test summary", "different detail"), + expected: false, + }, + "different-summary": { + diag: diag.NewErrorDiagnostic("test summary", "test detail"), + other: diag.NewErrorDiagnostic("different summary", "test detail"), + expected: false, + }, + "different-type": { + diag: diag.NewErrorDiagnostic("test summary", "test detail"), + other: diag.NewWarningDiagnostic("test summary", "test detail"), + expected: false, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.diag.Equal(tc.other) + + if got != tc.expected { + t.Errorf("Unexpected response: got: %t, wanted: %t", got, tc.expected) + } + }) + } +} diff --git a/diag/severity.go b/diag/severity.go new file mode 100644 index 000000000..1aebdea76 --- /dev/null +++ b/diag/severity.go @@ -0,0 +1,55 @@ +package diag + +import "github.com/hashicorp/terraform-plugin-go/tfprotov6" + +// Severity represents the level of feedback for a diagnostic. +// +// Each severity implies behavior changes for the feedback and potentially the +// further execution of logic. +type Severity int + +const ( + // SeverityInvalid represents an undefined severity. + // + // It should not be used directly in implementations. + SeverityInvalid Severity = 0 + + // SeverityError represents a terminating condition. + // + // This can cause a failing status code for command line programs. + // + // Most implementations should return early when encountering an error. + SeverityError Severity = 1 + + // SeverityWarning represents a condition with explicit feedback. + // + // Most implementations should continue when encountering a warning. + SeverityWarning Severity = 2 +) + +// String returns a textual representation of the severity. +func (s Severity) String() string { + switch s { + case SeverityError: + return "Error" + case SeverityWarning: + return "Warning" + default: + return "Invalid" + } +} + +// ToTfprotov6DiagnosticSeverity converts the severity into the tfprotov6 type. +// +// Usage of this method outside the framework is not supported nor considered +// for backwards compatibility promises. +func (s Severity) ToTfprotov6DiagnosticSeverity() tfprotov6.DiagnosticSeverity { + switch s { + case SeverityError: + return tfprotov6.DiagnosticSeverityError + case SeverityWarning: + return tfprotov6.DiagnosticSeverityWarning + default: + return tfprotov6.DiagnosticSeverityInvalid + } +} diff --git a/diag/warning_diagnostic.go b/diag/warning_diagnostic.go new file mode 100644 index 000000000..e659c4149 --- /dev/null +++ b/diag/warning_diagnostic.go @@ -0,0 +1,43 @@ +package diag + +var _ Diagnostic = WarningDiagnostic{} + +// WarningDiagnostic is a generic diagnostic with warning severity. +type WarningDiagnostic struct { + detail string + summary string +} + +// Detail returns the diagnostic detail. +func (d WarningDiagnostic) Detail() string { + return d.detail +} + +// Equal returns true if the other diagnostic is wholly equivalent. +func (d WarningDiagnostic) Equal(other Diagnostic) bool { + wd, ok := other.(WarningDiagnostic) + + if !ok { + return false + } + + return wd.Summary() == d.Summary() && wd.Detail() == d.Detail() +} + +// Severity returns the diagnostic severity. +func (d WarningDiagnostic) Severity() Severity { + return SeverityWarning +} + +// Summary returns the diagnostic summary. +func (d WarningDiagnostic) Summary() string { + return d.summary +} + +// NewErrorDiagnostic returns a new warning severity diagnostic with the given summary and detail. +func NewWarningDiagnostic(summary string, detail string) WarningDiagnostic { + return WarningDiagnostic{ + detail: detail, + summary: summary, + } +} diff --git a/diag/warning_diagnostic_test.go b/diag/warning_diagnostic_test.go new file mode 100644 index 000000000..069a1eb30 --- /dev/null +++ b/diag/warning_diagnostic_test.go @@ -0,0 +1,56 @@ +package diag_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +func TestWarningDiagnosticEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + diag diag.WarningDiagnostic + other diag.Diagnostic + expected bool + }{ + "matching": { + diag: diag.NewWarningDiagnostic("test summary", "test detail"), + other: diag.NewWarningDiagnostic("test summary", "test detail"), + expected: true, + }, + "nil": { + diag: diag.NewWarningDiagnostic("test summary", "test detail"), + other: nil, + expected: false, + }, + "different-detail": { + diag: diag.NewWarningDiagnostic("test summary", "test detail"), + other: diag.NewWarningDiagnostic("test summary", "different detail"), + expected: false, + }, + "different-summary": { + diag: diag.NewWarningDiagnostic("test summary", "test detail"), + other: diag.NewWarningDiagnostic("different summary", "test detail"), + expected: false, + }, + "different-type": { + diag: diag.NewWarningDiagnostic("test summary", "test detail"), + other: diag.NewErrorDiagnostic("test summary", "test detail"), + expected: false, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.diag.Equal(tc.other) + + if got != tc.expected { + t.Errorf("Unexpected response: got: %t, wanted: %t", got, tc.expected) + } + }) + } +} diff --git a/internal/diagnostics/diagnostics.go b/internal/diagnostics/diagnostics.go deleted file mode 100644 index aa47621d6..000000000 --- a/internal/diagnostics/diagnostics.go +++ /dev/null @@ -1,71 +0,0 @@ -package diagnostics - -import ( - "fmt" - "strings" - - "github.com/hashicorp/terraform-plugin-go/tfprotov6" -) - -// TODO: Replace with diagnostics abstraction -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/24 -func DiagsContainsDetail(in []*tfprotov6.Diagnostic, detail string) bool { - for _, diag := range in { - if diag == nil { - continue - } - - if strings.Contains(diag.Detail, detail) { - return true - } - } - return false -} - -// TODO: Replace with diagnostics abstraction -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/24 -func DiagsHasErrors(in []*tfprotov6.Diagnostic) bool { - for _, diag := range in { - if diag == nil { - continue - } - if diag.Severity == tfprotov6.DiagnosticSeverityError { - return true - } - } - return false -} - -// TODO: Replace with diagnostics abstraction -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/24 -func DiagsString(in []*tfprotov6.Diagnostic) string { - var b strings.Builder - - for _, diag := range in { - if diag == nil { - continue - } - - // Diagnostic does not have .String() method - b.WriteString("\n") - b.WriteString(DiagString(diag)) - b.WriteString("\n") - } - - return b.String() -} - -// TODO: Replace with diagnostics abstraction -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/24 -func DiagString(diag *tfprotov6.Diagnostic) string { - var b strings.Builder - - b.WriteString(fmt.Sprintf("Severity: %s\n", diag.Severity)) - b.WriteString(fmt.Sprintf("Summary: %s\n", diag.Summary)) - b.WriteString(fmt.Sprintf("Detail: %s\n", diag.Detail)) - if diag.Attribute != nil { - b.WriteString(fmt.Sprintf("Attribute: %s\n", diag.Attribute)) - } - - return b.String() -} diff --git a/internal/reflect/build_value_test.go b/internal/reflect/build_value_test.go index a2b0b3cb4..fcd4e5969 100644 --- a/internal/reflect/build_value_test.go +++ b/internal/reflect/build_value_test.go @@ -5,34 +5,53 @@ import ( "reflect" "testing" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" refl "github.com/hashicorp/terraform-plugin-framework/internal/reflect" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func TestBuildValue_unhandledNull(t *testing.T) { +func TestBuildValue(t *testing.T) { t.Parallel() - var s string - _, diags := refl.BuildValue(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, nil), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, didn't get one") + testCases := map[string]struct { + tfValue tftypes.Value + expectedDiags diag.Diagnostics + }{ + "unhandled-null": { + tfValue: tftypes.NewValue(tftypes.String, nil), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "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\nunhandled null value", + ), + }, + }, + "unhandled-unknown": { + tfValue: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "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\nunhandled unknown value", + ), + }, + }, } - if expected := `unhandled null value`; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) - } -} -func TestBuildValue_unhandledUnknown(t *testing.T) { - t.Parallel() + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() - var s string - _, diags := refl.BuildValue(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, tftypes.UnknownValue), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, didn't get one") - } - if expected := `unhandled unknown value`; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + var s string + _, diags := refl.BuildValue(context.Background(), types.StringType, tc.tfValue, reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } + }) } } diff --git a/internal/reflect/diags.go b/internal/reflect/diags.go index 1ca780615..426e2ad73 100644 --- a/internal/reflect/diags.go +++ b/internal/reflect/diags.go @@ -1,42 +1,38 @@ package reflect import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func toTerraform5ValueErrorDiag(err error, path *tftypes.AttributePath) *tfprotov6.Diagnostic { - return &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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(), - Attribute: path, - } +func toTerraform5ValueErrorDiag(err error, path *tftypes.AttributePath) diag.AttributeErrorDiagnostic { + 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) *tfprotov6.Diagnostic { - return &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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(), - Attribute: path, - } +func toTerraformValueErrorDiag(err error, path *tftypes.AttributePath) diag.AttributeErrorDiagnostic { + 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) *tfprotov6.Diagnostic { - return &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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(), - Attribute: path, - } +func validateValueErrorDiag(err error, path *tftypes.AttributePath) diag.AttributeErrorDiagnostic { + 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) *tfprotov6.Diagnostic { - return &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert the Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - } +func valueFromTerraformErrorDiag(err error, path *tftypes.AttributePath) diag.AttributeErrorDiagnostic { + return diag.NewAttributeErrorDiagnostic( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert the Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) } diff --git a/internal/reflect/interfaces.go b/internal/reflect/interfaces.go index 2fa2e3183..b13762789 100644 --- a/internal/reflect/interfaces.go +++ b/internal/reflect/interfaces.go @@ -6,8 +6,7 @@ import ( "reflect" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -24,18 +23,18 @@ type Unknownable interface { // referencing, if it's a pointer) and calls its SetUnknown method. // // It is meant to be called through Into, not directly. -func NewUnknownable(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func NewUnknownable(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics receiver := pointerSafeZeroValue(ctx, target) method := receiver.MethodByName("SetUnknown") if !method.IsValid() { err := fmt.Errorf("cannot find SetUnknown method on type %s", receiver.Type().String()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } results := method.Call([]reflect.Value{ reflect.ValueOf(ctx), @@ -51,12 +50,12 @@ func NewUnknownable(ctx context.Context, typ attr.Type, val tftypes.Value, targe underlyingErr = fmt.Errorf("unknown error type %T: %v", e, e) } underlyingErr = fmt.Errorf("reflection error: %w", underlyingErr) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + underlyingErr.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+underlyingErr.Error(), + ) + return target, diags } return receiver, diags } @@ -64,16 +63,16 @@ func NewUnknownable(ctx context.Context, typ attr.Type, val tftypes.Value, targe // FromUnknownable creates an attr.Value from the data in an Unknownable. // // It is meant to be called through OutOf, not directly. -func FromUnknownable(ctx context.Context, typ attr.Type, val Unknownable, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromUnknownable(ctx context.Context, typ attr.Type, val Unknownable, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics if val.GetUnknown(ctx) { tfVal := tftypes.NewValue(typ.TerraformType(ctx), tftypes.UnknownValue) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -92,9 +91,9 @@ func FromUnknownable(ctx context.Context, typ attr.Type, val Unknownable, path * tfVal := tftypes.NewValue(typ.TerraformType(ctx), val.GetValue(ctx)) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -118,18 +117,18 @@ type Nullable interface { // referencing, if it's a pointer) and calls its SetNull method. // // It is meant to be called through Into, not directly. -func NewNullable(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func NewNullable(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics receiver := pointerSafeZeroValue(ctx, target) method := receiver.MethodByName("SetNull") if !method.IsValid() { err := fmt.Errorf("cannot find SetNull method on type %s", receiver.Type().String()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } results := method.Call([]reflect.Value{ reflect.ValueOf(ctx), @@ -145,12 +144,12 @@ func NewNullable(ctx context.Context, typ attr.Type, val tftypes.Value, target r underlyingErr = fmt.Errorf("unknown error type: %T", e) } underlyingErr = fmt.Errorf("reflection error: %w", underlyingErr) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + underlyingErr.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+underlyingErr.Error(), + ) + return target, diags } return receiver, diags } @@ -158,16 +157,16 @@ func NewNullable(ctx context.Context, typ attr.Type, val tftypes.Value, target r // FromNullable creates an attr.Value from the data in a Nullable. // // It is meant to be called through OutOf, not directly. -func FromNullable(ctx context.Context, typ attr.Type, val Nullable, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromNullable(ctx context.Context, typ attr.Type, val Nullable, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics if val.GetNull(ctx) { tfVal := tftypes.NewValue(typ.TerraformType(ctx), nil) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -186,9 +185,9 @@ func FromNullable(ctx context.Context, typ attr.Type, val Nullable, path *tftype tfVal := tftypes.NewValue(typ.TerraformType(ctx), val.GetValue(ctx)) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -205,18 +204,18 @@ func FromNullable(ctx context.Context, typ attr.Type, val Nullable, path *tftype // method. // // It is meant to be called through Into, not directly. -func NewValueConverter(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func NewValueConverter(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics receiver := pointerSafeZeroValue(ctx, target) method := receiver.MethodByName("FromTerraform5Value") if !method.IsValid() { err := fmt.Errorf("could not find FromTerraform5Type method on type %s", receiver.Type().String()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } results := method.Call([]reflect.Value{reflect.ValueOf(val)}) err := results[0].Interface() @@ -229,12 +228,12 @@ func NewValueConverter(ctx context.Context, typ attr.Type, val tftypes.Value, ta underlyingErr = fmt.Errorf("unknown error type: %T", e) } underlyingErr = fmt.Errorf("reflection error: %w", underlyingErr) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + underlyingErr.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+underlyingErr.Error(), + ) + return target, diags } return receiver, diags } @@ -244,8 +243,8 @@ func NewValueConverter(ctx context.Context, typ attr.Type, val tftypes.Value, ta // the result to an attr.Value using `typ`. // // It is meant to be called from OutOf, not directly. -func FromValueCreator(ctx context.Context, typ attr.Type, val tftypes.ValueCreator, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromValueCreator(ctx context.Context, typ attr.Type, val tftypes.ValueCreator, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics raw, err := val.ToTerraform5Value() if err != nil { return nil, append(diags, toTerraform5ValueErrorDiag(err, path)) @@ -257,9 +256,9 @@ func FromValueCreator(ctx context.Context, typ attr.Type, val tftypes.ValueCreat tfVal := tftypes.NewValue(typ.TerraformType(ctx), raw) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -276,13 +275,13 @@ func FromValueCreator(ctx context.Context, typ attr.Type, val tftypes.ValueCreat // `attr.Value` is not the same type as `target`. // // It is meant to be called through Into, not directly. -func NewAttributeValue(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func NewAttributeValue(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, val)...) + diags.Append(typeWithValidate.Validate(ctx, val)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return target, diags } } @@ -293,12 +292,12 @@ func NewAttributeValue(ctx context.Context, typ attr.Type, val tftypes.Value, ta } if reflect.TypeOf(res) != target.Type() { err := fmt.Errorf("Cannot use attr.Value %s, only %s is supported because %T is the type in the schema", target.Type(), reflect.TypeOf(res), typ) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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(), - Attribute: path, - }) + diags.AddAttributeError( + 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(), + ) + return target, diags } return reflect.ValueOf(res), diags } @@ -309,8 +308,8 @@ func NewAttributeValue(ctx context.Context, typ attr.Type, val tftypes.Value, ta // `typ`. // // It is meant to be called through OutOf, not directly. -func FromAttributeValue(ctx context.Context, typ attr.Type, val attr.Value, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromAttributeValue(ctx context.Context, typ attr.Type, val attr.Value, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { tfType := typ.TerraformType(ctx) @@ -320,9 +319,9 @@ func FromAttributeValue(ctx context.Context, typ attr.Type, val attr.Value, path return val, append(diags, toTerraformValueErrorDiag(err, path)) } - diags = append(diags, typeWithValidate.Validate(ctx, tftypes.NewValue(tfType, tfVal))...) + diags.Append(typeWithValidate.Validate(ctx, tftypes.NewValue(tfType, tfVal))...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return val, diags } } diff --git a/internal/reflect/interfaces_test.go b/internal/reflect/interfaces_test.go index d76466b2c..8514bffb1 100644 --- a/internal/reflect/interfaces_test.go +++ b/internal/reflect/interfaces_test.go @@ -8,7 +8,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" refl "github.com/hashicorp/terraform-plugin-framework/internal/reflect" "github.com/hashicorp/terraform-plugin-framework/types" @@ -218,333 +219,391 @@ func (v *valueCreator) Equal(o *valueCreator) bool { var _ tftypes.ValueCreator = &valueCreator{} -func TestNewUnknownable_known(t *testing.T) { +func TestNewUnknownable(t *testing.T) { t.Parallel() - unknownable := &unknownableString{ - Unknown: true, - } - res, diags := refl.NewUnknownable(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(unknownable), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(*unknownableString) - if got.Unknown != false { - t.Errorf("Expected %v, got %v", false, got.Unknown) - } -} - -func TestNewUnknownable_unknown(t *testing.T) { + testCases := map[string]struct { + val tftypes.Value + target reflect.Value + expected bool + expectedDiags diag.Diagnostics + }{ + "known": { + val: tftypes.NewValue(tftypes.String, "hello"), + target: reflect.ValueOf(&unknownableString{ + Unknown: true, + }), + expected: false, + }, + "unknown": { + val: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + target: reflect.ValueOf(new(unknownableString)), + expected: true, + }, + "error": { + val: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + target: reflect.ValueOf(new(unknownableStringError)), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\nreflection error: this is an error", + ), + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + res, diags := refl.NewUnknownable(context.Background(), types.StringType, tc.val, tc.target, refl.Options{}, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Fatalf("unexpected diagnostics (+wanted, -got): %s", diff) + } + + if diags.HasError() { + return + } + + got := res.Interface().(*unknownableString) + + if got.Unknown != tc.expected { + t.Errorf("Expected %v, got %v", tc.expected, got.Unknown) + } + }) + } +} + +func TestFromUnknownable(t *testing.T) { t.Parallel() - var unknownable *unknownableString - res, diags := refl.NewUnknownable(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, tftypes.UnknownValue), reflect.ValueOf(unknownable), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(*unknownableString) - if got.Unknown != true { - t.Errorf("Expected %v, got %v", true, got.Unknown) + testCases := map[string]struct { + val refl.Unknownable + expected attr.Value + expectedDiags diag.Diagnostics + }{ + "unknown": { + val: &unknownableString{ + Unknown: true, + }, + expected: types.String{Unknown: true}, + }, + "value": { + val: &unknownableString{ + String: "hello, world", + }, + expected: types.String{Value: "hello, world"}, + }, } -} -func TestNewUnknownable_error(t *testing.T) { - t.Parallel() + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() - var unknownable *unknownableStringError - _, diags := refl.NewUnknownable(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, tftypes.UnknownValue), reflect.ValueOf(unknownable), refl.Options{}, tftypes.NewAttributePath()) - if expected := "this is an error"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) - } -} + got, diags := refl.FromUnknownable(context.Background(), types.StringType, tc.val, tftypes.NewAttributePath()) -func TestFromUnknownable_unknown(t *testing.T) { - t.Parallel() + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } - foo := &unknownableString{ - Unknown: true, - } - expected := types.String{Unknown: true} - got, diags := refl.FromUnknownable(context.Background(), types.StringType, foo, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) + } + }) } } -func TestFromUnknownable_value(t *testing.T) { +func TestNewNullable(t *testing.T) { t.Parallel() - foo := &unknownableString{ - String: "hello, world", - } - expected := types.String{Value: "hello, world"} - got, diags := refl.FromUnknownable(context.Background(), types.StringType, foo, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - } -} - -func TestNewNullable_notNull(t *testing.T) { + testCases := map[string]struct { + val tftypes.Value + target reflect.Value + expected bool + expectedDiags diag.Diagnostics + }{ + "not-null": { + val: tftypes.NewValue(tftypes.String, "hello"), + target: reflect.ValueOf(&nullableString{ + Null: true, + }), + expected: false, + }, + "null": { + val: tftypes.NewValue(tftypes.String, nil), + target: reflect.ValueOf(new(nullableString)), + expected: true, + }, + "error": { + val: tftypes.NewValue(tftypes.String, "hello"), + target: reflect.ValueOf(new(nullableStringError)), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\nreflection error: this is an error", + ), + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + res, diags := refl.NewNullable(context.Background(), types.StringType, tc.val, tc.target, refl.Options{}, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } + + if diags.HasError() { + return + } + + got := res.Interface().(*nullableString) + + if got.Null != tc.expected { + t.Errorf("Expected %v, got %v", tc.expected, got.Null) + } + }) + } +} + +func TestFromNullable(t *testing.T) { t.Parallel() - nullable := &nullableString{ - Null: true, - } - res, diags := refl.NewNullable(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(nullable), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(*nullableString) - if got.Null != false { - t.Errorf("Expected %v, got %v", false, got.Null) + testCases := map[string]struct { + val refl.Nullable + expected attr.Value + expectedDiags diag.Diagnostics + }{ + "null": { + val: &nullableString{ + Null: true, + }, + expected: types.String{Null: true}, + }, + "value": { + val: &nullableString{ + String: "hello, world", + }, + expected: types.String{Value: "hello, world"}, + }, } -} -func TestNewNullable_null(t *testing.T) { - t.Parallel() + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() - var nullable *nullableString - res, diags := refl.NewNullable(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, nil), reflect.ValueOf(nullable), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(*nullableString) - if got.Null != true { - t.Errorf("Expected %v, got %v", true, got.Null) - } -} + got, diags := refl.FromNullable(context.Background(), types.StringType, tc.val, tftypes.NewAttributePath()) -func TestNewNullable_error(t *testing.T) { - t.Parallel() + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } - var nullable *nullableStringError - _, diags := refl.NewNullable(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(nullable), refl.Options{}, tftypes.NewAttributePath()) - if expected := "this is an error"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) + } + }) } } -func TestFromNullable_null(t *testing.T) { +func TestNewAttributeValue(t *testing.T) { t.Parallel() - foo := &nullableString{ - Null: true, - } - expected := types.String{Null: true} - got, diags := refl.FromNullable(context.Background(), types.StringType, foo, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - } -} - -func TestFromNullable_value(t *testing.T) { + testCases := map[string]struct { + val tftypes.Value + target reflect.Value + expected attr.Value + expectedDiags diag.Diagnostics + }{ + "unknown": { + val: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + target: reflect.ValueOf(types.String{}), + expected: types.String{Unknown: true}, + }, + "null": { + val: tftypes.NewValue(tftypes.String, nil), + target: reflect.ValueOf(types.String{}), + expected: types.String{Null: true}, + }, + "value": { + val: tftypes.NewValue(tftypes.String, "hello"), + target: reflect.ValueOf(types.String{}), + expected: types.String{Value: "hello"}, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + res, diags := refl.NewAttributeValue(context.Background(), types.StringType, tc.val, tc.target, refl.Options{}, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } + + if diags.HasError() { + return + } + + got := res.Interface().(types.String) + + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) + } + }) + } +} + +func TestFromAttributeValue(t *testing.T) { t.Parallel() - foo := &nullableString{ - String: "hello, world", - } - expected := types.String{Value: "hello, world"} - got, diags := refl.FromNullable(context.Background(), types.StringType, foo, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) + testCases := map[string]struct { + val attr.Value + expectedDiags diag.Diagnostics + }{ + "null": { + val: types.String{Null: true}, + }, + "unknown": { + val: types.String{Unknown: true}, + }, + "value": { + val: types.String{Value: "hello, world"}, + }, } -} -func TestNewAttributeValue_unknown(t *testing.T) { - t.Parallel() + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() - var av types.String - res, diags := refl.NewAttributeValue(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, tftypes.UnknownValue), reflect.ValueOf(av), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(types.String) - expected := types.String{Unknown: true} - if !got.Equal(expected) { - t.Errorf("Expected %+v, got %+v", expected, got) - } -} + got, diags := refl.FromAttributeValue(context.Background(), types.StringType, tc.val, tftypes.NewAttributePath()) -func TestNewAttributeValue_null(t *testing.T) { - t.Parallel() + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } - var av types.String - res, diags := refl.NewAttributeValue(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, nil), reflect.ValueOf(av), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(types.String) - expected := types.String{Null: true} - if !got.Equal(expected) { - t.Errorf("Expected %+v, got %+v", expected, got) - } -} - -func TestFromAttributeValue_null(t *testing.T) { - t.Parallel() - - expected := types.String{Null: true} - got, diags := refl.FromAttributeValue(context.Background(), types.StringType, expected, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) + if diff := cmp.Diff(got, tc.val); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) + } + }) } } -func TestFromAttributeValue_unknown(t *testing.T) { +func TestNewValueConverter(t *testing.T) { t.Parallel() - expected := types.String{Unknown: true} - got, diags := refl.FromAttributeValue(context.Background(), types.StringType, expected, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - } -} - -func TestFromAttributeValue_value(t *testing.T) { + testCases := map[string]struct { + val tftypes.Value + target reflect.Value + expected *valueConverter + expectedDiags diag.Diagnostics + }{ + "unknown": { + val: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + target: reflect.ValueOf(new(valueConverter)), + expected: &valueConverter{unknown: true}, + }, + "null": { + val: tftypes.NewValue(tftypes.String, nil), + target: reflect.ValueOf(new(valueConverter)), + expected: &valueConverter{null: true}, + }, + "value": { + val: tftypes.NewValue(tftypes.String, "hello"), + target: reflect.ValueOf(new(valueConverter)), + expected: &valueConverter{value: "hello"}, + }, + "error": { + val: tftypes.NewValue(tftypes.String, "hello"), + target: reflect.ValueOf(new(valueConverterError)), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\nreflection error: this is an error", + ), + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + res, diags := refl.NewValueConverter(context.Background(), types.StringType, tc.val, tc.target, refl.Options{}, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } + + if diags.HasError() { + return + } + + got := res.Interface().(*valueConverter) + + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) + } + }) + } +} + +func TestFromValueCreator(t *testing.T) { t.Parallel() - expected := types.String{Value: "hello, world"} - got, diags := refl.FromAttributeValue(context.Background(), types.StringType, expected, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - } -} - -func TestNewAttributeValue_value(t *testing.T) { - t.Parallel() - - var av types.String - res, diags := refl.NewAttributeValue(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(av), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(types.String) - expected := types.String{Value: "hello"} - if !got.Equal(expected) { - t.Errorf("Expected %+v, got %+v", expected, got) - } -} - -func TestNewValueConverter_unknown(t *testing.T) { - t.Parallel() - - var vc *valueConverter - res, diags := refl.NewValueConverter(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, tftypes.UnknownValue), reflect.ValueOf(vc), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(*valueConverter) - expected := &valueConverter{unknown: true} - if !got.Equal(expected) { - t.Errorf("Expected %+v, got %+v", expected, got) - } -} - -func TestNewValueConverter_null(t *testing.T) { - t.Parallel() - - var vc *valueConverter - res, diags := refl.NewValueConverter(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, nil), reflect.ValueOf(vc), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(*valueConverter) - expected := &valueConverter{null: true} - if !got.Equal(expected) { - t.Errorf("Expected %+v, got %+v", expected, got) - } -} - -func TestNewValueConverter_value(t *testing.T) { - t.Parallel() - - var vc *valueConverter - res, diags := refl.NewValueConverter(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(vc), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - got := res.Interface().(*valueConverter) - expected := &valueConverter{value: "hello"} - if !got.Equal(expected) { - t.Errorf("Expected %+v, got %+v", expected, got) - } -} - -func TestNewValueConverter_error(t *testing.T) { - t.Parallel() - - var vc *valueConverterError - _, diags := refl.NewValueConverter(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(vc), refl.Options{}, tftypes.NewAttributePath()) - if expected := "this is an error"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) - } -} - -func TestFromValueCreator_null(t *testing.T) { - t.Parallel() - - vc := &valueCreator{ - null: true, - } - expected := types.String{Null: true} - got, diags := refl.FromValueCreator(context.Background(), types.StringType, vc, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - } -} - -func TestFromValueCreator_unknown(t *testing.T) { - t.Parallel() - - vc := &valueCreator{ - unknown: true, - } - expected := types.String{Unknown: true} - got, diags := refl.FromValueCreator(context.Background(), types.StringType, vc, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - } -} - -func TestFromValueCreator_value(t *testing.T) { - t.Parallel() - - vc := &valueCreator{ - value: "hello, world", - } - expected := types.String{Value: "hello, world"} - got, diags := refl.FromValueCreator(context.Background(), types.StringType, vc, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) + testCases := map[string]struct { + vc *valueCreator + expected attr.Value + expectedDiags diag.Diagnostics + }{ + "null": { + vc: &valueCreator{ + null: true, + }, + expected: types.String{Null: true}, + }, + "unknown": { + vc: &valueCreator{ + unknown: true, + }, + expected: types.String{Unknown: true}, + }, + "value": { + vc: &valueCreator{ + value: "hello, world", + }, + expected: types.String{Value: "hello, world"}, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, diags := refl.FromValueCreator(context.Background(), types.StringType, tc.vc, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } + + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) + } + }) } } diff --git a/internal/reflect/into.go b/internal/reflect/into.go index b5ccd7151..d049d7244 100644 --- a/internal/reflect/into.go +++ b/internal/reflect/into.go @@ -7,8 +7,7 @@ import ( "reflect" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -22,20 +21,20 @@ import ( // in the tftypes.Value must have a corresponding property in the struct. Into // will be called for each struct field. Slices will have Into called for each // element. -func Into(ctx context.Context, typ attr.Type, val tftypes.Value, target interface{}, opts Options) []*tfprotov6.Diagnostic { +func Into(ctx context.Context, typ attr.Type, val tftypes.Value, target interface{}, opts Options) diag.Diagnostics { + var diags diag.Diagnostics + v := reflect.ValueOf(target) if v.Kind() != reflect.Ptr { err := fmt.Errorf("target must be a pointer, got %T, which is a %s", target, v.Kind()) - return []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert the value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }, - } + diags.AddError( + "Value Conversion Error", + "An unexpected error was encountered trying to convert the value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } result, diags := BuildValue(ctx, typ, val, v.Elem(), opts, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return diags } v.Elem().Set(result) @@ -47,19 +46,18 @@ func Into(ctx context.Context, typ attr.Type, val tftypes.Value, target interfac // to set, making it safe for use with pointer types which may be nil. It tries // to give consumers the ability to override its default behaviors wherever // possible. -func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics // if this isn't a valid reflect.Value, bail before we accidentally // panic if !target.IsValid() { err := fmt.Errorf("invalid target") - diags = append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "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"+err.Error(), + ) return target, diags } // if this is an attr.Value, build the type from that @@ -75,8 +73,8 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re // if this can explicitly be set to unknown, do that if target.Type().Implements(reflect.TypeOf((*Unknownable)(nil)).Elem()) { res, unknownableDiags := NewUnknownable(ctx, typ, val, target, opts, path) - diags = append(diags, unknownableDiags...) - if diagnostics.DiagsHasErrors(diags) { + diags.Append(unknownableDiags...) + if diags.HasError() { return target, diags } target = res @@ -90,8 +88,8 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re // if this can explicitly be set to null, do that if target.Type().Implements(reflect.TypeOf((*Nullable)(nil)).Elem()) { res, nullableDiags := NewNullable(ctx, typ, val, target, opts, path) - diags = append(diags, nullableDiags...) - if diagnostics.DiagsHasErrors(diags) { + diags.Append(nullableDiags...) + if diags.HasError() { return target, diags } target = res @@ -111,12 +109,11 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re // throw an error, depending on what's in opts if !opts.UnhandledUnknownAsEmpty { err := fmt.Errorf("unhandled unknown value") - diags = append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "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"+err.Error(), + ) return target, diags } // we want to set unhandled unknowns to the empty value @@ -135,12 +132,11 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re } err := fmt.Errorf("unhandled null value") - diags = append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "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"+err.Error(), + ) return target, diags } // *big.Float and *big.Int are technically pointers, but we want them @@ -151,10 +147,12 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re switch target.Kind() { case reflect.Struct: val, valDiags := Struct(ctx, typ, val, target, opts, path) - return val, append(diags, valDiags...) + diags.Append(valDiags...) + return val, diags case reflect.Bool, reflect.String: val, valDiags := Primitive(ctx, typ, val, target, path) - return val, append(diags, valDiags...) + diags.Append(valDiags...) + return val, diags case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: @@ -165,23 +163,27 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re // as a special case, so let's just special case numbers and // let people use the types they want val, valDiags := Number(ctx, typ, val, target, opts, path) - return val, append(diags, valDiags...) + diags.Append(valDiags...) + return val, diags case reflect.Slice: val, valDiags := reflectSlice(ctx, typ, val, target, opts, path) - return val, append(diags, valDiags...) + diags.Append(valDiags...) + return val, diags case reflect.Map: val, valDiags := Map(ctx, typ, val, target, opts, path) - return val, append(diags, valDiags...) + diags.Append(valDiags...) + return val, diags case reflect.Ptr: val, valDiags := Pointer(ctx, typ, val, target, opts, path) - return val, append(diags, valDiags...) + diags.Append(valDiags...) + return val, diags default: err := fmt.Errorf("don't know how to reflect %s into %s", val.Type(), target.Type()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "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"+err.Error(), + ) + return target, diags } } diff --git a/internal/reflect/map.go b/internal/reflect/map.go index 5fdd68ab4..b3616ac81 100644 --- a/internal/reflect/map.go +++ b/internal/reflect/map.go @@ -6,45 +6,44 @@ import ( "reflect" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) // Map creates a map value that matches the type of `target`, and populates it // with the contents of `val`. -func Map(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func Map(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics underlyingValue := trueReflectValue(target) // this only works with maps, so check that out first if underlyingValue.Kind() != reflect.Map { err := fmt.Errorf("expected a map type, got %s", target.Type()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } if !val.Type().Is(tftypes.Map{}) { err := fmt.Errorf("cannot reflect %s into a map, must be a map", val.Type().String()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } elemTyper, ok := typ.(attr.TypeWithElementType) if !ok { err := fmt.Errorf("cannot reflect map using type information provided by %T, %T must be an attr.TypeWithElementType", typ, typ) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // we need our value to become a map of values so we can iterate over @@ -52,12 +51,12 @@ 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 { - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // we need to know the type the slice is wrapping @@ -78,9 +77,9 @@ func Map(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.V // reflect the value into our new target result, elemDiags := BuildValue(ctx, elemAttrType, value, targetValue, opts, path) - diags = append(diags, elemDiags...) + diags.Append(elemDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return target, diags } @@ -95,17 +94,17 @@ func Map(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.V // will be of the type produced by `typ`. // // It is meant to be called through OutOf, not directly. -func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Value, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Value, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics tfType := typ.TerraformType(ctx) if val.IsNil() { tfVal := tftypes.NewValue(tfType, nil) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -113,15 +112,15 @@ func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Valu attrVal, err := typ.ValueFromTerraform(ctx, tfVal) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from map value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from map value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } - return attrVal, nil + return attrVal, diags } elemType := typ.ElementType() @@ -129,17 +128,17 @@ func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Valu for _, key := range val.MapKeys() { if key.Kind() != reflect.String { err := fmt.Errorf("map keys must be strings, got %s", key.Type()) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "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(), - Attribute: path, - }) + diags.AddAttributeError( + 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(), + ) + return nil, diags } val, valDiags := FromValue(ctx, elemType, val.MapIndex(key).Interface(), path.WithElementKeyString(key.String())) - diags = append(diags, valDiags...) + diags.Append(valDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } tfVal, err := val.ToTerraformValue(ctx) @@ -157,9 +156,9 @@ func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Valu tfElemVal := tftypes.NewValue(tfElemType, tfVal) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfElemVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfElemVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -175,9 +174,9 @@ func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Valu tfVal := tftypes.NewValue(tfType, tfElems) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -185,12 +184,12 @@ func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Valu attrVal, err := typ.ValueFromTerraform(ctx, tfVal) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return attrVal, diags diff --git a/internal/reflect/map_test.go b/internal/reflect/map_test.go index 620d339b0..b63e0e306 100644 --- a/internal/reflect/map_test.go +++ b/internal/reflect/map_test.go @@ -5,7 +5,6 @@ import ( "reflect" "testing" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" refl "github.com/hashicorp/terraform-plugin-framework/internal/reflect" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -31,8 +30,8 @@ func TestReflectMap_string(t *testing.T) { "b": tftypes.NewValue(tftypes.String, "blue"), "c": tftypes.NewValue(tftypes.String, "green"), }), reflect.ValueOf(m), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&m).Elem().Set(result) for k, v := range expected { diff --git a/internal/reflect/number.go b/internal/reflect/number.go index 31c77e338..520febe31 100644 --- a/internal/reflect/number.go +++ b/internal/reflect/number.go @@ -9,8 +9,7 @@ import ( "strconv" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -25,25 +24,24 @@ import ( // things, as a general rule of thumb. // // It is meant to be called through Into, not directly. -func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics result := big.NewFloat(0) err := val.As(&result) if err != nil { - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } roundingError := fmt.Errorf("cannot store %s in %s", result.String(), target.Type()) - roundingErrorDiag := &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n" + roundingError.Error(), - Attribute: path, - } + roundingErrorDiag := diag.NewAttributeErrorDiagnostic( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+roundingError.Error(), + ) switch target.Type() { case reflect.TypeOf(big.NewFloat(0)): @@ -174,12 +172,12 @@ func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflec floatResult = math.SmallestNonzeroFloat32 } else if acc != big.Exact { err := fmt.Errorf("unsure how to round %s and %f", acc, floatResult) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } return reflect.ValueOf(floatResult), diags case reflect.Float64: @@ -194,12 +192,12 @@ func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflec floatResult = -math.SmallestNonzeroFloat64 } else { err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } } else if acc == big.Below { if floatResult == math.Inf(-1) || floatResult == -math.MaxFloat64 { @@ -208,38 +206,38 @@ func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflec floatResult = math.SmallestNonzeroFloat64 } else { err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } } else if acc != big.Exact { err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } return reflect.ValueOf(floatResult), diags } err = fmt.Errorf("cannot convert number to %s", target.Type()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // FromInt creates an attr.Value using `typ` from an int64. // // It is meant to be called through OutOf, not directly. -func FromInt(ctx context.Context, typ attr.Type, val int64, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromInt(ctx context.Context, typ attr.Type, val int64, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics err := tftypes.ValidateValue(tftypes.Number, val) if err != nil { return nil, append(diags, validateValueErrorDiag(err, path)) @@ -247,9 +245,9 @@ func FromInt(ctx context.Context, typ attr.Type, val int64, path *tftypes.Attrib tfNum := tftypes.NewValue(tftypes.Number, val) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfNum)...) + diags.Append(typeWithValidate.Validate(ctx, tfNum)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -265,8 +263,8 @@ func FromInt(ctx context.Context, typ attr.Type, val int64, path *tftypes.Attrib // FromUint creates an attr.Value using `typ` from a uint64. // // It is meant to be called through OutOf, not directly. -func FromUint(ctx context.Context, typ attr.Type, val uint64, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromUint(ctx context.Context, typ attr.Type, val uint64, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics err := tftypes.ValidateValue(tftypes.Number, val) if err != nil { return nil, append(diags, validateValueErrorDiag(err, path)) @@ -274,9 +272,9 @@ func FromUint(ctx context.Context, typ attr.Type, val uint64, path *tftypes.Attr tfNum := tftypes.NewValue(tftypes.Number, val) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfNum)...) + diags.Append(typeWithValidate.Validate(ctx, tfNum)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -292,8 +290,8 @@ func FromUint(ctx context.Context, typ attr.Type, val uint64, path *tftypes.Attr // FromFloat creates an attr.Value using `typ` from a float64. // // It is meant to be called through OutOf, not directly. -func FromFloat(ctx context.Context, typ attr.Type, val float64, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromFloat(ctx context.Context, typ attr.Type, val float64, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics err := tftypes.ValidateValue(tftypes.Number, val) if err != nil { return nil, append(diags, validateValueErrorDiag(err, path)) @@ -301,9 +299,9 @@ func FromFloat(ctx context.Context, typ attr.Type, val float64, path *tftypes.At tfNum := tftypes.NewValue(tftypes.Number, val) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfNum)...) + diags.Append(typeWithValidate.Validate(ctx, tfNum)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -319,8 +317,8 @@ func FromFloat(ctx context.Context, typ attr.Type, val float64, path *tftypes.At // FromBigFloat creates an attr.Value using `typ` from a *big.Float. // // It is meant to be called through OutOf, not directly. -func FromBigFloat(ctx context.Context, typ attr.Type, val *big.Float, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromBigFloat(ctx context.Context, typ attr.Type, val *big.Float, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics err := tftypes.ValidateValue(tftypes.Number, val) if err != nil { return nil, append(diags, validateValueErrorDiag(err, path)) @@ -328,9 +326,9 @@ func FromBigFloat(ctx context.Context, typ attr.Type, val *big.Float, path *tfty tfNum := tftypes.NewValue(tftypes.Number, val) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfNum)...) + diags.Append(typeWithValidate.Validate(ctx, tfNum)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -346,8 +344,8 @@ func FromBigFloat(ctx context.Context, typ attr.Type, val *big.Float, path *tfty // FromBigInt creates an attr.Value using `typ` from a *big.Int. // // It is meant to be called through OutOf, not directly. -func FromBigInt(ctx context.Context, typ attr.Type, val *big.Int, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromBigInt(ctx context.Context, typ attr.Type, val *big.Int, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics fl := big.NewFloat(0).SetInt(val) err := tftypes.ValidateValue(tftypes.Number, fl) if err != nil { @@ -356,9 +354,9 @@ func FromBigInt(ctx context.Context, typ attr.Type, val *big.Int, path *tftypes. tfNum := tftypes.NewValue(tftypes.Number, fl) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfNum)...) + diags.Append(typeWithValidate.Validate(ctx, tfNum)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } diff --git a/internal/reflect/number_test.go b/internal/reflect/number_test.go index a81c2bc1c..6ce6019c9 100644 --- a/internal/reflect/number_test.go +++ b/internal/reflect/number_test.go @@ -10,11 +10,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/diag" refl "github.com/hashicorp/terraform-plugin-framework/internal/reflect" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -34,8 +33,8 @@ func TestNumber_bigFloat(t *testing.T) { var f *big.Float result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123456), reflect.ValueOf(f), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&f).Elem().Set(result) if f == nil { @@ -53,8 +52,8 @@ func TestNumber_bigInt(t *testing.T) { var n *big.Int result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123456), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n == nil { @@ -74,8 +73,8 @@ func TestNumber_bigIntRounded(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123456.123), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n == nil { @@ -91,14 +90,18 @@ func TestNumber_bigIntRoundingError(t *testing.T) { t.Parallel() var n *big.Int + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 123456.123 in *big.Int", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123456.123), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 123456.123 in *big.Int"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -108,8 +111,8 @@ func TestNumber_int(t *testing.T) { var n int result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 123 { @@ -125,8 +128,8 @@ func TestNumber_intOverflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowInt), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if strconv.IntSize == 64 && n != math.MaxInt64 { @@ -140,14 +143,18 @@ func TestNumber_intOverflowError(t *testing.T) { t.Parallel() var n int + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store "+overflowInt.String()+" in int", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowInt), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store " + overflowInt.String() + " in int"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -159,8 +166,8 @@ func TestNumber_intUnderflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, underflowInt), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if strconv.IntSize == 64 && n != math.MinInt64 { @@ -174,14 +181,18 @@ func TestNumber_intUnderflowError(t *testing.T) { t.Parallel() var n int + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store "+underflowInt.String()+" in int", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, underflowInt), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store " + underflowInt.String() + " in int"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -191,8 +202,8 @@ func TestNumber_int8(t *testing.T) { var n int8 result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 123 { @@ -208,8 +219,8 @@ func TestNumber_int8Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxInt8+1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxInt8 { @@ -221,14 +232,18 @@ func TestNumber_int8OverflowError(t *testing.T) { t.Parallel() var n int8 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 128 in int8", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxInt8+1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 128 in int8"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -240,8 +255,8 @@ func TestNumber_int8Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MinInt8-1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MinInt8 { @@ -253,14 +268,18 @@ func TestNumber_int8UnderflowError(t *testing.T) { t.Parallel() var n int8 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -129 in int8", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MinInt8-1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -129 in int8"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -276,8 +295,8 @@ func TestNumber_int16Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxInt16+1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxInt16 { @@ -289,14 +308,18 @@ func TestNumber_int16OverflowError(t *testing.T) { t.Parallel() var n int16 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 32768 in int16", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxInt16+1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 32768 in int16"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -308,8 +331,8 @@ func TestNumber_int16Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MinInt16-1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MinInt16 { @@ -321,14 +344,18 @@ func TestNumber_int16UnderflowError(t *testing.T) { t.Parallel() var n int16 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -32769 in int16", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MinInt16-1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -32769 in int16"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -344,8 +371,8 @@ func TestNumber_int32Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxInt32+1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxInt32 { @@ -357,14 +384,18 @@ func TestNumber_int32OverflowError(t *testing.T) { t.Parallel() var n int32 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 2147483648 in int32", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxInt32+1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 2147483648 in int32"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -376,8 +407,8 @@ func TestNumber_int32Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MinInt32-1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MinInt32 { @@ -389,14 +420,18 @@ func TestNumber_int32UnderflowError(t *testing.T) { t.Parallel() var n int32 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -2147483649 in int32", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MinInt32-1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -2147483649 in int32"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -406,8 +441,8 @@ func TestNumber_int64(t *testing.T) { var n int64 result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 123 { @@ -423,8 +458,8 @@ func TestNumber_int64Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowInt), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxInt64 { @@ -436,14 +471,18 @@ func TestNumber_int64OverflowError(t *testing.T) { t.Parallel() var n int64 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 9.223372037e+18 in int64", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowInt), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 9.223372037e+18 in int64"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -455,8 +494,8 @@ func TestNumber_int64Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, underflowInt), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MinInt64 { @@ -468,14 +507,18 @@ func TestNumber_int64UnderflowError(t *testing.T) { t.Parallel() var n int64 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -9.223372037e+18 in int64", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, underflowInt), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -9.223372037e+18 in int64"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -485,8 +528,8 @@ func TestNumber_uint(t *testing.T) { var n uint result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 123 { @@ -502,8 +545,8 @@ func TestNumber_uintOverflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowUint), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if strconv.IntSize == 64 && n != math.MaxUint64 { @@ -517,14 +560,18 @@ func TestNumber_uintOverflowError(t *testing.T) { t.Parallel() var n uint + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store "+overflowUint.String()+" in uint", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowUint), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store " + overflowUint.String() + " in uint"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -536,8 +583,8 @@ func TestNumber_uintUnderflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 0 { @@ -549,14 +596,18 @@ func TestNumber_uintUnderflowError(t *testing.T) { t.Parallel() var n uint + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -1 in uint", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -1 in uint"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -566,8 +617,8 @@ func TestNumber_uint8(t *testing.T) { var n uint8 result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 123 { @@ -583,8 +634,8 @@ func TestNumber_uint8Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxUint8+1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxUint8 { @@ -596,14 +647,18 @@ func TestNumber_uint8OverflowError(t *testing.T) { t.Parallel() var n uint8 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 256 in uint8", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxUint8+1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 256 in uint8"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -615,8 +670,8 @@ func TestNumber_uint8Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 0 { @@ -628,14 +683,18 @@ func TestNumber_uint8UnderflowError(t *testing.T) { t.Parallel() var n uint8 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -1 in uint8", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -1 in uint8"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -651,8 +710,8 @@ func TestNumber_uint16Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxUint16+1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxUint16 { @@ -664,14 +723,18 @@ func TestNumber_uint16OverflowError(t *testing.T) { t.Parallel() var n uint16 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 65536 in uint16", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxUint16+1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 65536 in uint16"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -683,8 +746,8 @@ func TestNumber_uint16Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 0 { @@ -696,14 +759,18 @@ func TestNumber_uint16UnderflowError(t *testing.T) { t.Parallel() var n uint16 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -1 in uint16", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -1 in uint16"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -719,8 +786,8 @@ func TestNumber_uint32Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxUint32+1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxUint32 { @@ -732,14 +799,18 @@ func TestNumber_uint32OverflowError(t *testing.T) { t.Parallel() var n uint32 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 4294967296 in uint32", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxUint32+1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 4294967296 in uint32"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -751,8 +822,8 @@ func TestNumber_uint32Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 0 { @@ -764,14 +835,18 @@ func TestNumber_uint32UnderflowError(t *testing.T) { t.Parallel() var n uint32 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -1 in uint32", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -1 in uint32"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -781,8 +856,8 @@ func TestNumber_uint64(t *testing.T) { var n uint64 result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 123 { @@ -798,8 +873,8 @@ func TestNumber_uint64Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowUint), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxUint64 { @@ -811,14 +886,18 @@ func TestNumber_uint64OverflowError(t *testing.T) { t.Parallel() var n uint64 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 1.844674407e+19 in uint64", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowUint), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 1.844674407e+19 in uint64"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -830,8 +909,8 @@ func TestNumber_uint64Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 0 { @@ -843,14 +922,18 @@ func TestNumber_uint64UnderflowError(t *testing.T) { t.Parallel() var n uint64 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -1 in uint64", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, -1), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -1 in uint64"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -866,8 +949,8 @@ func TestNumber_float32Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxFloat64), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxFloat32 { @@ -879,15 +962,18 @@ func TestNumber_float32OverflowError(t *testing.T) { t.Parallel() var n float32 - - result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxFloat64), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 1.797693135e+308 in float32", + ), } - reflect.ValueOf(&n).Elem().Set(result) - if expected := "cannot store 1.797693135e+308 in float32"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.MaxFloat64), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -899,8 +985,8 @@ func TestNumber_float32Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.SmallestNonzeroFloat64), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.SmallestNonzeroFloat32 { @@ -912,14 +998,18 @@ func TestNumber_float32UnderflowError(t *testing.T) { t.Parallel() var n float32 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 4.940656458e-324 in float32", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, math.SmallestNonzeroFloat64), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 4.940656458e-324 in float32"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -929,8 +1019,8 @@ func TestNumber_float64(t *testing.T) { var n float64 result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, 123), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != 123 { @@ -946,8 +1036,8 @@ func TestNumber_float64Overflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowFloat), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.MaxFloat64 { @@ -959,14 +1049,18 @@ func TestNumber_float64OverflowError(t *testing.T) { t.Parallel() var n float64 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 1e+10000 in float64", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowFloat), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 1e+10000 in float64"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -978,8 +1072,8 @@ func TestNumber_float64OverflowNegative(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowNegativeFloat), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != -math.MaxFloat64 { @@ -991,14 +1085,18 @@ func TestNumber_float64OverflowNegativeError(t *testing.T) { t.Parallel() var n float64 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -1e+10000 in float64", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, overflowNegativeFloat), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -1e+10000 in float64"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -1010,8 +1108,8 @@ func TestNumber_float64Underflow(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, underflowFloat), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != math.SmallestNonzeroFloat64 { @@ -1023,14 +1121,18 @@ func TestNumber_float64UnderflowError(t *testing.T) { t.Parallel() var n float64 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store 1e-1000 in float64", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, underflowFloat), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store 1e-1000 in float64"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -1042,8 +1144,8 @@ func TestNumber_float64UnderflowNegative(t *testing.T) { result, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, underflowNegativeFloat), reflect.ValueOf(n), refl.Options{ AllowRoundingNumbers: true, }, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&n).Elem().Set(result) if n != -math.SmallestNonzeroFloat64 { @@ -1055,14 +1157,18 @@ func TestNumber_float64UnderflowNegativeError(t *testing.T) { t.Parallel() var n float64 + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot store -1e-1000 in float64", + ), + } _, diags := refl.Number(context.Background(), types.NumberType, tftypes.NewValue(tftypes.Number, underflowNegativeFloat), reflect.ValueOf(n), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, got none") - return - } - if expected := "cannot store -1e-1000 in float64"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -1070,10 +1176,10 @@ func TestFromInt(t *testing.T) { t.Parallel() cases := map[string]struct { - val int64 - typ attr.Type - expected attr.Value - expectedDiag *tfprotov6.Diagnostic + val int64 + typ attr.Type + expected attr.Value + expectedDiags diag.Diagnostics }{ "0": { val: 0, @@ -1095,12 +1201,16 @@ func TestFromInt(t *testing.T) { expected: types.Number{ Value: big.NewFloat(1), }, - expectedDiag: testtypes.TestWarningDiagnostic, + expectedDiags: diag.Diagnostics{ + testtypes.TestWarningDiagnostic, + }, }, "WithValidateError": { - val: 1, - typ: testtypes.NumberTypeWithValidateError{}, - expectedDiag: testtypes.TestErrorDiagnostic, + val: 1, + typ: testtypes.NumberTypeWithValidateError{}, + expectedDiags: diag.Diagnostics{ + testtypes.TestErrorDiagnostic, + }, }, } @@ -1109,19 +1219,12 @@ func TestFromInt(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() actualVal, diags := refl.FromInt(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) - if tc.expectedDiag == nil && diagnostics.DiagsHasErrors(diags) { - t.Fatalf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if tc.expectedDiag != nil { - if len(diags) == 0 { - t.Fatalf("Expected diagnostic, got none") - } - - if !cmp.Equal(tc.expectedDiag, diags[0]) { - t.Fatalf("Expected diagnostic:\n\n%s\n\nGot diagnostic:\n\n%s\n\n", diagnostics.DiagString(tc.expectedDiag), diagnostics.DiagString(diags[0])) - } + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } - if !diagnostics.DiagsHasErrors(diags) && !tc.expected.Equal(actualVal) { + + if !diags.HasError() && !tc.expected.Equal(actualVal) { t.Fatalf("fail: got %+v, wanted %+v", actualVal, tc.expected) } }) @@ -1132,10 +1235,10 @@ func TestFromUint(t *testing.T) { t.Parallel() cases := map[string]struct { - val uint64 - typ attr.Type - expected attr.Value - expectedDiag *tfprotov6.Diagnostic + val uint64 + typ attr.Type + expected attr.Value + expectedDiags diag.Diagnostics }{ "0": { val: 0, @@ -1157,12 +1260,16 @@ func TestFromUint(t *testing.T) { expected: types.Number{ Value: big.NewFloat(1), }, - expectedDiag: testtypes.TestWarningDiagnostic, + expectedDiags: diag.Diagnostics{ + testtypes.TestWarningDiagnostic, + }, }, "WithValidateError": { - val: 1, - typ: testtypes.NumberTypeWithValidateError{}, - expectedDiag: testtypes.TestErrorDiagnostic, + val: 1, + typ: testtypes.NumberTypeWithValidateError{}, + expectedDiags: diag.Diagnostics{ + testtypes.TestErrorDiagnostic, + }, }, } @@ -1171,19 +1278,12 @@ func TestFromUint(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() actualVal, diags := refl.FromUint(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) - if tc.expectedDiag == nil && diagnostics.DiagsHasErrors(diags) { - t.Fatalf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if tc.expectedDiag != nil { - if len(diags) == 0 { - t.Fatalf("Expected diagnostic, got none") - } - - if !cmp.Equal(tc.expectedDiag, diags[0]) { - t.Fatalf("Expected diagnostic:\n\n%s\n\nGot diagnostic:\n\n%s\n\n", diagnostics.DiagString(tc.expectedDiag), diagnostics.DiagString(diags[0])) - } + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } - if !diagnostics.DiagsHasErrors(diags) && !tc.expected.Equal(actualVal) { + + if !diags.HasError() && !tc.expected.Equal(actualVal) { t.Fatalf("fail: got %+v, wanted %+v", actualVal, tc.expected) } }) @@ -1194,10 +1294,10 @@ func TestFromFloat(t *testing.T) { t.Parallel() cases := map[string]struct { - val float64 - typ attr.Type - expected attr.Value - expectedDiag *tfprotov6.Diagnostic + val float64 + typ attr.Type + expected attr.Value + expectedDiags diag.Diagnostics }{ "0": { val: 0, @@ -1226,12 +1326,16 @@ func TestFromFloat(t *testing.T) { expected: types.Number{ Value: big.NewFloat(1), }, - expectedDiag: testtypes.TestWarningDiagnostic, + expectedDiags: diag.Diagnostics{ + testtypes.TestWarningDiagnostic, + }, }, "WithValidateError": { - val: 1, - typ: testtypes.NumberTypeWithValidateError{}, - expectedDiag: testtypes.TestErrorDiagnostic, + val: 1, + typ: testtypes.NumberTypeWithValidateError{}, + expectedDiags: diag.Diagnostics{ + testtypes.TestErrorDiagnostic, + }, }, } @@ -1240,19 +1344,12 @@ func TestFromFloat(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() actualVal, diags := refl.FromFloat(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) - if tc.expectedDiag == nil && diagnostics.DiagsHasErrors(diags) { - t.Fatalf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if tc.expectedDiag != nil { - if len(diags) == 0 { - t.Fatalf("Expected diagnostic, got none") - } - - if !cmp.Equal(tc.expectedDiag, diags[0]) { - t.Fatalf("Expected diagnostic:\n\n%s\n\nGot diagnostic:\n\n%s\n\n", diagnostics.DiagString(tc.expectedDiag), diagnostics.DiagString(diags[0])) - } + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } - if !diagnostics.DiagsHasErrors(diags) && !tc.expected.Equal(actualVal) { + + if !diags.HasError() && !tc.expected.Equal(actualVal) { t.Fatalf("fail: got %+v, wanted %+v", actualVal, tc.expected) } }) @@ -1263,10 +1360,10 @@ func TestFromBigFloat(t *testing.T) { t.Parallel() cases := map[string]struct { - val *big.Float - typ attr.Type - expected attr.Value - expectedDiag *tfprotov6.Diagnostic + val *big.Float + typ attr.Type + expected attr.Value + expectedDiags diag.Diagnostics }{ "0": { val: big.NewFloat(0), @@ -1295,12 +1392,16 @@ func TestFromBigFloat(t *testing.T) { expected: types.Number{ Value: big.NewFloat(1), }, - expectedDiag: testtypes.TestWarningDiagnostic, + expectedDiags: diag.Diagnostics{ + testtypes.TestWarningDiagnostic, + }, }, "WithValidateError": { - val: big.NewFloat(1), - typ: testtypes.NumberTypeWithValidateError{}, - expectedDiag: testtypes.TestErrorDiagnostic, + val: big.NewFloat(1), + typ: testtypes.NumberTypeWithValidateError{}, + expectedDiags: diag.Diagnostics{ + testtypes.TestErrorDiagnostic, + }, }, } @@ -1309,19 +1410,12 @@ func TestFromBigFloat(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() actualVal, diags := refl.FromBigFloat(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) - if tc.expectedDiag == nil && diagnostics.DiagsHasErrors(diags) { - t.Fatalf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if tc.expectedDiag != nil { - if len(diags) == 0 { - t.Fatalf("Expected diagnostic, got none") - } - - if !cmp.Equal(tc.expectedDiag, diags[0]) { - t.Fatalf("Expected diagnostic:\n\n%s\n\nGot diagnostic:\n\n%s\n\n", diagnostics.DiagString(tc.expectedDiag), diagnostics.DiagString(diags[0])) - } + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } - if !diagnostics.DiagsHasErrors(diags) && !tc.expected.Equal(actualVal) { + + if !diags.HasError() && !tc.expected.Equal(actualVal) { t.Fatalf("fail: got %+v, wanted %+v", actualVal, tc.expected) } }) @@ -1332,10 +1426,10 @@ func TestFromBigInt(t *testing.T) { t.Parallel() cases := map[string]struct { - val *big.Int - typ attr.Type - expected attr.Value - expectedDiag *tfprotov6.Diagnostic + val *big.Int + typ attr.Type + expected attr.Value + expectedDiags diag.Diagnostics }{ "0": { val: big.NewInt(0), @@ -1357,12 +1451,16 @@ func TestFromBigInt(t *testing.T) { expected: types.Number{ Value: big.NewFloat(1), }, - expectedDiag: testtypes.TestWarningDiagnostic, + expectedDiags: diag.Diagnostics{ + testtypes.TestWarningDiagnostic, + }, }, "WithValidateError": { - val: big.NewInt(1), - typ: testtypes.NumberTypeWithValidateError{}, - expectedDiag: testtypes.TestErrorDiagnostic, + val: big.NewInt(1), + typ: testtypes.NumberTypeWithValidateError{}, + expectedDiags: diag.Diagnostics{ + testtypes.TestErrorDiagnostic, + }, }, } @@ -1371,19 +1469,12 @@ func TestFromBigInt(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() actualVal, diags := refl.FromBigInt(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) - if tc.expectedDiag == nil && diagnostics.DiagsHasErrors(diags) { - t.Fatalf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if tc.expectedDiag != nil { - if len(diags) == 0 { - t.Fatalf("Expected diagnostic, got none") - } - - if !cmp.Equal(tc.expectedDiag, diags[0]) { - t.Fatalf("Expected diagnostic:\n\n%s\n\nGot diagnostic:\n\n%s\n\n", diagnostics.DiagString(tc.expectedDiag), diagnostics.DiagString(diags[0])) - } + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } - if !diagnostics.DiagsHasErrors(diags) && !tc.expected.Equal(actualVal) { + + if !diags.HasError() && !tc.expected.Equal(actualVal) { t.Fatalf("fail: got %+v, wanted %+v", actualVal, tc.expected) } }) diff --git a/internal/reflect/outof.go b/internal/reflect/outof.go index 0c0abd304..416d5f010 100644 --- a/internal/reflect/outof.go +++ b/internal/reflect/outof.go @@ -7,7 +7,7 @@ import ( "reflect" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -15,7 +15,7 @@ import ( // into an attr.Value using the attr.Type supplied. `val` will first be // transformed into a tftypes.Value, then passed to `typ`'s ValueFromTerraform // method. -func OutOf(ctx context.Context, typ attr.Type, val interface{}) (attr.Value, []*tfprotov6.Diagnostic) { +func OutOf(ctx context.Context, typ attr.Type, val interface{}) (attr.Value, diag.Diagnostics) { return FromValue(ctx, typ, val, tftypes.NewAttributePath()) } @@ -23,8 +23,8 @@ func OutOf(ctx context.Context, typ attr.Type, val interface{}) (attr.Value, []* // `typ`. // // It is meant to be called through OutOf, not directly. -func FromValue(ctx context.Context, typ attr.Type, val interface{}, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromValue(ctx context.Context, typ attr.Type, val interface{}, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics if v, ok := val.(attr.Value); ok { return FromAttributeValue(ctx, typ, v, path) @@ -51,12 +51,12 @@ func FromValue(ctx context.Context, typ attr.Type, val interface{}, path *tftype t, ok := typ.(attr.TypeWithAttributeTypes) if !ok { err := fmt.Errorf("cannot use type %T as schema type %T; %T must be an attr.TypeWithAttributeTypes to hold %T", val, typ, typ, val) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return FromStruct(ctx, t, value, path) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, @@ -77,23 +77,23 @@ func FromValue(ctx context.Context, typ attr.Type, val interface{}, path *tftype t, ok := typ.(attr.TypeWithElementType) if !ok { err := fmt.Errorf("cannot use type %T as schema type %T; %T must be an attr.TypeWithElementType to hold %T", val, typ, typ, val) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return FromMap(ctx, t, value, path) case reflect.Ptr: return FromPointer(ctx, typ, value, path) default: err := fmt.Errorf("cannot construct attr.Type from %T (%s)", val, kind) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } } diff --git a/internal/reflect/pointer.go b/internal/reflect/pointer.go index e9eb41054..bb1795ee5 100644 --- a/internal/reflect/pointer.go +++ b/internal/reflect/pointer.go @@ -6,8 +6,7 @@ import ( "reflect" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -15,26 +14,26 @@ import ( // references, populates it with BuildValue, and takes a pointer to it. // // It is meant to be called through Into, not directly. -func Pointer(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func Pointer(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics if target.Kind() != reflect.Ptr { err := fmt.Errorf("cannot dereference pointer, not a pointer, is a %s (%s)", target.Type(), target.Kind()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // we may have gotten a nil pointer, so we need to create our own that // we can set pointer := reflect.New(target.Type().Elem()) // build out whatever the pointer is pointing to pointed, pointedDiags := BuildValue(ctx, typ, val, pointer.Elem(), opts, path) - diags = append(diags, pointedDiags...) + diags.Append(pointedDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return target, diags } // to be able to set the pointer to our new pointer, we need to create @@ -75,25 +74,25 @@ func pointerSafeZeroValue(ctx context.Context, target reflect.Value) reflect.Val // the pointer is referencing. // // It is meant to be called through OutOf, not directly. -func FromPointer(ctx context.Context, typ attr.Type, value reflect.Value, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromPointer(ctx context.Context, typ attr.Type, value reflect.Value, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics if value.Kind() != reflect.Ptr { err := fmt.Errorf("cannot use type %s as a pointer", value.Type()) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } if value.IsNil() { tfVal := tftypes.NewValue(typ.TerraformType(ctx), nil) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -101,19 +100,19 @@ func FromPointer(ctx context.Context, typ attr.Type, value reflect.Value, path * attrVal, err := typ.ValueFromTerraform(ctx, tfVal) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return attrVal, diags } attrVal, attrValDiags := FromValue(ctx, typ, value.Elem().Interface(), path) - diags = append(diags, attrValDiags...) + diags.Append(attrValDiags...) return attrVal, diags } diff --git a/internal/reflect/pointer_test.go b/internal/reflect/pointer_test.go index b6bc1f852..d8fdd4961 100644 --- a/internal/reflect/pointer_test.go +++ b/internal/reflect/pointer_test.go @@ -6,7 +6,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" refl "github.com/hashicorp/terraform-plugin-framework/internal/reflect" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/types" @@ -18,9 +19,18 @@ func TestPointer_notAPointer(t *testing.T) { t.Parallel() var s string + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot dereference pointer, not a pointer, is a string (string)", + ), + } + _, diags := refl.Pointer(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if expected := "cannot dereference pointer, not a pointer, is a string (string)"; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -29,8 +39,8 @@ func TestPointer_nilPointer(t *testing.T) { var s *string got, diags := refl.Pointer(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } if got.Interface() == nil { t.Error("Expected \"hello\", got nil") @@ -45,8 +55,8 @@ func TestPointer_simple(t *testing.T) { var s string got, diags := refl.Pointer(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(&s), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } if got.Interface() == nil { t.Error("Expected \"hello\", got nil") @@ -61,8 +71,8 @@ func TestPointer_pointerPointer(t *testing.T) { var s *string got, diags := refl.Pointer(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(&s), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } if got.Interface() == nil { t.Error("Expected \"hello\", got nil") @@ -72,62 +82,66 @@ func TestPointer_pointerPointer(t *testing.T) { } } -func TestFromPointer_simple(t *testing.T) { - t.Parallel() - - v := "hello, world" - got, diags := refl.FromPointer(context.Background(), types.StringType, reflect.ValueOf(&v), tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("unexpected error: %s", diagnostics.DiagsString(diags)) - } - expected := types.String{ - Value: "hello, world", - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - } -} - -func TestFromPointer_null(t *testing.T) { +func TestFromPointer(t *testing.T) { t.Parallel() - var v *string - got, diags := refl.FromPointer(context.Background(), types.StringType, reflect.ValueOf(v), tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("unexpected error: %s", diagnostics.DiagsString(diags)) - } - expected := types.String{ - Null: true, - } - if diff := cmp.Diff(expected, got); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - } -} - -func TestFromPointer_AttrTypeWithValidate_Error(t *testing.T) { - v := "hello, world" - _, diags := refl.FromPointer(context.Background(), testtypes.StringTypeWithValidateError{}, reflect.ValueOf(&v), tftypes.NewAttributePath()) - if len(diags) == 0 { - t.Fatalf("expected diagnostics, got none") - } - if !cmp.Equal(diags[0], testtypes.TestErrorDiagnostic) { - t.Fatalf("expected diagnostic:\n\n%s\n\ngot diagnostic:\n\n%s\n\n", diagnostics.DiagString(testtypes.TestErrorDiagnostic), diagnostics.DiagString(diags[0])) + testCases := map[string]struct { + typ attr.Type + val reflect.Value + expected attr.Value + expectedDiags diag.Diagnostics + }{ + "simple": { + typ: types.StringType, + val: reflect.ValueOf(strPtr("hello, world")), + expected: types.String{ + Value: "hello, world", + }, + }, + "null": { + typ: types.StringType, + val: reflect.ValueOf(new(*string)), + expected: types.String{ + Null: true, + }, + }, + "WithValidateError": { + typ: testtypes.StringTypeWithValidateError{}, + val: reflect.ValueOf(strPtr("hello, world")), + expectedDiags: diag.Diagnostics{ + testtypes.TestErrorDiagnostic, + }, + }, + "WithValidateWarning": { + typ: testtypes.StringTypeWithValidateWarning{}, + val: reflect.ValueOf(strPtr("hello, world")), + expected: types.String{ + Value: "hello, world", + }, + expectedDiags: diag.Diagnostics{ + testtypes.TestWarningDiagnostic, + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, diags := refl.FromPointer(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } + + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) + } + }) } } -func TestFromPointer_AttrTypeWithValidate_Warning(t *testing.T) { - expectedVal := types.String{ - Value: "hello, world", - } - v := "hello, world" - actualVal, diags := refl.FromPointer(context.Background(), testtypes.StringTypeWithValidateWarning{}, reflect.ValueOf(&v), tftypes.NewAttributePath()) - if len(diags) == 0 { - t.Fatalf("expected diagnostics, got none") - } - if !cmp.Equal(diags[0], testtypes.TestWarningDiagnostic) { - t.Fatalf("expected diagnostic:\n\n%s\n\ngot diagnostic:\n\n%s\n\n", diagnostics.DiagString(testtypes.TestWarningDiagnostic), diagnostics.DiagString(diags[0])) - } - if !expectedVal.Equal(actualVal) { - t.Fatalf("unexpected value: got %+v, wanted %+v", actualVal, expectedVal) - } +func strPtr(s string) *string { + return &s } diff --git a/internal/reflect/primitive.go b/internal/reflect/primitive.go index 86283e8c3..2e387bdee 100644 --- a/internal/reflect/primitive.go +++ b/internal/reflect/primitive.go @@ -6,8 +6,7 @@ import ( "reflect" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -15,50 +14,50 @@ import ( // populates it with the data in `val`. // // It is meant to be called through `Into`, not directly. -func Primitive(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func Primitive(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics switch target.Kind() { case reflect.Bool: var b bool err := val.As(&b) if err != nil { - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to boolean. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to boolean. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } return reflect.ValueOf(b).Convert(target.Type()), nil case reflect.String: var s string err := val.As(&s) if err != nil { - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to string. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to string. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } return reflect.ValueOf(s).Convert(target.Type()), nil default: err := fmt.Errorf("unrecognized type: %s", target.Kind()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to primitive. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to primitive. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } } // FromString returns an attr.Value as produced by `typ` from a string. // // It is meant to be called through OutOf, not directly. -func FromString(ctx context.Context, typ attr.Type, val string, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromString(ctx context.Context, typ attr.Type, val string, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics err := tftypes.ValidateValue(tftypes.String, val) if err != nil { return nil, append(diags, validateValueErrorDiag(err, path)) @@ -66,9 +65,9 @@ func FromString(ctx context.Context, typ attr.Type, val string, path *tftypes.At tfStr := tftypes.NewValue(tftypes.String, val) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfStr)...) + diags.Append(typeWithValidate.Validate(ctx, tfStr)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -84,8 +83,8 @@ func FromString(ctx context.Context, typ attr.Type, val string, path *tftypes.At // FromBool returns an attr.Value as produced by `typ` from a bool. // // It is meant to be called through OutOf, not directly. -func FromBool(ctx context.Context, typ attr.Type, val bool, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromBool(ctx context.Context, typ attr.Type, val bool, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics err := tftypes.ValidateValue(tftypes.Bool, val) if err != nil { return nil, append(diags, validateValueErrorDiag(err, path)) @@ -93,9 +92,9 @@ func FromBool(ctx context.Context, typ attr.Type, val bool, path *tftypes.Attrib tfBool := tftypes.NewValue(tftypes.Bool, val) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfBool)...) + diags.Append(typeWithValidate.Validate(ctx, tfBool)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } diff --git a/internal/reflect/primitive_test.go b/internal/reflect/primitive_test.go index 5e01f9279..bb831fb06 100644 --- a/internal/reflect/primitive_test.go +++ b/internal/reflect/primitive_test.go @@ -7,11 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/diag" refl "github.com/hashicorp/terraform-plugin-framework/internal/reflect" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -21,8 +20,8 @@ func TestPrimitive_string(t *testing.T) { var s string result, diags := refl.Primitive(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(s), tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&s).Elem().Set(result) if s != "hello" { @@ -37,8 +36,8 @@ func TestPrimitive_stringAlias(t *testing.T) { var s testString result, diags := refl.Primitive(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(s), tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&s).Elem().Set(result) if s != "hello" { @@ -52,8 +51,8 @@ func TestPrimitive_bool(t *testing.T) { var b bool result, diags := refl.Primitive(context.Background(), types.BoolType, tftypes.NewValue(tftypes.Bool, true), reflect.ValueOf(b), tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&b).Elem().Set(result) if b != true { @@ -68,8 +67,8 @@ func TestPrimitive_boolAlias(t *testing.T) { var b testBool result, diags := refl.Primitive(context.Background(), types.BoolType, tftypes.NewValue(tftypes.Bool, true), reflect.ValueOf(b), tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&b).Elem().Set(result) if b != true { @@ -78,51 +77,66 @@ func TestPrimitive_boolAlias(t *testing.T) { } func TestFromString(t *testing.T) { - expectedVal := types.String{ - Value: "mystring", - } - actualVal, diags := refl.FromString(context.Background(), types.StringType, "mystring", tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Fatalf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if !expectedVal.Equal(actualVal) { - t.Fatalf("fail: got %+v, wanted %+v", actualVal, expectedVal) - } -} + t.Parallel() -func TestFromString_AttrTypeWithValidate_Error(t *testing.T) { - _, diags := refl.FromString(context.Background(), testtypes.StringTypeWithValidateError{}, "mystring", tftypes.NewAttributePath()) - if len(diags) == 0 { - t.Fatalf("expected diagnostics, got none") - } - if !cmp.Equal(diags[0], testtypes.TestErrorDiagnostic) { - t.Fatalf("expected diagnostic:\n\n%s\n\ngot diagnostic:\n\n%s\n\n", diagnostics.DiagString(testtypes.TestErrorDiagnostic), diagnostics.DiagString(diags[0])) + cases := map[string]struct { + val string + typ attr.Type + expected attr.Value + expectedDiags diag.Diagnostics + }{ + "basic": { + val: "mystring", + typ: types.StringType, + expected: types.String{ + Value: "mystring", + }, + }, + "WithValidateWarning": { + val: "mystring", + typ: testtypes.StringTypeWithValidateWarning{}, + expected: types.String{ + Value: "mystring", + }, + expectedDiags: diag.Diagnostics{ + testtypes.TestWarningDiagnostic, + }, + }, + "WithValidateError": { + val: "mystring", + typ: testtypes.StringTypeWithValidateError{}, + expectedDiags: diag.Diagnostics{ + testtypes.TestErrorDiagnostic, + }, + }, } -} -func TestFromString_AttrTypeWithValidate_Warning(t *testing.T) { - expectedVal := types.String{ - Value: "mystring", - } - actualVal, diags := refl.FromString(context.Background(), testtypes.StringTypeWithValidateWarning{}, "mystring", tftypes.NewAttributePath()) - if len(diags) == 0 { - t.Fatalf("expected diagnostics, got none") - } - if !cmp.Equal(diags[0], testtypes.TestWarningDiagnostic) { - t.Fatalf("expected diagnostic:\n\n%s\n\ngot diagnostic:\n\n%s\n\n", diagnostics.DiagString(testtypes.TestWarningDiagnostic), diagnostics.DiagString(diags[0])) - } - if !expectedVal.Equal(actualVal) { - t.Fatalf("unexpected value: got %+v, wanted %+v", actualVal, expectedVal) + for name, tc := range cases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, diags := refl.FromString(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) + } + + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) + } + }) } } func TestFromBool(t *testing.T) { - // the rare exhaustive test + t.Parallel() + cases := map[string]struct { - val bool - typ attr.Type - expected attr.Value - expectedDiag *tfprotov6.Diagnostic + val bool + typ attr.Type + expected attr.Value + expectedDiags diag.Diagnostics }{ "true": { val: true, @@ -144,31 +158,33 @@ func TestFromBool(t *testing.T) { expected: types.Bool{ Value: true, }, - expectedDiag: testtypes.TestWarningDiagnostic, + expectedDiags: diag.Diagnostics{ + testtypes.TestWarningDiagnostic, + }, }, "WithValidateError": { - val: true, - typ: testtypes.BoolTypeWithValidateError{}, - expectedDiag: testtypes.TestErrorDiagnostic, + val: true, + typ: testtypes.BoolTypeWithValidateError{}, + expectedDiags: diag.Diagnostics{ + testtypes.TestErrorDiagnostic, + }, }, } - for _, tc := range cases { - actualVal, diags := refl.FromBool(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) - if tc.expectedDiag == nil && diagnostics.DiagsHasErrors(diags) { - t.Fatalf("Unexpected error: %s", diagnostics.DiagsString(diags)) - } - if tc.expectedDiag != nil { - if len(diags) == 0 { - t.Fatalf("Expected diagnostic, got none") + for name, tc := range cases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, diags := refl.FromBool(context.Background(), tc.typ, tc.val, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } - if !cmp.Equal(tc.expectedDiag, diags[0]) { - t.Fatalf("Expected diagnostic:\n\n%s\n\nGot diagnostic:\n\n%s\n\n", diagnostics.DiagString(tc.expectedDiag), diagnostics.DiagString(diags[0])) + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("unexpected result (+wanted, -got): %s", diff) } - } - if !diagnostics.DiagsHasErrors(diags) && !tc.expected.Equal(actualVal) { - t.Fatalf("fail: got %+v, wanted %+v", actualVal, tc.expected) - } + }) } } diff --git a/internal/reflect/slice.go b/internal/reflect/slice.go index 99c9cdfc3..0be5e4641 100644 --- a/internal/reflect/slice.go +++ b/internal/reflect/slice.go @@ -6,36 +6,35 @@ import ( "reflect" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) // build a slice of elements, matching the type of `target`, and fill it with // the data in `val`. -func reflectSlice(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func reflectSlice(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics // this only works with slices, so check that out first if target.Kind() != reflect.Slice { err := fmt.Errorf("expected a slice type, got %s", target.Type()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to slice. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to slice. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // TODO: check that the val is a list or set or tuple elemTyper, ok := typ.(attr.TypeWithElementType) if !ok { err := fmt.Errorf("cannot reflect %s using type information provided by %T, %T must be an attr.TypeWithElementType", val.Type(), typ, typ) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to slice. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to slice. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // we need our value to become a list of values so we can iterate over @@ -43,12 +42,12 @@ func reflectSlice(ctx context.Context, typ attr.Type, val tftypes.Value, target var values []tftypes.Value err := val.As(&values) if err != nil { - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to slice. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to slice. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // we need to know the type the slice is wrapping @@ -69,9 +68,9 @@ func reflectSlice(ctx context.Context, typ attr.Type, val tftypes.Value, target // reflect the value into our new target val, valDiags := BuildValue(ctx, elemAttrType, value, targetValue, opts, path) - diags = append(diags, valDiags...) + diags.Append(valDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return target, diags } @@ -90,8 +89,8 @@ func reflectSlice(ctx context.Context, typ attr.Type, val tftypes.Value, target // `typ` to construct values for them. // // It is meant to be called through OutOf, not directly. -func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics // TODO: support tuples, which are attr.TypeWithElementTypes tfType := typ.TerraformType(ctx) @@ -100,9 +99,9 @@ func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tfty tfVal := tftypes.NewValue(tfType, nil) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -110,12 +109,12 @@ func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tfty attrVal, err := typ.ValueFromTerraform(ctx, tfVal) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return attrVal, diags @@ -124,21 +123,21 @@ func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tfty t, ok := typ.(attr.TypeWithElementType) if !ok { err := fmt.Errorf("cannot use type %T as schema type %T; %T must be an attr.TypeWithElementType to hold %T", val, typ, typ, val) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } elemType := t.ElementType() tfElems := make([]tftypes.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { val, valDiags := FromValue(ctx, elemType, val.Index(i).Interface(), path.WithElementKeyInt(int64(i))) - diags = append(diags, valDiags...) + diags.Append(valDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } tfVal, err := val.ToTerraformValue(ctx) @@ -153,9 +152,9 @@ func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tfty tfElemVal := tftypes.NewValue(elemType.TerraformType(ctx), tfVal) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfElemVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfElemVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -171,9 +170,9 @@ func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tfty tfVal := tftypes.NewValue(tfType, tfElems) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -181,12 +180,12 @@ func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tfty attrVal, err := typ.ValueFromTerraform(ctx, tfVal) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return attrVal, diags diff --git a/internal/reflect/struct.go b/internal/reflect/struct.go index 2174301cd..b0b486b14 100644 --- a/internal/reflect/struct.go +++ b/internal/reflect/struct.go @@ -7,8 +7,7 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -25,50 +24,50 @@ import ( // and other mistakes early. // // Struct is meant to be called from Into, not directly. -func Struct(ctx context.Context, typ attr.Type, object tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func Struct(ctx context.Context, typ attr.Type, object tftypes.Value, target reflect.Value, opts Options, path *tftypes.AttributePath) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics // this only works with object values, so make sure that constraint is // met if target.Kind() != reflect.Struct { err := fmt.Errorf("expected a struct type, got %s", target.Type()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } if !object.Type().Is(tftypes.Object{}) { err := fmt.Errorf("cannot reflect %s into a struct, must be an object", object.Type().String()) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } attrsType, ok := typ.(attr.TypeWithAttributeTypes) if !ok { err := fmt.Errorf("cannot reflect object using type information provided by %T, %T must be an attr.TypeWithAttributeTypes", typ, typ) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // collect a map of fields that are in the object passed in var objectFields map[string]tftypes.Value err := object.As(&objectFields) if err != nil { - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // collect a map of fields that are defined in the tags of the struct @@ -76,12 +75,12 @@ func Struct(ctx context.Context, typ attr.Type, object tftypes.Value, target ref targetFields, err := getStructTags(ctx, target, path) if err != nil { err = fmt.Errorf("error retrieving field names from struct tags: %w", err) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } // we require an exact, 1:1 match of these fields to avoid typos @@ -107,12 +106,12 @@ func Struct(ctx context.Context, typ attr.Type, object tftypes.Value, target ref missing = append(missing, fmt.Sprintf("Object defines fields not found in struct: %s.", commaSeparatedString(targetMissing))) } err := fmt.Errorf("mismatch between struct and object: %s", strings.Join(missing, " ")) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } attrTypes := attrsType.AttributeTypes() @@ -124,18 +123,18 @@ func Struct(ctx context.Context, typ attr.Type, object tftypes.Value, target ref attrType, ok := attrTypes[field] if !ok { err := fmt.Errorf("could not find type information for attribute in supplied attr.Type %T", typ) - return target, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path.WithAttributeName(field), - }) + diags.AddAttributeError( + path.WithAttributeName(field), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags } structField := result.Field(structFieldPos) fieldVal, fieldValDiags := BuildValue(ctx, attrType, objectFields[field], structField, opts, path.WithAttributeName(field)) - diags = append(diags, fieldValDiags...) + diags.Append(fieldValDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return target, diags } structField.Set(fieldVal) @@ -150,8 +149,8 @@ func Struct(ctx context.Context, typ attr.Type, object tftypes.Value, target ref // reported by `typ`. // // It is meant to be called through OutOf, not directly. -func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflect.Value, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflect.Value, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics objTypes := map[string]tftypes.Type{} objValues := map[string]tftypes.Value{} @@ -160,12 +159,12 @@ func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflec targetFields, err := getStructTags(ctx, val, path) if err != nil { err = fmt.Errorf("error retrieving field names from struct tags: %w", err) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from struct value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from struct value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } attrTypes := typ.AttributeTypes() @@ -174,21 +173,21 @@ func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflec fieldValue := val.Field(fieldNo) attrVal, attrValDiags := FromValue(ctx, attrTypes[name], fieldValue.Interface(), path) - diags = append(diags, attrValDiags...) + diags.Append(attrValDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } attrType, ok := attrTypes[name] if !ok || attrType == nil { err := fmt.Errorf("couldn't find type information for attribute in supplied attr.Type %T", typ) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Value Conversion Error", - Detail: "An unexpected error was encountered trying to convert from struct value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from struct value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } objTypes[name] = attrType.TerraformType(ctx) @@ -205,9 +204,9 @@ func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflec tfObjVal := tftypes.NewValue(objTypes[name], tfVal) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfObjVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfObjVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -220,9 +219,9 @@ func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflec }, objValues) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags = append(diags, typeWithValidate.Validate(ctx, tfVal)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } diff --git a/internal/reflect/struct_test.go b/internal/reflect/struct_test.go index 2d4000c50..53ecaaef4 100644 --- a/internal/reflect/struct_test.go +++ b/internal/reflect/struct_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/diag" refl "github.com/hashicorp/terraform-plugin-framework/internal/reflect" "github.com/hashicorp/terraform-plugin-framework/types" @@ -19,12 +19,18 @@ func TestNewStruct_notAnObject(t *testing.T) { t.Parallel() var s struct{} - _, diags := refl.Struct(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, didn't get one") + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\ncannot reflect tftypes.String into a struct, must be an object", + ), } - if expected := `cannot reflect tftypes.String into a struct, must be an object`; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + _, diags := refl.Struct(context.Background(), types.StringType, tftypes.NewValue(tftypes.String, "hello"), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -32,6 +38,14 @@ func TestNewStruct_notAStruct(t *testing.T) { t.Parallel() var s string + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\nexpected a struct type, got string", + ), + } + _, diags := refl.Struct(context.Background(), types.ObjectType{ AttrTypes: map[string]attr.Type{ "a": types.StringType, @@ -43,11 +57,9 @@ func TestNewStruct_notAStruct(t *testing.T) { }, map[string]tftypes.Value{ "a": tftypes.NewValue(tftypes.String, "hello"), }), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, didn't get one") - } - if expected := `expected a struct type, got string`; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -57,14 +69,20 @@ func TestNewStruct_objectMissingFields(t *testing.T) { var s struct { A string `tfsdk:"a"` } + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\nmismatch between struct and object: Struct defines fields not found in object: a.", + ), + } + _, diags := refl.Struct(context.Background(), types.ObjectType{}, tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{}, }, map[string]tftypes.Value{}), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, didn't get one") - } - if expected := `mismatch between struct and object: Struct defines fields not found in object: a.`; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -72,6 +90,14 @@ func TestNewStruct_structMissingProperties(t *testing.T) { t.Parallel() var s struct{} + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\nmismatch between struct and object: Object defines fields not found in struct: a.", + ), + } + _, diags := refl.Struct(context.Background(), types.ObjectType{ AttrTypes: map[string]attr.Type{ "a": types.StringType, @@ -83,11 +109,9 @@ func TestNewStruct_structMissingProperties(t *testing.T) { }, map[string]tftypes.Value{ "a": tftypes.NewValue(tftypes.String, "hello"), }), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, didn't get one") - } - if expected := `mismatch between struct and object: Object defines fields not found in struct: a.`; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -97,6 +121,14 @@ func TestNewStruct_objectMissingFieldsAndStructMissingProperties(t *testing.T) { var s struct { A string `tfsdk:"a"` } + expectedDiags := diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath(), + "Value Conversion Error", + "An unexpected error was encountered trying to convert to struct. This is always an error in the provider. Please report the following to the provider developer:\n\nmismatch between struct and object: Struct defines fields not found in object: a. Object defines fields not found in struct: b.", + ), + } + _, diags := refl.Struct(context.Background(), types.ObjectType{ AttrTypes: map[string]attr.Type{ "a": types.StringType, @@ -108,11 +140,9 @@ func TestNewStruct_objectMissingFieldsAndStructMissingProperties(t *testing.T) { }, map[string]tftypes.Value{ "b": tftypes.NewValue(tftypes.String, "hello"), }), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if !diagnostics.DiagsHasErrors(diags) { - t.Error("Expected error, didn't get one") - } - if expected := `mismatch between struct and object: Struct defines fields not found in object: a. Object defines fields not found in struct: b.`; !diagnostics.DiagsContainsDetail(diags, expected) { - t.Errorf("Expected error to be %q, got %s", expected, diagnostics.DiagsString(diags)) + + if diff := cmp.Diff(diags, expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } } @@ -141,8 +171,8 @@ func TestNewStruct_primitives(t *testing.T) { "b": tftypes.NewValue(tftypes.Number, 123), "c": tftypes.NewValue(tftypes.Bool, true), }), reflect.ValueOf(s), refl.Options{}, tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } reflect.ValueOf(&s).Elem().Set(result) if s.A != "hello" { @@ -330,8 +360,8 @@ func TestNewStruct_complex(t *testing.T) { UnhandledUnknownAsEmpty: true, }, tftypes.NewAttributePath()) reflect.ValueOf(&s).Elem().Set(result) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } str := "pointed" expected := myStruct{ @@ -400,8 +430,8 @@ func TestFromStruct_primitives(t *testing.T) { "opted_in": types.BoolType, }, }, reflect.ValueOf(disk1), tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Fatalf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Fatalf("Unexpected error: %v", diags) } expectedVal := types.Object{ @@ -525,8 +555,8 @@ func TestFromStruct_complex(t *testing.T) { "uint": types.NumberType, }, }, reflect.ValueOf(s), tftypes.NewAttributePath()) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } expected := types.Object{ AttrTypes: map[string]attr.Type{ diff --git a/internal/testing/types/boolwithvalidate.go b/internal/testing/types/boolwithvalidate.go index 0250a30ad..bbc7d5d84 100644 --- a/internal/testing/types/boolwithvalidate.go +++ b/internal/testing/types/boolwithvalidate.go @@ -4,7 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -21,10 +21,10 @@ type BoolTypeWithValidateWarning struct { BoolType } -func (t BoolTypeWithValidateError) Validate(ctx context.Context, in tftypes.Value) []*tfprotov6.Diagnostic { - return []*tfprotov6.Diagnostic{TestErrorDiagnostic} +func (t BoolTypeWithValidateError) Validate(ctx context.Context, in tftypes.Value) diag.Diagnostics { + return diag.Diagnostics{TestErrorDiagnostic} } -func (t BoolTypeWithValidateWarning) Validate(ctx context.Context, in tftypes.Value) []*tfprotov6.Diagnostic { - return []*tfprotov6.Diagnostic{TestWarningDiagnostic} +func (t BoolTypeWithValidateWarning) Validate(ctx context.Context, in tftypes.Value) diag.Diagnostics { + return diag.Diagnostics{TestWarningDiagnostic} } diff --git a/internal/testing/types/diags.go b/internal/testing/types/diags.go index c4dda0ab6..3564b36a9 100644 --- a/internal/testing/types/diags.go +++ b/internal/testing/types/diags.go @@ -1,18 +1,16 @@ package types import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" ) var ( - TestErrorDiagnostic = &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error Diagnostic", - Detail: "This is an error.", - } - TestWarningDiagnostic = &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "Warning Diagnostic", - Detail: "This is a warning.", - } + TestErrorDiagnostic = diag.NewErrorDiagnostic( + "Error Diagnostic", + "This is an error.", + ) + TestWarningDiagnostic = diag.NewWarningDiagnostic( + "Warning Diagnostic", + "This is a warning.", + ) ) diff --git a/internal/testing/types/numberwithvalidate.go b/internal/testing/types/numberwithvalidate.go index 37e76bea9..40719dc62 100644 --- a/internal/testing/types/numberwithvalidate.go +++ b/internal/testing/types/numberwithvalidate.go @@ -4,7 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -21,10 +21,10 @@ type NumberTypeWithValidateWarning struct { NumberType } -func (t NumberTypeWithValidateError) Validate(ctx context.Context, in tftypes.Value) []*tfprotov6.Diagnostic { - return []*tfprotov6.Diagnostic{TestErrorDiagnostic} +func (t NumberTypeWithValidateError) Validate(ctx context.Context, in tftypes.Value) diag.Diagnostics { + return diag.Diagnostics{TestErrorDiagnostic} } -func (t NumberTypeWithValidateWarning) Validate(ctx context.Context, in tftypes.Value) []*tfprotov6.Diagnostic { - return []*tfprotov6.Diagnostic{TestWarningDiagnostic} +func (t NumberTypeWithValidateWarning) Validate(ctx context.Context, in tftypes.Value) diag.Diagnostics { + return diag.Diagnostics{TestWarningDiagnostic} } diff --git a/internal/testing/types/stringwithvalidate.go b/internal/testing/types/stringwithvalidate.go index b6caaa735..2b9b3c5ab 100644 --- a/internal/testing/types/stringwithvalidate.go +++ b/internal/testing/types/stringwithvalidate.go @@ -4,7 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -21,10 +21,10 @@ type StringTypeWithValidateWarning struct { StringType } -func (t StringTypeWithValidateError) Validate(ctx context.Context, in tftypes.Value) []*tfprotov6.Diagnostic { - return []*tfprotov6.Diagnostic{TestErrorDiagnostic} +func (t StringTypeWithValidateError) Validate(ctx context.Context, in tftypes.Value) diag.Diagnostics { + return diag.Diagnostics{TestErrorDiagnostic} } -func (t StringTypeWithValidateWarning) Validate(ctx context.Context, in tftypes.Value) []*tfprotov6.Diagnostic { - return []*tfprotov6.Diagnostic{TestWarningDiagnostic} +func (t StringTypeWithValidateWarning) Validate(ctx context.Context, in tftypes.Value) diag.Diagnostics { + return diag.Diagnostics{TestWarningDiagnostic} } diff --git a/tfsdk/attribute.go b/tfsdk/attribute.go index 3a6905d09..a85f0655e 100644 --- a/tfsdk/attribute.go +++ b/tfsdk/attribute.go @@ -7,7 +7,6 @@ import ( "sort" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -228,43 +227,40 @@ func (a Attribute) tfprotov6SchemaAttribute(ctx context.Context, name string, pa // validate performs all Attribute validation. func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { if (a.Attributes == nil || len(a.Attributes.GetAttributes()) == 0) && a.Type == nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Invalid Attribute Definition", - Detail: "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.", - Attribute: req.AttributePath, - }) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Attribute Definition", + "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.", + ) return } if a.Attributes != nil && len(a.Attributes.GetAttributes()) > 0 && a.Type != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Invalid Attribute Definition", - Detail: "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.", - Attribute: req.AttributePath, - }) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Attribute Definition", + "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.", + ) return } if !a.Required && !a.Optional && !a.Computed { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Invalid Attribute Definition", - Detail: "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.", - Attribute: req.AttributePath, - }) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Attribute Definition", + "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.", + ) return } attributeConfig, diags := req.Config.GetAttribute(ctx, req.AttributePath) - resp.Diagnostics = append(resp.Diagnostics, diags...) + resp.Diagnostics.Append(diags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return } @@ -282,12 +278,11 @@ func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, r if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Attribute Validation Error", - Detail: "Attribute validation cannot walk schema. Report this to the provider developer:\n\n" + err.Error(), - Attribute: req.AttributePath, - }) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) return } @@ -315,12 +310,11 @@ func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, r if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Attribute Validation Error", - Detail: "Attribute validation cannot walk schema. Report this to the provider developer:\n\n" + err.Error(), - Attribute: req.AttributePath, - }) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) return } @@ -356,12 +350,11 @@ func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, r } default: err := fmt.Errorf("unknown attribute validation nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Attribute Validation Error", - Detail: "Attribute validation cannot walk schema. Report this to the provider developer:\n\n" + err.Error(), - Attribute: req.AttributePath, - }) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) return } @@ -371,23 +364,21 @@ func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, r tfValue, err := attributeConfig.ToTerraformValue(ctx) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Attribute Validation Error", - Detail: "Attribute validation cannot convert value. Report this to the provider developer:\n\n" + err.Error(), - Attribute: req.AttributePath, - }) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot convert value. Report this to the provider developer:\n\n"+err.Error(), + ) return } if tfValue != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "Attribute Deprecated", - Detail: a.DeprecationMessage, - Attribute: req.AttributePath, - }) + resp.Diagnostics.AddAttributeWarning( + req.AttributePath, + "Attribute Deprecated", + a.DeprecationMessage, + ) } } } @@ -395,22 +386,22 @@ func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, r // modifyPlan runs all AttributePlanModifiers func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { attrConfig, diags := req.Config.GetAttribute(ctx, req.AttributePath) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(diags) { + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { return } req.AttributeConfig = attrConfig attrState, diags := req.State.GetAttribute(ctx, req.AttributePath) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(diags) { + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { return } req.AttributeState = attrState attrPlan, diags := req.Plan.GetAttribute(ctx, req.AttributePath) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(diags) { + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { return } req.AttributePlan = attrPlan @@ -428,7 +419,7 @@ func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanReques for _, planModifier := range a.PlanModifiers { planModifier.Modify(ctx, modifyReq, resp) modifyReq.AttributePlan = resp.AttributePlan - if diagnostics.DiagsHasErrors(resp.Diagnostics) { + if resp.Diagnostics.HasError() { return } } diff --git a/tfsdk/attribute_plan_modification.go b/tfsdk/attribute_plan_modification.go index 8eac8aa37..9771ad4f9 100644 --- a/tfsdk/attribute_plan_modification.go +++ b/tfsdk/attribute_plan_modification.go @@ -4,7 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -158,47 +158,29 @@ type ModifyAttributePlanResponse struct { // planned state of the requested resource. Returning an empty slice // indicates a successful validation with no warnings or errors // generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // AddWarning appends a warning diagnostic to the response. If the warning // concerns a particular attribute, AddAttributeWarning should be used instead. func (r *ModifyAttributePlanResponse) AddWarning(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddWarning(summary, detail) } // AddAttributeWarning appends a warning diagnostic to the response and labels // it with a specific attribute. func (r *ModifyAttributePlanResponse) AddAttributeWarning(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddAttributeWarning(attributePath, summary, detail) } // AddError appends an error diagnostic to the response. If the error concerns a // particular attribute, AddAttributeError should be used instead. func (r *ModifyAttributePlanResponse) AddError(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddError(summary, detail) } // AddAttributeError appends an error diagnostic to the response and labels it // with a specific attribute. func (r *ModifyAttributePlanResponse) AddAttributeError(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddAttributeError(attributePath, summary, detail) } diff --git a/tfsdk/attribute_test.go b/tfsdk/attribute_test.go index fb6f35e95..489210b5d 100644 --- a/tfsdk/attribute_test.go +++ b/tfsdk/attribute_test.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" @@ -709,13 +710,12 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Invalid Attribute Definition", - Detail: "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.", - Attribute: tftypes.NewAttributePath().WithAttributeName("test"), - }, + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Invalid Attribute Definition", + "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.", + ), }, }, }, @@ -747,13 +747,12 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Invalid Attribute Definition", - Detail: "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.", - Attribute: tftypes.NewAttributePath().WithAttributeName("test"), - }, + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Invalid Attribute Definition", + "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.", + ), }, }, }, @@ -778,13 +777,12 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Invalid Attribute Definition", - Detail: "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.", - Attribute: tftypes.NewAttributePath().WithAttributeName("test"), - }, + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Invalid Attribute Definition", + "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.", + ), }, }, }, @@ -810,13 +808,12 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Configuration Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", - Attribute: tftypes.NewAttributePath().WithAttributeName("test"), - }, + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", + ), }, }, }, @@ -866,13 +863,12 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "Attribute Deprecated", - Detail: "Use something else instead.", - Attribute: tftypes.NewAttributePath().WithAttributeName("test"), - }, + Diagnostics: diag.Diagnostics{ + diag.NewAttributeWarningDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Attribute Deprecated", + "Use something else instead.", + ), }, }, }, @@ -923,13 +919,12 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "Attribute Deprecated", - Detail: "Use something else instead.", - Attribute: tftypes.NewAttributePath().WithAttributeName("test"), - }, + Diagnostics: diag.Diagnostics{ + diag.NewAttributeWarningDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Attribute Deprecated", + "Use something else instead.", + ), }, }, }, @@ -959,9 +954,9 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - testWarningDiagnostic, - testWarningDiagnostic, + Diagnostics: diag.Diagnostics{ + testWarningDiagnostic1, + testWarningDiagnostic2, }, }, }, @@ -991,9 +986,9 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - testErrorDiagnostic, - testErrorDiagnostic, + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + testErrorDiagnostic2, }, }, }, @@ -1019,7 +1014,7 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ + Diagnostics: diag.Diagnostics{ testtypes.TestErrorDiagnostic, }, }, @@ -1046,7 +1041,7 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ + Diagnostics: diag.Diagnostics{ testtypes.TestWarningDiagnostic, }, }, @@ -1167,8 +1162,8 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - testErrorDiagnostic, + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, }, }, }, @@ -1288,8 +1283,8 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - testErrorDiagnostic, + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, }, }, }, @@ -1382,8 +1377,8 @@ func TestAttributeValidate(t *testing.T) { }, }, resp: ValidateAttributeResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - testErrorDiagnostic, + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, }, }, }, @@ -1398,7 +1393,7 @@ func TestAttributeValidate(t *testing.T) { attribute, err := tc.req.Config.Schema.AttributeAtPath(tc.req.AttributePath) if err != nil { - t.Fatalf("Unexpected error getting Attribute: %s", err) + t.Fatalf("Unexpected error getting %s", err) } attribute.validate(context.Background(), tc.req, &got) diff --git a/tfsdk/attribute_validation.go b/tfsdk/attribute_validation.go index 1192b826b..95feb6bb6 100644 --- a/tfsdk/attribute_validation.go +++ b/tfsdk/attribute_validation.go @@ -4,7 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -45,5 +45,5 @@ type ValidateAttributeResponse struct { // Diagnostics report errors or warnings related to validating the data // source configuration. An empty slice indicates success, with no warnings // or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } diff --git a/tfsdk/attribute_validation_test.go b/tfsdk/attribute_validation_test.go index a897ffe7e..e72eb502f 100644 --- a/tfsdk/attribute_validation_test.go +++ b/tfsdk/attribute_validation_test.go @@ -3,20 +3,26 @@ package tfsdk import ( "context" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" ) var ( - testErrorDiagnostic = &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error Diagnostic", - Detail: "This is an error.", - } - testWarningDiagnostic = &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "Warning Diagnostic", - Detail: "This is a warning.", - } + testErrorDiagnostic1 = diag.NewErrorDiagnostic( + "Error Diagnostic 1", + "This is an error.", + ) + testErrorDiagnostic2 = diag.NewErrorDiagnostic( + "Error Diagnostic 2", + "This is an error.", + ) + testWarningDiagnostic1 = diag.NewWarningDiagnostic( + "Warning Diagnostic 1", + "This is a warning.", + ) + testWarningDiagnostic2 = diag.NewWarningDiagnostic( + "Warning Diagnostic 2", + "This is a warning.", + ) ) type testErrorAttributeValidator struct { @@ -32,7 +38,11 @@ func (v testErrorAttributeValidator) MarkdownDescription(ctx context.Context) st } func (v testErrorAttributeValidator) Validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { - resp.Diagnostics = append(resp.Diagnostics, testErrorDiagnostic) + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.Append(testErrorDiagnostic1) + } else { + resp.Diagnostics.Append(testErrorDiagnostic2) + } } type testWarningAttributeValidator struct { @@ -48,5 +58,9 @@ func (v testWarningAttributeValidator) MarkdownDescription(ctx context.Context) } func (v testWarningAttributeValidator) Validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { - resp.Diagnostics = append(resp.Diagnostics, testWarningDiagnostic) + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.Append(testWarningDiagnostic1) + } else { + resp.Diagnostics.Append(testWarningDiagnostic2) + } } diff --git a/tfsdk/config.go b/tfsdk/config.go index a7e05715d..e0fc52a59 100644 --- a/tfsdk/config.go +++ b/tfsdk/config.go @@ -5,9 +5,8 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/reflect" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -18,24 +17,24 @@ type Config struct { } // Get populates the struct passed as `target` with the entire config. -func (c Config) Get(ctx context.Context, target interface{}) []*tfprotov6.Diagnostic { +func (c Config) Get(ctx context.Context, target interface{}) diag.Diagnostics { return reflect.Into(ctx, c.Schema.AttributeType(), c.Raw, target, reflect.Options{}) } // GetAttribute retrieves the attribute found at `path` and returns it as an // attr.Value. Consumers should assert the type of the returned value with the // desired attr.Type. -func (c Config) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func (c Config) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics attrType, err := c.Schema.AttributeTypeAtPath(path) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Configuration Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } // if the whole config is nil, the value of a valid attribute is also @@ -46,18 +45,18 @@ func (c Config) GetAttribute(ctx context.Context, path *tftypes.AttributePath) ( tfValue, err := c.terraformValueAtPath(path) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Configuration Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { - diags = append(diags, attrTypeWithValidate.Validate(ctx, tfValue)...) + diags.Append(attrTypeWithValidate.Validate(ctx, tfValue)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -65,12 +64,12 @@ func (c Config) GetAttribute(ctx context.Context, path *tftypes.AttributePath) ( attrValue, err := attrType.ValueFromTerraform(ctx, tfValue) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Configuration Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return attrValue, diags diff --git a/tfsdk/config_test.go b/tfsdk/config_test.go index f6605f564..8ed77fe8a 100644 --- a/tfsdk/config_test.go +++ b/tfsdk/config_test.go @@ -6,9 +6,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -22,7 +22,7 @@ func TestConfigGet(t *testing.T) { type testCase struct { config Config expected testConfigGetData - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -69,7 +69,7 @@ func TestConfigGet(t *testing.T) { expected: testConfigGetData{ Name: types.String{Value: ""}, }, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { config: Config{ @@ -92,7 +92,7 @@ func TestConfigGet(t *testing.T) { expected: testConfigGetData{ Name: types.String{Value: "namevalue"}, }, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestWarningDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic}, }, } @@ -122,7 +122,7 @@ func TestConfigGetAttribute(t *testing.T) { type testCase struct { config Config expected attr.Value - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -165,7 +165,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, expected: nil, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { config: Config{ @@ -186,7 +186,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, expected: types.String{Value: "namevalue"}, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestWarningDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic}, }, } diff --git a/tfsdk/data_source.go b/tfsdk/data_source.go index d66cf2aca..2b99a826c 100644 --- a/tfsdk/data_source.go +++ b/tfsdk/data_source.go @@ -3,7 +3,7 @@ package tfsdk import ( "context" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" ) // A DataSourceType is a type of data source. For each type of data source this @@ -11,10 +11,10 @@ import ( // return an instance of it in the map returned by Provider.GetDataSources. type DataSourceType interface { // GetSchema returns the schema for this data source. - GetSchema(context.Context) (Schema, []*tfprotov6.Diagnostic) + GetSchema(context.Context) (Schema, diag.Diagnostics) // NewDataSource instantiates a new DataSource of this DataSourceType. - NewDataSource(context.Context, Provider) (DataSource, []*tfprotov6.Diagnostic) + NewDataSource(context.Context, Provider) (DataSource, diag.Diagnostics) } // DataSource represents a data source instance. This is the core interface that diff --git a/tfsdk/plan.go b/tfsdk/plan.go index ff01d6ecb..38c0a0795 100644 --- a/tfsdk/plan.go +++ b/tfsdk/plan.go @@ -5,9 +5,8 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/reflect" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -18,25 +17,25 @@ type Plan struct { } // Get populates the struct passed as `target` with the entire plan. -func (p Plan) Get(ctx context.Context, target interface{}) []*tfprotov6.Diagnostic { +func (p Plan) Get(ctx context.Context, target interface{}) diag.Diagnostics { return reflect.Into(ctx, p.Schema.AttributeType(), p.Raw, target, reflect.Options{}) } // GetAttribute retrieves the attribute found at `path` and returns it as an // attr.Value. Consumers should assert the type of the returned value with the // desired attr.Type. -func (p Plan) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func (p Plan) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics attrType, err := p.Schema.AttributeTypeAtPath(path) if err != nil { err = fmt.Errorf("error getting attribute type in schema: %w", err) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Plan Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } // if the whole plan is nil, the value of a valid attribute is also nil @@ -46,18 +45,18 @@ func (p Plan) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (at tfValue, err := p.terraformValueAtPath(path) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Plan Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { - diags = append(diags, attrTypeWithValidate.Validate(ctx, tfValue)...) + diags.Append(attrTypeWithValidate.Validate(ctx, tfValue)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -65,12 +64,12 @@ func (p Plan) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (at attrValue, err := attrType.ValueFromTerraform(ctx, tfValue) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Plan Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return attrValue, diags @@ -79,20 +78,20 @@ func (p Plan) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (at // Set populates the entire plan using the supplied Go value. The value `val` // should be a struct whose values have one of the attr.Value types. Each field // must be tagged with the corresponding schema field. -func (p *Plan) Set(ctx context.Context, val interface{}) []*tfprotov6.Diagnostic { +func (p *Plan) Set(ctx context.Context, val interface{}) diag.Diagnostics { newPlanAttrValue, diags := reflect.OutOf(ctx, p.Schema.AttributeType(), val) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return diags } newPlanVal, err := newPlanAttrValue.ToTerraformValue(ctx) if err != nil { err = fmt.Errorf("error running ToTerraformValue on plan: %w", err) - return append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Plan Write Error", - Detail: "An unexpected error was encountered trying to write the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) + diags.AddError( + "Plan Write Error", + "An unexpected error was encountered trying to write the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } newPlan := tftypes.NewValue(p.Schema.AttributeType().TerraformType(ctx), newPlanVal) @@ -102,36 +101,36 @@ func (p *Plan) Set(ctx context.Context, val interface{}) []*tfprotov6.Diagnostic } // SetAttribute sets the attribute at `path` using the supplied Go value. -func (p *Plan) SetAttribute(ctx context.Context, path *tftypes.AttributePath, val interface{}) []*tfprotov6.Diagnostic { - var diags []*tfprotov6.Diagnostic +func (p *Plan) SetAttribute(ctx context.Context, path *tftypes.AttributePath, val interface{}) diag.Diagnostics { + var diags diag.Diagnostics attrType, err := p.Schema.AttributeTypeAtPath(path) if err != nil { err = fmt.Errorf("error getting attribute type in schema: %w", err) - return append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Plan Write Error", - Detail: "An unexpected error was encountered trying to write an attribute to the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Plan Write Error", + "An unexpected error was encountered trying to write an attribute to the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } newVal, newValDiags := reflect.OutOf(ctx, attrType, val) - diags = append(diags, newValDiags...) + diags.Append(newValDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return diags } newTfVal, err := newVal.ToTerraformValue(ctx) if err != nil { err = fmt.Errorf("error running ToTerraformValue on new plan value: %w", err) - return append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Plan Write Error", - Detail: "An unexpected error was encountered trying to write an attribute to the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Plan Write Error", + "An unexpected error was encountered trying to write an attribute to the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } transformFunc := func(p *tftypes.AttributePath, v tftypes.Value) (tftypes.Value, error) { @@ -139,9 +138,9 @@ func (p *Plan) SetAttribute(ctx context.Context, path *tftypes.AttributePath, va tfVal := tftypes.NewValue(attrType.TerraformType(ctx), newTfVal) if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { - diags = append(diags, attrTypeWithValidate.Validate(ctx, tfVal)...) + diags.Append(attrTypeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return v, nil } } @@ -153,12 +152,12 @@ func (p *Plan) SetAttribute(ctx context.Context, path *tftypes.AttributePath, va p.Raw, err = tftypes.Transform(p.Raw, transformFunc) if err != nil { - return append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Plan Write Error", - Detail: "An unexpected error was encountered trying to write an attribute to the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "Plan Write Error", + "An unexpected error was encountered trying to write an attribute to the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } return diags diff --git a/tfsdk/plan_test.go b/tfsdk/plan_test.go index 2977a813e..d08a555a7 100644 --- a/tfsdk/plan_test.go +++ b/tfsdk/plan_test.go @@ -6,9 +6,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -22,7 +22,7 @@ func TestPlanGet(t *testing.T) { type testCase struct { plan Plan expected testPlanGetData - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -69,7 +69,7 @@ func TestPlanGet(t *testing.T) { expected: testPlanGetData{ Name: types.String{Value: ""}, }, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { plan: Plan{ @@ -92,7 +92,7 @@ func TestPlanGet(t *testing.T) { expected: testPlanGetData{ Name: types.String{Value: "namevalue"}, }, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestWarningDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic}, }, } @@ -122,7 +122,7 @@ func TestPlanGetAttribute(t *testing.T) { type testCase struct { plan Plan expected attr.Value - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -165,7 +165,7 @@ func TestPlanGetAttribute(t *testing.T) { }, }, expected: nil, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { plan: Plan{ @@ -186,7 +186,7 @@ func TestPlanGetAttribute(t *testing.T) { }, }, expected: types.String{Value: "namevalue"}, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestWarningDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic}, }, } @@ -215,7 +215,7 @@ func TestPlanSet(t *testing.T) { plan Plan val interface{} expected tftypes.Value - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -293,7 +293,7 @@ func TestPlanSet(t *testing.T) { Name: "newvalue", }, expected: tftypes.Value{}, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { plan: Plan{ @@ -319,7 +319,7 @@ func TestPlanSet(t *testing.T) { }, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "newvalue"), }), - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestWarningDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic}, }, } @@ -349,7 +349,7 @@ func TestPlanSetAttribute(t *testing.T) { path *tftypes.AttributePath val interface{} expected tftypes.Value - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -408,7 +408,7 @@ func TestPlanSetAttribute(t *testing.T) { }, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "originalname"), }), - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { plan: Plan{ @@ -437,10 +437,7 @@ func TestPlanSetAttribute(t *testing.T) { }, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "newname"), }), - expectedDiags: []*tfprotov6.Diagnostic{ - testtypes.TestWarningDiagnostic, - // TODO: Consider duplicate diagnostic consolidation functionality with diagnostic abstraction - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/24 + expectedDiags: diag.Diagnostics{ testtypes.TestWarningDiagnostic, }, }, diff --git a/tfsdk/provider.go b/tfsdk/provider.go index 976166307..117fc0fc0 100644 --- a/tfsdk/provider.go +++ b/tfsdk/provider.go @@ -3,14 +3,14 @@ package tfsdk import ( "context" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" ) // Provider is the core interface that all Terraform providers must implement. type Provider interface { // GetSchema returns the schema for this provider's configuration. If // this provider has no configuration, return an empty schema.Schema. - GetSchema(context.Context) (Schema, []*tfprotov6.Diagnostic) + GetSchema(context.Context) (Schema, diag.Diagnostics) // Configure is called at the beginning of the provider lifecycle, when // Terraform sends to the provider the values the user specified in the @@ -23,11 +23,11 @@ type Provider interface { // GetResources returns a map of the resource types this provider // supports. - GetResources(context.Context) (map[string]ResourceType, []*tfprotov6.Diagnostic) + GetResources(context.Context) (map[string]ResourceType, diag.Diagnostics) // GetDataSources returns a map of the data source types this provider // supports. - GetDataSources(context.Context) (map[string]DataSourceType, []*tfprotov6.Diagnostic) + GetDataSources(context.Context) (map[string]DataSourceType, diag.Diagnostics) } // ProviderWithProviderMeta is a provider with a provider meta schema. @@ -37,5 +37,5 @@ type Provider interface { type ProviderWithProviderMeta interface { Provider // GetMetaSchema returns the provider meta schema. - GetMetaSchema(context.Context) (Schema, []*tfprotov6.Diagnostic) + GetMetaSchema(context.Context) (Schema, diag.Diagnostics) } diff --git a/tfsdk/resource.go b/tfsdk/resource.go index eb37f5087..00bcb7c14 100644 --- a/tfsdk/resource.go +++ b/tfsdk/resource.go @@ -3,7 +3,7 @@ package tfsdk import ( "context" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" ) // A ResourceType is a type of resource. For each type of resource this provider @@ -11,10 +11,10 @@ import ( // instance of it in the map returned by Provider.GeResources. type ResourceType interface { // GetSchema returns the schema for this resource. - GetSchema(context.Context) (Schema, []*tfprotov6.Diagnostic) + GetSchema(context.Context) (Schema, diag.Diagnostics) // NewResource instantiates a new Resource of this ResourceType. - NewResource(context.Context, Provider) (Resource, []*tfprotov6.Diagnostic) + NewResource(context.Context, Provider) (Resource, diag.Diagnostics) } // Resource represents a resource instance. This is the core interface that all diff --git a/tfsdk/response.go b/tfsdk/response.go index b62e0373d..28b007345 100644 --- a/tfsdk/response.go +++ b/tfsdk/response.go @@ -1,7 +1,7 @@ package tfsdk import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -13,49 +13,31 @@ type ConfigureProviderResponse struct { // Diagnostics report errors or warnings related to configuring the // provider. An empty slice indicates success, with no warnings or // errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // AddWarning appends a warning diagnostic to the response. If the warning // concerns a particular attribute, AddAttributeWarning should be used instead. func (r *ConfigureProviderResponse) AddWarning(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddWarning(summary, detail) } // AddAttributeWarning appends a warning diagnostic to the response and labels // it with a specific attribute. func (r *ConfigureProviderResponse) AddAttributeWarning(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddAttributeWarning(attributePath, summary, detail) } // AddError appends an error diagnostic to the response. If the error concerns a // particular attribute, AddAttributeError should be used instead. func (r *ConfigureProviderResponse) AddError(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddError(summary, detail) } // AddAttributeError appends an error diagnostic to the response and labels it // with a specific attribute. func (r *ConfigureProviderResponse) AddAttributeError(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddAttributeError(attributePath, summary, detail) } // CreateResourceResponse represents a response to a CreateResourceRequest. An @@ -71,49 +53,31 @@ type CreateResourceResponse struct { // Diagnostics report errors or warnings related to creating the // resource. An empty slice indicates a successful operation with no // warnings or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // AddWarning appends a warning diagnostic to the response. If the warning // concerns a particular attribute, AddAttributeWarning should be used instead. func (r *CreateResourceResponse) AddWarning(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddWarning(summary, detail) } // AddAttributeWarning appends a warning diagnostic to the response and labels // it with a specific attribute. func (r *CreateResourceResponse) AddAttributeWarning(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddAttributeWarning(attributePath, summary, detail) } // AddError appends an error diagnostic to the response. If the error concerns a // particular attribute, AddAttributeError should be used instead. func (r *CreateResourceResponse) AddError(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddError(summary, detail) } // AddAttributeError appends an error diagnostic to the response and labels it // with a specific attribute. func (r *CreateResourceResponse) AddAttributeError(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddAttributeError(attributePath, summary, detail) } // ReadResourceResponse represents a response to a ReadResourceRequest. An @@ -129,49 +93,31 @@ type ReadResourceResponse struct { // Diagnostics report errors or warnings related to reading the // resource. An empty slice indicates a successful operation with no // warnings or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // AddWarning appends a warning diagnostic to the response. If the warning // concerns a particular attribute, AddAttributeWarning should be used instead. func (r *ReadResourceResponse) AddWarning(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddWarning(summary, detail) } // AddAttributeWarning appends a warning diagnostic to the response and labels // it with a specific attribute. func (r *ReadResourceResponse) AddAttributeWarning(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddAttributeWarning(attributePath, summary, detail) } // AddError appends an error diagnostic to the response. If the error concerns a // particular attribute, AddAttributeError should be used instead. func (r *ReadResourceResponse) AddError(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddError(summary, detail) } // AddAttributeError appends an error diagnostic to the response and labels it // with a specific attribute. func (r *ReadResourceResponse) AddAttributeError(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddAttributeError(attributePath, summary, detail) } // UpdateResourceResponse represents a response to an UpdateResourceRequest. An @@ -187,49 +133,31 @@ type UpdateResourceResponse struct { // Diagnostics report errors or warnings related to updating the // resource. An empty slice indicates a successful operation with no // warnings or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // AddWarning appends a warning diagnostic to the response. If the warning // concerns a particular attribute, AddAttributeWarning should be used instead. func (r *UpdateResourceResponse) AddWarning(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddWarning(summary, detail) } // AddAttributeWarning appends a warning diagnostic to the response and labels // it with a specific attribute. func (r *UpdateResourceResponse) AddAttributeWarning(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddAttributeWarning(attributePath, summary, detail) } // AddError appends an error diagnostic to the response. If the error concerns a // particular attribute, AddAttributeError should be used instead. func (r *UpdateResourceResponse) AddError(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddError(summary, detail) } // AddAttributeError appends an error diagnostic to the response and labels it // with a specific attribute. func (r *UpdateResourceResponse) AddAttributeError(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddAttributeError(attributePath, summary, detail) } // DeleteResourceResponse represents a response to a DeleteResourceRequest. An @@ -245,49 +173,31 @@ type DeleteResourceResponse struct { // Diagnostics report errors or warnings related to deleting the // resource. An empty slice indicates a successful operation with no // warnings or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // AddWarning appends a warning diagnostic to the response. If the warning // concerns a particular attribute, AddAttributeWarning should be used instead. func (r *DeleteResourceResponse) AddWarning(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddWarning(summary, detail) } // AddAttributeWarning appends a warning diagnostic to the response and labels // it with a specific attribute. func (r *DeleteResourceResponse) AddAttributeWarning(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddAttributeWarning(attributePath, summary, detail) } // AddError appends an error diagnostic to the response. If the error concerns a // particular attribute, AddAttributeError should be used instead. func (r *DeleteResourceResponse) AddError(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddError(summary, detail) } // AddAttributeError appends an error diagnostic to the response and labels it // with a specific attribute. func (r *DeleteResourceResponse) AddAttributeError(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddAttributeError(attributePath, summary, detail) } // ModifyResourcePlanResponse represents a response to a @@ -308,49 +218,31 @@ type ModifyResourcePlanResponse struct { // planned state of the requested resource. Returning an empty slice // indicates a successful plan modification with no warnings or errors // generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // AddWarning appends a warning diagnostic to the response. If the warning // concerns a particular attribute, AddAttributeWarning should be used instead. func (r *ModifyResourcePlanResponse) AddWarning(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddWarning(summary, detail) } // AddAttributeWarning appends a warning diagnostic to the response and labels // it with a specific attribute. func (r *ModifyResourcePlanResponse) AddAttributeWarning(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddAttributeWarning(attributePath, summary, detail) } // AddError appends an error diagnostic to the response. If the error concerns a // particular attribute, AddAttributeError should be used instead. func (r *ModifyResourcePlanResponse) AddError(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddError(summary, detail) } // AddAttributeError appends an error diagnostic to the response and labels it // with a specific attribute. func (r *ModifyResourcePlanResponse) AddAttributeError(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddAttributeError(attributePath, summary, detail) } // ReadDataSourceResponse represents a response to a ReadDataSourceRequest. An @@ -365,47 +257,29 @@ type ReadDataSourceResponse struct { // Diagnostics report errors or warnings related to reading the data // source. An empty slice indicates a successful operation with no // warnings or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // AddWarning appends a warning diagnostic to the response. If the warning // concerns a particular attribute, AddAttributeWarning should be used instead. func (r *ReadDataSourceResponse) AddWarning(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddWarning(summary, detail) } // AddAttributeWarning appends a warning diagnostic to the response and labels // it with a specific attribute. func (r *ReadDataSourceResponse) AddAttributeWarning(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityWarning, - }) + r.Diagnostics.AddAttributeWarning(attributePath, summary, detail) } // AddError appends an error diagnostic to the response. If the error concerns a // particular attribute, AddAttributeError should be used instead. func (r *ReadDataSourceResponse) AddError(summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddError(summary, detail) } // AddAttributeError appends an error diagnostic to the response and labels it // with a specific attribute. func (r *ReadDataSourceResponse) AddAttributeError(attributePath *tftypes.AttributePath, summary, detail string) { - r.Diagnostics = append(r.Diagnostics, &tfprotov6.Diagnostic{ - Attribute: attributePath, - Summary: summary, - Detail: detail, - Severity: tfprotov6.DiagnosticSeverityError, - }) + r.Diagnostics.AddAttributeError(attributePath, summary, detail) } diff --git a/tfsdk/response_validation.go b/tfsdk/response_validation.go index b4931a125..65123800c 100644 --- a/tfsdk/response_validation.go +++ b/tfsdk/response_validation.go @@ -1,7 +1,7 @@ package tfsdk import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" ) // ValidateDataSourceConfigResponse represents a response to a @@ -12,7 +12,7 @@ type ValidateDataSourceConfigResponse struct { // Diagnostics report errors or warnings related to validating the data // source configuration. An empty slice indicates success, with no warnings // or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // ValidateResourceConfigResponse represents a response to a @@ -23,7 +23,7 @@ type ValidateResourceConfigResponse struct { // Diagnostics report errors or warnings related to validating the resource // configuration. An empty slice indicates success, with no warnings or // errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } // ValidateProviderConfigResponse represents a response to a @@ -34,5 +34,5 @@ type ValidateProviderConfigResponse struct { // Diagnostics report errors or warnings related to validating the provider // configuration. An empty slice indicates success, with no warnings or // errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } diff --git a/tfsdk/schema.go b/tfsdk/schema.go index 193d05ed6..56a797855 100644 --- a/tfsdk/schema.go +++ b/tfsdk/schema.go @@ -7,7 +7,6 @@ import ( "sort" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -203,11 +202,10 @@ func (s Schema) validate(ctx context.Context, req ValidateSchemaRequest, resp *V } if s.DeprecationMessage != "" { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "Deprecated", - Detail: s.DeprecationMessage, - }) + resp.Diagnostics.AddWarning( + "Deprecated", + s.DeprecationMessage, + ) } } @@ -220,8 +218,8 @@ func modifyAttributesPlans(ctx context.Context, attrs map[string]Attribute, path for name, nestedAttr := range attrs { attrPath := path.WithAttributeName(name) attrPlan, diags := req.Plan.GetAttribute(ctx, attrPath) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(diags) { + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { return } nestedAttrReq := ModifyAttributePlanRequest{ @@ -242,8 +240,8 @@ func modifyAttributesPlans(ctx context.Context, attrs map[string]Attribute, path } setAttrDiags := resp.Plan.SetAttribute(ctx, attrPath, nestedAttrResp.AttributePlan) - resp.Diagnostics = append(resp.Diagnostics, setAttrDiags...) - if diagnostics.DiagsHasErrors(setAttrDiags) { + resp.Diagnostics.Append(setAttrDiags...) + if resp.Diagnostics.HasError() { return } resp.Diagnostics = nestedAttrResp.Diagnostics @@ -256,19 +254,18 @@ func modifyAttributesPlans(ctx context.Context, attrs map[string]Attribute, path if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", attrPlan, nm, attrPath) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Attribute Plan Modification Error", - Detail: "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n" + err.Error(), - Attribute: attrPath, - }) + resp.Diagnostics.AddAttributeError( + attrPath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) return } for idx := range l.Elems { modifyAttributesPlans(ctx, nestedAttr.Attributes.GetAttributes(), attrPath.WithElementKeyInt(int64(idx)), req, resp) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { + if resp.Diagnostics.HasError() { return } } @@ -280,19 +277,18 @@ func modifyAttributesPlans(ctx context.Context, attrs map[string]Attribute, path if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", attrPlan, nm, attrPath) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Attribute Plan Modification Error", - Detail: "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n" + err.Error(), - Attribute: attrPath, - }) + resp.Diagnostics.AddAttributeError( + attrPath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) return } for key := range m.Elems { modifyAttributesPlans(ctx, nestedAttr.Attributes.GetAttributes(), attrPath.WithElementKeyString(key), req, resp) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { + if resp.Diagnostics.HasError() { return } } @@ -301,12 +297,11 @@ func modifyAttributesPlans(ctx context.Context, attrs map[string]Attribute, path if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", attrPlan, nm, attrPath) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Attribute Validation Error", - Detail: "Attribute validation cannot walk schema. Report this to the provider developer:\n\n" + err.Error(), - Attribute: attrPath, - }) + resp.Diagnostics.AddAttributeError( + attrPath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) return } @@ -315,12 +310,11 @@ func modifyAttributesPlans(ctx context.Context, attrs map[string]Attribute, path } default: err := fmt.Errorf("unknown attribute nesting mode (%T: %v) at path: %s", nm, nm, attrPath) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Attribute Plan Modification Error", - Detail: "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n" + err.Error(), - Attribute: attrPath, - }) + resp.Diagnostics.AddAttributeError( + attrPath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) return } diff --git a/tfsdk/schema_plan_modification.go b/tfsdk/schema_plan_modification.go index d92f86c25..73c59f223 100644 --- a/tfsdk/schema_plan_modification.go +++ b/tfsdk/schema_plan_modification.go @@ -1,7 +1,7 @@ package tfsdk import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -35,5 +35,5 @@ type ModifySchemaPlanResponse struct { // Diagnostics report errors or warnings related to running all attribute // plan modifiers. Returning an empty slice indicates a successful // plan modification with no warnings or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } diff --git a/tfsdk/schema_test.go b/tfsdk/schema_test.go index cb877df49..981a29d25 100644 --- a/tfsdk/schema_test.go +++ b/tfsdk/schema_test.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -584,12 +585,11 @@ func TestSchemaValidate(t *testing.T) { }, }, resp: ValidateSchemaResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "Deprecated", - Detail: "Use something else instead.", - }, + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic( + "Deprecated", + "Use something else instead.", + ), }, }, }, @@ -626,9 +626,9 @@ func TestSchemaValidate(t *testing.T) { }, }, resp: ValidateSchemaResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - testWarningDiagnostic, - testWarningDiagnostic, + Diagnostics: diag.Diagnostics{ + testWarningDiagnostic1, + testWarningDiagnostic2, }, }, }, @@ -665,9 +665,9 @@ func TestSchemaValidate(t *testing.T) { }, }, resp: ValidateSchemaResponse{ - Diagnostics: []*tfprotov6.Diagnostic{ - testErrorDiagnostic, - testErrorDiagnostic, + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + testErrorDiagnostic2, }, }, }, diff --git a/tfsdk/schema_validation.go b/tfsdk/schema_validation.go index 8e6f1276b..261c17873 100644 --- a/tfsdk/schema_validation.go +++ b/tfsdk/schema_validation.go @@ -1,7 +1,7 @@ package tfsdk import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-framework/diag" ) // ValidateSchemaRequest repesents a request for validating a Schema. @@ -19,5 +19,5 @@ type ValidateSchemaRequest struct { type ValidateSchemaResponse struct { // Diagnostics report errors or warnings related to validating the schema. // An empty slice indicates success, with no warnings or errors generated. - Diagnostics []*tfprotov6.Diagnostic + Diagnostics diag.Diagnostics } diff --git a/tfsdk/serve.go b/tfsdk/serve.go index e237958f0..c1219473c 100644 --- a/tfsdk/serve.go +++ b/tfsdk/serve.go @@ -7,7 +7,7 @@ import ( "sort" "sync" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/proto6" "github.com/hashicorp/terraform-plugin-go/tfprotov6" tf6server "github.com/hashicorp/terraform-plugin-go/tfprotov6/server" @@ -63,58 +63,81 @@ func (s *server) cancelRegisteredContexts(ctx context.Context) { s.contextCancels = nil } -func (s *server) getResourceType(ctx context.Context, typ string) (ResourceType, []*tfprotov6.Diagnostic) { +func (s *server) getResourceType(ctx context.Context, typ string) (ResourceType, diag.Diagnostics) { resourceTypes, diags := s.p.GetResources(ctx) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } resourceType, ok := resourceTypes[typ] if !ok { - return nil, append(diags, &tfprotov6.Diagnostic{ - Summary: "Resource not found", - Detail: fmt.Sprintf("No resource named %q is configured on the provider", typ), - }) + diags.AddError( + "Resource not found", + fmt.Sprintf("No resource named %q is configured on the provider", typ), + ) + return nil, diags } - return resourceType, nil + return resourceType, diags } -func (s *server) getDataSourceType(ctx context.Context, typ string) (DataSourceType, []*tfprotov6.Diagnostic) { +func (s *server) getDataSourceType(ctx context.Context, typ string) (DataSourceType, diag.Diagnostics) { dataSourceTypes, diags := s.p.GetDataSources(ctx) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } dataSourceType, ok := dataSourceTypes[typ] if !ok { - return nil, append(diags, &tfprotov6.Diagnostic{ - Summary: "Data source not found", - Detail: fmt.Sprintf("No data source named %q is configured on the provider", typ), - }) + diags.AddError( + "Data source not found", + fmt.Sprintf("No data source named %q is configured on the provider", typ), + ) + return nil, diags + } + return dataSourceType, diags +} + +// getProviderSchemaResponse is a thin abstraction to allow native Diagnostics usage +type getProviderSchemaResponse struct { + Provider *tfprotov6.Schema + ProviderMeta *tfprotov6.Schema + ResourceSchemas map[string]*tfprotov6.Schema + DataSourceSchemas map[string]*tfprotov6.Schema + Diagnostics diag.Diagnostics +} + +func (r getProviderSchemaResponse) toTfprotov6() *tfprotov6.GetProviderSchemaResponse { + return &tfprotov6.GetProviderSchemaResponse{ + Provider: r.Provider, + ProviderMeta: r.ProviderMeta, + ResourceSchemas: r.ResourceSchemas, + DataSourceSchemas: r.DataSourceSchemas, + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), } - return dataSourceType, nil } func (s *server) GetProviderSchema(ctx context.Context, _ *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) { ctx = s.registerContext(ctx) + resp := new(getProviderSchemaResponse) + + s.getProviderSchema(ctx, resp) - resp := new(tfprotov6.GetProviderSchemaResponse) + return resp.toTfprotov6(), nil +} +func (s *server) getProviderSchema(ctx context.Context, resp *getProviderSchemaResponse) { // get the provider schema providerSchema, diags := s.p.GetSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if diags.HasError() { + return } // convert the provider schema to a *tfprotov6.Schema provider6Schema, err := providerSchema.tfprotov6Schema(ctx) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting provider schema", - Detail: "The provider schema couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting provider schema", + "The provider schema couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } // don't set the schema on the response yet, we want it to be able to @@ -126,78 +149,67 @@ func (s *server) GetProviderSchema(ctx context.Context, _ *tfprotov6.GetProvider var providerMeta6Schema *tfprotov6.Schema if pm, ok := s.p.(ProviderWithProviderMeta); ok { providerMetaSchema, diags := pm.GetMetaSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } + pm6Schema, err := providerMetaSchema.tfprotov6Schema(ctx) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting provider_meta schema", - Detail: "The provider_meta schema couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting provider_meta schema", + "The provider_meta schema couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } providerMeta6Schema = pm6Schema } // get our resource schemas resourceSchemas, diags := s.p.GetResources(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } resource6Schemas := map[string]*tfprotov6.Schema{} for k, v := range resourceSchemas { schema, diags := v.GetSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } schema6, err := schema.tfprotov6Schema(ctx) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting resource schema", - Detail: "The schema for the resource \"" + k + "\" couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting resource schema", + "The schema for the resource \""+k+"\" couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } resource6Schemas[k] = schema6 } // get our data source schemas dataSourceSchemas, diags := s.p.GetDataSources(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } dataSource6Schemas := map[string]*tfprotov6.Schema{} for k, v := range dataSourceSchemas { schema, diags := v.GetSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } schema6, err := schema.tfprotov6Schema(ctx) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting data sourceschema", - Detail: "The schema for the data source \"" + k + "\" couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting data sourceschema", + "The schema for the data source \""+k+"\" couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } dataSource6Schemas[k] = schema6 } @@ -208,12 +220,24 @@ func (s *server) GetProviderSchema(ctx context.Context, _ *tfprotov6.GetProvider resp.ProviderMeta = providerMeta6Schema resp.ResourceSchemas = resource6Schemas resp.DataSourceSchemas = dataSource6Schemas - return resp, nil +} + +// validateProviderConfigResponse is a thin abstraction to allow native Diagnostics usage +type validateProviderConfigResponse struct { + PreparedConfig *tfprotov6.DynamicValue + Diagnostics diag.Diagnostics +} + +func (r validateProviderConfigResponse) toTfprotov6() *tfprotov6.ValidateProviderConfigResponse { + return &tfprotov6.ValidateProviderConfigResponse{ + PreparedConfig: r.PreparedConfig, + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), + } } func (s *server) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { ctx = s.registerContext(ctx) - resp := &tfprotov6.ValidateProviderConfigResponse{ + resp := &validateProviderConfigResponse{ // This RPC allows a modified configuration to be returned. This was // previously used to allow a "required" provider attribute (as defined // by a schema) to still be "optional" with a default value, typically @@ -225,23 +249,28 @@ func (s *server) ValidateProviderConfig(ctx context.Context, req *tfprotov6.Vali PreparedConfig: req.Config, } + s.validateProviderConfig(ctx, req, resp) + + return resp.toTfprotov6(), nil +} + +func (s *server) validateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest, resp *validateProviderConfigResponse) { schema, diags := s.p.GetSchema(ctx) - resp.Diagnostics = append(resp.Diagnostics, diags...) + resp.Diagnostics.Append(diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + if resp.Diagnostics.HasError() { + return } config, err := req.Config.Unmarshal(schema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing config", - Detail: "The provider had a problem parsing the config. Report this to the provider developer:\n\n" + err.Error(), - }) + resp.Diagnostics.AddError( + "Error parsing config", + "The provider had a problem parsing the config. Report this to the provider developer:\n\n"+err.Error(), + ) - return resp, nil + return } vpcReq := ValidateProviderConfigRequest{ @@ -287,28 +316,42 @@ func (s *server) ValidateProviderConfig(ctx context.Context, req *tfprotov6.Vali resp.Diagnostics = validateSchemaResp.Diagnostics - return resp, nil + return +} + +// configureProviderResponse is a thin abstraction to allow native Diagnostics usage +type configureProviderResponse struct { + Diagnostics diag.Diagnostics +} + +func (r configureProviderResponse) toTfprotov6() *tfprotov6.ConfigureProviderResponse { + return &tfprotov6.ConfigureProviderResponse{ + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), + } } func (s *server) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { ctx = s.registerContext(ctx) + resp := &configureProviderResponse{} + + s.configureProvider(ctx, req, resp) + + return resp.toTfprotov6(), nil +} - resp := &tfprotov6.ConfigureProviderResponse{} +func (s *server) configureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest, resp *configureProviderResponse) { schema, diags := s.p.GetSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } config, err := req.Config.Unmarshal(schema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing config", - Detail: "The provider had a problem parsing the config. Report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing config", + "The provider had a problem parsing the config. Report this to the provider developer:\n\n"+err.Error(), + ) + return } r := ConfigureProviderRequest{ TerraformVersion: req.TerraformVersion, @@ -319,8 +362,7 @@ func (s *server) ConfigureProvider(ctx context.Context, req *tfprotov6.Configure } res := &ConfigureProviderResponse{} s.p.Configure(ctx, r, res) - resp.Diagnostics = append(resp.Diagnostics, res.Diagnostics...) - return resp, nil + resp.Diagnostics.Append(res.Diagnostics...) } func (s *server) StopProvider(ctx context.Context, _ *tfprotov6.StopProviderRequest) (*tfprotov6.StopProviderResponse, error) { @@ -329,47 +371,63 @@ func (s *server) StopProvider(ctx context.Context, _ *tfprotov6.StopProviderRequ return &tfprotov6.StopProviderResponse{}, nil } +// validateResourceConfigResponse is a thin abstraction to allow native Diagnostics usage +type validateResourceConfigResponse struct { + Diagnostics diag.Diagnostics +} + +func (r validateResourceConfigResponse) toTfprotov6() *tfprotov6.ValidateResourceConfigResponse { + return &tfprotov6.ValidateResourceConfigResponse{ + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), + } +} + func (s *server) ValidateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) { ctx = s.registerContext(ctx) - resp := &tfprotov6.ValidateResourceConfigResponse{} + resp := &validateResourceConfigResponse{} + s.validateResourceConfig(ctx, req, resp) + + return resp.toTfprotov6(), nil +} + +func (s *server) validateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest, resp *validateResourceConfigResponse) { // Get the type of resource, so we can get its schema and create an // instance resourceType, diags := s.getResourceType(ctx, req.TypeName) - resp.Diagnostics = append(resp.Diagnostics, diags...) + resp.Diagnostics.Append(diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + if resp.Diagnostics.HasError() { + return } // Get the schema from the resource type, so we can embed it in the // config resourceSchema, diags := resourceType.GetSchema(ctx) - resp.Diagnostics = append(resp.Diagnostics, diags...) + resp.Diagnostics.Append(diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + if resp.Diagnostics.HasError() { + return } // Create the resource instance, so we can call its methods and handle // the request resource, diags := resourceType.NewResource(ctx, s.p) - resp.Diagnostics = append(resp.Diagnostics, diags...) + resp.Diagnostics.Append(diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + if resp.Diagnostics.HasError() { + return } config, err := req.Config.Unmarshal(resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing config", - Detail: "The provider had a problem parsing the config. Report this to the provider developer:\n\n" + err.Error(), - }) + resp.Diagnostics.AddError( + "Error parsing config", + "The provider had a problem parsing the config. Report this to the provider developer:\n\n"+err.Error(), + ) - return resp, nil + return } vrcReq := ValidateResourceConfigRequest{ @@ -414,8 +472,6 @@ func (s *server) ValidateResourceConfig(ctx context.Context, req *tfprotov6.Vali resourceSchema.validate(ctx, validateSchemaReq, &validateSchemaResp) resp.Diagnostics = validateSchemaResp.Diagnostics - - return resp, nil } func (s *server) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { @@ -430,33 +486,53 @@ func (s *server) UpgradeResourceState(ctx context.Context, req *tfprotov6.Upgrad }, nil } +// readResourceResponse is a thin abstraction to allow native Diagnostics usage +type readResourceResponse struct { + NewState *tfprotov6.DynamicValue + Diagnostics diag.Diagnostics + Private []byte +} + +func (r readResourceResponse) toTfprotov6() *tfprotov6.ReadResourceResponse { + return &tfprotov6.ReadResourceResponse{ + NewState: r.NewState, + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), + Private: r.Private, + } +} + func (s *server) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) { ctx = s.registerContext(ctx) - resp := &tfprotov6.ReadResourceResponse{} + resp := &readResourceResponse{} + + s.readResource(ctx, req, resp) + + return resp.toTfprotov6(), nil +} +func (s *server) readResource(ctx context.Context, req *tfprotov6.ReadResourceRequest, resp *readResourceResponse) { resourceType, diags := s.getResourceType(ctx, req.TypeName) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } resourceSchema, diags := resourceType.GetSchema(ctx) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } resource, diags := resourceType.NewResource(ctx, s.p) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } state, err := req.CurrentState.Unmarshal(resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing current state", - Detail: "There was an error parsing the current state. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing current state", + "There was an error parsing the current state. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } readReq := ReadResourceRequest{ State: State{ @@ -466,11 +542,9 @@ func (s *server) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRe } if pm, ok := s.p.(ProviderWithProviderMeta); ok { pmSchema, diags := pm.GetMetaSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } readReq.ProviderMeta = Config{ Schema: pmSchema, @@ -480,12 +554,11 @@ func (s *server) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRe if req.ProviderMeta != nil { pmValue, err := req.ProviderMeta.Unmarshal(pmSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing provider_meta", - Detail: "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing provider_meta", + "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } readReq.ProviderMeta.Raw = pmValue } @@ -504,15 +577,13 @@ func (s *server) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRe newState, err := tfprotov6.NewDynamicValue(resourceSchema.TerraformType(ctx), readResp.State.Raw) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting read response", - Detail: "An unexpected error was encountered when converting the read response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting read response", + "An unexpected error was encountered when converting the read response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n"+err.Error(), + ) + return } resp.NewState = &newState - return resp, nil } func markComputedNilsAsUnknown(ctx context.Context, resourceSchema Schema) func(*tftypes.AttributePath, tftypes.Value) (tftypes.Value, error) { @@ -535,69 +606,89 @@ func markComputedNilsAsUnknown(ctx context.Context, resourceSchema Schema) func( } } +// planResourceChangeResponse is a thin abstraction to allow native Diagnostics usage +type planResourceChangeResponse struct { + PlannedState *tfprotov6.DynamicValue + Diagnostics diag.Diagnostics + RequiresReplace []*tftypes.AttributePath + PlannedPrivate []byte +} + +func (r planResourceChangeResponse) toTfprotov6() *tfprotov6.PlanResourceChangeResponse { + return &tfprotov6.PlanResourceChangeResponse{ + PlannedState: r.PlannedState, + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), + RequiresReplace: r.RequiresReplace, + PlannedPrivate: r.PlannedPrivate, + } +} + func (s *server) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) { ctx = s.registerContext(ctx) - resp := &tfprotov6.PlanResourceChangeResponse{} + resp := &planResourceChangeResponse{} + + s.planResourceChange(ctx, req, resp) + return resp.toTfprotov6(), nil +} + +func (s *server) planResourceChange(ctx context.Context, req *tfprotov6.PlanResourceChangeRequest, resp *planResourceChangeResponse) { // get the type of resource, so we can get its schema and create an // instance resourceType, diags := s.getResourceType(ctx, req.TypeName) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } // get the schema from the resource type, so we can embed it in the // config and plan resourceSchema, diags := resourceType.GetSchema(ctx) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } config, err := req.Config.Unmarshal(resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing configuration", - Detail: "An unexpected error was encountered trying to parse the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing configuration", + "An unexpected error was encountered trying to parse the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } plan, err := req.ProposedNewState.Unmarshal(resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing plan", - Detail: "There was an unexpected error parsing the plan. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing plan", + "There was an unexpected error parsing the plan. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } state, err := req.PriorState.Unmarshal(resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing prior state", - Detail: "An unexpected error was encountered trying to parse the prior state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing prior state", + "An unexpected error was encountered trying to parse the prior state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } resp.PlannedState = req.ProposedNewState if plan.IsNull() || !plan.IsKnown() { // on null or unknown plans, just bail, we can't do anything - return resp, nil + return } // create the resource instance, so we can call its methods and handle // the request resource, diags := resourceType.NewResource(ctx, s.p) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } // first, execute any AttributePlanModifiers @@ -618,9 +709,9 @@ func (s *server) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanReso if pm, ok := s.p.(ProviderWithProviderMeta); ok { pmSchema, diags := pm.GetMetaSchema(ctx) if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } } modifySchemaPlanReq.ProviderMeta = Config{ @@ -631,12 +722,11 @@ func (s *server) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanReso if req.ProviderMeta != nil { pmValue, err := req.ProviderMeta.Unmarshal(pmSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing provider_meta", - Detail: "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing provider_meta", + "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } modifySchemaPlanReq.ProviderMeta.Raw = pmValue } @@ -654,8 +744,8 @@ func (s *server) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanReso resp.RequiresReplace = modifySchemaPlanResp.RequiresReplace plan = modifySchemaPlanResp.Plan.Raw resp.Diagnostics = modifySchemaPlanResp.Diagnostics - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + if resp.Diagnostics.HasError() { + return } // second, execute any ModifyPlan func @@ -677,11 +767,9 @@ func (s *server) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanReso } if pm, ok := s.p.(ProviderWithProviderMeta); ok { pmSchema, diags := pm.GetMetaSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } modifyPlanReq.ProviderMeta = Config{ Schema: pmSchema, @@ -691,12 +779,11 @@ func (s *server) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanReso if req.ProviderMeta != nil { pmValue, err := req.ProviderMeta.Unmarshal(pmSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing provider_meta", - Detail: "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing provider_meta", + "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } modifyPlanReq.ProviderMeta.Raw = pmValue } @@ -717,30 +804,41 @@ func (s *server) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanReso modifiedPlan, err := tftypes.Transform(plan, markComputedNilsAsUnknown(ctx, resourceSchema)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error modifying plan", - Detail: "There was an unexpected error updating the plan. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error modifying plan", + "There was an unexpected error updating the plan. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } plannedState, err := tfprotov6.NewDynamicValue(modifiedPlan.Type(), modifiedPlan) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting response", - Detail: "There was an unexpected error converting the state in the response to a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting response", + "There was an unexpected error converting the state in the response to a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } resp.PlannedState = &plannedState resp.RequiresReplace = append(resp.RequiresReplace, modifyPlanResp.RequiresReplace...) // ensure deterministic RequiresReplace by sorting and deduplicating resp.RequiresReplace = normaliseRequiresReplace(resp.RequiresReplace) +} - return resp, nil +// applyResourceChangeResponse is a thin abstraction to allow native Diagnostics usage +type applyResourceChangeResponse struct { + NewState *tfprotov6.DynamicValue + Private []byte + Diagnostics diag.Diagnostics +} + +func (r applyResourceChangeResponse) toTfprotov6() *tfprotov6.ApplyResourceChangeResponse { + return &tfprotov6.ApplyResourceChangeResponse{ + NewState: r.NewState, + Private: r.Private, + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), + } } // normaliseRequiresReplace sorts and deduplicates the slice of AttributePaths @@ -772,93 +870,93 @@ func normaliseRequiresReplace(rs []*tftypes.AttributePath) []*tftypes.AttributeP func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) { ctx = s.registerContext(ctx) - resp := &tfprotov6.ApplyResourceChangeResponse{ + resp := &applyResourceChangeResponse{ // default to the prior state, so the state won't change unless // we choose to change it NewState: req.PriorState, } + s.applyResourceChange(ctx, req, resp) + + return resp.toTfprotov6(), nil +} + +func (s *server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyResourceChangeRequest, resp *applyResourceChangeResponse) { // get the type of resource, so we can get its schema and create an // instance resourceType, diags := s.getResourceType(ctx, req.TypeName) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } // get the schema from the resource type, so we can embed it in the // config and plan resourceSchema, diags := resourceType.GetSchema(ctx) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } // create the resource instance, so we can call its methods and handle // the request resource, diags := resourceType.NewResource(ctx, s.p) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } config, err := req.Config.Unmarshal(resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing configuration", - Detail: "An unexpected error was encountered trying to parse the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing configuration", + "An unexpected error was encountered trying to parse the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } plan, err := req.PlannedState.Unmarshal(resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing plan", - Detail: "An unexpected error was encountered trying to parse the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing plan", + "An unexpected error was encountered trying to parse the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } priorState, err := req.PriorState.Unmarshal(resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing prior state", - Detail: "An unexpected error was encountered trying to parse the prior state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing prior state", + "An unexpected error was encountered trying to parse the prior state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } // figure out what kind of request we're serving create, err := proto6.IsCreate(ctx, req, resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error understanding request", - Detail: "An unexpected error was encountered trying to understand the type of request being made. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error understanding request", + "An unexpected error was encountered trying to understand the type of request being made. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } update, err := proto6.IsUpdate(ctx, req, resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error understanding request", - Detail: "An unexpected error was encountered trying to understand the type of request being made. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error understanding request", + "An unexpected error was encountered trying to understand the type of request being made. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } destroy, err := proto6.IsDestroy(ctx, req, resourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error understanding request", - Detail: "An unexpected error was encountered trying to understand the type of request being made. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error understanding request", + "An unexpected error was encountered trying to understand the type of request being made. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return } switch { @@ -875,11 +973,9 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe } if pm, ok := s.p.(ProviderWithProviderMeta); ok { pmSchema, diags := pm.GetMetaSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } createReq.ProviderMeta = Config{ Schema: pmSchema, @@ -889,12 +985,11 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe if req.ProviderMeta != nil { pmValue, err := req.ProviderMeta.Unmarshal(pmSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing provider_meta", - Detail: "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing provider_meta", + "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } createReq.ProviderMeta.Raw = pmValue } @@ -910,15 +1005,13 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe resp.Diagnostics = createResp.Diagnostics newState, err := tfprotov6.NewDynamicValue(resourceSchema.TerraformType(ctx), createResp.State.Raw) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting create response", - Detail: "An unexpected error was encountered when converting the create response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting create response", + "An unexpected error was encountered when converting the create response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n"+err.Error(), + ) + return } resp.NewState = &newState - return resp, nil case !create && update && !destroy: updateReq := UpdateResourceRequest{ Config: Config{ @@ -936,11 +1029,9 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe } if pm, ok := s.p.(ProviderWithProviderMeta); ok { pmSchema, diags := pm.GetMetaSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } updateReq.ProviderMeta = Config{ Schema: pmSchema, @@ -950,12 +1041,11 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe if req.ProviderMeta != nil { pmValue, err := req.ProviderMeta.Unmarshal(pmSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing provider_meta", - Detail: "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing provider_meta", + "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } updateReq.ProviderMeta.Raw = pmValue } @@ -971,15 +1061,13 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe resp.Diagnostics = updateResp.Diagnostics newState, err := tfprotov6.NewDynamicValue(resourceSchema.TerraformType(ctx), updateResp.State.Raw) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting update response", - Detail: "An unexpected error was encountered when converting the update response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting update response", + "An unexpected error was encountered when converting the update response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n"+err.Error(), + ) + return } resp.NewState = &newState - return resp, nil case !create && !update && destroy: destroyReq := DeleteResourceRequest{ State: State{ @@ -989,11 +1077,9 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe } if pm, ok := s.p.(ProviderWithProviderMeta); ok { pmSchema, diags := pm.GetMetaSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } destroyReq.ProviderMeta = Config{ Schema: pmSchema, @@ -1003,12 +1089,11 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe if req.ProviderMeta != nil { pmValue, err := req.ProviderMeta.Unmarshal(pmSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing provider_meta", - Detail: "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing provider_meta", + "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } destroyReq.ProviderMeta.Raw = pmValue } @@ -1024,22 +1109,18 @@ func (s *server) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe resp.Diagnostics = destroyResp.Diagnostics newState, err := tfprotov6.NewDynamicValue(resourceSchema.TerraformType(ctx), destroyResp.State.Raw) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting delete response", - Detail: "An unexpected error was encountered when converting the delete response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting delete response", + "An unexpected error was encountered when converting the delete response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n"+err.Error(), + ) + return } resp.NewState = &newState - return resp, nil default: - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error understanding request", - Detail: fmt.Sprintf("An unexpected error was encountered trying to understand the type of request being made. This is always an error in the provider. Please report the following to the provider developer:\n\nRequest matched unexpected number of methods: (create: %v, update: %v, delete: %v)", create, update, destroy), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error understanding request", + fmt.Sprintf("An unexpected error was encountered trying to understand the type of request being made. This is always an error in the provider. Please report the following to the provider developer:\n\nRequest matched unexpected number of methods: (create: %v, update: %v, delete: %v)", create, update, destroy), + ) } } @@ -1051,47 +1132,64 @@ func (s *server) ImportResourceState(ctx context.Context, _ *tfprotov6.ImportRes return &tfprotov6.ImportResourceStateResponse{}, nil } +// validateDataResourceConfigResponse is a thin abstraction to allow native Diagnostics usage +type validateDataResourceConfigResponse struct { + Diagnostics diag.Diagnostics +} + +func (r validateDataResourceConfigResponse) toTfprotov6() *tfprotov6.ValidateDataResourceConfigResponse { + return &tfprotov6.ValidateDataResourceConfigResponse{ + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), + } +} + func (s *server) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { ctx = s.registerContext(ctx) - resp := &tfprotov6.ValidateDataResourceConfigResponse{} + resp := &validateDataResourceConfigResponse{} + + s.validateDataResourceConfig(ctx, req, resp) + + return resp.toTfprotov6(), nil +} + +func (s *server) validateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest, resp *validateDataResourceConfigResponse) { // Get the type of data source, so we can get its schema and create an // instance dataSourceType, diags := s.getDataSourceType(ctx, req.TypeName) - resp.Diagnostics = append(resp.Diagnostics, diags...) + resp.Diagnostics.Append(diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + if resp.Diagnostics.HasError() { + return } // Get the schema from the data source type, so we can embed it in the // config dataSourceSchema, diags := dataSourceType.GetSchema(ctx) - resp.Diagnostics = append(resp.Diagnostics, diags...) + resp.Diagnostics.Append(diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + if resp.Diagnostics.HasError() { + return } // Create the data source instance, so we can call its methods and handle // the request dataSource, diags := dataSourceType.NewDataSource(ctx, s.p) - resp.Diagnostics = append(resp.Diagnostics, diags...) + resp.Diagnostics.Append(diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + if resp.Diagnostics.HasError() { + return } config, err := req.Config.Unmarshal(dataSourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing config", - Detail: "The provider had a problem parsing the config. Report this to the provider developer:\n\n" + err.Error(), - }) + resp.Diagnostics.AddError( + "Error parsing config", + "The provider had a problem parsing the config. Report this to the provider developer:\n\n"+err.Error(), + ) - return resp, nil + return } vrcReq := ValidateDataSourceConfigRequest{ @@ -1136,37 +1234,53 @@ func (s *server) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6. dataSourceSchema.validate(ctx, validateSchemaReq, &validateSchemaResp) resp.Diagnostics = validateSchemaResp.Diagnostics +} - return resp, nil +// readDataSourceResponse is a thin abstraction to allow native Diagnostics usage +type readDataSourceResponse struct { + State *tfprotov6.DynamicValue + Diagnostics diag.Diagnostics +} + +func (r readDataSourceResponse) toTfprotov6() *tfprotov6.ReadDataSourceResponse { + return &tfprotov6.ReadDataSourceResponse{ + State: r.State, + Diagnostics: r.Diagnostics.ToTfprotov6Diagnostics(), + } } func (s *server) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSourceRequest) (*tfprotov6.ReadDataSourceResponse, error) { ctx = s.registerContext(ctx) - resp := &tfprotov6.ReadDataSourceResponse{} + resp := &readDataSourceResponse{} + s.readDataSource(ctx, req, resp) + + return resp.toTfprotov6(), nil +} + +func (s *server) readDataSource(ctx context.Context, req *tfprotov6.ReadDataSourceRequest, resp *readDataSourceResponse) { dataSourceType, diags := s.getDataSourceType(ctx, req.TypeName) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } dataSourceSchema, diags := dataSourceType.GetSchema(ctx) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } dataSource, diags := dataSourceType.NewDataSource(ctx, s.p) - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } config, err := req.Config.Unmarshal(dataSourceSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing current state", - Detail: "There was an error parsing the current state. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing current state", + "There was an error parsing the current state. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } readReq := ReadDataSourceRequest{ Config: Config{ @@ -1176,11 +1290,9 @@ func (s *server) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSour } if pm, ok := s.p.(ProviderWithProviderMeta); ok { pmSchema, diags := pm.GetMetaSchema(ctx) - if diags != nil { - resp.Diagnostics = append(resp.Diagnostics, diags...) - if diagnostics.DiagsHasErrors(resp.Diagnostics) { - return resp, nil - } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } readReq.ProviderMeta = Config{ Schema: pmSchema, @@ -1190,12 +1302,11 @@ func (s *server) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSour if req.ProviderMeta != nil { pmValue, err := req.ProviderMeta.Unmarshal(pmSchema.TerraformType(ctx)) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error parsing provider_meta", - Detail: "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error parsing provider_meta", + "There was an error parsing the provider_meta block. Please report this to the provider developer:\n\n"+err.Error(), + ) + return } readReq.ProviderMeta.Raw = pmValue } @@ -1217,13 +1328,11 @@ func (s *server) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSour state, err := tfprotov6.NewDynamicValue(dataSourceSchema.TerraformType(ctx), readResp.State.Raw) if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error converting read response", - Detail: "An unexpected error was encountered when converting the read response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n" + err.Error(), - }) - return resp, nil + resp.Diagnostics.AddError( + "Error converting read response", + "An unexpected error was encountered when converting the read response to a usable type. This is always a problem with the provider. Please give the following information to the provider developer:\n\n"+err.Error(), + ) + return } resp.State = &state - return resp, nil } diff --git a/tfsdk/serve_data_source_config_validators_test.go b/tfsdk/serve_data_source_config_validators_test.go index 0bf4017c2..767f214f5 100644 --- a/tfsdk/serve_data_source_config_validators_test.go +++ b/tfsdk/serve_data_source_config_validators_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,7 +12,7 @@ import ( type testServeDataSourceTypeConfigValidators struct{} -func (dt testServeDataSourceTypeConfigValidators) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (dt testServeDataSourceTypeConfigValidators) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "string": { @@ -22,7 +23,7 @@ func (dt testServeDataSourceTypeConfigValidators) GetSchema(_ context.Context) ( }, nil } -func (dt testServeDataSourceTypeConfigValidators) NewDataSource(_ context.Context, p Provider) (DataSource, []*tfprotov6.Diagnostic) { +func (dt testServeDataSourceTypeConfigValidators) NewDataSource(_ context.Context, p Provider) (DataSource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) diff --git a/tfsdk/serve_data_source_one_test.go b/tfsdk/serve_data_source_one_test.go index 7000c8d8e..eb4ef901c 100644 --- a/tfsdk/serve_data_source_one_test.go +++ b/tfsdk/serve_data_source_one_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,7 +12,7 @@ import ( type testServeDataSourceTypeOne struct{} -func (dt testServeDataSourceTypeOne) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (dt testServeDataSourceTypeOne) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "current_time": { @@ -30,7 +31,7 @@ func (dt testServeDataSourceTypeOne) GetSchema(_ context.Context) (Schema, []*tf }, nil } -func (dt testServeDataSourceTypeOne) NewDataSource(_ context.Context, p Provider) (DataSource, []*tfprotov6.Diagnostic) { +func (dt testServeDataSourceTypeOne) NewDataSource(_ context.Context, p Provider) (DataSource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) diff --git a/tfsdk/serve_data_source_two_test.go b/tfsdk/serve_data_source_two_test.go index a3cc8836c..86507bb32 100644 --- a/tfsdk/serve_data_source_two_test.go +++ b/tfsdk/serve_data_source_two_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,7 +12,7 @@ import ( type testServeDataSourceTypeTwo struct{} -func (dt testServeDataSourceTypeTwo) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (dt testServeDataSourceTypeTwo) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "family": { @@ -32,7 +33,7 @@ func (dt testServeDataSourceTypeTwo) GetSchema(_ context.Context) (Schema, []*tf }, nil } -func (dt testServeDataSourceTypeTwo) NewDataSource(_ context.Context, p Provider) (DataSource, []*tfprotov6.Diagnostic) { +func (dt testServeDataSourceTypeTwo) NewDataSource(_ context.Context, p Provider) (DataSource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) diff --git a/tfsdk/serve_data_source_validate_config_test.go b/tfsdk/serve_data_source_validate_config_test.go index a6ca6bb61..244ce8e2a 100644 --- a/tfsdk/serve_data_source_validate_config_test.go +++ b/tfsdk/serve_data_source_validate_config_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,7 +12,7 @@ import ( type testServeDataSourceTypeValidateConfig struct{} -func (dt testServeDataSourceTypeValidateConfig) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (dt testServeDataSourceTypeValidateConfig) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "string": { @@ -22,7 +23,7 @@ func (dt testServeDataSourceTypeValidateConfig) GetSchema(_ context.Context) (Sc }, nil } -func (dt testServeDataSourceTypeValidateConfig) NewDataSource(_ context.Context, p Provider) (DataSource, []*tfprotov6.Diagnostic) { +func (dt testServeDataSourceTypeValidateConfig) NewDataSource(_ context.Context, p Provider) (DataSource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) diff --git a/tfsdk/serve_provider_config_validators_test.go b/tfsdk/serve_provider_config_validators_test.go index 41abb4038..d2f07cdb0 100644 --- a/tfsdk/serve_provider_config_validators_test.go +++ b/tfsdk/serve_provider_config_validators_test.go @@ -3,8 +3,8 @@ package tfsdk import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -12,7 +12,7 @@ type testServeProviderWithConfigValidators struct { *testServeProvider } -func (t *testServeProviderWithConfigValidators) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (t *testServeProviderWithConfigValidators) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "string": { diff --git a/tfsdk/serve_provider_test.go b/tfsdk/serve_provider_test.go index 0d57a798a..e18a506bc 100644 --- a/tfsdk/serve_provider_test.go +++ b/tfsdk/serve_provider_test.go @@ -4,6 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -71,7 +72,7 @@ type testServeProvider struct { readDataSourceCalledDataSourceType string } -func (t *testServeProvider) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Version: 1, DeprecationMessage: "Deprecated in favor of other_resource", @@ -427,7 +428,7 @@ var testServeProviderProviderType = tftypes.Object{ }, } -func (t *testServeProvider) GetResources(_ context.Context) (map[string]ResourceType, []*tfprotov6.Diagnostic) { +func (t *testServeProvider) GetResources(_ context.Context) (map[string]ResourceType, diag.Diagnostics) { return map[string]ResourceType{ "test_one": testServeResourceTypeOne{}, "test_two": testServeResourceTypeTwo{}, @@ -437,7 +438,7 @@ func (t *testServeProvider) GetResources(_ context.Context) (map[string]Resource }, nil } -func (t *testServeProvider) GetDataSources(_ context.Context) (map[string]DataSourceType, []*tfprotov6.Diagnostic) { +func (t *testServeProvider) GetDataSources(_ context.Context) (map[string]DataSourceType, diag.Diagnostics) { return map[string]DataSourceType{ "test_one": testServeDataSourceTypeOne{}, "test_two": testServeDataSourceTypeTwo{}, @@ -456,7 +457,7 @@ type testServeProviderWithMetaSchema struct { *testServeProvider } -func (t *testServeProviderWithMetaSchema) GetMetaSchema(context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (t *testServeProviderWithMetaSchema) GetMetaSchema(context.Context) (Schema, diag.Diagnostics) { return Schema{ Version: 2, Attributes: map[string]Attribute{ diff --git a/tfsdk/serve_provider_validate_config_test.go b/tfsdk/serve_provider_validate_config_test.go index ea3cee779..7f4f553ed 100644 --- a/tfsdk/serve_provider_validate_config_test.go +++ b/tfsdk/serve_provider_validate_config_test.go @@ -3,8 +3,8 @@ package tfsdk import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -12,7 +12,7 @@ type testServeProviderWithValidateConfig struct { *testServeProvider } -func (t *testServeProviderWithValidateConfig) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (t *testServeProviderWithValidateConfig) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "string": { diff --git a/tfsdk/serve_resource_attribute_plan_modifiers_test.go b/tfsdk/serve_resource_attribute_plan_modifiers_test.go index 39945be9e..17a7f8fa2 100644 --- a/tfsdk/serve_resource_attribute_plan_modifiers_test.go +++ b/tfsdk/serve_resource_attribute_plan_modifiers_test.go @@ -5,12 +5,13 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Version: 1, Attributes: map[string]Attribute{ @@ -88,7 +89,7 @@ func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Contex }, nil } -func (rt testServeResourceTypeAttributePlanModifiers) NewResource(_ context.Context, p Provider) (Resource, []*tfprotov6.Diagnostic) { +func (rt testServeResourceTypeAttributePlanModifiers) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -198,12 +199,9 @@ func (t testWarningDiagModifier) Modify(ctx context.Context, req ModifyAttribute } if attrVal.Value == "TESTDIAG" { - resp.Diagnostics = append(resp.Diagnostics, - &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "Warning diag", - Detail: "This is a warning", - }, + resp.Diagnostics.AddWarning( + "Warning diag", + "This is a warning", ) } } @@ -225,12 +223,9 @@ func (t testErrorDiagModifier) Modify(ctx context.Context, req ModifyAttributePl } if attrVal.Value == "TESTDIAG" { - resp.Diagnostics = append(resp.Diagnostics, - &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Error diag", - Detail: "This is an error", - }, + resp.Diagnostics.AddError( + "Error diag", + "This is an error", ) } } diff --git a/tfsdk/serve_resource_config_validators_test.go b/tfsdk/serve_resource_config_validators_test.go index d162509c7..1cec52d5e 100644 --- a/tfsdk/serve_resource_config_validators_test.go +++ b/tfsdk/serve_resource_config_validators_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,7 +12,7 @@ import ( type testServeResourceTypeConfigValidators struct{} -func (dt testServeResourceTypeConfigValidators) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (dt testServeResourceTypeConfigValidators) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "string": { @@ -22,7 +23,7 @@ func (dt testServeResourceTypeConfigValidators) GetSchema(_ context.Context) (Sc }, nil } -func (dt testServeResourceTypeConfigValidators) NewResource(_ context.Context, p Provider) (Resource, []*tfprotov6.Diagnostic) { +func (dt testServeResourceTypeConfigValidators) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) diff --git a/tfsdk/serve_resource_one_test.go b/tfsdk/serve_resource_one_test.go index 967a3c542..9348c16ae 100644 --- a/tfsdk/serve_resource_one_test.go +++ b/tfsdk/serve_resource_one_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,7 +12,7 @@ import ( type testServeResourceTypeOne struct{} -func (rt testServeResourceTypeOne) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (rt testServeResourceTypeOne) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Version: 1, Attributes: map[string]Attribute{ @@ -31,7 +32,7 @@ func (rt testServeResourceTypeOne) GetSchema(_ context.Context) (Schema, []*tfpr }, nil } -func (rt testServeResourceTypeOne) NewResource(_ context.Context, p Provider) (Resource, []*tfprotov6.Diagnostic) { +func (rt testServeResourceTypeOne) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) diff --git a/tfsdk/serve_resource_two_test.go b/tfsdk/serve_resource_two_test.go index d6aae6995..fcc8909bb 100644 --- a/tfsdk/serve_resource_two_test.go +++ b/tfsdk/serve_resource_two_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,7 +12,7 @@ import ( type testServeResourceTypeTwo struct{} -func (rt testServeResourceTypeTwo) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (rt testServeResourceTypeTwo) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "id": { @@ -41,7 +42,7 @@ func (rt testServeResourceTypeTwo) GetSchema(_ context.Context) (Schema, []*tfpr }, nil } -func (rt testServeResourceTypeTwo) NewResource(_ context.Context, p Provider) (Resource, []*tfprotov6.Diagnostic) { +func (rt testServeResourceTypeTwo) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) diff --git a/tfsdk/serve_resource_validate_config_test.go b/tfsdk/serve_resource_validate_config_test.go index f3df85c55..6547ba56e 100644 --- a/tfsdk/serve_resource_validate_config_test.go +++ b/tfsdk/serve_resource_validate_config_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,7 +12,7 @@ import ( type testServeResourceTypeValidateConfig struct{} -func (dt testServeResourceTypeValidateConfig) GetSchema(_ context.Context) (Schema, []*tfprotov6.Diagnostic) { +func (dt testServeResourceTypeValidateConfig) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { return Schema{ Attributes: map[string]Attribute{ "string": { @@ -22,7 +23,7 @@ func (dt testServeResourceTypeValidateConfig) GetSchema(_ context.Context) (Sche }, nil } -func (dt testServeResourceTypeValidateConfig) NewResource(_ context.Context, p Provider) (Resource, []*tfprotov6.Diagnostic) { +func (dt testServeResourceTypeValidateConfig) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) diff --git a/tfsdk/serve_test.go b/tfsdk/serve_test.go index 9020e1f1e..917ccf05d 100644 --- a/tfsdk/serve_test.go +++ b/tfsdk/serve_test.go @@ -9,7 +9,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -507,11 +506,17 @@ func TestServerValidateProviderConfig(t *testing.T) { provider: &testServeProviderWithConfigValidators{ &testServeProvider{ validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }) + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) + } else { + resp.Diagnostics.AddError( + "This is another error", + "Oops again.", + ) + } }, }, }, @@ -525,9 +530,9 @@ func TestServerValidateProviderConfig(t *testing.T) { }, // ConfigValidators includes multiple calls { - Summary: "This is an error", + Summary: "This is another error", Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", + Detail: "Oops again.", }, }, }, @@ -538,19 +543,27 @@ func TestServerValidateProviderConfig(t *testing.T) { provider: &testServeProviderWithConfigValidators{ &testServeProvider{ validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, []*tfprotov6.Diagnostic{ - { - Summary: "This is a warning", - Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", - Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), - }, - { - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }, - }...) + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is a warning", + "This is your final warning", + ) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) + } else { + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is another warning", + "This is really your final warning", + ) + resp.Diagnostics.AddError( + "This is another error", + "Oops again.", + ) + } }, }, }, @@ -570,15 +583,15 @@ func TestServerValidateProviderConfig(t *testing.T) { }, // ConfigValidators includes multiple calls { - Summary: "This is a warning", + Summary: "This is another warning", Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", + Detail: "This is really your final warning", Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), }, { - Summary: "This is an error", + Summary: "This is another error", Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", + Detail: "Oops again.", }, }, }, @@ -600,11 +613,10 @@ func TestServerValidateProviderConfig(t *testing.T) { provider: &testServeProviderWithValidateConfig{ &testServeProvider{ validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) }, }, }, @@ -625,19 +637,15 @@ func TestServerValidateProviderConfig(t *testing.T) { provider: &testServeProviderWithValidateConfig{ &testServeProvider{ validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, []*tfprotov6.Diagnostic{ - { - Summary: "This is a warning", - Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", - Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), - }, - { - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }, - }...) + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is a warning", + "This is your final warning", + ) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) }, }, }, @@ -981,11 +989,17 @@ func TestServerValidateResourceConfig(t *testing.T) { resourceType: testServeResourceTypeConfigValidatorsType, impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }) + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) + } else { + resp.Diagnostics.AddError( + "This is another error", + "Oops again.", + ) + } }, expectedDiags: []*tfprotov6.Diagnostic{ @@ -996,9 +1010,9 @@ func TestServerValidateResourceConfig(t *testing.T) { }, // ConfigValidators includes multiple calls { - Summary: "This is an error", + Summary: "This is another error", Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", + Detail: "Oops again.", }, }, }, @@ -1010,19 +1024,27 @@ func TestServerValidateResourceConfig(t *testing.T) { resourceType: testServeResourceTypeConfigValidatorsType, impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, []*tfprotov6.Diagnostic{ - { - Summary: "This is a warning", - Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", - Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), - }, - { - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }, - }...) + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is a warning", + "This is your final warning", + ) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) + } else { + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is another warning", + "This is really your final warning", + ) + resp.Diagnostics.AddError( + "This is another error", + "Oops again.", + ) + } }, expectedDiags: []*tfprotov6.Diagnostic{ @@ -1039,15 +1061,15 @@ func TestServerValidateResourceConfig(t *testing.T) { }, // ConfigValidators includes multiple calls { - Summary: "This is a warning", + Summary: "This is another warning", Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", + Detail: "This is really your final warning", Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), }, { - Summary: "This is an error", + Summary: "This is another error", Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", + Detail: "Oops again.", }, }, }, @@ -1068,11 +1090,10 @@ func TestServerValidateResourceConfig(t *testing.T) { resourceType: testServeResourceTypeValidateConfigType, impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) }, expectedDiags: []*tfprotov6.Diagnostic{ @@ -1091,19 +1112,15 @@ func TestServerValidateResourceConfig(t *testing.T) { resourceType: testServeResourceTypeValidateConfigType, impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, []*tfprotov6.Diagnostic{ - { - Summary: "This is a warning", - Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", - Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), - }, - { - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }, - }...) + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is a warning", + "This is your final warning", + ) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) }, expectedDiags: []*tfprotov6.Diagnostic{ @@ -1372,19 +1389,15 @@ func TestServerReadResource(t *testing.T) { }), }), }) - resp.Diagnostics = []*tfprotov6.Diagnostic{ - { - Summary: "This is a warning", - Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", - Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), - }, - { - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }, - } + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is a warning", + "This is your final warning", + ) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) }, expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ @@ -2634,8 +2647,10 @@ func TestServerPlanResourceChange(t *testing.T) { if diff := cmp.Diff(got.Diagnostics, tc.expectedDiags); diff != "" { t.Errorf("Unexpected diff in diagnostics (+wanted, -got): %s", diff) } - if diagnostics.DiagsHasErrors(got.Diagnostics) { - return + for _, diagnostic := range got.Diagnostics { + if diagnostic != nil && diagnostic.Severity == tfprotov6.DiagnosticSeverityError { + return + } } gotPlannedState, err := got.PlannedState.Unmarshal(tc.resourceType) if err != nil { @@ -2744,12 +2759,11 @@ func TestServerApplyResourceChange(t *testing.T) { }), "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), }) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "This is a warning", - Detail: "I'm warning you", - Attribute: tftypes.NewAttributePath().WithAttributeName("favorite_colors").WithElementKeyInt(0), - }) + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("favorite_colors").WithElementKeyInt(0), + "This is a warning", + "I'm warning you", + ) }, expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), @@ -2856,12 +2870,11 @@ func TestServerApplyResourceChange(t *testing.T) { }), "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), }) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "I'm warning you...", - Detail: "This is a warning!", - Attribute: tftypes.NewAttributePath().WithAttributeName("name"), - }) + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("name"), + "I'm warning you...", + "This is a warning!", + ) }, expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), @@ -2920,12 +2933,11 @@ func TestServerApplyResourceChange(t *testing.T) { }), "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), }) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Oops!", - Detail: "This is an error! Don't update the state!", - Attribute: tftypes.NewAttributePath().WithAttributeName("name"), - }) + resp.Diagnostics.AddAttributeError( + tftypes.NewAttributePath().WithAttributeName("name"), + "Oops!", + "This is an error! Don't update the state!", + ) }, expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), @@ -2974,12 +2986,11 @@ func TestServerApplyResourceChange(t *testing.T) { resourceType: testServeResourceTypeOneType, destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, nil) - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "This is a warning", - Detail: "just a warning diagnostic, no behavior changes", - Attribute: tftypes.NewAttributePath().WithAttributeName("created_timestamp"), - }) + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("created_timestamp"), + "This is a warning", + "just a warning diagnostic, no behavior changes", + ) }, expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil), expectedDiags: []*tfprotov6.Diagnostic{ @@ -3003,11 +3014,10 @@ func TestServerApplyResourceChange(t *testing.T) { action: "delete", resourceType: testServeResourceTypeOneType, destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "This is an error", - Detail: "Something went wrong, keep the old state around", - }) + resp.Diagnostics.AddError( + "This is an error", + "Something went wrong, keep the old state around", + ) }, expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), @@ -3875,11 +3885,17 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSourceType: testServeDataSourceTypeConfigValidatorsType, impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }) + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) + } else { + resp.Diagnostics.AddError( + "This is another error", + "Oops again.", + ) + } }, expectedDiags: []*tfprotov6.Diagnostic{ @@ -3890,9 +3906,9 @@ func TestServerValidateDataResourceConfig(t *testing.T) { }, // ConfigValidators includes multiple calls { - Summary: "This is an error", + Summary: "This is another error", Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", + Detail: "Oops again.", }, }, }, @@ -3904,19 +3920,27 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSourceType: testServeDataSourceTypeConfigValidatorsType, impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, []*tfprotov6.Diagnostic{ - { - Summary: "This is a warning", - Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", - Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), - }, - { - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }, - }...) + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is a warning", + "This is your final warning", + ) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) + } else { + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is another warning", + "This is really your final warning", + ) + resp.Diagnostics.AddError( + "This is another error", + "Oops again.", + ) + } }, expectedDiags: []*tfprotov6.Diagnostic{ @@ -3933,15 +3957,15 @@ func TestServerValidateDataResourceConfig(t *testing.T) { }, // ConfigValidators includes multiple calls { - Summary: "This is a warning", + Summary: "This is another warning", Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", + Detail: "This is really your final warning", Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), }, { - Summary: "This is an error", + Summary: "This is another error", Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", + Detail: "Oops again.", }, }, }, @@ -3962,11 +3986,10 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSourceType: testServeDataSourceTypeValidateConfigType, impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) }, expectedDiags: []*tfprotov6.Diagnostic{ @@ -3985,19 +4008,15 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSourceType: testServeDataSourceTypeValidateConfigType, impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { - resp.Diagnostics = append(resp.Diagnostics, []*tfprotov6.Diagnostic{ - { - Summary: "This is a warning", - Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", - Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), - }, - { - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }, - }...) + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is a warning", + "This is your final warning", + ) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) }, expectedDiags: []*tfprotov6.Diagnostic{ @@ -4175,19 +4194,15 @@ func TestServerReadDataSource(t *testing.T) { "name": tftypes.NewValue(tftypes.String, "123foo-askjgsio"), "id": tftypes.NewValue(tftypes.String, "a random id or something I dunno"), }) - resp.Diagnostics = []*tfprotov6.Diagnostic{ - { - Summary: "This is a warning", - Severity: tfprotov6.DiagnosticSeverityWarning, - Detail: "This is your final warning", - Attribute: tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), - }, - { - Summary: "This is an error", - Severity: tfprotov6.DiagnosticSeverityError, - Detail: "Oops.", - }, - } + resp.Diagnostics.AddAttributeWarning( + tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), + "This is a warning", + "This is your final warning", + ) + resp.Diagnostics.AddError( + "This is an error", + "Oops.", + ) }, expectedNewState: tftypes.NewValue(testServeDataSourceTypeTwoType, map[string]tftypes.Value{ diff --git a/tfsdk/state.go b/tfsdk/state.go index a22bd574f..0598ebe2e 100644 --- a/tfsdk/state.go +++ b/tfsdk/state.go @@ -5,9 +5,8 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/reflect" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -18,25 +17,25 @@ type State struct { } // Get populates the struct passed as `target` with the entire state. -func (s State) Get(ctx context.Context, target interface{}) []*tfprotov6.Diagnostic { +func (s State) Get(ctx context.Context, target interface{}) diag.Diagnostics { return reflect.Into(ctx, s.Schema.AttributeType(), s.Raw, target, reflect.Options{}) } // GetAttribute retrieves the attribute found at `path` and returns it as an // attr.Value. Consumers should assert the type of the returned value with the // desired attr.Type. -func (s State) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (attr.Value, []*tfprotov6.Diagnostic) { - var diags []*tfprotov6.Diagnostic +func (s State) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics attrType, err := s.Schema.AttributeTypeAtPath(path) if err != nil { err = fmt.Errorf("error getting attribute type in schema: %w", err) - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "State Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } // if the whole state is nil, the value of a valid attribute is also nil @@ -46,18 +45,18 @@ func (s State) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (a tfValue, err := s.terraformValueAtPath(path) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "State Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { - diags = append(diags, attrTypeWithValidate.Validate(ctx, tfValue)...) + diags.Append(attrTypeWithValidate.Validate(ctx, tfValue)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return nil, diags } } @@ -65,12 +64,12 @@ func (s State) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (a attrValue, err := attrType.ValueFromTerraform(ctx, tfValue) if err != nil { - return nil, append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "State Read Error", - Detail: "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags } return attrValue, diags @@ -79,30 +78,29 @@ func (s State) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (a // Set populates the entire state using the supplied Go value. The value `val` // should be a struct whose values have one of the attr.Value types. Each field // must be tagged with the corresponding schema field. -func (s *State) Set(ctx context.Context, val interface{}) []*tfprotov6.Diagnostic { +func (s *State) Set(ctx context.Context, val interface{}) diag.Diagnostics { if val == nil { err := fmt.Errorf("cannot set nil as entire state; to remove a resource from state, call State.RemoveResource, instead") - return []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "State Read Error", - Detail: "An unexpected error was encountered trying to write the state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }, + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "State Read Error", + "An unexpected error was encountered trying to write the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ), } } newStateAttrValue, diags := reflect.OutOf(ctx, s.Schema.AttributeType(), val) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return diags } newStateVal, err := newStateAttrValue.ToTerraformValue(ctx) if err != nil { err = fmt.Errorf("error running ToTerraformValue on state: %w", err) - return append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "State Write Error", - Detail: "An unexpected error was encountered trying to write the state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }) + diags.AddError( + "State Write Error", + "An unexpected error was encountered trying to write the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } newState := tftypes.NewValue(s.Schema.AttributeType().TerraformType(ctx), newStateVal) @@ -112,36 +110,36 @@ func (s *State) Set(ctx context.Context, val interface{}) []*tfprotov6.Diagnosti } // SetAttribute sets the attribute at `path` using the supplied Go value. -func (s *State) SetAttribute(ctx context.Context, path *tftypes.AttributePath, val interface{}) []*tfprotov6.Diagnostic { - var diags []*tfprotov6.Diagnostic +func (s *State) SetAttribute(ctx context.Context, path *tftypes.AttributePath, val interface{}) diag.Diagnostics { + var diags diag.Diagnostics attrType, err := s.Schema.AttributeTypeAtPath(path) if err != nil { err = fmt.Errorf("error getting attribute type in schema: %w", err) - return append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "State Write Error", - Detail: "An unexpected error was encountered trying to write an attribute to the state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "State Write Error", + "An unexpected error was encountered trying to write an attribute to the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } newVal, newValDiags := reflect.OutOf(ctx, attrType, val) - diags = append(diags, newValDiags...) + diags.Append(newValDiags...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return diags } newTfVal, err := newVal.ToTerraformValue(ctx) if err != nil { err = fmt.Errorf("error running ToTerraformValue on new state value: %w", err) - return append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "State Write Error", - Detail: "An unexpected error was encountered trying to write an attribute to the state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "State Write Error", + "An unexpected error was encountered trying to write an attribute to the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } transformFunc := func(p *tftypes.AttributePath, v tftypes.Value) (tftypes.Value, error) { @@ -149,9 +147,9 @@ func (s *State) SetAttribute(ctx context.Context, path *tftypes.AttributePath, v tfVal := tftypes.NewValue(attrType.TerraformType(ctx), newTfVal) if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { - diags = append(diags, attrTypeWithValidate.Validate(ctx, tfVal)...) + diags.Append(attrTypeWithValidate.Validate(ctx, tfVal)...) - if diagnostics.DiagsHasErrors(diags) { + if diags.HasError() { return v, nil } } @@ -163,12 +161,12 @@ func (s *State) SetAttribute(ctx context.Context, path *tftypes.AttributePath, v s.Raw, err = tftypes.Transform(s.Raw, transformFunc) if err != nil { - return append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "State Write Error", - Detail: "An unexpected error was encountered trying to write an attribute to the state. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - Attribute: path, - }) + diags.AddAttributeError( + path, + "State Write Error", + "An unexpected error was encountered trying to write an attribute to the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags } return diags diff --git a/tfsdk/state_test.go b/tfsdk/state_test.go index dfddbac8e..7fdfd28e8 100644 --- a/tfsdk/state_test.go +++ b/tfsdk/state_test.go @@ -6,9 +6,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -35,7 +35,7 @@ func TestStateGet(t *testing.T) { type testCase struct { state State expected interface{} - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -359,7 +359,7 @@ func TestStateGet(t *testing.T) { Interface: "", }, }, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { state: State{ @@ -530,7 +530,7 @@ func TestStateGet(t *testing.T) { Interface: "SCSI", }, }, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestWarningDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic}, }, } @@ -561,7 +561,7 @@ func TestStateGetAttribute(t *testing.T) { state State path *tftypes.AttributePath expected attr.Value - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -827,7 +827,7 @@ func TestStateGetAttribute(t *testing.T) { }, path: tftypes.NewAttributePath().WithAttributeName("name"), expected: nil, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { state: State{ @@ -849,7 +849,7 @@ func TestStateGetAttribute(t *testing.T) { }, path: tftypes.NewAttributePath().WithAttributeName("name"), expected: types.String{Value: "namevalue"}, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestWarningDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic}, }, } @@ -878,7 +878,7 @@ func TestStateSet(t *testing.T) { state State val interface{} expected tftypes.Value - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -1189,7 +1189,7 @@ func TestStateSet(t *testing.T) { Name: "newvalue", }, expected: tftypes.Value{}, - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { state: State{ @@ -1215,7 +1215,7 @@ func TestStateSet(t *testing.T) { }, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "newvalue"), }), - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestWarningDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic}, }, } @@ -1245,7 +1245,7 @@ func TestStateSetAttribute(t *testing.T) { path *tftypes.AttributePath val interface{} expected tftypes.Value - expectedDiags []*tfprotov6.Diagnostic + expectedDiags diag.Diagnostics } testCases := map[string]testCase{ @@ -1539,7 +1539,7 @@ func TestStateSetAttribute(t *testing.T) { }, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "originalname"), }), - expectedDiags: []*tfprotov6.Diagnostic{testtypes.TestErrorDiagnostic}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic}, }, "AttrTypeWithValidateWarning": { state: State{ @@ -1568,10 +1568,7 @@ func TestStateSetAttribute(t *testing.T) { }, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "newname"), }), - expectedDiags: []*tfprotov6.Diagnostic{ - testtypes.TestWarningDiagnostic, - // TODO: Consider duplicate diagnostic consolidation functionality with diagnostic abstraction - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/24 + expectedDiags: diag.Diagnostics{ testtypes.TestWarningDiagnostic, }, }, diff --git a/types/list.go b/types/list.go index ea5da557b..f2c8e560e 100644 --- a/types/list.go +++ b/types/list.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/reflect" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -127,17 +127,16 @@ type List struct { // ElementsAs populates `target` with the elements of the List, throwing an // error if the elements cannot be stored in `target`. -func (l List) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) []*tfprotov6.Diagnostic { +func (l List) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics { // we need a tftypes.Value for this List to be able to use it with our // reflection code values, err := l.ToTerraformValue(ctx) if err != nil { - return []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "List Element Conversion Error", - Detail: "An unexpected error was encountered trying to convert list elements. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }, + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "List Element Conversion Error", + "An unexpected error was encountered trying to convert list elements. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ), } } return reflect.Into(ctx, ListType{ElemType: l.ElemType}, tftypes.NewValue(tftypes.List{ diff --git a/types/list_test.go b/types/list_test.go index 977084699..ed1dc29ed 100644 --- a/types/list_test.go +++ b/types/list_test.go @@ -6,7 +6,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -242,8 +241,8 @@ func TestListElementsAs_stringSlice(t *testing.T) { String{Value: "hello"}, String{Value: "world"}, }}).ElementsAs(context.Background(), &stringSlice, false) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } if diff := cmp.Diff(stringSlice, expected); diff != "" { t.Errorf("Unexpected diff (-expected, +got): %s", diff) @@ -265,8 +264,8 @@ func TestListElementsAs_attributeValueSlice(t *testing.T) { String{Value: "hello"}, String{Value: "world"}, }}).ElementsAs(context.Background(), &stringSlice, false) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } if diff := cmp.Diff(stringSlice, expected); diff != "" { t.Errorf("Unexpected diff (-expected, +got): %s", diff) diff --git a/types/map.go b/types/map.go index f1c22680f..06d7bf8a8 100644 --- a/types/map.go +++ b/types/map.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/reflect" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -125,7 +125,7 @@ type Map struct { // ElementsAs populates `target` with the elements of the Map, throwing an // error if the elements cannot be stored in `target`. -func (m Map) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) []*tfprotov6.Diagnostic { +func (m Map) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics { // we need a tftypes.Value for this Map to be able to use it with our // reflection code values := make(map[string]tftypes.Value, len(m.Elems)) @@ -133,23 +133,21 @@ func (m Map) ElementsAs(ctx context.Context, target interface{}, allowUnhandled val, err := elem.ToTerraformValue(ctx) if err != nil { err := fmt.Errorf("error getting Terraform value for element %q: %w", key, err) - return []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Map Element Conversion Error", - Detail: "An unexpected error was encountered trying to convert map elements. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }, + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Map Element Conversion Error", + "An unexpected error was encountered trying to convert map elements. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ), } } err = tftypes.ValidateValue(m.ElemType.TerraformType(ctx), val) if err != nil { err := fmt.Errorf("error using created Terraform value for element %q: %w", key, err) - return []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Map Element Conversion Error", - Detail: "An unexpected error was encountered trying to convert map elements. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }, + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Map Element Conversion Error", + "An unexpected error was encountered trying to convert map elements. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ), } } values[key] = tftypes.NewValue(m.ElemType.TerraformType(ctx), val) diff --git a/types/map_test.go b/types/map_test.go index 4ffcbfbf7..49d7cb04f 100644 --- a/types/map_test.go +++ b/types/map_test.go @@ -7,7 +7,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -242,8 +241,8 @@ func TestMapElementsAs_mapStringString(t *testing.T) { "h": String{Value: "hello"}, "w": String{Value: "world"}, }}).ElementsAs(context.Background(), &stringSlice, false) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } if diff := cmp.Diff(stringSlice, expected); diff != "" { t.Errorf("Unexpected diff (-expected, +got): %s", diff) @@ -265,8 +264,8 @@ func TestMapElementsAs_mapStringAttributeValue(t *testing.T) { "h": String{Value: "hello"}, "w": String{Value: "world"}, }}).ElementsAs(context.Background(), &stringSlice, false) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("Unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("Unexpected error: %v", diags) } if diff := cmp.Diff(stringSlice, expected); diff != "" { t.Errorf("Unexpected diff (-expected, +got): %s", diff) diff --git a/types/object.go b/types/object.go index 1e2685279..212910b91 100644 --- a/types/object.go +++ b/types/object.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/reflect" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -150,29 +150,27 @@ type ObjectAsOptions struct { // As populates `target` with the data in the Object, throwing an error if the // data cannot be stored in `target`. -func (o Object) As(ctx context.Context, target interface{}, opts ObjectAsOptions) []*tfprotov6.Diagnostic { +func (o Object) As(ctx context.Context, target interface{}, opts ObjectAsOptions) diag.Diagnostics { // we need a tftypes.Value for this Object to be able to use it with // our reflection code obj := ObjectType{AttrTypes: o.AttrTypes} typ := obj.TerraformType(ctx) val, err := o.ToTerraformValue(ctx) if err != nil { - return []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Object Conversion Error", - Detail: "An unexpected error was encountered trying to convert object. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }, + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Object Conversion Error", + "An unexpected error was encountered trying to convert object. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ), } } err = tftypes.ValidateValue(typ, val) if err != nil { - return []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Object Conversion Error", - Detail: "An unexpected error was encountered trying to convert object. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(), - }, + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Object Conversion Error", + "An unexpected error was encountered trying to convert object. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ), } } return reflect.Into(ctx, obj, tftypes.NewValue(typ, val), target, reflect.Options{ diff --git a/types/object_test.go b/types/object_test.go index 6c5929626..11a32a0fd 100644 --- a/types/object_test.go +++ b/types/object_test.go @@ -7,7 +7,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/diagnostics" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -516,8 +515,8 @@ func TestObjectAs_struct(t *testing.T) { } var target myStruct diags := object.As(context.Background(), &target, ObjectAsOptions{}) - if diagnostics.DiagsHasErrors(diags) { - t.Errorf("unexpected error: %s", diagnostics.DiagsString(diags)) + if diags.HasError() { + t.Errorf("unexpected error: %v", diags) } expected := myStruct{ A: "hello",