From 72dc0407c6a3f1f7baadb549ca364cdd806f7181 Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Mon, 16 Jan 2023 00:49:09 -0800 Subject: [PATCH 1/5] added paginator for listResourceRecordSets --- service/route53/handwritten_paginators.go | 106 ++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 service/route53/handwritten_paginators.go diff --git a/service/route53/handwritten_paginators.go b/service/route53/handwritten_paginators.go new file mode 100644 index 00000000000..dd34d408aa9 --- /dev/null +++ b/service/route53/handwritten_paginators.go @@ -0,0 +1,106 @@ +package route53 + +import ( + "context" + "fmt" + "github.com/aws/aws-sdk-go-v2/service/route53/types" +) + +type ListResourceRecordSetsAPIClient interface { + ListResourceRecordSets(context.Context, *ListResourceRecordSetsInput, ...func(*Options)) (*ListResourceRecordSetsOutput, error) +} + +var _ ListResourceRecordSetsAPIClient = (*Client)(nil) + +// ListResourceRecordSetsPaginatorOptions is the paginator options for ListResourceRecordSets +type ListResourceRecordSetsPaginatorOptions struct { + // (Optional) The maximum number of ResourceRecordSets that you want Amazon Route 53 to + // return. + 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 +} + +// ListResourceRecordSetsPaginator is a paginator for ListResourceRecordSets +type ListResourceRecordSetsPaginator struct { + options ListResourceRecordSetsPaginatorOptions + client ListResourceRecordSetsAPIClient + params *ListResourceRecordSetsInput + firstPage bool + startRecordName *string + startRecordType types.RRType + startRecordIdentifier *string +} + +// NewListResourceRecordSetsPaginator returns a new ListResourceRecordSetsPaginator +func NewListResourceRecordSetsPaginator(client ListResourceRecordSetsAPIClient, params *ListResourceRecordSetsInput, optFns ...func(*ListResourceRecordSetsPaginatorOptions)) *ListResourceRecordSetsPaginator { + if params == nil { + params = &ListResourceRecordSetsInput{} + } + + options := ListResourceRecordSetsPaginatorOptions{} + if params.MaxItems != nil { + options.Limit = *params.MaxItems + } + + for _, fn := range optFns { + fn(&options) + } + + return &ListResourceRecordSetsPaginator{ + options: options, + client: client, + params: params, + firstPage: true, + startRecordName: params.StartRecordName, + startRecordType: params.StartRecordType, + startRecordIdentifier: params.StartRecordIdentifier, + } +} + +// HasMorePages returns a boolean indicating whether more pages are available +func (p *ListResourceRecordSetsPaginator) HasMorePages() bool { + return p.firstPage || (p.startRecordName != nil && len(*p.startRecordName) != 0) +} + +// NextPage retrieves the next ListResourceRecordSets page. +func (p *ListResourceRecordSetsPaginator) NextPage(ctx context.Context, optFns ...func(*Options)) (*ListResourceRecordSetsOutput, error) { + if !p.HasMorePages() { + return nil, fmt.Errorf("no more pages available") + } + + params := *p.params + params.StartRecordName = p.startRecordName + + params.StartRecordIdentifier = p.startRecordIdentifier + params.StartRecordType = p.startRecordType + + var limit *int32 + if p.options.Limit > 0 { + limit = &p.options.Limit + } + params.MaxItems = limit + + result, err := p.client.ListResourceRecordSets(ctx, ¶ms, optFns...) + if err != nil { + return nil, err + } + p.firstPage = false + + prevToken := p.startRecordName + p.startRecordName = result.NextRecordName + + p.startRecordIdentifier = result.NextRecordIdentifier + p.startRecordType = result.NextRecordType + + if p.options.StopOnDuplicateToken && + prevToken != nil && + p.startRecordName != nil && + *prevToken == *p.startRecordName { + p.startRecordName = nil + } + + return result, nil +} From ae9383db6cff6f33eec681c09e1e5f4a600d4229 Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Mon, 16 Jan 2023 00:54:41 -0800 Subject: [PATCH 2/5] Added changelog --- .changelog/85820b40858247f8a975883ac9ae7fb3.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changelog/85820b40858247f8a975883ac9ae7fb3.json diff --git a/.changelog/85820b40858247f8a975883ac9ae7fb3.json b/.changelog/85820b40858247f8a975883ac9ae7fb3.json new file mode 100644 index 00000000000..53ed9044c67 --- /dev/null +++ b/.changelog/85820b40858247f8a975883ac9ae7fb3.json @@ -0,0 +1,8 @@ +{ + "id": "85820b40-8582-47f8-a975-883ac9ae7fb3", + "type": "feature", + "description": "added paginator for listResourceRecordSets", + "modules": [ + "service/route53" + ] +} \ No newline at end of file From b2c32c8122de703b1819525957f34d101df6740b Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Mon, 16 Jan 2023 20:03:00 -0800 Subject: [PATCH 3/5] added comment to exported type --- service/route53/handwritten_paginators.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/service/route53/handwritten_paginators.go b/service/route53/handwritten_paginators.go index dd34d408aa9..53258af633e 100644 --- a/service/route53/handwritten_paginators.go +++ b/service/route53/handwritten_paginators.go @@ -6,6 +6,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/route53/types" ) +// ListResourceRecordSetsAPIClient is a client that implements the ListResourceRecordSets +// operation type ListResourceRecordSetsAPIClient interface { ListResourceRecordSets(context.Context, *ListResourceRecordSetsInput, ...func(*Options)) (*ListResourceRecordSetsOutput, error) } From 94143993fac1646fea5aa8c16fde443866339b66 Mon Sep 17 00:00:00 2001 From: Tianyi Wang Date: Thu, 13 Apr 2023 23:09:52 -0400 Subject: [PATCH 4/5] add unit test for route53 paginator --- service/route53/handwritten_paginators.go | 2 - .../route53/handwritten_paginators_test.go | 113 ++++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 service/route53/handwritten_paginators_test.go diff --git a/service/route53/handwritten_paginators.go b/service/route53/handwritten_paginators.go index 53258af633e..53551b9c53e 100644 --- a/service/route53/handwritten_paginators.go +++ b/service/route53/handwritten_paginators.go @@ -75,7 +75,6 @@ func (p *ListResourceRecordSetsPaginator) NextPage(ctx context.Context, optFns . params := *p.params params.StartRecordName = p.startRecordName - params.StartRecordIdentifier = p.startRecordIdentifier params.StartRecordType = p.startRecordType @@ -93,7 +92,6 @@ func (p *ListResourceRecordSetsPaginator) NextPage(ctx context.Context, optFns . prevToken := p.startRecordName p.startRecordName = result.NextRecordName - p.startRecordIdentifier = result.NextRecordIdentifier p.startRecordType = result.NextRecordType diff --git a/service/route53/handwritten_paginators_test.go b/service/route53/handwritten_paginators_test.go new file mode 100644 index 00000000000..46580a4f670 --- /dev/null +++ b/service/route53/handwritten_paginators_test.go @@ -0,0 +1,113 @@ +package route53 + +import ( + "context" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/route53/types" + "github.com/aws/smithy-go/middleware" + "testing" +) + +var limit int32 +var startRecordName *string +var startRecordIdentifier *string +var startRecordType types.RRType + +type testListRRSMiddleware struct { + id int +} + +func (m *testListRRSMiddleware) ID() string { + return fmt.Sprintf("mock middleware %d", m.id) +} + +func (m *testListRRSMiddleware) HandleInitialize(ctx context.Context, input middleware.InitializeInput, next middleware.InitializeHandler) ( + output middleware.InitializeOutput, metadata middleware.Metadata, err error, +) { + params := input.Parameters.(*ListResourceRecordSetsInput) + startRecordName = params.StartRecordName + limit = *params.MaxItems + startRecordIdentifier = params.StartRecordIdentifier + startRecordType = params.StartRecordType + return middleware.InitializeOutput{Result: &ListResourceRecordSetsOutput{}}, metadata, nil +} + +type testCase struct { + startRecordName *string + limit int32 + startRecordIdentifier *string + startRecordType types.RRType +} + +func TestListResourceRecordSetsPaginator(t *testing.T) { + cases := map[string]testCase{ + "page limit 5 with record name but without record type and identifier": { + startRecordName: aws.String("testRecord1"), + limit: 5, + }, + "page limit 10 with record name and type": { + startRecordName: aws.String("testRecord2"), + limit: 10, + startRecordType: types.RRTypeTxt, + }, + "page limit 15 with record name, type and identifier": { + startRecordName: aws.String("testRecord3"), + limit: 15, + startRecordIdentifier: aws.String("testID1"), + startRecordType: types.RRTypeTxt, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + client := NewFromConfig(aws.Config{}) + + paginator := NewListResourceRecordSetsPaginator(client, &ListResourceRecordSetsInput{ + StartRecordName: c.startRecordName, + StartRecordType: c.startRecordType, + StartRecordIdentifier: c.startRecordIdentifier, + }, func(options *ListResourceRecordSetsPaginatorOptions) { + options.Limit = c.limit + }) + if !paginator.HasMorePages() { + t.Errorf("Expect paginator has more page, got not") + } + + paginator.NextPage(context.TODO(), initializeMiddlewareFn(&testListRRSMiddleware{1})) + + testNextPageResult(c, paginator, t) + }) + } +} + +// insert middleware at the beginning of initialize step to see if page limit and other params +// can be passed to API call's stack input +func initializeMiddlewareFn(initializeMiddleware middleware.InitializeMiddleware) func(*Options) { + return func(options *Options) { + options.APIOptions = append(options.APIOptions, func(stack *middleware.Stack) error { + return stack.Initialize.Add(initializeMiddleware, middleware.Before) + }) + } +} + +// unit test can not control client API call's output, so just check params' default nil value +func testNextPageResult(c testCase, p *ListResourceRecordSetsPaginator, t *testing.T) { + if c.limit != limit { + t.Errorf("Expect page limit to be %d, got %d", c.limit, limit) + } + if *c.startRecordName != *startRecordName { + t.Errorf("Expect startRecordName to be %s, got %s", *c.startRecordName, *startRecordName) + } + if c.startRecordType != startRecordType { + t.Errorf("Expect startRecordType to be %s, got %s", c.startRecordType, startRecordType) + } + if c.startRecordIdentifier != nil && *c.startRecordIdentifier != *startRecordIdentifier { + t.Errorf("Expect startRecordIdentifier to be %s, got %s", + *c.startRecordIdentifier, *startRecordIdentifier) + } + if p.startRecordName != nil || p.startRecordType != "" || p.startRecordIdentifier != nil { + t.Errorf("Expect paginator record params to be zero value, got %s, %s and %s", + *p.startRecordName, p.startRecordType, *p.startRecordIdentifier) + } +} From 04fc1d75d9854f0617b40849fa4393bb46df37be Mon Sep 17 00:00:00 2001 From: Tianyi Wang Date: Tue, 18 Apr 2023 21:24:39 -0400 Subject: [PATCH 5/5] modify route53 paginator unit test logic --- service/route53/handwritten_paginators.go | 17 +- .../route53/handwritten_paginators_test.go | 200 ++++++++++-------- 2 files changed, 129 insertions(+), 88 deletions(-) diff --git a/service/route53/handwritten_paginators.go b/service/route53/handwritten_paginators.go index 53551b9c53e..2f397c8ac9e 100644 --- a/service/route53/handwritten_paginators.go +++ b/service/route53/handwritten_paginators.go @@ -34,6 +34,7 @@ type ListResourceRecordSetsPaginator struct { startRecordName *string startRecordType types.RRType startRecordIdentifier *string + isTruncated bool } // NewListResourceRecordSetsPaginator returns a new ListResourceRecordSetsPaginator @@ -64,7 +65,7 @@ func NewListResourceRecordSetsPaginator(client ListResourceRecordSetsAPIClient, // HasMorePages returns a boolean indicating whether more pages are available func (p *ListResourceRecordSetsPaginator) HasMorePages() bool { - return p.firstPage || (p.startRecordName != nil && len(*p.startRecordName) != 0) + return p.firstPage || p.isTruncated } // NextPage retrieves the next ListResourceRecordSets page. @@ -91,15 +92,21 @@ func (p *ListResourceRecordSetsPaginator) NextPage(ctx context.Context, optFns . p.firstPage = false prevToken := p.startRecordName - p.startRecordName = result.NextRecordName - p.startRecordIdentifier = result.NextRecordIdentifier - p.startRecordType = result.NextRecordType + p.isTruncated = result.IsTruncated + p.startRecordName = nil + p.startRecordIdentifier = nil + p.startRecordType = "" + if result.IsTruncated { + p.startRecordName = result.NextRecordName + p.startRecordIdentifier = result.NextRecordIdentifier + p.startRecordType = result.NextRecordType + } if p.options.StopOnDuplicateToken && prevToken != nil && p.startRecordName != nil && *prevToken == *p.startRecordName { - p.startRecordName = nil + p.isTruncated = false } return result, nil diff --git a/service/route53/handwritten_paginators_test.go b/service/route53/handwritten_paginators_test.go index 46580a4f670..3e6efdf9c13 100644 --- a/service/route53/handwritten_paginators_test.go +++ b/service/route53/handwritten_paginators_test.go @@ -2,112 +2,146 @@ package route53 import ( "context" - "fmt" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/route53/types" - "github.com/aws/smithy-go/middleware" "testing" ) -var limit int32 -var startRecordName *string -var startRecordIdentifier *string -var startRecordType types.RRType - -type testListRRSMiddleware struct { - id int -} - -func (m *testListRRSMiddleware) ID() string { - return fmt.Sprintf("mock middleware %d", m.id) +type mockListResourceRecordSetsClient struct { + outputs []*ListResourceRecordSetsOutput + inputs []*ListResourceRecordSetsInput + t *testing.T } -func (m *testListRRSMiddleware) HandleInitialize(ctx context.Context, input middleware.InitializeInput, next middleware.InitializeHandler) ( - output middleware.InitializeOutput, metadata middleware.Metadata, err error, -) { - params := input.Parameters.(*ListResourceRecordSetsInput) - startRecordName = params.StartRecordName - limit = *params.MaxItems - startRecordIdentifier = params.StartRecordIdentifier - startRecordType = params.StartRecordType - return middleware.InitializeOutput{Result: &ListResourceRecordSetsOutput{}}, metadata, nil +func (c *mockListResourceRecordSetsClient) ListResourceRecordSets(ctx context.Context, input *ListResourceRecordSetsInput, optFns ...func(*Options)) (*ListResourceRecordSetsOutput, error) { + c.inputs = append(c.inputs, input) + requestCnt := len(c.inputs) + if *input.MaxItems != *c.outputs[requestCnt-1].MaxItems { + c.t.Errorf("Expect page limit to be %d, got %d", *c.outputs[requestCnt-1].MaxItems, *input.MaxItems) + } + if outputLen := len(c.outputs); requestCnt > outputLen { + c.t.Errorf("Paginator calls client more than expected %d times", outputLen) + } + return c.outputs[requestCnt-1], nil } -type testCase struct { - startRecordName *string - limit int32 - startRecordIdentifier *string - startRecordType types.RRType +type listRRSTestCase struct { + limit int32 + requestCnt int + stopOnDuplicationToken bool + outputs []*ListResourceRecordSetsOutput } func TestListResourceRecordSetsPaginator(t *testing.T) { - cases := map[string]testCase{ - "page limit 5 with record name but without record type and identifier": { - startRecordName: aws.String("testRecord1"), - limit: 5, - }, - "page limit 10 with record name and type": { - startRecordName: aws.String("testRecord2"), - limit: 10, - startRecordType: types.RRTypeTxt, + cases := map[string]listRRSTestCase{ + "page limit 5": { + limit: 5, + requestCnt: 3, + outputs: []*ListResourceRecordSetsOutput{ + &ListResourceRecordSetsOutput{ + MaxItems: aws.Int32(5), + NextRecordName: aws.String("testRecord1"), + NextRecordIdentifier: aws.String("testID1"), + NextRecordType: types.RRTypeA, + IsTruncated: true, + }, + &ListResourceRecordSetsOutput{ + MaxItems: aws.Int32(5), + NextRecordName: aws.String("testRecord2"), + NextRecordIdentifier: aws.String("testID2"), + NextRecordType: types.RRTypeA, + IsTruncated: true, + }, + &ListResourceRecordSetsOutput{ + MaxItems: aws.Int32(5), + NextRecordName: aws.String("testRecord3"), + NextRecordIdentifier: aws.String("testID3"), + NextRecordType: types.RRTypeA, + IsTruncated: false, + }, + }, }, - "page limit 15 with record name, type and identifier": { - startRecordName: aws.String("testRecord3"), - limit: 15, - startRecordIdentifier: aws.String("testID1"), - startRecordType: types.RRTypeTxt, + "page limit 10 with duplicate record": { + limit: 10, + requestCnt: 4, + stopOnDuplicationToken: true, + outputs: []*ListResourceRecordSetsOutput{ + &ListResourceRecordSetsOutput{ + MaxItems: aws.Int32(10), + NextRecordName: aws.String("testRecord1"), + NextRecordIdentifier: aws.String("testID1"), + NextRecordType: types.RRTypeA, + IsTruncated: true, + }, + &ListResourceRecordSetsOutput{ + MaxItems: aws.Int32(10), + NextRecordName: aws.String("testRecord2"), + NextRecordIdentifier: aws.String("testID2"), + NextRecordType: types.RRTypeA, + IsTruncated: true, + }, + &ListResourceRecordSetsOutput{ + MaxItems: aws.Int32(10), + NextRecordName: aws.String("testRecord3"), + NextRecordIdentifier: aws.String("testID3"), + NextRecordType: types.RRTypeA, + IsTruncated: true, + }, + &ListResourceRecordSetsOutput{ + MaxItems: aws.Int32(10), + NextRecordName: aws.String("testRecord3"), + NextRecordIdentifier: aws.String("testID3"), + NextRecordType: types.RRTypeA, + IsTruncated: true, + }, + &ListResourceRecordSetsOutput{ + MaxItems: aws.Int32(10), + NextRecordName: aws.String("testRecord5"), + NextRecordIdentifier: aws.String("testID5"), + NextRecordType: types.RRTypeA, + IsTruncated: false, + }, + }, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { - client := NewFromConfig(aws.Config{}) + client := mockListResourceRecordSetsClient{ + inputs: []*ListResourceRecordSetsInput{}, + outputs: c.outputs, + t: t, + } - paginator := NewListResourceRecordSetsPaginator(client, &ListResourceRecordSetsInput{ - StartRecordName: c.startRecordName, - StartRecordType: c.startRecordType, - StartRecordIdentifier: c.startRecordIdentifier, - }, func(options *ListResourceRecordSetsPaginatorOptions) { + paginator := NewListResourceRecordSetsPaginator(&client, &ListResourceRecordSetsInput{}, func(options *ListResourceRecordSetsPaginatorOptions) { options.Limit = c.limit + options.StopOnDuplicateToken = c.stopOnDuplicationToken }) - if !paginator.HasMorePages() { - t.Errorf("Expect paginator has more page, got not") + for paginator.HasMorePages() { + _, err := paginator.NextPage(context.TODO()) + if err != nil { + t.Errorf("error: %v", err) + } } - paginator.NextPage(context.TODO(), initializeMiddlewareFn(&testListRRSMiddleware{1})) - - testNextPageResult(c, paginator, t) - }) - } -} - -// insert middleware at the beginning of initialize step to see if page limit and other params -// can be passed to API call's stack input -func initializeMiddlewareFn(initializeMiddleware middleware.InitializeMiddleware) func(*Options) { - return func(options *Options) { - options.APIOptions = append(options.APIOptions, func(stack *middleware.Stack) error { - return stack.Initialize.Add(initializeMiddleware, middleware.Before) + inputLen := len(client.inputs) + if inputLen != c.requestCnt { + t.Errorf("Expect total request number to be %d, got %d", c.requestCnt, inputLen) + } + for i := 1; i < inputLen; i++ { + if *client.inputs[i].StartRecordName != *c.outputs[i-1].NextRecordName { + t.Errorf("Expect next input's RecordName to be eaqul to %s, got %s", + *c.outputs[i-1].NextRecordName, *client.inputs[i].StartRecordName) + } + if *client.inputs[i].StartRecordIdentifier != *c.outputs[i-1].NextRecordIdentifier { + t.Errorf("Expect next input's RecordIdentifier to be eaqul to %s, got %s", + *c.outputs[i-1].NextRecordIdentifier, *client.inputs[i].StartRecordIdentifier) + } + if client.inputs[i].StartRecordType != c.outputs[i-1].NextRecordType { + t.Errorf("Expect next input's RecordType to be eaqul to %s, got %s", + c.outputs[i-1].NextRecordType, client.inputs[i].StartRecordType) + } + } }) } } - -// unit test can not control client API call's output, so just check params' default nil value -func testNextPageResult(c testCase, p *ListResourceRecordSetsPaginator, t *testing.T) { - if c.limit != limit { - t.Errorf("Expect page limit to be %d, got %d", c.limit, limit) - } - if *c.startRecordName != *startRecordName { - t.Errorf("Expect startRecordName to be %s, got %s", *c.startRecordName, *startRecordName) - } - if c.startRecordType != startRecordType { - t.Errorf("Expect startRecordType to be %s, got %s", c.startRecordType, startRecordType) - } - if c.startRecordIdentifier != nil && *c.startRecordIdentifier != *startRecordIdentifier { - t.Errorf("Expect startRecordIdentifier to be %s, got %s", - *c.startRecordIdentifier, *startRecordIdentifier) - } - if p.startRecordName != nil || p.startRecordType != "" || p.startRecordIdentifier != nil { - t.Errorf("Expect paginator record params to be zero value, got %s, %s and %s", - *p.startRecordName, p.startRecordType, *p.startRecordIdentifier) - } -}