@@ -3,6 +3,7 @@ package openapi3gen
3
3
4
4
import (
5
5
"encoding/json"
6
+ "fmt"
6
7
"math"
7
8
"reflect"
8
9
"strings"
@@ -22,6 +23,7 @@ type Option func(*generatorOpt)
22
23
23
24
type generatorOpt struct {
24
25
useAllExportedFields bool
26
+ throwErrorOnCycle bool
25
27
}
26
28
27
29
// UseAllExportedFields changes the default behavior of only
@@ -30,6 +32,12 @@ func UseAllExportedFields() Option {
30
32
return func (x * generatorOpt ) { x .useAllExportedFields = true }
31
33
}
32
34
35
+ // ThrowErrorOnCycle changes the default behavior of creating cycle
36
+ // refs to instead error if a cycle is detected.
37
+ func ThrowErrorOnCycle () Option {
38
+ return func (x * generatorOpt ) { x .throwErrorOnCycle = true }
39
+ }
40
+
33
41
// NewSchemaRefForValue uses reflection on the given value to produce a SchemaRef.
34
42
func NewSchemaRefForValue (value interface {}, opts ... Option ) (* openapi3.SchemaRef , map [* openapi3.SchemaRef ]int , error ) {
35
43
g := NewGenerator (opts ... )
@@ -104,6 +112,10 @@ func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflec
104
112
if a && b {
105
113
vs , err := g .generateSchemaRefFor (parents , v .Type )
106
114
if err != nil {
115
+ if _ , ok := err .(* CycleError ); ok && ! g .opts .throwErrorOnCycle {
116
+ g .SchemaRefs [vs ]++
117
+ return vs , nil
118
+ }
107
119
return nil , err
108
120
}
109
121
refSchemaRef := RefSchemaRef
@@ -185,7 +197,11 @@ func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflec
185
197
schema .Type = "array"
186
198
items , err := g .generateSchemaRefFor (parents , t .Elem ())
187
199
if err != nil {
188
- return nil , err
200
+ if _ , ok := err .(* CycleError ); ok && ! g .opts .throwErrorOnCycle {
201
+ items = g .generateCycleSchemaRef (t .Elem (), schema )
202
+ } else {
203
+ return nil , err
204
+ }
189
205
}
190
206
if items != nil {
191
207
g .SchemaRefs [items ]++
@@ -197,7 +213,11 @@ func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflec
197
213
schema .Type = "object"
198
214
additionalProperties , err := g .generateSchemaRefFor (parents , t .Elem ())
199
215
if err != nil {
200
- return nil , err
216
+ if _ , ok := err .(* CycleError ); ok && ! g .opts .throwErrorOnCycle {
217
+ additionalProperties = g .generateCycleSchemaRef (t .Elem (), schema )
218
+ } else {
219
+ return nil , err
220
+ }
201
221
}
202
222
if additionalProperties != nil {
203
223
g .SchemaRefs [additionalProperties ]++
@@ -221,7 +241,11 @@ func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflec
221
241
if t .Field (fieldInfo .Index [0 ]).Anonymous {
222
242
ref , err := g .generateSchemaRefFor (parents , fType )
223
243
if err != nil {
224
- return nil , err
244
+ if _ , ok := err .(* CycleError ); ok && ! g .opts .throwErrorOnCycle {
245
+ ref = g .generateCycleSchemaRef (fType , schema )
246
+ } else {
247
+ return nil , err
248
+ }
225
249
}
226
250
if ref != nil {
227
251
g .SchemaRefs [ref ]++
@@ -237,7 +261,11 @@ func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflec
237
261
238
262
ref , err := g .generateSchemaRefFor (parents , fType )
239
263
if err != nil {
240
- return nil , err
264
+ if _ , ok := err .(* CycleError ); ok && ! g .opts .throwErrorOnCycle {
265
+ ref = g .generateCycleSchemaRef (fType , schema )
266
+ } else {
267
+ return nil , err
268
+ }
241
269
}
242
270
if ref != nil {
243
271
g .SchemaRefs [ref ]++
@@ -255,6 +283,30 @@ func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflec
255
283
return openapi3 .NewSchemaRef (t .Name (), schema ), nil
256
284
}
257
285
286
+ func (g * Generator ) generateCycleSchemaRef (t reflect.Type , schema * openapi3.Schema ) * openapi3.SchemaRef {
287
+ var typeName string
288
+ switch t .Kind () {
289
+ case reflect .Ptr :
290
+ return g .generateCycleSchemaRef (t .Elem (), schema )
291
+ case reflect .Slice :
292
+ ref := g .generateCycleSchemaRef (t .Elem (), schema )
293
+ sliceSchema := openapi3 .NewSchema ()
294
+ sliceSchema .Type = "array"
295
+ sliceSchema .Items = ref
296
+ return openapi3 .NewSchemaRef ("" , sliceSchema )
297
+ case reflect .Map :
298
+ ref := g .generateCycleSchemaRef (t .Elem (), schema )
299
+ mapSchema := openapi3 .NewSchema ()
300
+ mapSchema .Type = "object"
301
+ mapSchema .AdditionalProperties = ref
302
+ return openapi3 .NewSchemaRef ("" , mapSchema )
303
+ default :
304
+ typeName = t .Name ()
305
+ }
306
+
307
+ return openapi3 .NewSchemaRef (fmt .Sprintf ("#/components/schemas/%s" , typeName ), schema )
308
+ }
309
+
258
310
var RefSchemaRef = openapi3 .NewSchemaRef ("Ref" ,
259
311
openapi3 .NewObjectSchema ().WithProperty ("$ref" , openapi3 .NewStringSchema ().WithMinLength (1 )))
260
312
0 commit comments