diff --git a/.gitignore b/.gitignore index 66fd13c..0dcba2d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# Jetbrains files +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 667268c..6c0e9e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Adds missing delta token for OData query parameters dollar sign injection. +- Adds PageIterator task ### Changed diff --git a/go.mod b/go.mod index 55a6e3d..51d13fe 100644 --- a/go.mod +++ b/go.mod @@ -9,14 +9,11 @@ require ( github.com/stretchr/testify v1.7.0 ) +require github.com/microsoft/kiota/serialization/go/json v0.0.0-20220118084409-ae624d0bd384 + require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/microsoft/kiota/authentication/go/azure v0.0.0-20211201125630-3501743a5dc5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/yosida95/uritemplate/v3 v3.0.1 // indirect - golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect - golang.org/x/text v0.3.7 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index b19c9fc..8ef0d43 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,15 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0 h1:KQgdWmEOmaJKxaUUZwHAYh12t+b+ZJf8q3friycK1kA= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0/go.mod h1:ZPW/Z0kLCTdDZaDbYTetxc9Cxl/2lNqxYHYNOF2bti0= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.2 h1:rImM7Yjz9yUgpdxp3A4cZLm1JZuo4XbtIp2LrUZnwYw= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.2/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/microsoft/kiota/abstractions/go v0.0.0-20211129093841-858bd540489b/go.mod h1:pIT6aBVb0aHisY52bSbYvb0nyxe+TqdZ8lkpKB7pLIo= -github.com/microsoft/kiota/abstractions/go v0.0.0-20211130101617-a4871ba0f35f h1:nmvVToTSNolrvh7OBJjmVJSyZ+VB9Ql2I5chDAqhPWc= -github.com/microsoft/kiota/abstractions/go v0.0.0-20211130101617-a4871ba0f35f/go.mod h1:m37MbLdeSZ7DBzKtGI+GmlDSIybJBkKhsvLMqkYosb8= -github.com/microsoft/kiota/abstractions/go v0.0.0-20211201125630-3501743a5dc5 h1:IL8rUlP+UMFRvL/bu26HkVMnhU7I5IWKciDIWOAQfEQ= -github.com/microsoft/kiota/abstractions/go v0.0.0-20211201125630-3501743a5dc5/go.mod h1:m37MbLdeSZ7DBzKtGI+GmlDSIybJBkKhsvLMqkYosb8= github.com/microsoft/kiota/abstractions/go v0.0.0-20211202082735-099f3c37853a h1:c2vBeXjRwGJUL7aG1gHFwB+LpdDL20XKIOKQWKUoNQk= github.com/microsoft/kiota/abstractions/go v0.0.0-20211202082735-099f3c37853a/go.mod h1:m37MbLdeSZ7DBzKtGI+GmlDSIybJBkKhsvLMqkYosb8= -github.com/microsoft/kiota/authentication/go/azure v0.0.0-20211201125630-3501743a5dc5 h1:d1ilFuzPOvADm4xiFHJ1SZ00SufAgyqfVXd3TCtS8c0= -github.com/microsoft/kiota/authentication/go/azure v0.0.0-20211201125630-3501743a5dc5/go.mod h1:bmdumwBCIHlVdafcpHNNGzySN/v2mImqWTPgV1adBmA= -github.com/microsoft/kiota/http/go/nethttp v0.0.0-20211130101617-a4871ba0f35f h1:1VRJ6bj+YljEUqXRf+MgPWdfJZf85/juVCwkS7GviZ8= -github.com/microsoft/kiota/http/go/nethttp v0.0.0-20211130101617-a4871ba0f35f/go.mod h1:GHdBYJaaiuZKz78KcLSrjcin5FGXUK3e0wlX69NJMwE= -github.com/microsoft/kiota/http/go/nethttp v0.0.0-20211202082735-099f3c37853a h1:w93KbR22weJ4CKHepKHkirx18j0XMShPWjSRn5ziMwA= -github.com/microsoft/kiota/http/go/nethttp v0.0.0-20211202082735-099f3c37853a/go.mod h1:GHdBYJaaiuZKz78KcLSrjcin5FGXUK3e0wlX69NJMwE= github.com/microsoft/kiota/http/go/nethttp v0.0.0-20211203130928-8449c9e67101 h1:kTG9Tg52O2gNm8LzwyiPyjFendknhU4iqnylDjKQgNU= github.com/microsoft/kiota/http/go/nethttp v0.0.0-20211203130928-8449c9e67101/go.mod h1:GHdBYJaaiuZKz78KcLSrjcin5FGXUK3e0wlX69NJMwE= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/microsoft/kiota/serialization/go/json v0.0.0-20220118084409-ae624d0bd384 h1:Ry9Nxq2JKda89pd3E9v3y5N8xN8dDnj4MDyP9SM5Nik= +github.com/microsoft/kiota/serialization/go/json v0.0.0-20220118084409-ae624d0bd384/go.mod h1:IpPcnMiN1topbt3+Tk2NAZiwvMI2Sa+xpSapGHC3wyw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -32,29 +17,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yosida95/uritemplate/v3 v3.0.1 h1:+Fs//CsT+x231WmUQhMHWMxZizMvpnkOVWop02mVCfs= github.com/yosida95/uritemplate/v3 v3.0.1/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI= -golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/user.go b/internal/user.go new file mode 100644 index 0000000..28d402f --- /dev/null +++ b/internal/user.go @@ -0,0 +1,171 @@ +package internal + +import ( + i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e "time" + + i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55 "github.com/microsoft/kiota/abstractions/go/serialization" +) + +type User struct { + DisplayName *string + DirectoryObject +} + +func (u *User) GetDisplayName() *string { + return u.DisplayName +} + +var displayName = "A User" + +func NewUser() *User { + return &User{ + DisplayName: &displayName, + } +} + +type DirectoryObject struct { + Entity + // + deletedDateTime *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time +} + +// NewDirectoryObject instantiates a new directoryObject and sets the default values. +func NewDirectoryObject() *DirectoryObject { + m := &DirectoryObject{ + Entity: *NewEntity(), + } + return m +} + +// GetDeletedDateTime gets the deletedDateTime property value. +func (m *DirectoryObject) GetDeletedDateTime() *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time { + if m == nil { + return nil + } else { + return m.deletedDateTime + } +} + +// GetFieldDeserializers the deserialization information for the current model +func (m *DirectoryObject) GetFieldDeserializers() map[string]func(interface{}, i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error { + res := m.Entity.GetFieldDeserializers() + res["deletedDateTime"] = func(o interface{}, n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error { + val, err := n.GetTimeValue() + if err != nil { + return err + } + if val != nil { + m.SetDeletedDateTime(val) + } + return nil + } + return res +} +func (m *DirectoryObject) IsNil() bool { + return m == nil +} + +// Serialize serializes information the current object +func (m *DirectoryObject) Serialize(writer i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.SerializationWriter) error { + err := m.Entity.Serialize(writer) + if err != nil { + return err + } + { + err = writer.WriteTimeValue("deletedDateTime", m.GetDeletedDateTime()) + if err != nil { + return err + } + } + return nil +} + +// SetDeletedDateTime sets the deletedDateTime property value. +func (m *DirectoryObject) SetDeletedDateTime(value *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time) { + if m != nil { + m.deletedDateTime = value + } +} + +// Entity +type Entity struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]interface{} + // Read-only. + id *string +} + +// NewEntity instantiates a new entity and sets the default values. +func NewEntity() *Entity { + m := &Entity{} + m.SetAdditionalData(make(map[string]interface{})) + return m +} + +// GetAdditionalData gets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Entity) GetAdditionalData() map[string]interface{} { + if m == nil { + return nil + } else { + return m.additionalData + } +} + +// GetId gets the id property value. Read-only. +func (m *Entity) GetId() *string { + if m == nil { + return nil + } else { + return m.id + } +} + +// GetFieldDeserializers the deserialization information for the current model +func (m *Entity) GetFieldDeserializers() map[string]func(interface{}, i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error { + res := make(map[string]func(interface{}, i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error) + res["id"] = func(o interface{}, n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetId(val) + } + return nil + } + return res +} +func (m *Entity) IsNil() bool { + return m == nil +} + +// Serialize serializes information the current object +func (m *Entity) Serialize(writer i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.SerializationWriter) error { + { + err := writer.WriteStringValue("id", m.GetId()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Entity) SetAdditionalData(value map[string]interface{}) { + if m != nil { + m.additionalData = value + } +} + +// SetId sets the id property value. Read-only. +func (m *Entity) SetId(value *string) { + if m != nil { + m.id = value + } +} diff --git a/internal/user_response.go b/internal/user_response.go new file mode 100644 index 0000000..314fc45 --- /dev/null +++ b/internal/user_response.go @@ -0,0 +1,133 @@ +package internal + +import ( + i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55 "github.com/microsoft/kiota/abstractions/go/serialization" +) + +// UsersResponse +type UsersResponse struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]interface{} + // + nextLink *string + // + value []User +} + +// NewUsersResponse instantiates a new usersResponse and sets the default values. +func NewUsersResponse() *UsersResponse { + m := &UsersResponse{} + m.SetAdditionalData(make(map[string]interface{})) + return m +} + +// GetAdditionalData gets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *UsersResponse) GetAdditionalData() map[string]interface{} { + if m == nil { + return nil + } else { + return m.additionalData + } +} + +// GetNextLink gets the @odata.nextLink property value. +func (m *UsersResponse) GetNextLink() *string { + if m == nil { + return nil + } else { + return m.nextLink + } +} + +// GetValue gets the value property value. +func (m *UsersResponse) GetValue() []User { + if m == nil { + return nil + } else { + return m.value + } +} + +// GetFieldDeserializers the deserialization information for the current model +func (m *UsersResponse) GetFieldDeserializers() map[string]func(interface{}, i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error { + res := make(map[string]func(interface{}, i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error) + res["@odata.nextLink"] = func(o interface{}, n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetNextLink(val) + } + return nil + } + res["value"] = func(o interface{}, n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(func() i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.Parsable { + return NewUser() + }) + if err != nil { + return err + } + if val != nil { + res := make([]User, len(val)) + for i, v := range val { + res[i] = *(v.(*User)) + } + m.SetValue(res) + } + return nil + } + return res +} +func (m *UsersResponse) IsNil() bool { + return m == nil +} + +// Serialize serializes information the current object +func (m *UsersResponse) Serialize(writer i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.SerializationWriter) error { + { + err := writer.WriteStringValue("@odata.nextLink", m.GetNextLink()) + if err != nil { + return err + } + } + { + cast := make([]i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.Parsable, len(m.GetValue())) + for i, v := range m.GetValue() { + temp := v + cast[i] = i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.Parsable(&temp) + } + err := writer.WriteCollectionOfObjectValues("value", cast) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *UsersResponse) SetAdditionalData(value map[string]interface{}) { + if m != nil { + m.additionalData = value + } +} + +// SetNextLink sets the @odata.nextLink property value. +func (m *UsersResponse) SetNextLink(value *string) { + if m != nil { + m.nextLink = value + } +} + +// SetValue sets the value property value. +func (m *UsersResponse) SetValue(value []User) { + if m != nil { + m.value = value + } +} diff --git a/page_iterator.go b/page_iterator.go new file mode 100644 index 0000000..15cdca7 --- /dev/null +++ b/page_iterator.go @@ -0,0 +1,214 @@ +package msgraphgocore + +import ( + "errors" + "net/url" + "reflect" + "unsafe" + + 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. +type PageIterator struct { + currentPage *PageResult + reqAdapter GraphRequestAdapterBase + pauseIndex int + constructorFunc ParsableConstructor + headers map[string]string + reqOptions []abstractions.RequestOption +} + +type ParsableConstructor func() serialization.Parsable + +// PageResult represents a page object built from a graph response object +type PageResult struct { + nextLink *string + value []interface{} +} + +func (p *PageResult) getValue() []interface{} { + if p == nil { + return nil + } + + return p.value +} + +func (p *PageResult) getNextLink() *string { + if p == nil { + return nil + } + + return p.nextLink +} + +// NewpageIterator creates an iterator instance +// +// It has three parameters. res is the graph response from the initial request and represents the first page. +// reqAdapter is used for getting the next page and constructorFunc is used for serializing next page's response to the specified type. +func NewPageIterator(res interface{}, reqAdapter GraphRequestAdapterBase, constructorFunc ParsableConstructor) (*PageIterator, error) { + page, err := convertToPage(res) + if err != nil { + return nil, err + } + + return &PageIterator{ + currentPage: page, + reqAdapter: reqAdapter, + pauseIndex: 0, + constructorFunc: constructorFunc, + headers: map[string]string{}, + }, nil +} + +// Iterate traverses all pages and enumerates all items in the current page and returns an error if something goes wrong. +// +// Iterate receives a callback function which is called with each item in the current page as an argument. The callback function +// returns a boolean. To traverse and enumerate all pages always return true and to pause traversal and enumeration +// return false from the callback. +// +// Example +// pageIterator, err := NewPageIterator(resp, reqAdapter, parsableCons) +// callbackFunc := func (pageItem interface{}) bool { +// fmt.Println(pageitem.GetDisplayName()) +// return true +// } +// err := pageIterator.Iterate(callbackFunc) +func (pI *PageIterator) Iterate(callback func(pageItem interface{}) bool) error { + for pI.currentPage != nil { + keepIterating := pI.enumerate(callback) + + if !keepIterating { + // Callback returned false, stop iterating through pages. + return nil + } + + err := pI.next() + if err != nil { + return err + } + pI.pauseIndex = 0 // when moving to the next page reset pauseIndex + } + + return nil +} + +// SetHeaders provides headers for requests made to get subsequent pages +// +// Headers in the initial request -- request to get the first page -- are not included in subsequent page requests. +func (pI *PageIterator) SetHeaders(headers map[string]string) { + pI.headers = headers +} + +// SetReqOptions provides configuration for handlers during requests for subsequent pages +func (pI *PageIterator) SetReqOptions(reqOptions []abstractions.RequestOption) { + pI.reqOptions = reqOptions +} + +func (pI *PageIterator) hasNext() bool { + if pI.currentPage == nil || pI.currentPage.getNextLink() == nil { + return false + } + return true +} + +func (pI *PageIterator) next() error { + nextPage, err := pI.getNextPage() + if err != nil { + return err + } + + pI.currentPage = nextPage + return nil +} + +func (pI *PageIterator) getNextPage() (*PageResult, error) { + if pI.currentPage.getNextLink() == nil { + return nil, nil + } + + nextLink, err := url.Parse(*pI.currentPage.getNextLink()) + if err != nil { + return nil, errors.New("Parsing nextLink url failed") + } + + requestInfo := abstractions.NewRequestInformation() + requestInfo.Method = abstractions.GET + requestInfo.SetUri(*nextLink) + requestInfo.Headers = pI.headers + requestInfo.AddRequestOptions(pI.reqOptions...) + + res, err := pI.reqAdapter.SendAsync(*requestInfo, pI.constructorFunc, nil) + if err != nil { + return nil, errors.New("Fetching next page failed") + } + + return convertToPage(res) +} + +func (pI *PageIterator) enumerate(callback func(item interface{}) bool) bool { + keepIterating := true + + if pI.currentPage == nil { + return false + } + + pageItems := pI.currentPage.getValue() + if pageItems == nil { + return false + } + + if pI.pauseIndex >= len(pageItems) { + return false + } + + // start/continue enumerating page items from pauseIndex. + // this makes it possible to resume iteration from where we paused iteration. + for i := pI.pauseIndex; i < len(pageItems); i++ { + keepIterating = callback(pageItems[i]) + + 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 + } + } + + return keepIterating +} + +func convertToPage(response interface{}) (*PageResult, error) { + if response == nil { + return nil, errors.New("response cannot be nil") + } + ref := reflect.ValueOf(response).Elem() + + value := ref.FieldByName("value") + if value.IsNil() { + return nil, errors.New("value property missing in response object") + } + value = reflect.NewAt(value.Type(), unsafe.Pointer(value.UnsafeAddr())).Elem() + + nextLink := ref.FieldByName("nextLink") + var link *string + if !nextLink.IsNil() { + nextLink = reflect.NewAt(nextLink.Type(), unsafe.Pointer(nextLink.UnsafeAddr())).Elem() + link = nextLink.Interface().(*string) + } + + // Collect all entities in the value slice. + // This converts a graph slice ie []graph.User to a dynamic slice []interface{} + collected := make([]interface{}, 0) + for i := 0; i < value.Len(); i++ { + collected = append(collected, value.Index(i).Interface()) + } + + return &PageResult{ + nextLink: link, + value: collected, + }, nil +} diff --git a/page_iterator_test.go b/page_iterator_test.go new file mode 100644 index 0000000..a9ca442 --- /dev/null +++ b/page_iterator_test.go @@ -0,0 +1,177 @@ +package msgraphgocore + +import ( + "fmt" + nethttp "net/http" + httptest "net/http/httptest" + testing "testing" + + abstractions "github.com/microsoft/kiota/abstractions/go" + "github.com/microsoft/kiota/abstractions/go/authentication" + "github.com/microsoft/kiota/abstractions/go/serialization" + jsonserialization "github.com/microsoft/kiota/serialization/go/json" + "github.com/microsoftgraph/msgraph-sdk-go-core/internal" + "github.com/stretchr/testify/assert" +) + +func init() { + abstractions.RegisterDefaultSerializer(func() serialization.SerializationWriterFactory { + return jsonserialization.NewJsonSerializationWriterFactory() + }) + abstractions.RegisterDefaultDeserializer(func() serialization.ParseNodeFactory { + return jsonserialization.NewJsonParseNodeFactory() + }) +} + +type PageItem struct { + DisplayName string +} + +type UserPage struct { + Value []interface{} + NextLink *string +} + +var reqAdapter, _ = NewGraphRequestAdapterBase(&authentication.AnonymousAuthenticationProvider{}, GraphClientOptions{ + GraphServiceVersion: "", + GraphServiceLibraryVersion: "", +}) + +func ParsableCons() serialization.Parsable { + return internal.NewUsersResponse() +} + +func TestConstructorWithInvalidGraphResponse(t *testing.T) { + graphResponse := internal.NewUsersResponse() + + _, err := NewPageIterator(graphResponse, *reqAdapter, ParsableCons) + + assert.NotNil(t, err) +} + +func TestIterateStopsWhenCallbackReturnsFalse(t *testing.T) { + res := make([]string, 0) + graphResponse := buildGraphResponse() + testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, ` + { + "nextLink": "", + "value": [ + { + "id": "10" + } + ] + } + `) + + })) + defer testServer.Close() + pageIterator, _ := NewPageIterator(graphResponse, *reqAdapter, ParsableCons) + + pageIterator.Iterate(func(pageItem interface{}) bool { + item := pageItem.(internal.User) + + res = append(res, *item.GetDisplayName()) + return !(*item.GetId() == "2") + }) + + assert.Equal(t, len(res), 3) +} + +func TestIterateEnumeratesAllPages(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, ` + { + "nextLink": "", + "value": [ + { + "id": "10" + } + ] + } + `) + + })) + defer testServer.Close() + + graphResponse := buildGraphResponse() + mockPath := testServer.URL + "/next-page" + graphResponse.SetNextLink(&mockPath) + + pageIterator, _ := NewPageIterator(graphResponse, *reqAdapter, ParsableCons) + res := make([]string, 0) + + pageIterator.Iterate(func(pageItem interface{}) bool { + item := pageItem.(internal.User) + res = append(res, *item.GetId()) + return true + }) + + // Initial page has 5 items and the next page has 1 item. + assert.Equal(t, len(res), 6) +} + +func TestIterateCanBePausedAndResumed(t *testing.T) { + res := make([]string, 0) + res2 := make([]string, 0) + + testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, ` + { + "nextLink": "", + "value": [ + { + "id": "10" + } + ] + } + `) + + })) + defer testServer.Close() + + response := buildGraphResponse() + mockPath := testServer.URL + "/next-page" + response.SetNextLink(&mockPath) + + pageIterator, _ := NewPageIterator(response, *reqAdapter, ParsableCons) + pageIterator.Iterate(func(pageItem interface{}) bool { + item := pageItem.(internal.User) + res = append(res, *item.GetId()) + + return *item.GetId() != "2" + }) + + assert.Equal(t, res, []string{"0", "1", "2"}) + + pageIterator.Iterate(func(pageItem interface{}) bool { + item := pageItem.(internal.User) + res2 = append(res2, *item.GetId()) + + return true + }) + assert.Equal(t, res2, []string{"3", "4", "10"}) +} + +func buildGraphResponse() *internal.UsersResponse { + var res = internal.NewUsersResponse() + + nextLink := "next-page" + users := make([]internal.User, 0) + + for i := 0; i < 5; i++ { + u := internal.NewUser() + id := fmt.Sprint(i) + u.SetId(&id) + + users = append(users, *u) + } + + res.SetNextLink(&nextLink) + res.SetValue(users) + + return res +}