gitlink-cli/cmd/api/api.go

112 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"encoding/json"
"fmt"
"net/url"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/gitlink-org/gitlink-cli/cmd/cmdutil"
"github.com/gitlink-org/gitlink-cli/internal/client"
"github.com/gitlink-org/gitlink-cli/internal/output"
)
func NewAPICmd() *cobra.Command {
apiCmd := &cobra.Command{
Use: "api <METHOD> <PATH>",
Short: "Make raw API requests to GitLink",
Long: `Send arbitrary HTTP requests to the GitLink API. Authentication is injected automatically.`,
Example: ` gitlink-cli api GET /users/me
gitlink-cli api GET /projects --query 'page=1&limit=10'
gitlink-cli api POST /:owner/:repo/issues --body '{"subject":"Bug","description":"..."}'
gitlink-cli api POST /:owner/:repo/issues --body-file ./issue.json`,
Args: cobra.ExactArgs(2),
RunE: runAPI,
}
apiCmd.Flags().String("body", "", "Request body (JSON string)")
apiCmd.Flags().String("body-file", "", "Read JSON body from a file (avoids shell quoting issues)")
apiCmd.Flags().String("query", "", "Query parameters (key=val&key2=val2)")
apiCmd.Flags().StringSlice("header", nil, "Additional headers (key:value)")
return apiCmd
}
func runAPI(c *cobra.Command, args []string) error {
method := strings.ToUpper(args[0])
path := args[1]
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
cli, err := client.New()
if err != nil {
return err
}
cli.Debug = cmdutil.Debug
var body interface{}
bodyStr, _ := c.Flags().GetString("body")
bodyFile, _ := c.Flags().GetString("body-file")
if bodyStr != "" && bodyFile != "" {
return printAPIError(400, "cannot use both --body and --body-file", "请只用其中一个:--body 用于内联 JSON--body-file 用于从文件读取")
}
if bodyFile != "" {
raw, err := os.ReadFile(bodyFile)
if err != nil {
return printAPIError(400, fmt.Sprintf("read --body-file failed: %v", err), "检查 --body-file 路径是否正确、文件是否存在且有读权限")
}
bodyStr = string(raw)
}
if bodyStr != "" {
if err := json.Unmarshal([]byte(bodyStr), &body); err != nil {
return printAPIError(400, fmt.Sprintf("invalid JSON body: %v", err), "确认 body 是合法 JSONPowerShell 调用 .exe 时会剥离内嵌双引号,推荐改用 --body-file 从文件读取")
}
}
var query url.Values
queryStr, _ := c.Flags().GetString("query")
if queryStr != "" {
var err error
query, err = url.ParseQuery(queryStr)
if err != nil {
return printAPIError(400, fmt.Sprintf("invalid query string: %v", err), "query 应为 key=value&key2=value2 形式,注意值需要 URL 编码")
}
}
env, err := cli.Do(method, path, body, query)
if err != nil {
if apiErr, ok := err.(*client.APIError); ok {
errEnv := output.ErrorEnvelope(apiErr.Code, apiErr.Message, apiErr.Suggestion)
_ = output.Print(errEnv, resolveFormat())
return cmdutil.ErrSilent
}
// 网络错误 / DNS 失败 / 超时等非 API 错误,也按 envelope 输出保持一致
return printAPIError(503, fmt.Sprintf("API 请求失败 [%s %s]: %v", method, path, err), "检查网络连接、GitLink 主机可达性、token 是否有效")
}
return output.Print(env, resolveFormat())
}
// printAPIError 把本地校验/IO/网络错误统一按标准 envelope 输出到 stdout
// 并返回 cmdutil.ErrSilent 让 cmd.Execute 跳过 stderr 重复打印,仅保留非零退出码。
// 设计意图:让 `api` 命令的所有错误(包括 JSON 解析、参数冲突、读文件失败、APIError、
// 网络错误)输出格式与 shortcut 一致,便于 `--format json` + jq 自动化解析。
func printAPIError(code int, message, suggestion string) error {
env := output.ErrorEnvelope(code, message, suggestion)
_ = output.Print(env, resolveFormat())
return cmdutil.ErrSilent
}
func resolveFormat() string {
f := cmdutil.Format
if f == "" {
return "json"
}
return f
}