Skip to content

Commit

Permalink
Iterator preserves last delta link (#192)
Browse files Browse the repository at this point in the history
* Iterator preserves delta link

* Update CHANGELOG
  • Loading branch information
eric-millin authored May 3, 2023
1 parent 278b468 commit edadb21
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

## [0.36.2] - 2023-05-01

### Added

- `PageIterator` exposes `odata.nextLink` and `odata.deltaLink` of most recent page.

## [0.36.1] - 2023-04-17

### Added
Expand Down
65 changes: 65 additions & 0 deletions internal/user_delta_response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package internal

import (
i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55 "github.com/microsoft/kiota-abstractions-go/serialization"
)

// UsersDeltaResponse
type UsersDeltaResponse struct {
//
UsersResponse
//
odataDeltaLink *string
}

// NewDeltasResponse instantiates a new usersResponse and sets the default values.
func NewUsersDeltaResponse() *UsersDeltaResponse {
m := &UsersDeltaResponse{
UsersResponse: *NewUsersResponse(),
}
return m
}

// GetOdataDeltaLink gets the @odata.nextLink property value.
func (m *UsersDeltaResponse) GetOdataDeltaLink() *string {
if m == nil {
return nil
} else {
return m.odataDeltaLink
}
}

// GetFieldDeserializers the deserialization information for the current model
func (m *UsersDeltaResponse) GetFieldDeserializers() map[string]func(i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
res := m.UsersResponse.GetFieldDeserializers()
res["@odata.deltaLink"] = func(n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
val, err := n.GetStringValue()
if err != nil {
return err
}
if val != nil {
m.SetOdataDeltaLink(val)
}
return nil
}
return res
}

// Serialize serializes information the current object
func (m *UsersDeltaResponse) Serialize(writer i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.SerializationWriter) error {
{
err := writer.WriteStringValue("@odata.deltaLink", m.GetOdataDeltaLink())
if err != nil {
return err
}
}

return m.UsersResponse.Serialize(writer)
}

// SetOdataDeltaLink sets the @odata.deltaLink property value.
func (m *UsersDeltaResponse) SetOdataDeltaLink(value *string) {
if m != nil {
m.odataDeltaLink = value
}
}
46 changes: 34 additions & 12 deletions page_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package msgraphgocore
import (
"context"
"errors"
abstractions "github.com/microsoft/kiota-abstractions-go"
"github.com/microsoft/kiota-abstractions-go/serialization"
"net/url"
"reflect"

abstractions "github.com/microsoft/kiota-abstractions-go"
"github.com/microsoft/kiota-abstractions-go/serialization"
)

// PageIterator represents an iterator object that can be used to get subsequent pages of a collection.
Expand All @@ -21,8 +22,9 @@ type PageIterator[T interface{}] struct {

// PageResult represents a page object built from a graph response object
type PageResult[T interface{}] struct {
oDataNextLink *string
value []T
oDataNextLink *string
oDataDeltaLink *string
value []T
}

func (p *PageResult[T]) getValue() []T {
Expand Down Expand Up @@ -87,6 +89,10 @@ func (pI *PageIterator[T]) Iterate(context context.Context, callback func(pageIt
return nil
}

if pI.currentPage.getOdataNextLink() == nil || *pI.currentPage.getOdataNextLink() == "" {
return nil
}

nextPage, err := pI.next(context)
if err != nil {
return err
Expand All @@ -109,13 +115,19 @@ func (pI *PageIterator[T]) SetReqOptions(reqOptions []abstractions.RequestOption
pI.reqOptions = reqOptions
}

// GetOdataNextLink returns the @odata.nextLink value in the current page result.
func (pI *PageIterator[T]) GetOdataNextLink() *string {
return pI.currentPage.oDataNextLink
}

// GetOdataDeltaLink returns the @odata.deltaLink value in current paged result.
func (pI *PageIterator[T]) GetOdataDeltaLink() *string {
return pI.currentPage.oDataDeltaLink
}

func (pI *PageIterator[T]) next(context context.Context) (PageResult[T], error) {
var page PageResult[T]

if pI.currentPage.getOdataNextLink() == nil || *pI.currentPage.getOdataNextLink() == "" {
return page, nil
}

resp, err := pI.fetchNextPage(context)
if err != nil {
return page, err
Expand Down Expand Up @@ -174,11 +186,11 @@ func (pI *PageIterator[T]) enumerate(callback func(item T) bool) bool {
for i := pI.pauseIndex; i < len(pageItems); i++ {
keepIterating = callback(pageItems[i])

// Set pauseIndex so that we know where to resume from.
// Resumes from the next item
pI.pauseIndex = i + 1

if !keepIterating {
// Callback returned false, pause! stop enumerating page items. Set pauseIndex so that we know
// where to resume from.
// Resumes from the next item
pI.pauseIndex = i + 1
break
}
}
Expand All @@ -191,6 +203,11 @@ type PageWithOdataNextLink interface {
GetOdataNextLink() *string
}

// PageWithOdataDeltaLink represents a contract with the GetOdataDeltaLink() method
type PageWithOdataDeltaLink interface {
GetOdataDeltaLink() *string
}

func convertToPage[T interface{}](response interface{}) (PageResult[T], error) {
var page PageResult[T]

Expand All @@ -216,6 +233,11 @@ func convertToPage[T interface{}](response interface{}) (PageResult[T], error) {
return page, errors.New("response does not have next link accessor")
}

deltablePage, ok := response.(PageWithOdataDeltaLink)
if ok {
page.oDataDeltaLink = deltablePage.GetOdataDeltaLink()
}

page.oDataNextLink = parsablePage.GetOdataNextLink()
page.value = collected

Expand Down
73 changes: 73 additions & 0 deletions page_iterator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ func ParsableCons(pn serialization.ParseNode) (serialization.Parsable, error) {
return internal.NewUsersResponse(), nil
}

func ParsableDeltaCons(pn serialization.ParseNode) (serialization.Parsable, error) {
return internal.NewUsersDeltaResponse(), nil
}

func TestConstructorWithInvalidRequestAdapter(t *testing.T) {
graphResponse := internal.NewUsersResponse()

Expand Down Expand Up @@ -160,13 +164,82 @@ func TestIterateCanBePausedAndResumed(t *testing.T) {
})

assert.Equal(t, res, []string{"0", "1", "2", "3", "4"})
assert.Equal(t, pageIterator.GetOdataNextLink(), response.GetOdataNextLink())

pageIterator.Iterate(context.Background(), func(item internal.User) bool {
res2 = append(res2, *item.GetId())

return true
})
assert.Equal(t, res2, []string{"10"})
assert.Empty(t, pageIterator.GetOdataNextLink())

pageIterator.Iterate(context.Background(), func(item internal.User) bool {
assert.Fail(t, "Should not re-iterate over items")
return true
})
}

func TestGetOdataNextLink(t *testing.T) {
testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `
{
"@odata.nextLink": "",
"value": [
{
"id": "10"
}
]
}
`)
}))
defer testServer.Close()

graphResponse := buildGraphResponse()
mockPath := testServer.URL + "/next-page"
graphResponse.SetOdataNextLink(&mockPath)

pageIterator, _ := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableDeltaCons)
pageIterator.Iterate(context.Background(), func(item internal.User) bool {
return true
})

assert.Empty(t, pageIterator.GetOdataNextLink())
}

func TestGetOdataDeltaLink(t *testing.T) {
testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `
{
"@odata.nextLink": "",
"@odata.deltaLink": "delta-page-2",
"value": [
{
"id": "10"
}
]
}
`)
}))
defer testServer.Close()

dl := "delta-page-1"
mockPath := testServer.URL + "/next-page"

graphResponse := &internal.UsersDeltaResponse{
UsersResponse: *buildGraphResponse(),
}
graphResponse.SetOdataDeltaLink(&dl)
graphResponse.SetOdataNextLink(&mockPath)

pageIterator, _ := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableDeltaCons)
pageIterator.Iterate(context.Background(), func(item internal.User) bool {
return true
})

assert.Equal(t, *pageIterator.GetOdataDeltaLink(), "delta-page-2")
}

func buildGraphResponse() *internal.UsersResponse {
Expand Down

0 comments on commit edadb21

Please sign in to comment.