Skip to content

Commit 53f6a7c

Browse files
committed
fwserver: validate each ListResult
1 parent 877b2b8 commit 53f6a7c

File tree

2 files changed

+48
-17
lines changed

2 files changed

+48
-17
lines changed

internal/fwserver/server_listresource.go

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package fwserver
55

66
import (
77
"context"
8-
"iter"
98

109
"github.com/hashicorp/terraform-plugin-framework/diag"
1110
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
@@ -33,17 +32,17 @@ type ListRequest struct {
3332

3433
// ListResultsStream represents a streaming response to a ListRequest. An
3534
// instance of this struct is supplied as an argument to the provider's List
36-
// function. The provider should set a Results iterator function that yields
35+
// function. The provider should set a Results iterator function that pushes
3736
// zero or more results of type ListResult.
3837
//
3938
// For convenience, a provider implementation may choose to convert a slice of
4039
// results into an iterator using [slices.Values].
4140
//
4241
// [slices.Values]: https://pkg.go.dev/slices#Values
4342
type ListResourceStream struct {
44-
// Results is a function that emits ListResult values via its yield
43+
// Results is a function that emits ListResult values via its push
4544
// function argument.
46-
Results iter.Seq[ListResult]
45+
Results ListResults
4746
}
4847

4948
// ListResult represents a listed managed resource instance.
@@ -69,6 +68,11 @@ type ListResult struct {
6968
Diagnostics diag.Diagnostics
7069
}
7170

71+
// ListResults is a function type that accepts a function to push ListResult
72+
// values. This type can be used anywhere that an iterator of type
73+
// iter.Seq[ListResult] can be used.
74+
type ListResults func(push func(ListResult) bool)
75+
7276
// ListResource implements the framework server ListResource RPC.
7377
func (s *Server) ListResource(ctx context.Context, fwReq *ListRequest, fwStream *ListResourceStream) {
7478
listResource := fwReq.ListResource
@@ -85,19 +89,40 @@ func (s *Server) ListResource(ctx context.Context, fwReq *ListRequest, fwStream
8589
logging.FrameworkTrace(ctx, "Called provider defined ListResource")
8690

8791
if stream.Results == nil {
88-
// If the provider returned a nil results stream, we treat it as an empty stream.
89-
stream.Results = func(func(list.ListResult) bool) {}
92+
// If the provider returned a nil results stream, we return an empty stream.
93+
stream.Results = list.NoListResults
9094
}
9195

92-
fwStream.Results = listResourceEventStreamAdapter(stream.Results)
96+
fwStream.Results = processListResults(req, stream.Results)
97+
}
98+
99+
func processListResults(req list.ListRequest, stream list.ListResults) ListResults {
100+
return func(push func(ListResult) bool) {
101+
for result := range stream {
102+
if !push(processListResult(req, result)) {
103+
return
104+
}
105+
}
106+
}
93107
}
94108

95-
func listResourceEventStreamAdapter(stream iter.Seq[list.ListResult]) iter.Seq[ListResult] {
96-
// TODO: is this any more efficient than a for-range?
97-
return func(yieldFw func(ListResult) bool) {
98-
yield := func(event list.ListResult) bool {
99-
return yieldFw(ListResult(event))
109+
// processListResult validates the content of a list.ListResult and returns a
110+
// ListResult
111+
func processListResult(req list.ListRequest, result list.ListResult) ListResult {
112+
if result.Identity == nil {
113+
return ListResult{
114+
Diagnostics: diag.Diagnostics{
115+
diag.NewErrorDiagnostic("Incomplete List Result", "ListResult.Identity is nil."),
116+
},
100117
}
101-
stream(yield)
102118
}
119+
120+
if req.IncludeResource && result.Resource == nil {
121+
result.Diagnostics.AddWarning(
122+
"Incomplete List Result",
123+
"ListRequest.IncludeResource is true and ListResult.Resource is nil.",
124+
)
125+
}
126+
127+
return ListResult(result)
103128
}

list/list_resource.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package list
55

66
import (
77
"context"
8-
"iter"
98

109
"github.com/hashicorp/terraform-plugin-framework/diag"
1110
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -102,18 +101,25 @@ type ListRequest struct {
102101
// ListResultsStream represents a streaming response to a ListRequest. An
103102
// instance of this struct is supplied as an argument to the provider's
104103
// ListResource function. The provider should set a Results iterator function
105-
// that yields zero or more results of type ListResult.
104+
// that pushes zero or more results of type ListResult.
106105
//
107106
// For convenience, a provider implementation may choose to convert a slice of
108107
// results into an iterator using [slices.Values].
109108
//
110109
// [slices.Values]: https://pkg.go.dev/slices#Values
111110
type ListResultsStream struct {
112-
// Results is a function that emits ListResult values via its yield
111+
// Results is a function that emits ListResult values via its push
113112
// function argument.
114-
Results iter.Seq[ListResult]
113+
Results ListResults
115114
}
116115

116+
// ListResults is a function type that accepts a function to push ListResult
117+
// values. This type can be used anywhere that an iterator of type
118+
// iter.Seq[ListResult] can be used.
119+
type ListResults func(push func(ListResult) bool)
120+
121+
var NoListResults = func(func(ListResult) bool) {}
122+
117123
// ListResult represents a listed managed resource instance.
118124
type ListResult struct {
119125
// Identity is the identity of the managed resource instance.

0 commit comments

Comments
 (0)