Repository: swaggest/openapi-go (go)

Issue:
panic at self-referential structs
**Describe the bug**
When using self-referential structs,  the code panics with "too deep recursion" error instead of properly handling.


**To Reproduce**
[[If feasible/relevant, please provide a code snippet (inline or with Go playground) to reproduce the issue.](https://go.dev/play/p/Ru52eOhA5rz)]


**Expected behavior**

```
openapi: 3.0.3
info:
  description: Put something here
  title: Things API
  version: 1.2.3
paths: {}
components:
  schemas:
    Req:
      properties:
        subentity:
          $ref: '#/components/schemas/SubEntity'
      type: object
    SubEntity:
      properties:
        self:
          $ref: '#/components/schemas/SubEntity'
      type: object
```




Pull request:
Update deps to fix infinite recursion in cyclic references
Fixes #137.

Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index ae2c76d..a3b265d 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -24,10 +24,10 @@ jobs:
           go-version: stable
       - uses: actions/checkout@v4
       - name: golangci-lint
-        uses: golangci/golangci-lint-action@v6.2.0
+        uses: golangci/golangci-lint-action@v6.5.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.63.4
+          version: v1.64.5
 
           # Optional: working directory, useful for monorepos
           # working-directory: somedir
diff --git a/.golangci.yml b/.golangci.yml
index 02a8b67..f3269dd 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -12,8 +12,6 @@ linters-settings:
     threshold: 100
   misspell:
     locale: US
-  unused:
-    check-exported: false
   unparam:
     check-exported: true
 
@@ -27,9 +25,10 @@ linters:
     - cyclop
     - gocognit
     - musttag
+    - intrange
+    - copyloopvar
     - lll
     - gochecknoglobals
-    - gomnd
     - wrapcheck
     - paralleltest
     - forbidigo
@@ -44,7 +43,6 @@ linters:
     - dupword
     - depguard
     - tagalign
-    - execinquery
     - mnd
     - testifylint
     - recvcheck
@@ -57,17 +55,13 @@ issues:
       text: "SA1019: strings.Title .+ deprecated"
 
     - linters:
-        - gomnd
         - mnd
         - goconst
-        - err113
         - noctx
         - funlen
         - dupl
-        - structcheck
         - unused
         - unparam
-        - nosnakecase
       path: "_test.go"
     - linters:
         - errcheck # Error checking omitted for brevity.
diff --git a/Makefile b/Makefile
index 47df075..4f4b2fa 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-#GOLANGCI_LINT_VERSION := "v1.63.4" # Optional configuration to pinpoint golangci-lint version.
+#GOLANGCI_LINT_VERSION := "v1.64.5" # 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 82867f3..ff3967c 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,11 @@ module github.com/swaggest/openapi-go
 go 1.18
 
 require (
-	github.com/bool64/dev v0.2.38
+	github.com/bool64/dev v0.2.39
 	github.com/stretchr/testify v1.8.2
 	github.com/swaggest/assertjson v1.9.0
 	github.com/swaggest/jsonschema-go v0.3.73
-	github.com/swaggest/refl v1.3.0
+	github.com/swaggest/refl v1.3.1
 	gopkg.in/yaml.v2 v2.4.0
 )
 
diff --git a/go.sum b/go.sum
index d085122..6233c49 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/bool64/dev v0.2.38 h1:C5H9wkx/BhTYRfV14X90iIQKpSuhzsG+OHQvWdQ5YQ4=
-github.com/bool64/dev v0.2.38/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
+github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE=
+github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
 github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -35,8 +35,8 @@ github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7
 github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
 github.com/swaggest/jsonschema-go v0.3.73 h1:gU1pBzF3pkZ1GDD3dRMdQoCjrA0sldJ+QcM7aSSPgvc=
 github.com/swaggest/jsonschema-go v0.3.73/go.mod h1:qp+Ym2DIXHlHzch3HKz50gPf2wJhKOrAB/VYqLS2oJU=
-github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I=
-github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg=
+github.com/swaggest/refl v1.3.1 h1:XGplEkYftR7p9cz1lsiwXMM2yzmOymTE9vneVVpaOh4=
+github.com/swaggest/refl v1.3.1/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA=
 github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
 github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
diff --git a/operation.go b/operation.go
index 92794f4..74b6d4c 100644
--- a/operation.go
+++ b/operation.go
@@ -163,7 +163,7 @@ func WithOperationCtx(oc OperationContext, isProcessingResponse bool, in In) fun
 		oc.SetIsProcessingResponse(isProcessingResponse)
 		oc.SetProcessingIn(in)
 
-		rc.Context = context.WithValue(rc.Context, ocCtxKey{}, oc) //nolint:fatcontext
+		rc.Context = context.WithValue(rc.Context, ocCtxKey{}, oc)
 	}
 }
 


Test patch:
diff --git a/openapi3/reflect_test.go b/openapi3/reflect_test.go
index 7d21156..0defcd0 100644
--- a/openapi3/reflect_test.go
+++ b/openapi3/reflect_test.go
@@ -1402,3 +1402,62 @@ func TestRawBody(t *testing.T) {
 	  }
 	}`, r.SpecSchema())
 }
+
+func TestSelfReference(t *testing.T) {
+	reflector := openapi3.NewReflector()
+
+	type SubEntity struct {
+		Self *SubEntity `json:"self"`
+	}
+
+	type My struct {
+		Foo       string     `json:"foo"`
+		SubEntity *SubEntity `json:"subentity"`
+	}
+
+	putOp, err := reflector.NewOperationContext(http.MethodPut, "/things/")
+	require.NoError(t, err)
+
+	putOp.AddReqStructure(My{})
+	putOp.AddRespStructure(My{})
+
+	require.NoError(t, reflector.AddOperation(putOp))
+
+	assertjson.EqMarshal(t, `{
+	  "openapi":"3.0.3","info":{"title":"","version":""},
+	  "paths":{
+		"/things/":{
+		  "put":{
+			"requestBody":{
+			  "content":{
+				"application/json":{"schema":{"$ref":"#/components/schemas/Openapi3TestMy"}}
+			  }
+			},
+			"responses":{
+			  "200":{
+				"description":"OK",
+				"content":{
+				  "application/json":{"schema":{"$ref":"#/components/schemas/Openapi3TestMy"}}
+				}
+			  }
+			}
+		  }
+		}
+	  },
+	  "components":{
+		"schemas":{
+		  "Openapi3TestMy":{
+			"type":"object",
+			"properties":{
+			  "foo":{"type":"string"},
+			  "subentity":{"$ref":"#/components/schemas/Openapi3TestSubEntity"}
+			}
+		  },
+		  "Openapi3TestSubEntity":{
+			"type":"object",
+			"properties":{"self":{"$ref":"#/components/schemas/Openapi3TestSubEntity"}}
+		  }
+		}
+	  }
+	}`, reflector.SpecSchema())
+}
diff --git a/openapi31/reflect_test.go b/openapi31/reflect_test.go
index ff69e90..dc1912e 100644
--- a/openapi31/reflect_test.go
+++ b/openapi31/reflect_test.go
@@ -1530,3 +1530,62 @@ func TestRawBody(t *testing.T) {
 	  }
 	}`, r.SpecSchema())
 }
+
+func TestSelfReference(t *testing.T) {
+	reflector := openapi31.NewReflector()
+
+	type SubEntity struct {
+		Self *SubEntity `json:"self"`
+	}
+
+	type My struct {
+		Foo       string     `json:"foo"`
+		SubEntity *SubEntity `json:"subentity"`
+	}
+
+	putOp, err := reflector.NewOperationContext(http.MethodPut, "/things/")
+	require.NoError(t, err)
+
+	putOp.AddReqStructure(My{})
+	putOp.AddRespStructure(My{})
+
+	require.NoError(t, reflector.AddOperation(putOp))
+
+	assertjson.EqMarshal(t, `{
+	  "openapi":"3.1.0","info":{"title":"","version":""},
+	  "paths":{
+		"/things/":{
+		  "put":{
+			"requestBody":{
+			  "content":{
+				"application/json":{"schema":{"$ref":"#/components/schemas/Openapi31TestMy"}}
+			  }
+			},
+			"responses":{
+			  "200":{
+				"description":"OK",
+				"content":{
+				  "application/json":{"schema":{"$ref":"#/components/schemas/Openapi31TestMy"}}
+				}
+			  }
+			}
+		  }
+		}
+	  },
+	  "components":{
+		"schemas":{
+		  "Openapi31TestMy":{
+			"properties":{
+			  "foo":{"type":"string"},
+			  "subentity":{"$ref":"#/components/schemas/Openapi31TestSubEntity"}
+			},
+			"type":"object"
+		  },
+		  "Openapi31TestSubEntity":{
+			"properties":{"self":{"$ref":"#/components/schemas/Openapi31TestSubEntity"}},
+			"type":"object"
+		  }
+		}
+	  }
+	}`, reflector.SpecSchema())
+}