Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ toolchain go1.23.7

require (
github.com/google/go-cmp v0.7.0
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250616135123-a19df43120ea
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250703143221-06cc08e56c87
github.com/hashicorp/terraform-plugin-log v0.9.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0U
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250616135123-a19df43120ea h1:U9EAAeQtszGlR7mDS7rY77B/a4/XiMDB8HfAtqLAuAQ=
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250616135123-a19df43120ea/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY=
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250703143221-06cc08e56c87 h1:7GqdqQtDa2XSOL80U8cfgrnmisAfzZtH2FPe0SxvCJM=
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250703143221-06cc08e56c87/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-registry-address v0.3.0 h1:HMpK3nqaGFPS9VmgRXrJL/dzHNdheGVKk5k7VlFxzCo=
Expand Down
31 changes: 31 additions & 0 deletions internal/fromproto5/validatelistresourceconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto5

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/list"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
)

// ValidateListResourceConfigRequest returns the *fwserver.ValidateListResourceConfigRequest
// equivalent of a *tfprotov5.ValidateListResourceConfigRequest.
func ValidateListResourceConfigRequest(ctx context.Context, proto5 *tfprotov5.ValidateListResourceConfigRequest, listResource list.ListResource, listResourceSchema fwschema.Schema) (*fwserver.ValidateListResourceConfigRequest, diag.Diagnostics) {
if proto5 == nil {
return nil, nil
}

fw := &fwserver.ValidateListResourceConfigRequest{}

config, diags := Config(ctx, proto5.Config, listResourceSchema)

fw.Config = config
fw.ListResource = listResource

return fw, diags
}
108 changes: 108 additions & 0 deletions internal/fromproto5/validatelistresourceconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto5_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/list"
"github.com/hashicorp/terraform-plugin-framework/list/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

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

testProto5Type := tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_attribute": tftypes.String,
},
}

testProto5Value := tftypes.NewValue(testProto5Type, map[string]tftypes.Value{
"test_attribute": tftypes.NewValue(tftypes.String, "test-value"),
})

testProto5DynamicValue, err := tfprotov5.NewDynamicValue(testProto5Type, testProto5Value)

if err != nil {
t.Fatalf("unexpected error calling tfprotov5.NewDynamicValue(): %s", err)
}

testFwSchema := schema.Schema{
Attributes: map[string]schema.Attribute{
"test_attribute": schema.StringAttribute{
Required: true,
},
},
}

testCases := map[string]struct {
input *tfprotov5.ValidateListResourceConfigRequest
listResourceSchema fwschema.Schema
listResource list.ListResource
expected *fwserver.ValidateListResourceConfigRequest
expectedDiagnostics diag.Diagnostics
}{
"nil": {
input: nil,
expected: nil,
},
"empty": {
input: &tfprotov5.ValidateListResourceConfigRequest{},
expected: &fwserver.ValidateListResourceConfigRequest{},
},
"config-missing-schema": {
input: &tfprotov5.ValidateListResourceConfigRequest{
Config: &testProto5DynamicValue,
},
expected: &fwserver.ValidateListResourceConfigRequest{},
expectedDiagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Unable to Convert Configuration",
"An unexpected error was encountered when converting the configuration from the protocol type. "+
"This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+
"Please report this to the provider developer:\n\n"+
"Missing schema.",
),
},
},
"config": {
input: &tfprotov5.ValidateListResourceConfigRequest{
Config: &testProto5DynamicValue,
},
listResourceSchema: testFwSchema,
expected: &fwserver.ValidateListResourceConfigRequest{
Config: &tfsdk.Config{
Raw: testProto5Value,
Schema: testFwSchema,
},
},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

got, diags := fromproto5.ValidateListResourceConfigRequest(context.Background(), testCase.input, testCase.listResource, testCase.listResourceSchema)

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}

if diff := cmp.Diff(diags, testCase.expectedDiagnostics); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)
}
})
}
}
31 changes: 31 additions & 0 deletions internal/fromproto6/validatelistresourceconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto6

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/list"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)

// ValidateListResourceConfigRequest returns the *fwserver.ValidateListResourceConfigRequest
// equivalent of a *tfprotov6.ValidateListResourceConfigRequest.
func ValidateListResourceConfigRequest(ctx context.Context, proto6 *tfprotov6.ValidateListResourceConfigRequest, listResource list.ListResource, listResourceSchema fwschema.Schema) (*fwserver.ValidateListResourceConfigRequest, diag.Diagnostics) {
if proto6 == nil {
return nil, nil
}

fw := &fwserver.ValidateListResourceConfigRequest{}

config, diags := Config(ctx, proto6.Config, listResourceSchema)

fw.Config = config
fw.ListResource = listResource

return fw, diags
}
108 changes: 108 additions & 0 deletions internal/fromproto6/validatelistresourceconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto6_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/list"
"github.com/hashicorp/terraform-plugin-framework/list/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

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

testProto6Type := tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_attribute": tftypes.String,
},
}

testProto6Value := tftypes.NewValue(testProto6Type, map[string]tftypes.Value{
"test_attribute": tftypes.NewValue(tftypes.String, "test-value"),
})

testProto6DynamicValue, err := tfprotov6.NewDynamicValue(testProto6Type, testProto6Value)

if err != nil {
t.Fatalf("unexpected error calling tfprotov6.NewDynamicValue(): %s", err)
}

testFwSchema := schema.Schema{
Attributes: map[string]schema.Attribute{
"test_attribute": schema.StringAttribute{
Required: true,
},
},
}

testCases := map[string]struct {
input *tfprotov6.ValidateListResourceConfigRequest
listResourceSchema fwschema.Schema
listResource list.ListResource
expected *fwserver.ValidateListResourceConfigRequest
expectedDiagnostics diag.Diagnostics
}{
"nil": {
input: nil,
expected: nil,
},
"empty": {
input: &tfprotov6.ValidateListResourceConfigRequest{},
expected: &fwserver.ValidateListResourceConfigRequest{},
},
"config-missing-schema": {
input: &tfprotov6.ValidateListResourceConfigRequest{
Config: &testProto6DynamicValue,
},
expected: &fwserver.ValidateListResourceConfigRequest{},
expectedDiagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Unable to Convert Configuration",
"An unexpected error was encountered when converting the configuration from the protocol type. "+
"This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+
"Please report this to the provider developer:\n\n"+
"Missing schema.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Here's a thought to consider – completely non-blocking for merging this PR.

While we're adding new kinds of schemas, I wonder if this diagnostic is precise enough to be actionable by a provider developer.

"Missing list resource schema for ``random_pet``" seems nice to have here. And also at odds with the universal message in fromproto6.Config().

So I'm curious how this diagnostic reads when it is rendered by the Terraform CLI and whether it includes precise context. If it's not precise, I suggest we adjust the Config diagnostic to be more flexible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this error message is only for our team, and isn't actionable by the provider developer outside of reporting it to us:

// Panic prevention here to simplify the calling implementations.
// This should not happen, but just in case.

The only way we'd hit this error is if there was a bug in Terraform core

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, no action here 🏁

),
},
},
"config": {
input: &tfprotov6.ValidateListResourceConfigRequest{
Config: &testProto6DynamicValue,
},
listResourceSchema: testFwSchema,
expected: &fwserver.ValidateListResourceConfigRequest{
Config: &tfsdk.Config{
Raw: testProto6Value,
Schema: testFwSchema,
},
},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

got, diags := fromproto6.ValidateListResourceConfigRequest(context.Background(), testCase.input, testCase.listResource, testCase.listResourceSchema)

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}

if diff := cmp.Diff(diags, testCase.expectedDiagnostics); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)
}
})
}
}
5 changes: 5 additions & 0 deletions internal/fwserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ type Server struct {
// to [ephemeral.ConfigureRequest.ProviderData].
EphemeralResourceConfigureData any

// ListResourceConfigureData is the
// [provider.ConfigureResponse.ListResourceData] field value which is passed
// to [list.ConfigureRequest.ProviderData].
ListResourceConfigureData any

// dataSourceSchemas is the cached DataSource Schemas for RPCs that need to
// convert configuration data from the protocol. If not found, it will be
// fetched from the DataSourceType.GetSchema() method.
Expand Down
10 changes: 7 additions & 3 deletions internal/fwserver/server_listresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package fwserver

import (
"context"
"errors"
"iter"

"github.com/hashicorp/terraform-plugin-framework/diag"
Expand Down Expand Up @@ -80,12 +79,17 @@ type ListResult struct {
var NoListResults = func(func(ListResult) bool) {}

// ListResource implements the framework server ListResource RPC.
func (s *Server) ListResource(ctx context.Context, fwReq *ListRequest, fwStream *ListResultsStream) error {
func (s *Server) ListResource(ctx context.Context, fwReq *ListRequest, fwStream *ListResultsStream) diag.Diagnostics {
listResource := fwReq.ListResource

if fwReq.Config == nil {
fwStream.Results = NoListResults
return errors.New("Invalid ListResource request: Config cannot be nil")
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid ListResource Request",
"Config cannot be nil",
),
}
}

req := list.ListRequest{
Expand Down
10 changes: 6 additions & 4 deletions internal/fwserver/server_listresource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestServerListResource(t *testing.T) {
server *fwserver.Server
request *fwserver.ListRequest
expectedStreamEvents []fwserver.ListResult
expectedError string
expectedError diag.Diagnostics
}{
"success-with-zero-results": {
server: &fwserver.Server{
Expand Down Expand Up @@ -179,7 +179,9 @@ func TestServerListResource(t *testing.T) {
},
},
},
expectedError: "Invalid ListResource request: Config cannot be nil",
expectedError: diag.Diagnostics{
diag.NewErrorDiagnostic("Invalid ListResource Request", "Config cannot be nil"),
},
expectedStreamEvents: []fwserver.ListResult{},
},
"error-on-nil-resource-identity": {
Expand Down Expand Up @@ -319,8 +321,8 @@ func TestServerListResource(t *testing.T) {

response := &fwserver.ListResultsStream{}
err := testCase.server.ListResource(context.Background(), testCase.request, response)
if err != nil && err.Error() != testCase.expectedError {
t.Fatalf("unexpected error: %s", err)
if diff := cmp.Diff(testCase.expectedError, err); diff != "" {
t.Errorf("unexpected error difference: %s", diff)
}

opts := cmp.Options{
Expand Down
Loading
Loading