Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .greptile/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Be pragmatic: do not demand identical changes in seven places. Do your best to a
- `openapi31/` — OpenAPI 3.1-specific behavior
- `options/` — output-options flags (name-normalizer, filters, skip-prune, yaml-tags, …)
- `parameters/` — parameter binding, styles, encoding, nil handling (incl. the cross-framework `roundtrip/` harness)
- `paths/` — path-level routing edge cases: literal colons in paths, URL escaping of reserved characters, and path-parameter precedence. Because colon-in-path routing is backend-specific, leaves here may fan out into per-framework subpackages (`echo/`, `gin/`, `fiber/`, `fiberv3/`, …) each with its own `config.yaml` and generated server, driven by one shared `spec.yaml` and a single top-level routing test — mirroring `parameters/roundtrip/`. This multi-subpackage shape is expected for routing fixtures and is not the "standard leaf layout" churn to flag.
- `references/` — external `$ref`s, import-mapping, multi-package generation, overlays
- `schemas/` — schema-to-type mapping (primitives, objects, enums, nullable, recursive, …)
- `servers/` — server codegen (routers, middleware, strict servers)
Expand All @@ -86,7 +87,7 @@ When a PR adds a test, enforce the following:

- **Do not allow issue-numbered test directories to come back.** Flag any new directory named after a GitHub issue (`internal/test/issues/…`, `issue-1234/`, `issueNNNN/`, and similar) anywhere under `internal/test/`. The old `issues/` tree was deliberately dissolved into the categories above.
- **Prefer extending an existing test case.** If the scenario fits an existing category leaf — same OpenAPI construct, same generation config — the new schemas/operations belong in that leaf's `spec.yaml` and its `*_test.go`, marked with a provenance comment (`# From issue-NNNN: <one-line summary>`) so the issue context is not lost. Suggest the specific leaf to extend when you can identify one.
- **A new leaf is fine when nothing matches.** A new subdirectory under the right category is the correct move when the scenario needs a *different generation config* (other `generate:` targets or `output-options:`) or inherently needs its own files (multi-file external-ref layouts). It should follow the standard layout — `doc.go` (with the `//go:generate` line), `config.yaml`, `spec.yaml`, `<name>_test.go` — with a snake_case scenario-named directory (never an issue number) and the issue reference in a comment.
- **A new leaf is fine when nothing matches.** A new subdirectory under the right category is the correct move when the scenario needs a *different generation config* (other `generate:` targets or `output-options:`) or inherently needs its own files (multi-file external-ref layouts, or per-framework routing fixtures like `paths/` and `parameters/roundtrip/`). It should follow the standard layout — `doc.go` (with the `//go:generate` line), `config.yaml`, `spec.yaml`, `<name>_test.go` — with a snake_case scenario-named directory (never an issue number) and the issue reference in a comment. Cross-framework fixtures legitimately depart from this by fanning out into per-framework subpackages; do not flag that shape.
- **Regression tests for bug fixes are still expected** — they just live in the category matching the feature, not in an issue-named directory.

## 7. General
Expand Down
101 changes: 101 additions & 0 deletions internal/test/paths/colon_paths_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package paths

import (
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/gin-gonic/gin"
fiberv2 "github.com/gofiber/fiber/v2"
fiberv3 "github.com/gofiber/fiber/v3"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

colonecho "github.com/oapi-codegen/oapi-codegen/v2/internal/test/paths/echo"
colonfiber "github.com/oapi-codegen/oapi-codegen/v2/internal/test/paths/fiber"
colonfiberv3 "github.com/oapi-codegen/oapi-codegen/v2/internal/test/paths/fiberv3"
colongin "github.com/oapi-codegen/oapi-codegen/v2/internal/test/paths/gin"
)

// Each handler writes its own operation name so the test can prove that
// /pets:validate and /pets:generate dispatch to distinct handlers rather than
// colliding on a single ":"-parameter route (issue #1726).

type echoServer struct{}

func (echoServer) ValidatePets(ctx echo.Context) error { return ctx.String(http.StatusOK, "validate") }
func (echoServer) GeneratePets(ctx echo.Context) error { return ctx.String(http.StatusOK, "generate") }

type ginServer struct{}

func (ginServer) ValidatePets(c *gin.Context) { c.String(http.StatusOK, "validate") }
func (ginServer) GeneratePets(c *gin.Context) { c.String(http.StatusOK, "generate") }

type fiberServer struct{}

func (fiberServer) ValidatePets(c *fiberv2.Ctx) error { return c.SendString("validate") }
func (fiberServer) GeneratePets(c *fiberv2.Ctx) error { return c.SendString("generate") }

type fiberv3Server struct{}

func (fiberv3Server) ValidatePets(c fiberv3.Ctx) error { return c.SendString("validate") }
func (fiberv3Server) GeneratePets(c fiberv3.Ctx) error { return c.SendString("generate") }

// wantBody maps each colon path to the body its dedicated handler returns.
var wantBody = map[string]string{
"/pets:validate": "validate",
"/pets:generate": "generate",
}

// assertServeHTTP drives an http.Handler-compatible router (echo, gin).
func assertServeHTTP(t *testing.T, name string, h http.Handler) {
t.Helper()
for path, want := range wantBody {
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, path, nil)
h.ServeHTTP(rec, req)
assert.Equalf(t, http.StatusOK, rec.Code, "%s POST %s status", name, path)
assert.Equalf(t, want, rec.Body.String(), "%s POST %s dispatched to wrong handler", name, path)
}
}

func TestEchoColonPathsRouteIndependently(t *testing.T) {
e := echo.New()
colonecho.RegisterHandlers(e, echoServer{})
assertServeHTTP(t, "echo", e)
}

func TestGinColonPathsRouteIndependently(t *testing.T) {
gin.SetMode(gin.TestMode)
g := gin.New()
colongin.RegisterHandlers(g, ginServer{})
assertServeHTTP(t, "gin", g)
}

func TestFiberColonPathsRouteIndependently(t *testing.T) {
app := fiberv2.New()
colonfiber.RegisterHandlers(app, fiberServer{})
for path, want := range wantBody {
req := httptest.NewRequest(http.MethodPost, path, nil)
resp, err := app.Test(req)
require.NoErrorf(t, err, "fiber POST %s", path)
assert.Equalf(t, http.StatusOK, resp.StatusCode, "fiber POST %s status", path)
body, _ := io.ReadAll(resp.Body)
assert.Equalf(t, want, string(body), "fiber POST %s dispatched to wrong handler", path)
}
}

func TestFiberV3ColonPathsRouteIndependently(t *testing.T) {
app := fiberv3.New()
colonfiberv3.RegisterHandlers(app, fiberv3Server{})
for path, want := range wantBody {
req := httptest.NewRequest(http.MethodPost, path, nil)
resp, err := app.Test(req)
require.NoErrorf(t, err, "fiberv3 POST %s", path)
assert.Equalf(t, http.StatusOK, resp.StatusCode, "fiberv3 POST %s status", path)
body, _ := io.ReadAll(resp.Body)
assert.Equalf(t, want, string(body), "fiberv3 POST %s dispatched to wrong handler", path)
}
}
10 changes: 10 additions & 0 deletions internal/test/paths/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Package paths verifies path-handling edge cases in generated servers. It
// currently checks that literal-colon path segments route independently on the
// ':'-parameter routers (Echo, Gin, Fiber v2/v3) rather than colliding as a
// single path parameter. See issue #1726.
package paths

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=echo/config.yaml spec.yaml
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=gin/config.yaml spec.yaml
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=fiber/config.yaml spec.yaml
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=fiberv3/config.yaml spec.yaml
Comment thread
mromaszewicz marked this conversation as resolved.
93 changes: 93 additions & 0 deletions internal/test/paths/echo/colon.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions internal/test/paths/echo/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# yaml-language-server: $schema=../../../../../configuration-schema.json
package: colonpathsecho
generate:
echo-server: true
models: true
output: echo/colon.gen.go
92 changes: 92 additions & 0 deletions internal/test/paths/fiber/colon.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions internal/test/paths/fiber/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# yaml-language-server: $schema=../../../../../configuration-schema.json
package: colonpathsfiber
generate:
fiber-server: true
models: true
output: fiber/colon.gen.go
Loading