feat: rainyun http client — adapter pattern with auth injection

This commit is contained in:
2026-07-28 16:00:33 +08:00
parent 11dd8bf0fc
commit 04f2942d77
2 changed files with 191 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
package rainyun
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const defaultEndpoint = "https://api.v2.rainyun.com"
type client struct {
httpClient *http.Client
baseURL string
apiKey string
}
func newClient(endpoint, apiKey string) *client {
if endpoint == "" {
endpoint = defaultEndpoint
}
return &client{
httpClient: &http.Client{Timeout: 30 * time.Second},
baseURL: endpoint,
apiKey: apiKey,
}
}
type apiResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
}
func (c *client) get(ctx context.Context, path string, result any) error {
return c.do(ctx, "GET", path, nil, result)
}
func (c *client) post(ctx context.Context, path string, body, result any) error {
return c.do(ctx, "POST", path, body, result)
}
func (c *client) patch(ctx context.Context, path string, body, result any) error {
return c.do(ctx, "PATCH", path, body, result)
}
func (c *client) delete(ctx context.Context, path string, result any) error {
return c.do(ctx, "DELETE", path, nil, result)
}
func (c *client) do(ctx context.Context, method, path string, body, result any) error {
url := c.baseURL + path
var bodyReader io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("marshal request: %w", err)
}
bodyReader = bytes.NewReader(b)
}
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
if err != nil {
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("X-Api-Key", c.apiKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read response: %w", err)
}
if resp.StatusCode >= 400 {
return &APIError{
StatusCode: resp.StatusCode,
Method: method,
URL: url,
Body: respBody,
}
}
var apiResp apiResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
return fmt.Errorf("unmarshal response: %w", err)
}
if apiResp.Code != 0 {
return &APIError{
StatusCode: resp.StatusCode,
Method: method,
URL: url,
Body: respBody,
APICode: apiResp.Code,
APIMsg: apiResp.Msg,
}
}
if result != nil {
if err := json.Unmarshal(respBody, result); err != nil {
return fmt.Errorf("unmarshal response: %w", err)
}
}
return nil
}
type APIError struct {
StatusCode int
Method string
URL string
Body []byte
APICode int
APIMsg string
}
func (e *APIError) Error() string {
msg := e.APIMsg
if msg == "" {
msg = http.StatusText(e.StatusCode)
}
return msg
}