refactor(client): PaginateToOutput takes an options struct

PaginateToOutput had 11 positional parameters (writers, callbacks, pagination
knobs), which read poorly at the call sites and invited positional-argument
mistakes. Introduce PaginateOutputOptions and reduce the signature to
(ctx, opts). The two production call sites (cmd/api, cmd/service) now pass named
fields; the pagination tests use a small adapter so each case stays one
statement. No behavior change.
This commit is contained in:
shanglei
2026-07-23 20:07:49 +08:00
parent ff7b7371fc
commit 4e221ed3be
6 changed files with 106 additions and 32 deletions

View File

@@ -280,8 +280,18 @@ func apiRun(opts *APIOptions) error {
out := f.IOStreams.Out
if opts.PageAll {
return client.PaginateToOutput(opts.Ctx, ac, request, format, opts.JqExpr, out, f.IOStreams.ErrOut, opts.Cmd.CommandPath(),
client.PaginationOptions{PageLimit: opts.PageLimit, PageDelay: opts.PageDelay}, ac.CheckResponse, errs.MarkRaw)
return client.PaginateToOutput(opts.Ctx, client.PaginateOutputOptions{
Client: ac,
Request: request,
Format: format,
JqExpr: opts.JqExpr,
Out: out,
ErrOut: f.IOStreams.ErrOut,
CommandPath: opts.Cmd.CommandPath(),
Pagination: client.PaginationOptions{PageLimit: opts.PageLimit, PageDelay: opts.PageDelay},
CheckErr: ac.CheckResponse,
MarkErr: errs.MarkRaw,
})
}
resp, err := ac.DoAPI(opts.Ctx, request)

View File

@@ -66,6 +66,23 @@ func apiPaginateRequest() client.RawApiRequest {
}
}
// apiPaginate adapts the positional test calls to PaginateToOutput's options
// struct so each test case stays a single readable statement.
func apiPaginate(ctx context.Context, ac *client.APIClient, request client.RawApiRequest, format output.Format, jqExpr string, out, errOut io.Writer, commandPath string, pag client.PaginationOptions, checkErr func(interface{}, core.Identity) error, markErr func(error) error) error {
return client.PaginateToOutput(ctx, client.PaginateOutputOptions{
Client: ac,
Request: request,
Format: format,
JqExpr: jqExpr,
Out: out,
ErrOut: errOut,
CommandPath: commandPath,
Pagination: pag,
CheckErr: checkErr,
MarkErr: markErr,
})
}
func assertAPIPaginateJSONBytes(t *testing.T, got []byte, want interface{}) {
t.Helper()
wantBytes, err := json.MarshalIndent(want, "", " ")
@@ -108,7 +125,7 @@ func TestAPIPaginate_DefaultAggregatesAllPages(t *testing.T) {
})
}
err := client.PaginateToOutput(context.Background(), ac, apiPaginateRequest(),
err := apiPaginate(context.Background(), ac, apiPaginateRequest(),
output.FormatJSON, "", out, errOut, "lark-cli api GET", client.PaginationOptions{
PageLimit: 10,
PageDelay: -1,
@@ -191,7 +208,7 @@ func TestAPIPaginate_StreamingFormatsEmitExactMultiPageBytes(t *testing.T) {
},
})
err := client.PaginateToOutput(context.Background(), ac, apiPaginateRequest(),
err := apiPaginate(context.Background(), ac, apiPaginateRequest(),
tt.format, "", out, errOut, "lark-cli api GET", client.PaginationOptions{
PageLimit: 10,
PageDelay: -1,
@@ -237,7 +254,7 @@ func TestAPIPaginate_StreamingWriteFailureStopsFurtherPages(t *testing.T) {
})
}
err := client.PaginateToOutput(context.Background(), ac, apiPaginateRequest(),
err := apiPaginate(context.Background(), ac, apiPaginateRequest(),
output.FormatNDJSON, "", out, errOut, "lark-cli api GET",
client.PaginationOptions{PageLimit: 10, PageDelay: -1}, ac.CheckResponse, errs.MarkRaw)
@@ -270,7 +287,7 @@ func TestAPIPaginate_StreamingFormatHonorsNDJSONWithoutList(t *testing.T) {
},
})
err := client.PaginateToOutput(context.Background(), ac, apiPaginateRequest(),
err := apiPaginate(context.Background(), ac, apiPaginateRequest(),
output.FormatNDJSON, "", out, errOut, "lark-cli api GET", client.PaginationOptions{PageDelay: -1}, ac.CheckResponse, errs.MarkRaw)
if err != nil {
@@ -308,7 +325,7 @@ func TestAPIPaginate_BusinessErrorsWriteRawAndAreMarkedRaw(t *testing.T) {
Body: businessResponse,
})
err := client.PaginateToOutput(context.Background(), ac, apiPaginateRequest(),
err := apiPaginate(context.Background(), ac, apiPaginateRequest(),
tt.format, tt.jqExpr, out, errOut, "lark-cli api GET", client.PaginationOptions{PageDelay: -1}, ac.CheckResponse, errs.MarkRaw)
if err == nil {
@@ -343,7 +360,7 @@ func TestAPIPaginate_TransportErrorsAreMarkedRaw(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
ac, out, errOut, _ := newAPIPaginateTestHarness(t)
err := client.PaginateToOutput(context.Background(), ac, apiPaginateRequest(),
err := apiPaginate(context.Background(), ac, apiPaginateRequest(),
tt.format, tt.jqExpr, out, errOut, "lark-cli api GET", client.PaginationOptions{PageDelay: -1}, ac.CheckResponse, errs.MarkRaw)
if err == nil {
@@ -373,7 +390,7 @@ func TestAPIPaginate_StreamBusinessErrorIsMarkedRaw(t *testing.T) {
},
})
err := client.PaginateToOutput(context.Background(), ac, apiPaginateRequest(),
err := apiPaginate(context.Background(), ac, apiPaginateRequest(),
output.FormatNDJSON, "", out, errOut, "lark-cli api GET", client.PaginationOptions{PageDelay: -1}, ac.CheckResponse, errs.MarkRaw)
if err == nil {

View File

@@ -444,8 +444,18 @@ func serviceMethodRun(opts *ServiceMethodOptions) error {
checkErr := ac.CheckResponse
if opts.PageAll {
return client.PaginateToOutput(opts.Ctx, ac, request, format, opts.JqExpr, out, f.IOStreams.ErrOut, opts.Cmd.CommandPath(),
client.PaginationOptions{PageLimit: opts.PageLimit, PageDelay: opts.PageDelay}, checkErr, nil)
return client.PaginateToOutput(opts.Ctx, client.PaginateOutputOptions{
Client: ac,
Request: request,
Format: format,
JqExpr: opts.JqExpr,
Out: out,
ErrOut: f.IOStreams.ErrOut,
CommandPath: opts.Cmd.CommandPath(),
Pagination: client.PaginationOptions{PageLimit: opts.PageLimit, PageDelay: opts.PageDelay},
CheckErr: checkErr,
MarkErr: nil,
})
}
resp, err := ac.DoAPI(opts.Ctx, request)

View File

@@ -66,6 +66,23 @@ func servicePaginateRequest() client.RawApiRequest {
}
}
// servicePaginate adapts the positional test calls to PaginateToOutput's options
// struct so each test case stays a single readable statement.
func servicePaginate(ctx context.Context, ac *client.APIClient, request client.RawApiRequest, format output.Format, jqExpr string, out, errOut io.Writer, commandPath string, pag client.PaginationOptions, checkErr func(interface{}, core.Identity) error, markErr func(error) error) error {
return client.PaginateToOutput(ctx, client.PaginateOutputOptions{
Client: ac,
Request: request,
Format: format,
JqExpr: jqExpr,
Out: out,
ErrOut: errOut,
CommandPath: commandPath,
Pagination: pag,
CheckErr: checkErr,
MarkErr: markErr,
})
}
func assertServicePaginateJSONBytes(t *testing.T, got []byte, want interface{}) {
t.Helper()
wantBytes, err := json.MarshalIndent(want, "", " ")
@@ -108,7 +125,7 @@ func TestServicePaginate_DefaultAggregatesAllPages(t *testing.T) {
})
}
err := client.PaginateToOutput(context.Background(), ac, servicePaginateRequest(),
err := servicePaginate(context.Background(), ac, servicePaginateRequest(),
output.FormatJSON, "", out, errOut, "lark-cli test items list", client.PaginationOptions{
PageLimit: 10,
PageDelay: -1,
@@ -191,7 +208,7 @@ func TestServicePaginate_StreamingFormatsEmitExactMultiPageBytes(t *testing.T) {
},
})
err := client.PaginateToOutput(context.Background(), ac, servicePaginateRequest(),
err := servicePaginate(context.Background(), ac, servicePaginateRequest(),
tt.format, "", out, errOut, "lark-cli test items list", client.PaginationOptions{
PageLimit: 10,
PageDelay: -1,
@@ -237,7 +254,7 @@ func TestServicePaginate_StreamingWriteFailureStopsFurtherPages(t *testing.T) {
})
}
err := client.PaginateToOutput(context.Background(), ac, servicePaginateRequest(),
err := servicePaginate(context.Background(), ac, servicePaginateRequest(),
output.FormatNDJSON, "", out, errOut, "lark-cli test items list",
client.PaginationOptions{PageLimit: 10, PageDelay: -1}, ac.CheckResponse, nil)
@@ -270,7 +287,7 @@ func TestServicePaginate_StreamingFormatHonorsNDJSONWithoutList(t *testing.T) {
},
})
err := client.PaginateToOutput(context.Background(), ac, servicePaginateRequest(),
err := servicePaginate(context.Background(), ac, servicePaginateRequest(),
output.FormatNDJSON, "", out, errOut, "lark-cli test items get",
client.PaginationOptions{PageDelay: -1}, ac.CheckResponse, nil)
@@ -309,7 +326,7 @@ func TestServicePaginate_BusinessErrorsWriteRawAndRemainUnmarked(t *testing.T) {
Body: businessResponse,
})
err := client.PaginateToOutput(context.Background(), ac, servicePaginateRequest(),
err := servicePaginate(context.Background(), ac, servicePaginateRequest(),
tt.format, tt.jqExpr, out, errOut, "lark-cli test items list",
client.PaginationOptions{PageDelay: -1}, ac.CheckResponse, nil)
@@ -345,7 +362,7 @@ func TestServicePaginate_TransportErrorsRemainUnmarked(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
ac, out, errOut, _ := newServicePaginateTestHarness(t)
err := client.PaginateToOutput(context.Background(), ac, servicePaginateRequest(),
err := servicePaginate(context.Background(), ac, servicePaginateRequest(),
tt.format, tt.jqExpr, out, errOut, "lark-cli test items list",
client.PaginationOptions{PageDelay: -1}, ac.CheckResponse, nil)
@@ -376,7 +393,7 @@ func TestServicePaginate_StreamBusinessErrorRemainsUnmarked(t *testing.T) {
},
})
err := client.PaginateToOutput(context.Background(), ac, servicePaginateRequest(),
err := servicePaginate(context.Background(), ac, servicePaginateRequest(),
output.FormatNDJSON, "", out, errOut, "lark-cli test items list",
client.PaginationOptions{PageDelay: -1}, ac.CheckResponse, nil)

View File

@@ -714,19 +714,13 @@ func TestCallAPI_ParseJSONFailureWrapsAsAPI(t *testing.T) {
func TestPaginateToOutputRejectsUnsupportedInternalFormat(t *testing.T) {
for _, format := range []output.Format{output.FormatPretty, output.Format(99)} {
err := PaginateToOutput(
context.Background(),
nil,
RawApiRequest{},
format,
"",
io.Discard,
io.Discard,
"lark-cli fixture",
PaginationOptions{},
nil,
nil,
)
err := PaginateToOutput(context.Background(), PaginateOutputOptions{
Request: RawApiRequest{},
Format: format,
Out: io.Discard,
ErrOut: io.Discard,
CommandPath: "lark-cli fixture",
})
var internalErr *errs.InternalError
if !errors.As(err, &internalErr) {
t.Fatalf("format %q error = %T, want *errs.InternalError", format, err)

View File

@@ -12,8 +12,34 @@ import (
"github.com/larksuite/cli/internal/output"
)
// PaginateOutputOptions bundles the inputs for PaginateToOutput. Grouping the
// writers, callbacks, and pagination knobs into one struct keeps the call sites
// readable and avoids positional-argument mistakes across the many parameters.
type PaginateOutputOptions struct {
Client *APIClient
Request RawApiRequest
Format output.Format
JqExpr string
Out io.Writer
ErrOut io.Writer
CommandPath string
Pagination PaginationOptions
CheckErr func(interface{}, core.Identity) error
MarkErr func(error) error
}
// PaginateToOutput fetches all requested pages and emits them in the selected format.
func PaginateToOutput(ctx context.Context, ac *APIClient, request RawApiRequest, format output.Format, jqExpr string, out, errOut io.Writer, commandPath string, pagOpts PaginationOptions, checkErr func(interface{}, core.Identity) error, markErr func(error) error) error {
func PaginateToOutput(ctx context.Context, opts PaginateOutputOptions) error {
ac := opts.Client
request := opts.Request
format := opts.Format
jqExpr := opts.JqExpr
out := opts.Out
errOut := opts.ErrOut
commandPath := opts.CommandPath
pagOpts := opts.Pagination
checkErr := opts.CheckErr
markErr := opts.MarkErr
if !format.Valid() || format == output.FormatPretty {
return errs.NewInternalError(errs.SubtypeUnknown,
"internal: unsupported pagination output format %q", format)