Skip to content

feat: Add path Prefix option to validate#54

Merged
mromaszewicz merged 9 commits intooapi-codegen:mainfrom
Fedott:feat/prefix-option
Apr 2, 2026
Merged

feat: Add path Prefix option to validate#54
mromaszewicz merged 9 commits intooapi-codegen:mainfrom
Fedott:feat/prefix-option

Conversation

@Fedott
Copy link
Copy Markdown
Contributor

@Fedott Fedott commented Aug 20, 2025

Hi!
Please review
This is the corrected version of request #40

@Fedott Fedott requested a review from a team as a code owner August 20, 2025 14:15
@Fedott Fedott mentioned this pull request Sep 16, 2025
@GreyXor
Copy link
Copy Markdown
Contributor

GreyXor commented Jan 31, 2026

hello @jamietanna
It's all good ? Can you merge ?
Thank you

@GreyXor
Copy link
Copy Markdown
Contributor

GreyXor commented Apr 2, 2026

hello @jamietanna It's all good ? Can you merge ? Thank you

hello @jamietanna ?

@mromaszewicz
Copy link
Copy Markdown
Member

I found several bugs in this PR. I'm uploading tests and fixes for them to your branch, so I can merge a working PR.

mromaszewicz and others added 2 commits April 2, 2026 16:26
…nal request

- makeRequestForValidation now checks that the prefix matches on a path
  segment boundary (followed by '/' or end of path), preventing partial
  matches like prefix "/api" incorrectly stripping "/api-v2/resource"
- performRequestValidationForErrorHandlerWithOpts no longer leaks the
  prefix-stripped request to the downstream handler; it was passing the
  modified request to next.ServeHTTP instead of the original
- Skip stripping RawPath when it is empty
- Add comprehensive prefix tests covering both error handler paths,
  segment boundary enforcement, and original-path preservation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mromaszewicz mromaszewicz merged commit b16f749 into oapi-codegen:main Apr 2, 2026
14 checks passed
@cgroschupp
Copy link
Copy Markdown

This change reintroduces an issue that was previously fixed in: v1.1.2.

That release explicitly addressed request body reuse after it has been read during validation (e.g. in AuthenticationFunc / request validation). With the current prefix handling (introduced in commit 4865bdf), this guarantee no longer holds.

Root cause

When Options.Prefix is set, the middleware creates a modified request via:

r = r.Clone(r.Context())

The cloned request is then used for routing and validation with a trimmed path.

However, (*http.Request).Clone() only performs a shallow copy of Body. As a result:

  • the validator reads and may replace the Body on the cloned request
  • the original request, which is later passed to the handler, still holds the old already-consumed body
  • downstream handlers that read the body again, for example via JSON decoding or render.Bind, fail with:
    http: invalid Read on closed Body

Reproduction

The regression can be reproduced with a test that:

defines a POST endpoint with a JSON request body
enables middleware with Options.Prefix
reads the request body again in the handler

Result:

  • without Prefix: works
  • with Prefix: fails with handler failed to decode body: EOF

oapi_validate_prefix_body_regression_test.go:

package nethttpmiddleware_test

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/getkin/kin-openapi/openapi3"
	middleware "github.com/oapi-codegen/nethttp-middleware"
)

func TestOapiRequestValidatorWithOptions_RequestBodyCanBeReadByHandler_WithAndWithoutPrefix(t *testing.T) {
	t.Parallel()

	const rawSpec = `
openapi: "3.0.0"
info:
  version: 1.0.0
  title: TestServer
paths:
  /resource:
    post:
      operationId: createResource
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
              additionalProperties: false
      responses:
        '204':
          description: No content
`

	tests := []struct {
		name        string
		prefix      string
		routePath   string
		requestPath string
	}{
		{
			name:        "without prefix",
			prefix:      "",
			routePath:   "/resource",
			requestPath: "/resource",
		},
		{
			name:        "with prefix",
			prefix:      "/api",
			routePath:   "/api/resource",
			requestPath: "/api/resource",
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			must := func(err error) {
				t.Helper()
				if err != nil {
					t.Fatal(err)
				}
			}

			spec, err := openapi3.NewLoader().LoadFromData([]byte(rawSpec))
			must(err)

			spec.Servers = nil

			router := http.NewServeMux()
			router.HandleFunc(tc.routePath, func(w http.ResponseWriter, r *http.Request) {
				var payload struct {
					Name string `json:"name"`
				}

				if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
					http.Error(w, "handler failed to decode body: "+err.Error(), http.StatusBadRequest)
					return
				}

				if payload.Name != "Jamie" {
					http.Error(w, "unexpected payload name", http.StatusBadRequest)
					return
				}

				w.WriteHeader(http.StatusNoContent)
			})

			mw := middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{
				Prefix: tc.prefix,
			})

			server := mw(router)

			body := map[string]string{
				"name": "Jamie",
			}
			data, err := json.Marshal(body)
			must(err)

			req, err := http.NewRequest(http.MethodPost, tc.requestPath, bytes.NewReader(data))
			must(err)
			req.Header.Set("Content-Type", "application/json")

			rr := httptest.NewRecorder()
			server.ServeHTTP(rr, req)

			if rr.Code != http.StatusNoContent {
				t.Fatalf("expected HTTP %d, got HTTP %d, body=%q", http.StatusNoContent, rr.Code, rr.Body.String())
			}
		})
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants