From 946c00e8abb725b62679414da8127598506460ee Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 14 Oct 2025 11:36:00 -0400 Subject: [PATCH] Add public MakeRequest method to client. Most of the time, sdk users will interact with the api using generated client methods. However, in some cases it may be useful to interact with the api more directly, analogous to `oxide api` in the cli. For example, users may want to invoke an api method that isn't yet supported in the sdk. This patch adds a new public method called MakeRequest that allows users to send custom requests to the api. --- oxide/lib.go | 19 ++++++++++++++ oxide/lib_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/oxide/lib.go b/oxide/lib.go index c3fa175..9607bbd 100644 --- a/oxide/lib.go +++ b/oxide/lib.go @@ -301,3 +301,22 @@ func (c *Client) buildRequest(ctx context.Context, body io.Reader, method, uri s return req, nil } + +type Request struct { + Method string + Path string + Body io.Reader + Params map[string]string + Query map[string]string +} + +// MakeRequest takes a `Request` that defines the desired API request, builds +// the URI using the configured API host, and sends the request to the API. +func (c *Client) MakeRequest(ctx context.Context, req Request) (*http.Response, error) { + uri := resolveRelative(c.host, req.Path) + httpReq, err := c.buildRequest(ctx, req.Body, req.Method, uri, req.Params, req.Query) + if err != nil { + return nil, fmt.Errorf("building request failed: %v", err) + } + return c.client.Do(httpReq) +} diff --git a/oxide/lib_test.go b/oxide/lib_test.go index 096d13f..971872a 100644 --- a/oxide/lib_test.go +++ b/oxide/lib_test.go @@ -10,6 +10,7 @@ import ( "encoding/json" "io" "net/http" + "net/http/httptest" "net/url" "os" "path/filepath" @@ -470,3 +471,66 @@ user = "other-user" return tmpDir } + +func Test_MakeRequest(t *testing.T) { + tests := []struct { + name string + request Request + expectedQuery string + }{ + { + name: "request without optional fields", + request: Request{ + Method: http.MethodGet, + Path: "/v1/projects", + }, + expectedQuery: "", + }, + { + name: "request with all fields", + request: Request{ + Method: http.MethodPost, + Path: "/v1/projects", + Body: strings.NewReader(`{"name":"my-project"}`), + Params: map[string]string{ + "project": "my-project", + }, + Query: map[string]string{ + "project": "my-project", + }, + }, + expectedQuery: "project=my-project", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var capturedRequest *http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + capturedRequest = r + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`{"status":"ok"}`)) + require.NoError(t, err) + })) + defer server.Close() + + client, err := NewClient(&Config{ + Host: server.URL, + Token: "test-token", + }) + require.NoError(t, err) + + resp, err := client.MakeRequest(context.Background(), tc.request) + require.NoError(t, err) + require.NotNil(t, resp) + defer resp.Body.Close() + + require.NotNil(t, capturedRequest) + assert.Equal(t, tc.request.Method, capturedRequest.Method) + assert.Equal(t, tc.request.Path, capturedRequest.URL.Path) + assert.Equal(t, tc.expectedQuery, capturedRequest.URL.RawQuery) + + assert.Equal(t, http.StatusOK, resp.StatusCode) + }) + } +}