Repository: swaggest/openapi-go (go)

Issue:
Improving the regular expression used to remove gorilla.mux-style regexp in paths
Hello,
Imagine I have the 2 routes in my API : `/users/{userID:[^/]+}` and `/users/{userID:[^/]+}/books/{bookID:.+}`.
The regexp are `[^/]+` because without it, calling `users/toto/books/openapi`, matches with `/users/{userID:[^/]+}` with userID being `toto/books/openapi`.

The problem is that during `Spec.SetupOperation()`, `{userID:[^/]+}` is not matched by `regexFindPathParameter` (`{([^}:]+)(:[^/]+)?(?:})`).

I think changing the regex to `{([^}:]+)(:[^}]+)?(?:})` would solve the problem.

Pull request description:
Update path param regex to have better tolerance and accuracy
Fixes #45.

Patch:
diff --git a/.github/workflows/cloc.yml b/.github/workflows/cloc.yml
index 4592bb4..619ca74 100644
--- a/.github/workflows/cloc.yml
+++ b/.github/workflows/cloc.yml
@@ -24,7 +24,9 @@ jobs:
       - name: Count Lines Of Code
         id: loc
         run: |
-          curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.2/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz && echo "b17e76bede22af0206b4918d3b3c4e7357f2a21b57f8de9e7c9dc0eb56b676c0  sccdiff" | shasum -c
+          curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.3/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz
+          sccdiff_hash=$(git hash-object ./sccdiff)
+          [ "$sccdiff_hash" == "ae8a07b687bd3dba60861584efe724351aa7ff63" ] || (echo "::error::unexpected hash for sccdiff, possible tampering: $sccdiff_hash" && exit 1)
           OUTPUT=$(cd pr && ../sccdiff -basedir ../base)
           echo "${OUTPUT}"
           OUTPUT="${OUTPUT//$'\n'/%0A}"
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 54723e6..a38a33d 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -27,7 +27,7 @@ jobs:
         uses: golangci/golangci-lint-action@v3.1.0
         with:
           # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
-          version: v1.48.0
+          version: v1.49.0
 
           # Optional: working directory, useful for monorepos
           # working-directory: somedir
diff --git a/.golangci.yml b/.golangci.yml
index 5c85c26..985b120 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -44,6 +44,10 @@ linters:
     - ireturn
     - exhaustruct
     - nonamedreturns
+    - nosnakecase
+    - structcheck
+    - varcheck
+    - deadcode
 
 issues:
   exclude:
diff --git a/Makefile b/Makefile
index 19ce935..f0bfa5f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-#GOLANGCI_LINT_VERSION := "v1.48.0" # Optional configuration to pinpoint golangci-lint version.
+#GOLANGCI_LINT_VERSION := "v1.49.0" # Optional configuration to pinpoint golangci-lint version.
 
 # The head of Makefile determines location of dev-go to include standard targets.
 GO ?= go
diff --git a/go.mod b/go.mod
index c8c475c..d021ca6 100644
--- a/go.mod
+++ b/go.mod
@@ -3,10 +3,10 @@ module github.com/swaggest/openapi-go
 go 1.17
 
 require (
-	github.com/bool64/dev v0.2.19
+	github.com/bool64/dev v0.2.20
 	github.com/stretchr/testify v1.8.0
 	github.com/swaggest/assertjson v1.7.0
-	github.com/swaggest/jsonschema-go v0.3.36
+	github.com/swaggest/jsonschema-go v0.3.40
 	github.com/swaggest/refl v1.1.0
 	gopkg.in/yaml.v2 v2.4.0
 )
diff --git a/go.sum b/go.sum
index 543f83d..fdd2b2a 100644
--- a/go.sum
+++ b/go.sum
@@ -2,9 +2,9 @@ github.com/bool64/dev v0.2.5/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zR
 github.com/bool64/dev v0.2.10/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
 github.com/bool64/dev v0.2.16/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
 github.com/bool64/dev v0.2.17/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
-github.com/bool64/dev v0.2.18/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
-github.com/bool64/dev v0.2.19 h1:s++kaqTDpAJ53JJuCZr0up64tpjiMJFDJYRWZEYaIxc=
 github.com/bool64/dev v0.2.19/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
+github.com/bool64/dev v0.2.20 h1:9eIRGdcg2kQW2NGza++QbOKidNNaK+KfWuUXcZFDejE=
+github.com/bool64/dev v0.2.20/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/shared v0.1.4/go.mod h1:ryGjsnQFh6BnEXClfVlEJrzjwzat7CmA8PNS5E+jPp0=
 github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
 github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
@@ -62,8 +62,8 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw=
 github.com/swaggest/assertjson v1.7.0/go.mod h1:vxMJMehbSVJd+dDWFCKv3QRZKNTpy/ktZKTz9LOEDng=
-github.com/swaggest/jsonschema-go v0.3.36 h1:S1LHKKtBpoyvlJYDKXT4U7+9n8PryAdbIEpBc561LzI=
-github.com/swaggest/jsonschema-go v0.3.36/go.mod h1:vOJ5t/RW4tbLttUFGiegE+jlsKj8+iQT4MVDXhWSuw0=
+github.com/swaggest/jsonschema-go v0.3.40 h1:9EqQ9RvtdW69xfYODmyEKWOSZ12x5eiK+wGD2EVh/L4=
+github.com/swaggest/jsonschema-go v0.3.40/go.mod h1:ipIOmoFP64QyRUgyPyU/P9tayq2m2TlvUhyZHrhe3S4=
 github.com/swaggest/refl v1.1.0 h1:a+9a75Kv6ciMozPjVbOfcVTEQe81t2R3emvaD9oGQGc=
 github.com/swaggest/refl v1.1.0/go.mod h1:g3Qa6ki0A/L2yxiuUpT+cuBURuRaltF5SDQpg1kMZSY=
 github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU=
diff --git a/openapi3/doc.go b/openapi3/doc.go
new file mode 100644
index 0000000..0da5f6e
--- /dev/null
+++ b/openapi3/doc.go
@@ -0,0 +1,2 @@
+// Package openapi3 provides entities and helpers to manage OpenAPI 3.0.x schema.
+package openapi3
diff --git a/openapi3/helper.go b/openapi3/helper.go
index da2ed4a..377f8e5 100644
--- a/openapi3/helper.go
+++ b/openapi3/helper.go
@@ -23,7 +23,7 @@ func (p *PathItem) WithOperation(method string, operation Operation) *PathItem {
 	return p.WithMapOfOperationValuesItem(strings.ToLower(method), operation)
 }
 
-var regexFindPathParameter = regexp.MustCompile(`{([^}:]+)(:[^/]+)?(?:})`)
+var regexFindPathParameter = regexp.MustCompile(`{([^}:]+)(:[^}]+)?(?:})`)
 
 // SetupOperation creates operation if it is not present and applies setup functions.
 func (s *Spec) SetupOperation(method, path string, setup ...func(*Operation) error) error {


Test patch:
diff --git a/openapi3/helper_test.go b/openapi3/helper_test.go
index 3cba48c..19311a3 100644
--- a/openapi3/helper_test.go
+++ b/openapi3/helper_test.go
@@ -40,3 +40,40 @@ func TestSpec_SetOperation(t *testing.T) {
 	assert.EqualError(t, s.AddOperation(http.MethodGet, "/another/{foo}", op),
 		"duplicate parameter in path: foo, duplicate parameter in query: bar")
 }
+
+func TestSpec_SetupOperation_pathRegex(t *testing.T) {
+	s := openapi3.Spec{}
+
+	for _, tc := range []struct {
+		path   string
+		params []string
+	}{
+		{`/{month}-{day}-{year}`, []string{"month", "day", "year"}},
+		{`/{month}/{day}/{year}`, []string{"month", "day", "year"}},
+		{`/{month:[\d]+}-{day:[\d]+}-{year:[\d]+}`, []string{"month", "day", "year"}},
+		{`/{articleSlug:[a-z-]+}`, []string{"articleSlug"}},
+		{"/articles/{rid:^[0-9]{5,6}}", []string{"rid"}},
+		{"/articles/{rid:^[0-9]{5,6}}/{zid:^[0-9]{5,6}}", []string{"rid", "zid"}},
+		{"/articles/{zid:^0[0-9]+}", []string{"zid"}},
+		{"/articles/{name:^@[a-z]+}/posts", []string{"name"}},
+		{"/articles/{op:^[0-9]+}/run", []string{"op"}},
+		{"/users/{userID:[^/]+}", []string{"userID"}},
+		{"/users/{userID:[^/]+}/books/{bookID:.+}", []string{"userID", "bookID"}},
+	} {
+		t.Run(tc.path, func(t *testing.T) {
+			assert.NoError(t, s.SetupOperation(http.MethodGet, tc.path,
+				func(operation *openapi3.Operation) error {
+					var pp []openapi3.ParameterOrRef
+
+					for _, p := range tc.params {
+						pp = append(pp, openapi3.Parameter{In: openapi3.ParameterInPath, Name: p}.ToParameterOrRef())
+					}
+
+					operation.WithParameters(pp...)
+
+					return nil
+				},
+			))
+		})
+	}
+}