From fcecdff30d422e06c2b6c9b18ae55603eaca717f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=99=E2=97=A6=20The=20Tablet=20=E2=9D=80=20GamerGirla?= =?UTF-8?q?ndCo=20=E2=97=A6=E2=9D=A7?= Date: Fri, 17 Jul 2026 18:57:22 -0400 Subject: [PATCH 1/4] fix: ensure path params with a regex constraint are parsed correcty --- internal/builder/builder.go | 2 +- internal/validate/path.go | 5 +++-- internal/validate/utils.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/builder/builder.go b/internal/builder/builder.go index ac91107..8de180c 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -171,7 +171,7 @@ func (b *Builder) ensurePathParameters(target string, op *openapi.Operation) { return } for _, m := range matches { - name := m[1] + name, _, _ := strings.Cut(m[1], ":") if _, ok := existing[name]; ok { continue } diff --git a/internal/validate/path.go b/internal/validate/path.go index 0edf144..d1f2817 100644 --- a/internal/validate/path.go +++ b/internal/validate/path.go @@ -81,7 +81,8 @@ func ValidatePathParams(path, method string, params []*openapi.Parameter) []erro matches := pathParamRe.FindAllStringSubmatch(path, -1) templateNames := map[string]struct{}{} for _, match := range matches { - templateNames[match[1]] = struct{}{} + name, _, _ := strings.Cut(match[1], ":") + templateNames[name] = struct{}{} } declared := map[string]bool{} for _, p := range params { @@ -104,7 +105,7 @@ func ValidatePathParams(path, method string, params []*openapi.Parameter) []erro } } for _, match := range matches { - name := match[1] + name, _, _ := strings.Cut(match[1], ":") if required, ok := declared[name]; !ok { errs = append(errs, Errorf("%s %s missing path parameter %q", strings.ToUpper(method), path, name)) } else if !required { diff --git a/internal/validate/utils.go b/internal/validate/utils.go index ee1aa5a..a0e4a42 100644 --- a/internal/validate/utils.go +++ b/internal/validate/utils.go @@ -14,7 +14,7 @@ import ( ) var ( - pathParamRe = regexp.MustCompile(`\{([^{}]+)\}`) + pathParamRe = regexp.MustCompile(`\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}`) componentRe = regexp.MustCompile(`^[a-zA-Z0-9.\-_]+$`) responseCodeRe = regexp.MustCompile(`^[1-5]([0-9]{2}|XX)$`) ) From 471a81d3564a63491b9c7818a895e03042255b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=99=E2=97=A6=20The=20Tablet=20=E2=9D=80=20GamerGirla?= =?UTF-8?q?ndCo=20=E2=97=A6=E2=9D=A7?= Date: Fri, 17 Jul 2026 18:57:39 -0400 Subject: [PATCH 2/4] refactor(tests): add extra path normalization test case --- internal/validate/utils_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/validate/utils_test.go b/internal/validate/utils_test.go index 24e3458..3e20ab9 100644 --- a/internal/validate/utils_test.go +++ b/internal/validate/utils_test.go @@ -14,6 +14,16 @@ import ( func TestNormalizeTemplatedPath(t *testing.T) { assert.Equal(t, "/users/{}", validate.NormalizeTemplatedPath("/users/{id}")) assert.Equal(t, "/orgs/{}/repos/{}", validate.NormalizeTemplatedPath("/orgs/{org}/repos/{repo}")) + assert.Equal(t, "/type/{}", validate.NormalizeTemplatedPath("/type/{type:foo|bar}")) + assert.Equal(t, "/more/{}/{}", validate.NormalizeTemplatedPath("/more/{param}/{type:foo|bar}")) + assert.Equal(t, "/complex/quantifier/{}", validate.NormalizeTemplatedPath("/complex/quantifier/{t:(\\d{4})}")) + assert.Equal( + t, + "/foo/bar/{}/two/{}/8/nine/{}", + validate.NormalizeTemplatedPath("/foo/bar/{one}/two/{three:(four|five){6,7}(eight|nine)}/8/nine/{wtf}"), + ) + assert.Equal(t, "/empty/{}", validate.NormalizeTemplatedPath("/empty/{type:}")) + assert.Equal(t, "/escaped/{}", validate.NormalizeTemplatedPath("/escaped/{type:foo\\{bar\\}}")) assert.Equal(t, "/static", validate.NormalizeTemplatedPath("/static")) } From 91bd9db6ea57733f77d97a347aa93ff2f204782e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=99=E2=97=A6=20The=20Tablet=20=E2=9D=80=20GamerGirla?= =?UTF-8?q?ndCo=20=E2=97=A6=E2=9D=A7?= Date: Fri, 17 Jul 2026 18:58:54 -0400 Subject: [PATCH 3/4] style: format files --- examples/petstore/main.go | 7 +++++-- internal/validate/utils_test.go | 2 -- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/petstore/main.go b/examples/petstore/main.go index a93c697..357aaa6 100644 --- a/examples/petstore/main.go +++ b/examples/petstore/main.go @@ -28,10 +28,13 @@ func main() { option.Request(new(Pet)), option.Response(201, new(Pet)), ) - pet.Get("/findByStatus", + pet.Get( + "/findByStatus", option.OperationID("findPetsByStatus"), option.Summary("Find pets by status"), - option.Description("Finds Pets by status. Multiple status values can be provided with comma separated strings."), + option.Description( + "Finds Pets by status. Multiple status values can be provided with comma separated strings.", + ), option.Request(new(struct { Status string `query:"status" enum:"available,pending,sold"` // Enum values for pet status })), diff --git a/internal/validate/utils_test.go b/internal/validate/utils_test.go index 3e20ab9..5ced9a8 100644 --- a/internal/validate/utils_test.go +++ b/internal/validate/utils_test.go @@ -22,8 +22,6 @@ func TestNormalizeTemplatedPath(t *testing.T) { "/foo/bar/{}/two/{}/8/nine/{}", validate.NormalizeTemplatedPath("/foo/bar/{one}/two/{three:(four|five){6,7}(eight|nine)}/8/nine/{wtf}"), ) - assert.Equal(t, "/empty/{}", validate.NormalizeTemplatedPath("/empty/{type:}")) - assert.Equal(t, "/escaped/{}", validate.NormalizeTemplatedPath("/escaped/{type:foo\\{bar\\}}")) assert.Equal(t, "/static", validate.NormalizeTemplatedPath("/static")) } From 94443110497929da961f067bb3a85c3b571f5f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=99=E2=97=A6=20The=20Tablet=20=E2=9D=80=20GamerGirla?= =?UTF-8?q?ndCo=20=E2=97=A6=E2=9D=A7?= Date: Fri, 17 Jul 2026 19:27:33 -0400 Subject: [PATCH 4/4] refactor: allow escaped curly braces in regex constraints --- internal/validate/utils.go | 2 +- internal/validate/utils_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/validate/utils.go b/internal/validate/utils.go index a0e4a42..d68e038 100644 --- a/internal/validate/utils.go +++ b/internal/validate/utils.go @@ -14,7 +14,7 @@ import ( ) var ( - pathParamRe = regexp.MustCompile(`\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}`) + pathParamRe = regexp.MustCompile(`\{((?:\\.|[^{}\\]|\{(?:\\.|[^{}\\])*\})*)\}`) componentRe = regexp.MustCompile(`^[a-zA-Z0-9.\-_]+$`) responseCodeRe = regexp.MustCompile(`^[1-5]([0-9]{2}|XX)$`) ) diff --git a/internal/validate/utils_test.go b/internal/validate/utils_test.go index 5ced9a8..d0bbc37 100644 --- a/internal/validate/utils_test.go +++ b/internal/validate/utils_test.go @@ -22,6 +22,7 @@ func TestNormalizeTemplatedPath(t *testing.T) { "/foo/bar/{}/two/{}/8/nine/{}", validate.NormalizeTemplatedPath("/foo/bar/{one}/two/{three:(four|five){6,7}(eight|nine)}/8/nine/{wtf}"), ) + assert.Equal(t, "/curly/brace/{}/{}", validate.NormalizeTemplatedPath(`/curly/brace/{one:\{}/{two:\}\}}`)) assert.Equal(t, "/static", validate.NormalizeTemplatedPath("/static")) }