github.com/aavshr/aws-sdk-go@v1.41.3/aws/request/request_pagination.go (about)

     1  package request
     2  
     3  import (
     4  	"reflect"
     5  	"sync/atomic"
     6  
     7  	"github.com/aavshr/aws-sdk-go/aws"
     8  	"github.com/aavshr/aws-sdk-go/aws/awsutil"
     9  )
    10  
    11  // A Pagination provides paginating of SDK API operations which are paginatable.
    12  // Generally you should not use this type directly, but use the "Pages" API
    13  // operations method to automatically perform pagination for you. Such as,
    14  // "S3.ListObjectsPages", and "S3.ListObjectsPagesWithContext" methods.
    15  //
    16  // Pagination differs from a Paginator type in that pagination is the type that
    17  // does the pagination between API operations, and Paginator defines the
    18  // configuration that will be used per page request.
    19  //
    20  //     for p.Next() {
    21  //         data := p.Page().(*s3.ListObjectsOutput)
    22  //         // process the page's data
    23  //         // ...
    24  //         // break out of loop to stop fetching additional pages
    25  //     }
    26  //
    27  //     return p.Err()
    28  //
    29  // See service client API operation Pages methods for examples how the SDK will
    30  // use the Pagination type.
    31  type Pagination struct {
    32  	// Function to return a Request value for each pagination request.
    33  	// Any configuration or handlers that need to be applied to the request
    34  	// prior to getting the next page should be done here before the request
    35  	// returned.
    36  	//
    37  	// NewRequest should always be built from the same API operations. It is
    38  	// undefined if different API operations are returned on subsequent calls.
    39  	NewRequest func() (*Request, error)
    40  	// EndPageOnSameToken, when enabled, will allow the paginator to stop on
    41  	// token that are the same as its previous tokens.
    42  	EndPageOnSameToken bool
    43  
    44  	started    bool
    45  	prevTokens []interface{}
    46  	nextTokens []interface{}
    47  
    48  	err     error
    49  	curPage interface{}
    50  }
    51  
    52  // HasNextPage will return true if Pagination is able to determine that the API
    53  // operation has additional pages. False will be returned if there are no more
    54  // pages remaining.
    55  //
    56  // Will always return true if Next has not been called yet.
    57  func (p *Pagination) HasNextPage() bool {
    58  	if !p.started {
    59  		return true
    60  	}
    61  
    62  	hasNextPage := len(p.nextTokens) != 0
    63  	if p.EndPageOnSameToken {
    64  		return hasNextPage && !awsutil.DeepEqual(p.nextTokens, p.prevTokens)
    65  	}
    66  	return hasNextPage
    67  }
    68  
    69  // Err returns the error Pagination encountered when retrieving the next page.
    70  func (p *Pagination) Err() error {
    71  	return p.err
    72  }
    73  
    74  // Page returns the current page. Page should only be called after a successful
    75  // call to Next. It is undefined what Page will return if Page is called after
    76  // Next returns false.
    77  func (p *Pagination) Page() interface{} {
    78  	return p.curPage
    79  }
    80  
    81  // Next will attempt to retrieve the next page for the API operation. When a page
    82  // is retrieved true will be returned. If the page cannot be retrieved, or there
    83  // are no more pages false will be returned.
    84  //
    85  // Use the Page method to retrieve the current page data. The data will need
    86  // to be cast to the API operation's output type.
    87  //
    88  // Use the Err method to determine if an error occurred if Page returns false.
    89  func (p *Pagination) Next() bool {
    90  	if !p.HasNextPage() {
    91  		return false
    92  	}
    93  
    94  	req, err := p.NewRequest()
    95  	if err != nil {
    96  		p.err = err
    97  		return false
    98  	}
    99  
   100  	if p.started {
   101  		for i, intok := range req.Operation.InputTokens {
   102  			awsutil.SetValueAtPath(req.Params, intok, p.nextTokens[i])
   103  		}
   104  	}
   105  	p.started = true
   106  
   107  	err = req.Send()
   108  	if err != nil {
   109  		p.err = err
   110  		return false
   111  	}
   112  
   113  	p.prevTokens = p.nextTokens
   114  	p.nextTokens = req.nextPageTokens()
   115  	p.curPage = req.Data
   116  
   117  	return true
   118  }
   119  
   120  // A Paginator is the configuration data that defines how an API operation
   121  // should be paginated. This type is used by the API service models to define
   122  // the generated pagination config for service APIs.
   123  //
   124  // The Pagination type is what provides iterating between pages of an API. It
   125  // is only used to store the token metadata the SDK should use for performing
   126  // pagination.
   127  type Paginator struct {
   128  	InputTokens     []string
   129  	OutputTokens    []string
   130  	LimitToken      string
   131  	TruncationToken string
   132  }
   133  
   134  // nextPageTokens returns the tokens to use when asking for the next page of data.
   135  func (r *Request) nextPageTokens() []interface{} {
   136  	if r.Operation.Paginator == nil {
   137  		return nil
   138  	}
   139  	if r.Operation.TruncationToken != "" {
   140  		tr, _ := awsutil.ValuesAtPath(r.Data, r.Operation.TruncationToken)
   141  		if len(tr) == 0 {
   142  			return nil
   143  		}
   144  
   145  		switch v := tr[0].(type) {
   146  		case *bool:
   147  			if !aws.BoolValue(v) {
   148  				return nil
   149  			}
   150  		case bool:
   151  			if !v {
   152  				return nil
   153  			}
   154  		}
   155  	}
   156  
   157  	tokens := []interface{}{}
   158  	tokenAdded := false
   159  	for _, outToken := range r.Operation.OutputTokens {
   160  		vs, _ := awsutil.ValuesAtPath(r.Data, outToken)
   161  		if len(vs) == 0 {
   162  			tokens = append(tokens, nil)
   163  			continue
   164  		}
   165  		v := vs[0]
   166  
   167  		switch tv := v.(type) {
   168  		case *string:
   169  			if len(aws.StringValue(tv)) == 0 {
   170  				tokens = append(tokens, nil)
   171  				continue
   172  			}
   173  		case string:
   174  			if len(tv) == 0 {
   175  				tokens = append(tokens, nil)
   176  				continue
   177  			}
   178  		}
   179  
   180  		tokenAdded = true
   181  		tokens = append(tokens, v)
   182  	}
   183  	if !tokenAdded {
   184  		return nil
   185  	}
   186  
   187  	return tokens
   188  }
   189  
   190  // Ensure a deprecated item is only logged once instead of each time its used.
   191  func logDeprecatedf(logger aws.Logger, flag *int32, msg string) {
   192  	if logger == nil {
   193  		return
   194  	}
   195  	if atomic.CompareAndSwapInt32(flag, 0, 1) {
   196  		logger.Log(msg)
   197  	}
   198  }
   199  
   200  var (
   201  	logDeprecatedHasNextPage int32
   202  	logDeprecatedNextPage    int32
   203  	logDeprecatedEachPage    int32
   204  )
   205  
   206  // HasNextPage returns true if this request has more pages of data available.
   207  //
   208  // Deprecated Use Pagination type for configurable pagination of API operations
   209  func (r *Request) HasNextPage() bool {
   210  	logDeprecatedf(r.Config.Logger, &logDeprecatedHasNextPage,
   211  		"Request.HasNextPage deprecated. Use Pagination type for configurable pagination of API operations")
   212  
   213  	return len(r.nextPageTokens()) > 0
   214  }
   215  
   216  // NextPage returns a new Request that can be executed to return the next
   217  // page of result data. Call .Send() on this request to execute it.
   218  //
   219  // Deprecated Use Pagination type for configurable pagination of API operations
   220  func (r *Request) NextPage() *Request {
   221  	logDeprecatedf(r.Config.Logger, &logDeprecatedNextPage,
   222  		"Request.NextPage deprecated. Use Pagination type for configurable pagination of API operations")
   223  
   224  	tokens := r.nextPageTokens()
   225  	if len(tokens) == 0 {
   226  		return nil
   227  	}
   228  
   229  	data := reflect.New(reflect.TypeOf(r.Data).Elem()).Interface()
   230  	nr := New(r.Config, r.ClientInfo, r.Handlers, r.Retryer, r.Operation, awsutil.CopyOf(r.Params), data)
   231  	for i, intok := range nr.Operation.InputTokens {
   232  		awsutil.SetValueAtPath(nr.Params, intok, tokens[i])
   233  	}
   234  	return nr
   235  }
   236  
   237  // EachPage iterates over each page of a paginated request object. The fn
   238  // parameter should be a function with the following sample signature:
   239  //
   240  //   func(page *T, lastPage bool) bool {
   241  //       return true // return false to stop iterating
   242  //   }
   243  //
   244  // Where "T" is the structure type matching the output structure of the given
   245  // operation. For example, a request object generated by
   246  // DynamoDB.ListTablesRequest() would expect to see dynamodb.ListTablesOutput
   247  // as the structure "T". The lastPage value represents whether the page is
   248  // the last page of data or not. The return value of this function should
   249  // return true to keep iterating or false to stop.
   250  //
   251  // Deprecated Use Pagination type for configurable pagination of API operations
   252  func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error {
   253  	logDeprecatedf(r.Config.Logger, &logDeprecatedEachPage,
   254  		"Request.EachPage deprecated. Use Pagination type for configurable pagination of API operations")
   255  
   256  	for page := r; page != nil; page = page.NextPage() {
   257  		if err := page.Send(); err != nil {
   258  			return err
   259  		}
   260  		if getNextPage := fn(page.Data, !page.HasNextPage()); !getNextPage {
   261  			return page.Error
   262  		}
   263  	}
   264  
   265  	return nil
   266  }