From ed4e5c9b0b57c13888977ad8d1d2a0ab0be4ed6d Mon Sep 17 00:00:00 2001 From: Jason Del Ponte Date: Mon, 15 Jan 2018 13:01:24 -0800 Subject: [PATCH] aws: Fix Pagination handling of empty string NextToken (#94) Fixes the SDK's handling of a Pagination NextToken's value being an empty string compared to a nil value. The SDK was expecting NextToken's to always be unset (nil) and treating any non-nil value as a valid value. This was not the case in MediaLive's List APIs. As those APIs return a empty string value instead of null or not setting the field at all. This issue exists in both the v1 and v2 SDKs. Fix #84 --- aws/request_pagination.go | 26 ++++++++-- aws/request_pagination_test.go | 92 +++++++++++++++++++++++++++++++--- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/aws/request_pagination.go b/aws/request_pagination.go index 74ced3d7174..e7edaace165 100644 --- a/aws/request_pagination.go +++ b/aws/request_pagination.go @@ -141,13 +141,29 @@ func (r *Request) nextPageTokens() []interface{} { tokens := []interface{}{} tokenAdded := false for _, outToken := range r.Operation.OutputTokens { - v, _ := awsutil.ValuesAtPath(r.Data, outToken) - if len(v) > 0 { - tokens = append(tokens, v[0]) - tokenAdded = true - } else { + vs, _ := awsutil.ValuesAtPath(r.Data, outToken) + + if len(vs) == 0 { tokens = append(tokens, nil) + continue + } + v := vs[0] + + switch tv := v.(type) { + case *string: + if len(StringValue(tv)) == 0 { + tokens = append(tokens, nil) + continue + } + case string: + if len(tv) == 0 { + tokens = append(tokens, nil) + continue + } } + + tokenAdded = true + tokens = append(tokens, v) } if !tokenAdded { return nil diff --git a/aws/request_pagination_test.go b/aws/request_pagination_test.go index d18f9e787a6..60536a636eb 100644 --- a/aws/request_pagination_test.go +++ b/aws/request_pagination_test.go @@ -511,15 +511,15 @@ func TestPaginationWithContextNilInput(t *testing.T) { } } -type testPageInput struct { - NextToken string -} -type testPageOutput struct { - Value string - NextToken *string -} - func TestPagination_Standalone(t *testing.T) { + type testPageInput struct { + NextToken string + } + type testPageOutput struct { + Value string + NextToken *string + } + expect := []struct { Value, PrevToken, NextToken string }{ @@ -586,6 +586,82 @@ func TestPagination_Standalone(t *testing.T) { } } +func TestPagination_Standalone_Pointers(t *testing.T) { + type testPageInput struct { + NextToken *string + } + type testPageOutput struct { + Value *string + NextToken *string + } + + expect := []struct { + Value, PrevToken, NextToken *string + }{ + {aws.String("FirstValue"), aws.String("InitalToken"), aws.String("FirstToken")}, + {aws.String("SecondValue"), aws.String("FirstToken"), aws.String("SecondToken")}, + {aws.String("ThirdValue"), aws.String("SecondToken"), nil}, + } + input := testPageInput{ + NextToken: expect[0].PrevToken, + } + + c := awstesting.NewClient(unit.Config()) + i := 0 + p := aws.Pagination{ + NewRequest: func() (*aws.Request, error) { + r := c.NewRequest( + &aws.Operation{ + Name: "Operation", + Paginator: &aws.Paginator{ + InputTokens: []string{"NextToken"}, + OutputTokens: []string{"NextToken"}, + }, + }, + &input, &testPageOutput{}, + ) + // Setup handlers for testing + r.Handlers.Clear() + r.Handlers.Build.PushBack(func(req *aws.Request) { + in := req.Params.(*testPageInput) + if e, a := aws.StringValue(expect[i].PrevToken), aws.StringValue(in.NextToken); e != a { + t.Errorf("%d, expect NextToken input %q, got %q", i, e, a) + } + }) + r.Handlers.Unmarshal.PushBack(func(req *aws.Request) { + out := &testPageOutput{ + Value: expect[i].Value, + } + if expect[i].NextToken != nil { + next := *expect[i].NextToken + out.NextToken = aws.String(next) + } + req.Data = out + }) + return r, nil + }, + } + + for p.Next() { + data := p.Page().(*testPageOutput) + + if e, a := expect[i].Value, data.Value; e != a { + t.Errorf("%d, expect Value to be %q, got %q", i, e, a) + } + if e, a := aws.StringValue(expect[i].NextToken), aws.StringValue(data.NextToken); e != a { + t.Errorf("%d, expect NextToken to be %q, got %q", i, e, a) + } + + i++ + } + if e, a := len(expect), i; e != a { + t.Errorf("expected to process %d pages, did %d", e, a) + } + if err := p.Err(); err != nil { + t.Fatalf("%d, expected no error, got %v", i, err) + } +} + // Benchmarks var benchResps = []dynamodb.ListTablesOutput{ {TableNames: []string{"TABLE", "NXT"}, LastEvaluatedTableName: aws.String("NXT")},