internal/dailyops + cli:日常操作命令 issues/pulls/labels/milestones/releases/repos/api(#8)
- internal/gitea:用戶端支援寫入動詞(DoJSON/DoRaw,共用 do 核心) - internal/dailyops:七組日常操作的 API 層(issues/pulls/labels/milestones/releases/repos/api) - internal/cli:子命令接線;寫入類一律 --yes 閘門(未確認不發請求、exit 2) - teai pulls 相容 #6 工作流用法(無子命令);有子命令走日常操作 - 修正 dispatch:命令旗標(如 --has-work、--comments)不再被誤判為未知全域選項 - teai api 支援 path 內含查詢字串 - 單元測試(httptest 假伺服器)+真實站點交叉驗證(讀取命令輸出對照 Gitea API;寫入回路 create→comment→close 於 alex/.profile 驗證後清理)
This commit is contained in:
+55
-10
@@ -7,6 +7,7 @@
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -79,36 +80,80 @@ const MaxLimit = 50
|
||||
// getJSON 對 path(相對 baseURL,以 / 開頭)發 GET,把 JSON 回應解到 out。
|
||||
// 失敗時回傳 *ErrAPI。rawQuery 為組好的查詢字串(見 ListOptions.query)。
|
||||
func (c *Client) getJSON(ctx context.Context, path, rawQuery string, out any) error {
|
||||
_, err := c.do(ctx, http.MethodGet, path, rawQuery, "", nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
// DoJSON 對 path 發 method 請求(POST/PATCH/PUT/DELETE…),body 為可 JSON
|
||||
// 化的值(nil 表示不帶 body),2xx 時把回應解到 out(out 非 nil 且回應
|
||||
// 有內容時)。供寫入類操作(issues create、pulls merge…)使用。
|
||||
func (c *Client) DoJSON(ctx context.Context, method, path, rawQuery string, body any, out any) error {
|
||||
var payload []byte
|
||||
if body != nil {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gitea: marshal request body: %w", err)
|
||||
}
|
||||
payload = b
|
||||
}
|
||||
_, err := c.do(ctx, method, path, rawQuery, "application/json", payload, out)
|
||||
return err
|
||||
}
|
||||
|
||||
// DoRaw 以原樣 body 送出請求並回傳原始回應內容(teai api 用)。
|
||||
// 非 2xx 回 *ErrAPI。
|
||||
func (c *Client) DoRaw(ctx context.Context, method, path, rawQuery string, body []byte) ([]byte, error) {
|
||||
contentType := ""
|
||||
if body != nil {
|
||||
contentType = "application/json"
|
||||
}
|
||||
return c.do(ctx, method, path, rawQuery, contentType, body, nil)
|
||||
}
|
||||
|
||||
// do 是所有請求的共同核心:組 URL、帶認證表頭、送出、檢查狀態碼、
|
||||
// 解析回應。2xx 時回傳原始回應位元組;out 非 nil 時另解到 out。
|
||||
// 錯誤訊息不含 token 或回應內容。
|
||||
func (c *Client) do(ctx context.Context, method, path, rawQuery, contentType string, payload []byte, out any) ([]byte, error) {
|
||||
u := *c.baseURL
|
||||
u.Path += path
|
||||
u.RawQuery = rawQuery
|
||||
op := "GET " + u.Path
|
||||
op := method + " " + u.Path
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
var body io.Reader
|
||||
if payload != nil {
|
||||
body = bytes.NewReader(payload)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, method, u.String(), body)
|
||||
if err != nil {
|
||||
return &ErrAPI{Op: op, Err: err}
|
||||
return nil, &ErrAPI{Op: op, Err: err}
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if payload != nil && contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
if c.token != "" {
|
||||
req.Header.Set("Authorization", "token "+c.token)
|
||||
}
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return &ErrAPI{Op: op, Err: err}
|
||||
return nil, &ErrAPI{Op: op, Err: err}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
// 把內容讀掉再丟棄,讓連線可重用;錯誤訊息不含回應內容。
|
||||
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4096))
|
||||
return &ErrAPI{Op: op, StatusCode: resp.StatusCode}
|
||||
return nil, &ErrAPI{Op: op, StatusCode: resp.StatusCode}
|
||||
}
|
||||
if out == nil {
|
||||
return nil
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, &ErrAPI{Op: op, Err: err}
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return &ErrAPI{Op: op, Err: fmt.Errorf("decode response: %w", err)}
|
||||
if out != nil && len(data) > 0 {
|
||||
if err := json.Unmarshal(data, out); err != nil {
|
||||
return nil, &ErrAPI{Op: op, Err: fmt.Errorf("decode response: %w", err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// GetJSON 是 getJSON 的公開介面,供非清單端點(如 /user)使用。
|
||||
|
||||
Reference in New Issue
Block a user