oapi-codegen icon indicating copy to clipboard operation
oapi-codegen copied to clipboard

OAS pattern generation without wildcard {$} causes panic in http.ServeMux due to pattern conflicts

Open olzhas-sabiyev opened this issue 6 months ago • 0 comments

When using paths from OAS spec without proper wildcard {$} suffix, the generator registers routes that conflict in http.ServeMux, causing runtime panic. The generated patterns should use "{$}" wildcard to prevent conflicts. This is because Go's http.ServeMux interprets trailing / as a wildcard match, leading to pattern conflicts when multiple routes can match the same path.

Current Problem:

Generated code creates conflicting patterns that cause panic:

panic: pattern "GET /api/test/{id}/test2/" (registered at .../api.gen.go:1001) conflicts with pattern "GET /api/test/test3/" (registered at .../api.gen.go:1000):
        GET /api/test/{id}/test2/ and GET /api/test/test3/ both match some paths, like "/api/test/test3/test2/".
        But neither is more specific than the other.
        GET /api/test/{id}/test2/ matches "/api/test/id/test2/", but GET /api/test/{id}/test2/ doesn't.
        GET /api/test/test3/ matches "/api/test/test3/", but GET /api/test/{id}/test2/ doesn't.
goroutine 1 [running]:
net/http.(*ServeMux).register(...)
        /opt/homebrew/Cellar/go/1.24.2/libexec/src/net/http/server.go:2872
net/http.(*ServeMux).HandleFunc(0x10000200fad701?, {0x140010b6240?, 0x1050993e8?}, 0x0?)

OAS spec causing the conflict:

openapi: 3.0.3

info:
  title: test
  description: test
  version: 1.0.0
paths:
  /api/test/{id}/test2/:
    get:
      operationId: test1
  /api/test/test3/:
    get:
      operationId: test2

Generated code that causes panic:

func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler {
    m := options.BaseRouter

    if m == nil {
       m = http.NewServeMux()
    }
    if options.ErrorHandlerFunc == nil {
       options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
          http.Error(w, err.Error(), http.StatusBadRequest)
       }
    }

    wrapper := ServerInterfaceWrapper{
       Handler:            si,
       HandlerMiddlewares: options.Middlewares,
       ErrorHandlerFunc:   options.ErrorHandlerFunc,
    }
    m.HandleFunc("GET "+options.BaseURL+"/api/test/test3/", wrapper.Test2)
    m.HandleFunc("GET "+options.BaseURL+"/api/test/{id}/test2/", wrapper.Test1)

    return m
}

Working solution with {$} wildcard:

func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler {
    // ... same setup code ...
    
    m.HandleFunc("GET "+options.BaseURL+"/api/test/test3/{$}", wrapper.Test2)
    m.HandleFunc("GET "+options.BaseURL+"/api/test/{id}/test2/", wrapper.Test1)

    return m
}

Proposed Solution:

Add an option to generate wildcard {$} suffix for exact path matching in spec file or detect and fix pattern conflict while generating code

Related issue: https://github.com/oapi-codegen/oapi-codegen/issues/1952

olzhas-sabiyev avatar Aug 23 '25 23:08 olzhas-sabiyev