feat: Add path Prefix option to validate#54
Conversation
|
hello @jamietanna |
hello @jamietanna ? |
|
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. |
…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>
|
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 Root causeWhen r = r.Clone(r.Context())The cloned request is then used for routing and validation with a trimmed path. However,
ReproductionThe regression can be reproduced with a test that: defines a POST endpoint with a JSON request body Result:
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())
}
})
}
}
|
Hi!
Please review
This is the corrected version of request #40