mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
12 KiB
433 lines
12 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package pagination_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/internal/boundary"
|
|
"github.com/hashicorp/boundary/internal/db/timestamp"
|
|
"github.com/hashicorp/boundary/internal/listtoken"
|
|
"github.com/hashicorp/boundary/internal/pagination"
|
|
"github.com/hashicorp/boundary/internal/types/resource"
|
|
)
|
|
|
|
// An example resource. In a real example, this would
|
|
// be a Target or Session or similar.
|
|
type ExampleResource struct {
|
|
boundary.Resource
|
|
Value int
|
|
}
|
|
|
|
func (e *ExampleResource) GetPublicId() string {
|
|
return "er_1234567890"
|
|
}
|
|
|
|
func (e *ExampleResource) GetUpdateTime() *timestamp.Timestamp {
|
|
return timestamp.New(time.Now().Add(-10 * time.Hour))
|
|
}
|
|
|
|
func (e *ExampleResource) GetCreateTime() *timestamp.Timestamp {
|
|
return timestamp.New(time.Now().Add(-20 * time.Hour))
|
|
}
|
|
|
|
func (e *ExampleResource) GetResourceType() resource.Type {
|
|
return resource.Target
|
|
}
|
|
|
|
func ExampleList() {
|
|
grantsHash := []byte("hash-of-grants") // Acquired from authorization logic
|
|
pageSize := 10 // From request or service default
|
|
filterItemFunc := func(ctx context.Context, item *ExampleResource) (bool, error) {
|
|
// Inspect item to determine whether we want to
|
|
// include it in the final list.
|
|
return item.Value < 5, nil
|
|
}
|
|
listItemsFunc := func(ctx context.Context, prevPageLast *ExampleResource, limit int) ([]*ExampleResource, time.Time, error) {
|
|
// Do the listing of the resource, generally using a
|
|
// repository method such as target.(*Repository).listTargets.
|
|
// Use the input to set up any options, for example a limit
|
|
// or a starting point.
|
|
if prevPageLast == nil {
|
|
// No previous page item means this is the first list request.
|
|
// List using limit.
|
|
// opts := target.Option{
|
|
// target.WithLimit(limit),
|
|
// }
|
|
} else {
|
|
// Use the previous page last item to start pagination from the
|
|
// next page.
|
|
// opts := target.Option{
|
|
// target.WithLimit(limit),
|
|
// target.WithStartPageAfterItem(prevPageLast),
|
|
// }
|
|
}
|
|
// Example result from the repository
|
|
return []*ExampleResource{
|
|
{nil, 0},
|
|
{nil, 1},
|
|
{nil, 2},
|
|
{nil, 3},
|
|
{nil, 4},
|
|
{nil, 5},
|
|
{nil, 6},
|
|
{nil, 7},
|
|
{nil, 8},
|
|
{nil, 9},
|
|
{nil, 10},
|
|
}, time.Now(), nil
|
|
}
|
|
estimatedCountFunc := func(ctx context.Context) (int, error) {
|
|
// Get an estimate from the database of the total number
|
|
// of entries for this resource, usually using some
|
|
// repository method.
|
|
return 1000, nil
|
|
}
|
|
resp, err := pagination.List(context.Background(), grantsHash, pageSize, filterItemFunc, listItemsFunc, estimatedCountFunc)
|
|
if err != nil {
|
|
fmt.Println("failed to paginate", err)
|
|
return
|
|
}
|
|
fmt.Println("Got results:")
|
|
for _, item := range resp.Items {
|
|
fmt.Printf("\tValue: %d\n", item.Value)
|
|
}
|
|
if resp.CompleteListing {
|
|
fmt.Println("Listing was complete")
|
|
} else {
|
|
fmt.Println("Listing was not complete")
|
|
}
|
|
fmt.Println("There are an estimated", resp.EstimatedItemCount, "total items available")
|
|
// Output: Got results:
|
|
// Value: 0
|
|
// Value: 1
|
|
// Value: 2
|
|
// Value: 3
|
|
// Value: 4
|
|
// Value: 0
|
|
// Value: 1
|
|
// Value: 2
|
|
// Value: 3
|
|
// Value: 4
|
|
// Listing was not complete
|
|
// There are an estimated 1000 total items available
|
|
}
|
|
|
|
func ExampleListPage() {
|
|
grantsHash := []byte("hash-of-grants") // Acquired from authorization logic
|
|
pageSize := 10 // From request or service default
|
|
listToken, err := listtoken.NewPagination( // Normally from incoming request
|
|
context.Background(),
|
|
time.Now(),
|
|
resource.Target,
|
|
grantsHash,
|
|
"ttcp_1234567890",
|
|
time.Now().Add(-time.Hour),
|
|
)
|
|
filterItemFunc := func(ctx context.Context, item *ExampleResource) (bool, error) {
|
|
// Inspect item to determine whether we want to
|
|
// include it in the final list.
|
|
return item.Value < 5, nil
|
|
}
|
|
listItemsFunc := func(ctx context.Context, prevPageLast *ExampleResource, limit int) ([]*ExampleResource, time.Time, error) {
|
|
// Do the listing of the resource, generally using a
|
|
// repository method such as target.(*Repository).listTargets.
|
|
// Use the input to set up any options, for example a limit
|
|
// or a starting point.
|
|
if prevPageLast == nil {
|
|
// No previous page item means this is the first list request.
|
|
// List using limit.
|
|
// opts := target.Option{
|
|
// target.WithLimit(limit),
|
|
// }
|
|
} else {
|
|
// Use the previous page last item to start pagination from the
|
|
// next page.
|
|
// opts := target.Option{
|
|
// target.WithLimit(limit),
|
|
// target.WithStartPageAfterItem(prevPageLast),
|
|
// }
|
|
}
|
|
// Example result from the repository
|
|
return []*ExampleResource{
|
|
{nil, 0},
|
|
{nil, 1},
|
|
{nil, 2},
|
|
{nil, 3},
|
|
{nil, 4},
|
|
{nil, 5},
|
|
{nil, 6},
|
|
{nil, 7},
|
|
{nil, 8},
|
|
{nil, 9},
|
|
{nil, 10},
|
|
}, time.Now(), nil
|
|
}
|
|
estimatedCountFunc := func(ctx context.Context) (int, error) {
|
|
// Get an estimate from the database of the total number
|
|
// of entries for this resource, usually using some
|
|
// repository method.
|
|
return 1000, nil
|
|
}
|
|
resp, err := pagination.ListPage(
|
|
context.Background(),
|
|
grantsHash,
|
|
pageSize,
|
|
filterItemFunc,
|
|
listItemsFunc,
|
|
estimatedCountFunc,
|
|
listToken,
|
|
)
|
|
if err != nil {
|
|
fmt.Println("failed to paginate", err)
|
|
return
|
|
}
|
|
fmt.Println("Got results:")
|
|
for _, item := range resp.Items {
|
|
fmt.Printf("\tValue: %d\n", item.Value)
|
|
}
|
|
if resp.CompleteListing {
|
|
fmt.Println("Listing was complete")
|
|
} else {
|
|
fmt.Println("Listing was not complete")
|
|
}
|
|
fmt.Println("There are an estimated", resp.EstimatedItemCount, "total items available")
|
|
// Output: Got results:
|
|
// Value: 0
|
|
// Value: 1
|
|
// Value: 2
|
|
// Value: 3
|
|
// Value: 4
|
|
// Value: 0
|
|
// Value: 1
|
|
// Value: 2
|
|
// Value: 3
|
|
// Value: 4
|
|
// Listing was not complete
|
|
// There are an estimated 1000 total items available
|
|
}
|
|
|
|
func ExampleListRefresh() {
|
|
grantsHash := []byte("hash-of-grants") // Acquired from authorization logic
|
|
pageSize := 10 // From request or service default
|
|
listToken, err := listtoken.NewStartRefresh( // Normally from incoming request
|
|
context.Background(),
|
|
time.Now(),
|
|
resource.Target,
|
|
grantsHash,
|
|
time.Now().Add(-2*time.Hour),
|
|
time.Now().Add(-time.Hour),
|
|
)
|
|
if err != nil {
|
|
fmt.Println("failed to paginate", err)
|
|
return
|
|
}
|
|
filterItemFunc := func(ctx context.Context, item *ExampleResource) (bool, error) {
|
|
// Inspect item to determine whether we want to
|
|
// include it in the final list.
|
|
return item.Value < 5, nil
|
|
}
|
|
listItemsFunc := func(ctx context.Context, prevPageLast *ExampleResource, limit int) ([]*ExampleResource, time.Time, error) {
|
|
// Do the listing of the resource, generally using a
|
|
// repository method such as target.(*Repository).listTargets.
|
|
// Use the input to set up any options, for example a limit
|
|
// or a starting point.
|
|
if prevPageLast == nil {
|
|
// No previous page item means use the values from the refresh token.
|
|
// lastItem, _ := tok.LastItem()
|
|
// opts := target.Option{
|
|
// target.WithLimit(limit),
|
|
// target.WithStartPageAfterItem(lastItem),
|
|
// }
|
|
} else {
|
|
// Use the previous page last item to start pagination from the next page.
|
|
// opts := target.Option{
|
|
// target.WithLimit(limit),
|
|
// target.WithStartPageAfterItem(prevPageLast),
|
|
// }
|
|
}
|
|
// Example result from the repository
|
|
return []*ExampleResource{
|
|
{nil, 0},
|
|
{nil, 1},
|
|
{nil, 2},
|
|
{nil, 3},
|
|
{nil, 4},
|
|
{nil, 5},
|
|
{nil, 6},
|
|
{nil, 7},
|
|
{nil, 8},
|
|
{nil, 9},
|
|
{nil, 10},
|
|
}, time.Now(), nil
|
|
}
|
|
estimatedCountFunc := func(ctx context.Context) (int, error) {
|
|
// Get an estimate from the database of the total number
|
|
// of entries for this resource, usually using some
|
|
// repository method.
|
|
return 1000, nil
|
|
}
|
|
deletedIdsFunc := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
|
// Return IDs of resources that have been deleted since the provided timestamp.
|
|
return []string{"er_0123456789"}, time.Now(), nil
|
|
}
|
|
resp, err := pagination.ListRefresh(
|
|
context.Background(),
|
|
grantsHash,
|
|
pageSize,
|
|
filterItemFunc,
|
|
listItemsFunc,
|
|
estimatedCountFunc,
|
|
deletedIdsFunc,
|
|
listToken,
|
|
)
|
|
if err != nil {
|
|
fmt.Println("failed to paginate", err)
|
|
return
|
|
}
|
|
fmt.Println("Got results:")
|
|
for _, item := range resp.Items {
|
|
fmt.Printf("\tValue: %d\n", item.Value)
|
|
}
|
|
if resp.CompleteListing {
|
|
fmt.Println("Listing was complete")
|
|
} else {
|
|
fmt.Println("Listing was not complete")
|
|
}
|
|
fmt.Println("There are an estimated", resp.EstimatedItemCount, "total items available")
|
|
fmt.Println("The following resources have been deleted since we last saw them:")
|
|
for _, id := range resp.DeletedIds {
|
|
fmt.Println("\t" + id)
|
|
}
|
|
// Output: Got results:
|
|
// Value: 0
|
|
// Value: 1
|
|
// Value: 2
|
|
// Value: 3
|
|
// Value: 4
|
|
// Value: 0
|
|
// Value: 1
|
|
// Value: 2
|
|
// Value: 3
|
|
// Value: 4
|
|
// Listing was not complete
|
|
// There are an estimated 1000 total items available
|
|
// The following resources have been deleted since we last saw them:
|
|
// er_0123456789
|
|
}
|
|
|
|
func ExampleListRefreshPage() {
|
|
grantsHash := []byte("hash-of-grants") // Acquired from authorization logic
|
|
pageSize := 10 // From request or service default
|
|
listToken, err := listtoken.NewRefresh( // Normally from incoming request
|
|
context.Background(),
|
|
time.Now(),
|
|
resource.Target,
|
|
grantsHash,
|
|
time.Now().Add(-time.Hour),
|
|
time.Now().Add(-2*time.Hour),
|
|
time.Now().Add(-3*time.Hour),
|
|
"ttcp_1234567890",
|
|
time.Now().Add(-4*time.Hour),
|
|
)
|
|
if err != nil {
|
|
fmt.Println("failed to paginate", err)
|
|
return
|
|
}
|
|
filterItemFunc := func(ctx context.Context, item *ExampleResource) (bool, error) {
|
|
// Inspect item to determine whether we want to
|
|
// include it in the final list.
|
|
return item.Value < 5, nil
|
|
}
|
|
listItemsFunc := func(ctx context.Context, prevPageLast *ExampleResource, limit int) ([]*ExampleResource, time.Time, error) {
|
|
// Do the listing of the resource, generally using a
|
|
// repository method such as target.(*Repository).listTargets.
|
|
// Use the input to set up any options, for example a limit
|
|
// or a starting point.
|
|
if prevPageLast == nil {
|
|
// No previous page item means use the values from the refresh token.
|
|
// lastItem, _ := tok.LastItem()
|
|
// opts := target.Option{
|
|
// target.WithLimit(limit),
|
|
// target.WithStartPageAfterItem(lastItem),
|
|
// }
|
|
} else {
|
|
// Use the previous page last item to start pagination from the next page.
|
|
// opts := target.Option{
|
|
// target.WithLimit(limit),
|
|
// target.WithStartPageAfterItem(prevPageLast),
|
|
// }
|
|
}
|
|
// Example result from the repository
|
|
return []*ExampleResource{
|
|
{nil, 0},
|
|
{nil, 1},
|
|
{nil, 2},
|
|
{nil, 3},
|
|
{nil, 4},
|
|
{nil, 5},
|
|
{nil, 6},
|
|
{nil, 7},
|
|
{nil, 8},
|
|
{nil, 9},
|
|
{nil, 10},
|
|
}, time.Now(), nil
|
|
}
|
|
estimatedCountFunc := func(ctx context.Context) (int, error) {
|
|
// Get an estimate from the database of the total number
|
|
// of entries for this resource, usually using some
|
|
// repository method.
|
|
return 1000, nil
|
|
}
|
|
deletedIdsFunc := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
|
// Return IDs of resources that have been deleted since the provided timestamp.
|
|
return []string{"er_0123456789"}, time.Now(), nil
|
|
}
|
|
resp, err := pagination.ListRefreshPage(
|
|
context.Background(),
|
|
grantsHash,
|
|
pageSize,
|
|
filterItemFunc,
|
|
listItemsFunc,
|
|
estimatedCountFunc,
|
|
deletedIdsFunc,
|
|
listToken,
|
|
)
|
|
if err != nil {
|
|
fmt.Println("failed to paginate", err)
|
|
return
|
|
}
|
|
fmt.Println("Got results:")
|
|
for _, item := range resp.Items {
|
|
fmt.Printf("\tValue: %d\n", item.Value)
|
|
}
|
|
if resp.CompleteListing {
|
|
fmt.Println("Listing was complete")
|
|
} else {
|
|
fmt.Println("Listing was not complete")
|
|
}
|
|
fmt.Println("There are an estimated", resp.EstimatedItemCount, "total items available")
|
|
fmt.Println("The following resources have been deleted since we last saw them:")
|
|
for _, id := range resp.DeletedIds {
|
|
fmt.Println("\t" + id)
|
|
}
|
|
// Output: Got results:
|
|
// Value: 0
|
|
// Value: 1
|
|
// Value: 2
|
|
// Value: 3
|
|
// Value: 4
|
|
// Value: 0
|
|
// Value: 1
|
|
// Value: 2
|
|
// Value: 3
|
|
// Value: 4
|
|
// Listing was not complete
|
|
// There are an estimated 1000 total items available
|
|
// The following resources have been deleted since we last saw them:
|
|
// er_0123456789
|
|
}
|