feat(api): api 命令新增 --paginate 自动翻页合并(对标 gh api --paginate)
This commit is contained in:
parent
dfa127c27a
commit
8c92076c52
|
|
@ -28,6 +28,7 @@ func NewAPICmd(translators ...*i18n.Translator) *cobra.Command {
|
|||
Long: tr.T("cmd.api.long"),
|
||||
Example: ` gitlink-cli api GET /users/me
|
||||
gitlink-cli api GET /projects --query 'page=1&limit=10'
|
||||
gitlink-cli api GET /:owner/:repo/issues --paginate
|
||||
gitlink-cli api POST /:owner/:repo/issues --body '{"subject":"Bug","description":"..."}'
|
||||
gitlink-cli api POST /:owner/:repo/issues --body-file issue.json
|
||||
gitlink-cli api --batch-file plan.json --dry-run
|
||||
|
|
@ -40,6 +41,7 @@ func NewAPICmd(translators ...*i18n.Translator) *cobra.Command {
|
|||
apiCmd.Flags().String("body-file", "", tr.T("flag.api.body_file"))
|
||||
apiCmd.Flags().Bool("body-stdin", false, tr.T("flag.api.body_stdin"))
|
||||
apiCmd.Flags().String("query", "", tr.T("flag.api.query"))
|
||||
apiCmd.Flags().Bool("paginate", false, tr.T("flag.api.paginate"))
|
||||
apiCmd.Flags().StringSlice("header", nil, tr.T("flag.api.header"))
|
||||
apiCmd.Flags().String("batch-file", "", tr.T("flag.api.batch_file"))
|
||||
apiCmd.Flags().Bool("dry-run", false, tr.T("flag.api.batch_dry_run"))
|
||||
|
|
@ -62,7 +64,11 @@ func validateAPIArgs(c *cobra.Command, args []string) error {
|
|||
|
||||
func runAPI(c *cobra.Command, args []string) error {
|
||||
batchFile, _ := c.Flags().GetString("batch-file")
|
||||
paginate, _ := c.Flags().GetBool("paginate")
|
||||
if batchFile != "" {
|
||||
if paginate {
|
||||
return fmt.Errorf("--paginate cannot be used with --batch-file")
|
||||
}
|
||||
return runAPIBatch(c, batchFile)
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +100,25 @@ func runAPI(c *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if paginate {
|
||||
if method != "GET" {
|
||||
return fmt.Errorf("--paginate only supports GET requests, got %s", method)
|
||||
}
|
||||
if body != nil {
|
||||
return fmt.Errorf("--paginate cannot be used with a request body")
|
||||
}
|
||||
items, err := cli.PaginateAll(path, query)
|
||||
if err != nil {
|
||||
var apiErr *client.APIError
|
||||
if errors.As(err, &apiErr) {
|
||||
errEnv := output.ErrorEnvelope(apiErr.Code, apiErr.Message, "")
|
||||
return output.Print(errEnv, resolveFormat())
|
||||
}
|
||||
return err
|
||||
}
|
||||
return output.Print(paginatedEnvelope(items), resolveFormat())
|
||||
}
|
||||
|
||||
env, err := cli.Do(method, path, body, query)
|
||||
if err != nil {
|
||||
var apiErr *client.APIError
|
||||
|
|
@ -107,6 +132,23 @@ func runAPI(c *cobra.Command, args []string) error {
|
|||
return output.Print(env, resolveFormat())
|
||||
}
|
||||
|
||||
// paginatedEnvelope wraps merged pages in the same shape as a single-page
|
||||
// list response: {"total_count": N, "items": [...]}.
|
||||
func paginatedEnvelope(items []json.RawMessage) *output.Envelope {
|
||||
decoded := make([]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(item, &v); err == nil {
|
||||
decoded = append(decoded, v)
|
||||
}
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"total_count": len(decoded),
|
||||
"items": decoded,
|
||||
}
|
||||
return output.SuccessEnvelope(data, &output.Meta{TotalCount: len(decoded)})
|
||||
}
|
||||
|
||||
func readJSONBody(c *cobra.Command) (interface{}, error) {
|
||||
bodyStr, _ := c.Flags().GetString("body")
|
||||
bodyFile, _ := c.Flags().GetString("body-file")
|
||||
|
|
|
|||
|
|
@ -131,6 +131,63 @@ func TestRunAPIBadQuery(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRunAPIPaginate(t *testing.T) {
|
||||
setupAPITest(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
page := r.URL.Query().Get("page")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch page {
|
||||
case "1":
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"total_count": 3,
|
||||
"issues": []interface{}{
|
||||
map[string]interface{}{"id": 1},
|
||||
map[string]interface{}{"id": 2},
|
||||
},
|
||||
})
|
||||
default:
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"total_count": 3,
|
||||
"issues": []interface{}{
|
||||
map[string]interface{}{"id": 3},
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
cmdutil.Format = "json"
|
||||
|
||||
cmd := NewAPICmd()
|
||||
cmd.SetArgs([]string{"GET", "/owner/repo/issues", "--paginate", "--query", "limit=2"})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("runAPI paginate error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunAPIPaginateRejectsNonGET(t *testing.T) {
|
||||
setupAPITest(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("server should not be reached")
|
||||
})
|
||||
cmdutil.Format = "json"
|
||||
|
||||
cmd := NewAPICmd()
|
||||
cmd.SetArgs([]string{"POST", "/owner/repo/issues", "--paginate"})
|
||||
if err := cmd.Execute(); err == nil {
|
||||
t.Fatal("expected error for --paginate with POST")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunAPIPaginateRejectsBatchFile(t *testing.T) {
|
||||
setupAPITest(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("server should not be reached")
|
||||
})
|
||||
cmdutil.Format = "json"
|
||||
|
||||
cmd := NewAPICmd()
|
||||
cmd.SetArgs([]string{"--batch-file", "plan.json", "--paginate"})
|
||||
if err := cmd.Execute(); err == nil {
|
||||
t.Fatal("expected error for --paginate with --batch-file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunAPIHTTPError(t *testing.T) {
|
||||
setupAPITest(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@
|
|||
"flag.api.body_file": "Read request body JSON from a file",
|
||||
"flag.api.body_stdin": "Read request body JSON from stdin",
|
||||
"flag.api.header": "Additional headers (key:value)",
|
||||
"flag.api.paginate": "Fetch all pages of a GET list endpoint and merge the results",
|
||||
"flag.api.query": "Query parameters (key=val&key2=val2)",
|
||||
"flag.auth.token": "Login by pasting an existing token",
|
||||
"flag.branch.from": "Source branch or commit",
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@
|
|||
"flag.api.body_file": "从文件读取 JSON 请求体",
|
||||
"flag.api.body_stdin": "从标准输入读取 JSON 请求体",
|
||||
"flag.api.header": "附加请求头(key:value)",
|
||||
"flag.api.paginate": "自动获取 GET 列表接口的全部分页并合并结果",
|
||||
"flag.api.query": "查询参数(key=val&key2=val2)",
|
||||
"flag.auth.token": "通过粘贴已有 Token 登录",
|
||||
"flag.branch.from": "源分支或 Commit",
|
||||
|
|
|
|||
Loading…
Reference in New Issue