Skip to content

Commit

Permalink
And handwritten missing paginators (#2310)
Browse files Browse the repository at this point in the history
  • Loading branch information
wty-Bryant authored Oct 17, 2023
1 parent 609f64a commit cd475ae
Show file tree
Hide file tree
Showing 7 changed files with 896 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .changelog/6cb6bc26277c41be84cb8aa523a78989.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "6cb6bc26-277c-41be-84cb-8aa523a78989",
"type": "feature",
"collapse": true,
"description": "Add handwritten paginators that were present in some services in the v1 SDK.",
"modules": [
"service/amplifybackend",
"service/dynamodb",
"service/kinesis"
]
}
96 changes: 96 additions & 0 deletions service/amplifybackend/handwritten_paginators.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package amplifybackend

import (
"context"
"fmt"
)

// ListBackendJobsPaginatorOptions is the paginator options for ListBackendJobs
type ListBackendJobsPaginatorOptions struct {
// (Optional) The maximum number of shards to return in a single call
Limit int32

// Set to true if pagination should stop if the service returns a pagination token
// that matches the most recent token provided to the service.
StopOnDuplicateToken bool
}

// ListBackendJobsPaginator is a paginator for ListBackendJobs
type ListBackendJobsPaginator struct {
options ListBackendJobsPaginatorOptions
client ListBackendJobsAPIClient
params *ListBackendJobsInput
firstPage bool
nextToken *string
isTruncated bool
}

// ListBackendJobsAPIClient is a client that implements the ListBackendJobs operation.
type ListBackendJobsAPIClient interface {
ListBackendJobs(context.Context, *ListBackendJobsInput, ...func(*Options)) (*ListBackendJobsOutput, error)
}

// NewListBackendJobsPaginator returns a new ListBackendJobsPaginator
func NewListBackendJobsPaginator(client ListBackendJobsAPIClient, params *ListBackendJobsInput, optFns ...func(options *ListBackendJobsPaginatorOptions)) *ListBackendJobsPaginator {
if params == nil {
params = &ListBackendJobsInput{}
}

options := ListBackendJobsPaginatorOptions{}
options.Limit = params.MaxResults

for _, fn := range optFns {
fn(&options)
}

return &ListBackendJobsPaginator{
options: options,
client: client,
params: params,
firstPage: true,
nextToken: params.NextToken,
}
}

// HasMorePages returns a boolean indicating whether more pages are available
func (p *ListBackendJobsPaginator) HasMorePages() bool {
return p.firstPage || p.isTruncated
}

// NextPage retrieves the next ListBackendJobs page.
func (p *ListBackendJobsPaginator) NextPage(ctx context.Context, optFns ...func(*Options)) (*ListBackendJobsOutput, error) {
if !p.HasMorePages() {
return nil, fmt.Errorf("no more pages available")
}

params := *p.params
params.NextToken = p.nextToken

var limit int32
if p.options.Limit > 0 {
limit = p.options.Limit
}
params.MaxResults = limit

result, err := p.client.ListBackendJobs(ctx, &params, optFns...)
if err != nil {
return nil, err
}
p.firstPage = false

prevToken := p.nextToken
p.isTruncated = result.NextToken != nil
p.nextToken = nil
if result.NextToken != nil {
p.nextToken = result.NextToken
}

if p.options.StopOnDuplicateToken &&
prevToken != nil &&
p.nextToken != nil &&
*prevToken == *p.nextToken {
p.isTruncated = false
}

return result, nil
}
241 changes: 241 additions & 0 deletions service/amplifybackend/handwritten_paginators_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package amplifybackend

import (
"context"
"github.com/aws/aws-sdk-go-v2/service/amplifybackend/types"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
)

type mockListBackendJobsClient struct {
outputs []*ListBackendJobsOutput
inputs []*ListBackendJobsInput
t *testing.T
limit int32
}

func (c *mockListBackendJobsClient) ListBackendJobs(ctx context.Context, input *ListBackendJobsInput, optFns ...func(*Options)) (*ListBackendJobsOutput, error) {
c.inputs = append(c.inputs, input)
requestCnt := len(c.inputs)
testCurRequest(len(c.outputs), requestCnt, c.limit, input.MaxResults, c.t)
return c.outputs[requestCnt-1], nil
}

type listBackendJobsTestCase struct {
limit int32
requestCnt int
stopOnDuplicationToken bool
outputs []*ListBackendJobsOutput
}

func TestListBackendJobsPaginator(t *testing.T) {
cases := map[string]listBackendJobsTestCase{
"page limit 3": {
limit: 3,
requestCnt: 3,
outputs: []*ListBackendJobsOutput{
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job1"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job2"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job3"),
},
},
NextToken: aws.String("token1"),
},
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job4"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job5"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job6"),
},
},
NextToken: aws.String("token2"),
},
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job7"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job8"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job9"),
},
},
},
},
},
"total count 2 due to nil nextToken": {
limit: 3,
requestCnt: 2,
outputs: []*ListBackendJobsOutput{
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job1"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job2"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job3"),
},
},
NextToken: aws.String("token1"),
},
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job4"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job5"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job6"),
},
},
},
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job7"),
},
},
},
},
},
"total count 2 due to duplicate nextToken": {
limit: 3,
requestCnt: 2,
stopOnDuplicationToken: true,
outputs: []*ListBackendJobsOutput{
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job1"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job2"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job3"),
},
},
NextToken: aws.String("token1"),
},
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job4"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job5"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job6"),
},
},
NextToken: aws.String("token1"),
},
{
Jobs: []types.BackendJobRespObj{
{
AppId: aws.String("App"),
JobId: aws.String("Job7"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job8"),
},
{
AppId: aws.String("App"),
JobId: aws.String("Job9"),
},
},
},
},
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {
client := mockListBackendJobsClient{
t: t,
outputs: c.outputs,
inputs: []*ListBackendJobsInput{},
limit: c.limit,
}
paginator := NewListBackendJobsPaginator(&client, &ListBackendJobsInput{}, func(options *ListBackendJobsPaginatorOptions) {
options.Limit = c.limit
options.StopOnDuplicateToken = c.stopOnDuplicationToken
})

for paginator.HasMorePages() {
_, err := paginator.NextPage(context.TODO())
if err != nil {
t.Errorf("error: %v", err)
}
}

inputLen := len(client.inputs)
testTotalRequests(c.requestCnt, inputLen, t)
for i := 1; i < inputLen; i++ {
if *client.inputs[i].NextToken != *c.outputs[i-1].NextToken {
t.Errorf("Expect next input's nextToken to be eaqul to %s, got %s",
*c.outputs[i-1].NextToken, *client.inputs[i].NextToken)
}
}
})
}
}

func testCurRequest(maxReqCnt, actualReqCnt int, expectLimit, actualLimit int32, t *testing.T) {
if actualReqCnt > maxReqCnt {
t.Errorf("Paginator calls client more than expected %d times", maxReqCnt)
}
if expectLimit != actualLimit {
t.Errorf("Expect page limit to be %d, got %d", expectLimit, actualLimit)
}
}

func testTotalRequests(expect, actual int, t *testing.T) {
if actual != expect {
t.Errorf("Expect total request number to be %d, got %d", expect, actual)
}
}
Loading

0 comments on commit cd475ae

Please sign in to comment.