diff --git a/client.go b/client.go index 39ab127..c436e12 100644 --- a/client.go +++ b/client.go @@ -86,8 +86,8 @@ func NewOpenAPIClient(opts ...OpenAPIClientOption) *OpenAPIClient { // // The params argument may be a map, struct, or nil. For convenience, a map may // include "path", "query", and "body" keys to explicitly control where the -// values are sent. When no explicit body is provided, non-GET requests send the -// remaining parameters as JSON. +// values are sent. When no explicit body is provided, requests with a declared +// requestBody send the remaining parameters as JSON. func (c *OpenAPIClient) Call(operationID string, params any) (any, error) { if err := c.load(); err != nil { return nil, err @@ -273,7 +273,7 @@ func (c *OpenAPIClient) buildURL(op openAPIOperation, params map[string]any) (st query.Set(key, fmt.Sprint(value)) } } - if op.method == http.MethodGet || op.method == http.MethodHead { + if op.method == http.MethodGet || (op.method == http.MethodHead && !op.hasRequestBody) { for key, value := range params { if key == "path" || key == "body" || key == "query" { continue @@ -300,7 +300,7 @@ func (c *OpenAPIClient) buildBody(op openAPIOperation, params map[string]any) (i return encodeJSONBody(explicitBody) } - if op.method == http.MethodGet || op.method == http.MethodHead { + if op.method == http.MethodGet || (op.method == http.MethodHead && !op.hasRequestBody) { return nil, nil } diff --git a/client_test.go b/client_test.go index d6409e4..ebe7e12 100644 --- a/client_test.go +++ b/client_test.go @@ -4,6 +4,7 @@ package api_test import ( "fmt" + "io" "net/http" "net/http/httptest" "os" @@ -124,6 +125,75 @@ paths: } } +func TestOpenAPIClient_Good_CallHeadOperationWithRequestBody(t *testing.T) { + errCh := make(chan error, 1) + mux := http.NewServeMux() + mux.HandleFunc("/head", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodHead { + errCh <- fmt.Errorf("expected HEAD, got %s", r.Method) + w.WriteHeader(http.StatusInternalServerError) + return + } + if got := r.URL.RawQuery; got != "" { + errCh <- fmt.Errorf("expected no query string, got %q", got) + w.WriteHeader(http.StatusInternalServerError) + return + } + body, err := io.ReadAll(r.Body) + if err != nil { + errCh <- fmt.Errorf("read body: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + if string(body) != `{"name":"Ada"}` { + errCh <- fmt.Errorf("expected JSON body {\"name\":\"Ada\"}, got %q", string(body)) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + }) + + srv := httptest.NewServer(mux) + defer srv.Close() + + specPath := writeTempSpec(t, `openapi: 3.1.0 +info: + title: Test API + version: 1.0.0 +paths: + /head: + head: + operationId: head_check + requestBody: + required: true + content: + application/json: + schema: + type: object +`) + + client := api.NewOpenAPIClient( + api.WithSpec(specPath), + api.WithBaseURL(srv.URL), + ) + + result, err := client.Call("head_check", map[string]any{ + "name": "Ada", + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + select { + case err := <-errCh: + t.Fatal(err) + default: + } + if result != nil { + t.Fatalf("expected nil result for empty HEAD response body, got %T", result) + } +} + func TestOpenAPIClient_Bad_MissingOperation(t *testing.T) { specPath := writeTempSpec(t, `openapi: 3.1.0 info: