diff --git a/internal/provider/rainyun/billing.go b/internal/provider/rainyun/billing.go index 70932c1..5a78ebc 100644 --- a/internal/provider/rainyun/billing.go +++ b/internal/provider/rainyun/billing.go @@ -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)) diff --git a/internal/provider/rainyun/client.go b/internal/provider/rainyun/client.go index bb76ee1..0b1bd3e 100644 --- a/internal/provider/rainyun/client.go +++ b/internal/provider/rainyun/client.go @@ -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 diff --git a/internal/provider/rainyun/client_test.go b/internal/provider/rainyun/client_test.go index f3a0d3b..13ee17c 100644 --- a/internal/provider/rainyun/client_test.go +++ b/internal/provider/rainyun/client_test.go @@ -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"` + ID int `json:"id"` + Name string `json:"name"` } 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") } } diff --git a/internal/provider/rainyun/domain.go b/internal/provider/rainyun/domain.go index 55ba8c6..9d4dd72 100644 --- a/internal/provider/rainyun/domain.go +++ b/internal/provider/rainyun/domain.go @@ -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)) diff --git a/internal/provider/rainyun/server.go b/internal/provider/rainyun/server.go index 12f675e..aeebd05 100644 --- a/internal/provider/rainyun/server.go +++ b/internal/provider/rainyun/server.go @@ -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 { diff --git a/internal/provider/rainyun/storage.go b/internal/provider/rainyun/storage.go index c168485..1d5c12e 100644 --- a/internal/provider/rainyun/storage.go +++ b/internal/provider/rainyun/storage.go @@ -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))