Loading pagination.go +34 −7 Original line number Diff line number Diff line Loading @@ -3,7 +3,6 @@ package gitlab import ( "fmt" "iter" "slices" ) type PaginationOptionFunc = RequestOptionFunc Loading Loading @@ -154,7 +153,7 @@ func Must[T any](it iter.Seq2[T, error]) iter.Seq[T] { } } // ScanAndCollect is a convenience function that collects all results and returns them as slice as well as an error if one happens. // ScanAndCollect is a convenience function that collects all results and returns them as a slice as well as an error if one happens. // // opts := &ListProjectsOptions{} // projects, err := ScanAndCollect(func(p PaginationOptionFunc) ([]*Project, *Response, error) { Loading @@ -167,10 +166,38 @@ func Must[T any](it iter.Seq2[T, error]) iter.Seq[T] { // // Attention: This API is experimental and may be subject to breaking changes to improve the API in the future. func ScanAndCollect[T any](f func(p PaginationOptionFunc) ([]T, *Response, error)) ([]T, error) { it, hasErr := Scan(f) allItems := slices.Collect(it) if err := hasErr(); err != nil { return ScanAndCollectN(f, -1) } // ScanAndCollectN is a convenience function that collects at most n results and // returns them as a slice as well as an error if one happens. // // This is useful when you need a slice, e.g. for marshaling the data // structures, passing the data to a function expecting a slice, or implementing // custom sorting logic. If you want to iterate over all items, the iterator // returned by [Scan2] is a more memory efficient alternative. // // n determines the number of items to collect: // - n > 0: at most n items are returned // - n == 0: the result is a nil slice (zero items) // - n < 0: all items are returned (no limit) // // Attention: This API is experimental and may be subject to breaking changes to // improve the API in the future. func ScanAndCollectN[T any](f func(p PaginationOptionFunc) ([]T, *Response, error), n int) ([]T, error) { var items []T for item, err := range Scan2(f) { if err != nil { return nil, err } return allItems, nil if n >= 0 && len(items) >= n { break } items = append(items, item) } return items, nil } pagination_example_test.go 0 → 100644 +77 −0 Original line number Diff line number Diff line package gitlab_test import ( "encoding/json" "fmt" "log" "os" gitlab "gitlab.com/gitlab-org/api/client-go" ) func ExampleScan2() { // Create a client (this would normally use your GitLab instance URL and token) client, err := gitlab.NewAuthSourceClient( gitlab.AccessTokenAuthSource{"your-token"}, gitlab.WithBaseURL("https://gitlab.example.com/api/v4"), ) if err != nil { // Handle the error panic(err) } opts := &gitlab.ListProjectsOptions{} pager := func(pageOpt gitlab.PaginationOptionFunc) ([]*gitlab.Project, *gitlab.Response, error) { // Call ListProjects with pageOpt to retrieve the next page return client.Projects.ListProjects(opts, pageOpt) } // Create a project iterator projects := gitlab.Scan2(pager) // Iterate over the project iterator for project, err := range projects { // Errors are delivered inline — check for them and break the loop before using the value if err != nil { log.Println("ERROR:", err) break } fmt.Printf("- %s (ID: %d)\n", project.PathWithNamespace, project.ID) } } func ExampleScanAndCollectN() { // Create a client (this would normally use your GitLab instance URL and token) client, err := gitlab.NewAuthSourceClient( gitlab.AccessTokenAuthSource{"your-token"}, gitlab.WithBaseURL("https://gitlab.example.com/api/v4"), ) if err != nil { // Handle the error panic(err) } opts := &gitlab.ListProjectsOptions{} pager := func(pageOpt gitlab.PaginationOptionFunc) ([]*gitlab.Project, *gitlab.Response, error) { // Call ListProjects with pageOpt to retrieve the next page return client.Projects.ListProjects(opts, pageOpt) } // Retrieve at most 42 projects const limit = 42 projects, err := gitlab.ScanAndCollectN(pager, limit) if err != nil { // Handle the error panic(err) } // Use the slice — here we serialize it to JSON, but you could sort it, pass it to another function, etc. // Note: if you want to iterate over items, use gitlab.Scan2() instead if err := json.NewEncoder(os.Stdout).Encode(projects); err != nil { panic(err) } } pagination_test.go +29 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,35 @@ func TestPagination_ScanAndCollect_Error(t *testing.T) { require.Nil(t, projects) } func TestPagination_ScanAndCollectN(t *testing.T) { t.Parallel() mux, client := setup(t) handleTwoPagesSuccessfully(t, mux) opt := &ListProjectsOptions{} projects, err := ScanAndCollectN(func(p PaginationOptionFunc) ([]*Project, *Response, error) { return client.Projects.ListProjects(opt, p) }, 1) require.NoError(t, err) want := []*Project{{ID: 1}} assert.Equal(t, want, projects) } func TestPagination_ScanAndCollectN_Zero(t *testing.T) { t.Parallel() mux, client := setup(t) handleTwoPagesSuccessfully(t, mux) opt := &ListProjectsOptions{} projects, err := ScanAndCollectN(func(p PaginationOptionFunc) ([]*Project, *Response, error) { return client.Projects.ListProjects(opt, p) }, 0) require.NoError(t, err) assert.Nil(t, projects) } func handleTwoPagesSuccessfully(t *testing.T, mux *http.ServeMux) { mux.HandleFunc("GET /api/v4/projects", func(w http.ResponseWriter, r *http.Request) { page := r.URL.Query().Get("page") Loading Loading
pagination.go +34 −7 Original line number Diff line number Diff line Loading @@ -3,7 +3,6 @@ package gitlab import ( "fmt" "iter" "slices" ) type PaginationOptionFunc = RequestOptionFunc Loading Loading @@ -154,7 +153,7 @@ func Must[T any](it iter.Seq2[T, error]) iter.Seq[T] { } } // ScanAndCollect is a convenience function that collects all results and returns them as slice as well as an error if one happens. // ScanAndCollect is a convenience function that collects all results and returns them as a slice as well as an error if one happens. // // opts := &ListProjectsOptions{} // projects, err := ScanAndCollect(func(p PaginationOptionFunc) ([]*Project, *Response, error) { Loading @@ -167,10 +166,38 @@ func Must[T any](it iter.Seq2[T, error]) iter.Seq[T] { // // Attention: This API is experimental and may be subject to breaking changes to improve the API in the future. func ScanAndCollect[T any](f func(p PaginationOptionFunc) ([]T, *Response, error)) ([]T, error) { it, hasErr := Scan(f) allItems := slices.Collect(it) if err := hasErr(); err != nil { return ScanAndCollectN(f, -1) } // ScanAndCollectN is a convenience function that collects at most n results and // returns them as a slice as well as an error if one happens. // // This is useful when you need a slice, e.g. for marshaling the data // structures, passing the data to a function expecting a slice, or implementing // custom sorting logic. If you want to iterate over all items, the iterator // returned by [Scan2] is a more memory efficient alternative. // // n determines the number of items to collect: // - n > 0: at most n items are returned // - n == 0: the result is a nil slice (zero items) // - n < 0: all items are returned (no limit) // // Attention: This API is experimental and may be subject to breaking changes to // improve the API in the future. func ScanAndCollectN[T any](f func(p PaginationOptionFunc) ([]T, *Response, error), n int) ([]T, error) { var items []T for item, err := range Scan2(f) { if err != nil { return nil, err } return allItems, nil if n >= 0 && len(items) >= n { break } items = append(items, item) } return items, nil }
pagination_example_test.go 0 → 100644 +77 −0 Original line number Diff line number Diff line package gitlab_test import ( "encoding/json" "fmt" "log" "os" gitlab "gitlab.com/gitlab-org/api/client-go" ) func ExampleScan2() { // Create a client (this would normally use your GitLab instance URL and token) client, err := gitlab.NewAuthSourceClient( gitlab.AccessTokenAuthSource{"your-token"}, gitlab.WithBaseURL("https://gitlab.example.com/api/v4"), ) if err != nil { // Handle the error panic(err) } opts := &gitlab.ListProjectsOptions{} pager := func(pageOpt gitlab.PaginationOptionFunc) ([]*gitlab.Project, *gitlab.Response, error) { // Call ListProjects with pageOpt to retrieve the next page return client.Projects.ListProjects(opts, pageOpt) } // Create a project iterator projects := gitlab.Scan2(pager) // Iterate over the project iterator for project, err := range projects { // Errors are delivered inline — check for them and break the loop before using the value if err != nil { log.Println("ERROR:", err) break } fmt.Printf("- %s (ID: %d)\n", project.PathWithNamespace, project.ID) } } func ExampleScanAndCollectN() { // Create a client (this would normally use your GitLab instance URL and token) client, err := gitlab.NewAuthSourceClient( gitlab.AccessTokenAuthSource{"your-token"}, gitlab.WithBaseURL("https://gitlab.example.com/api/v4"), ) if err != nil { // Handle the error panic(err) } opts := &gitlab.ListProjectsOptions{} pager := func(pageOpt gitlab.PaginationOptionFunc) ([]*gitlab.Project, *gitlab.Response, error) { // Call ListProjects with pageOpt to retrieve the next page return client.Projects.ListProjects(opts, pageOpt) } // Retrieve at most 42 projects const limit = 42 projects, err := gitlab.ScanAndCollectN(pager, limit) if err != nil { // Handle the error panic(err) } // Use the slice — here we serialize it to JSON, but you could sort it, pass it to another function, etc. // Note: if you want to iterate over items, use gitlab.Scan2() instead if err := json.NewEncoder(os.Stdout).Encode(projects); err != nil { panic(err) } }
pagination_test.go +29 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,35 @@ func TestPagination_ScanAndCollect_Error(t *testing.T) { require.Nil(t, projects) } func TestPagination_ScanAndCollectN(t *testing.T) { t.Parallel() mux, client := setup(t) handleTwoPagesSuccessfully(t, mux) opt := &ListProjectsOptions{} projects, err := ScanAndCollectN(func(p PaginationOptionFunc) ([]*Project, *Response, error) { return client.Projects.ListProjects(opt, p) }, 1) require.NoError(t, err) want := []*Project{{ID: 1}} assert.Equal(t, want, projects) } func TestPagination_ScanAndCollectN_Zero(t *testing.T) { t.Parallel() mux, client := setup(t) handleTwoPagesSuccessfully(t, mux) opt := &ListProjectsOptions{} projects, err := ScanAndCollectN(func(p PaginationOptionFunc) ([]*Project, *Response, error) { return client.Projects.ListProjects(opt, p) }, 0) require.NoError(t, err) assert.Nil(t, projects) } func handleTwoPagesSuccessfully(t *testing.T, mux *http.ServeMux) { mux.HandleFunc("GET /api/v4/projects", func(w http.ResponseWriter, r *http.Request) { page := r.URL.Query().Get("page") Loading