fix: query params support + remove duplicate response wrappers + fix tests

This commit is contained in:
2026-07-28 16:45:45 +08:00
parent 4e348031a6
commit 284a503541
6 changed files with 43 additions and 49 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ func (b *BillingService) Orders(ctx context.Context) ([]provider.Order, error) {
CreateDate string `json:"create_date"`
}
var raw []rainyunOrder
if err := b.client.get(ctx, "/expense/orders/list", &raw); err != nil {
if err := b.client.getWithQuery(ctx, "/expense/orders/list", map[string]string{"options": "1"}, &raw); err != nil {
return nil, err
}
orders := make([]provider.Order, len(raw))
+25 -12
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"time"
)
@@ -36,23 +37,35 @@ type apiResponse struct {
}
func (c *client) get(ctx context.Context, path string, result any) error {
return c.do(ctx, "GET", path, nil, result)
return c.do(ctx, "GET", path, nil, result, nil)
}
func (c *client) getWithQuery(ctx context.Context, path string, query map[string]string, result any) error {
return c.do(ctx, "GET", path, nil, result, query)
}
func (c *client) post(ctx context.Context, path string, body, result any) error {
return c.do(ctx, "POST", path, body, result)
return c.do(ctx, "POST", path, body, result, nil)
}
func (c *client) patch(ctx context.Context, path string, body, result any) error {
return c.do(ctx, "PATCH", path, body, result)
return c.do(ctx, "PATCH", path, body, result, nil)
}
func (c *client) delete(ctx context.Context, path string, result any) error {
return c.do(ctx, "DELETE", path, nil, result)
return c.do(ctx, "DELETE", path, nil, result, nil)
}
func (c *client) do(ctx context.Context, method, path string, body, result any) error {
url := c.baseURL + path
func (c *client) do(ctx context.Context, method, path string, body, result any, query map[string]string) error {
u := c.baseURL + path
if len(query) > 0 {
params := url.Values{}
for k, v := range query {
params.Set(k, v)
}
u += "?" + params.Encode()
}
var bodyReader io.Reader
if body != nil {
@@ -63,7 +76,7 @@ func (c *client) do(ctx context.Context, method, path string, body, result any)
bodyReader = bytes.NewReader(b)
}
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
req, err := http.NewRequestWithContext(ctx, method, u, bodyReader)
if err != nil {
return fmt.Errorf("create request: %w", err)
}
@@ -88,7 +101,7 @@ func (c *client) do(ctx context.Context, method, path string, body, result any)
return &APIError{
StatusCode: resp.StatusCode,
Method: method,
URL: url,
URL: u,
Body: respBody,
APICode: apiResp.Code,
APIMsg: apiResp.Msg,
@@ -98,14 +111,14 @@ func (c *client) do(ctx context.Context, method, path string, body, result any)
return &APIError{
StatusCode: resp.StatusCode,
Method: method,
URL: url,
URL: u,
Body: respBody,
}
}
if result != nil {
if err := json.Unmarshal(respBody, result); err != nil {
return fmt.Errorf("unmarshal response: %w", err)
if result != nil && apiResp.Data != nil {
if err := json.Unmarshal(apiResp.Data, result); err != nil {
return fmt.Errorf("unmarshal data: %w", err)
}
}
return nil
+2 -5
View File
@@ -18,11 +18,8 @@ func TestClientAuthHeader(t *testing.T) {
c := newClient(srv.URL, "sk-test")
var result struct {
Code int `json:"code"`
Data struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"data"`
}
if err := c.get(context.Background(), "/test", &result); err != nil {
t.Fatalf("get() error: %v", err)
@@ -30,8 +27,8 @@ func TestClientAuthHeader(t *testing.T) {
if gotKey != "sk-test" {
t.Errorf("X-Api-Key = %q, want %q", gotKey, "sk-test")
}
if result.Data.Name != "test" {
t.Errorf("Data.Name = %q, want %q", result.Data.Name, "test")
if result.Name != "test" {
t.Errorf("Name = %q, want %q", result.Name, "test")
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ type rainyunDomain struct {
func (d *DomainService) List(ctx context.Context) ([]provider.Domain, error) {
var raw []rainyunDomain
if err := d.client.get(ctx, "/product/domain/", &raw); err != nil {
if err := d.client.getWithQuery(ctx, "/product/domain/", map[string]string{"options": "1"}, &raw); err != nil {
return nil, err
}
domains := make([]provider.Domain, len(raw))
+11 -27
View File
@@ -27,43 +27,25 @@ type rainyunServer struct {
ExpireDate string `json:"expire_date"`
}
// Response wrappers — the client unmarshals the full API envelope (code + data).
type serverListResponse struct {
Code int `json:"code"`
Data []rainyunServer `json:"data"`
}
type serverGetResponse struct {
Code int `json:"code"`
Data rainyunServer `json:"data"`
}
type vncResponse struct {
Code int `json:"code"`
Data struct {
VNCURL string `json:"vnc_url"`
} `json:"data"`
}
func (s *ServerService) List(ctx context.Context) ([]provider.Server, error) {
var resp serverListResponse
if err := s.client.get(ctx, "/product/rcs/", &resp); err != nil {
var raw []rainyunServer
if err := s.client.getWithQuery(ctx, "/product/rcs/", map[string]string{"options": "1"}, &raw); err != nil {
return nil, err
}
servers := make([]provider.Server, len(resp.Data))
for i, r := range resp.Data {
servers := make([]provider.Server, len(raw))
for i, r := range raw {
servers[i] = toServer(r)
}
return servers, nil
}
func (s *ServerService) Get(ctx context.Context, id string) (*provider.Server, error) {
var resp serverGetResponse
var raw rainyunServer
path := fmt.Sprintf("/product/rcs/%s/", id)
if err := s.client.get(ctx, path, &resp); err != nil {
if err := s.client.get(ctx, path, &raw); err != nil {
return nil, err
}
sv := toServer(resp.Data)
sv := toServer(raw)
return &sv, nil
}
@@ -89,11 +71,13 @@ func (s *ServerService) ResetPassword(ctx context.Context, id string) error {
}
func (s *ServerService) GetVNC(ctx context.Context, id string) (string, error) {
var resp vncResponse
var resp struct {
VNCURL string `json:"vnc_url"`
}
if err := s.client.get(ctx, fmt.Sprintf("/product/rcs/%s/vnc", id), &resp); err != nil {
return "", err
}
return resp.Data.VNCURL, nil
return resp.VNCURL, nil
}
func (s *ServerService) Upgrade(ctx context.Context, id, plan string) error {
+1 -1
View File
@@ -17,7 +17,7 @@ func (s *StorageService) List(ctx context.Context) ([]provider.StorageInstance,
Name string `json:"name"`
Status string `json:"status"`
}
if err := s.client.get(ctx, "/product/ros/instance", &raw); err != nil {
if err := s.client.getWithQuery(ctx, "/product/ros/instance", map[string]string{"options": "1"}, &raw); err != nil {
return nil, err
}
instances := make([]provider.StorageInstance, len(raw))