6
6
"fmt"
7
7
"math"
8
8
"reflect"
9
+ "regexp"
9
10
"strings"
10
11
"time"
11
12
@@ -42,10 +43,20 @@ type SetSchemar interface {
42
43
SetSchema (* openapi3.Schema )
43
44
}
44
45
46
+ type ExportComponentSchemasOptions struct {
47
+ ExportComponentSchemas bool
48
+ ExportTopLevelSchema bool
49
+ ExportGenerics bool
50
+ }
51
+
52
+ type TypeNameGenerator func (t reflect.Type ) string
53
+
45
54
type generatorOpt struct {
46
- useAllExportedFields bool
47
- throwErrorOnCycle bool
48
- schemaCustomizer SchemaCustomizerFn
55
+ useAllExportedFields bool
56
+ throwErrorOnCycle bool
57
+ schemaCustomizer SchemaCustomizerFn
58
+ exportComponentSchemas ExportComponentSchemasOptions
59
+ typeNameGenerator TypeNameGenerator
49
60
}
50
61
51
62
// UseAllExportedFields changes the default behavior of only
@@ -54,6 +65,10 @@ func UseAllExportedFields() Option {
54
65
return func (x * generatorOpt ) { x .useAllExportedFields = true }
55
66
}
56
67
68
+ func CreateTypeNameGenerator (tngnrt TypeNameGenerator ) Option {
69
+ return func (x * generatorOpt ) { x .typeNameGenerator = tngnrt }
70
+ }
71
+
57
72
// ThrowErrorOnCycle changes the default behavior of creating cycle
58
73
// refs to instead error if a cycle is detected.
59
74
func ThrowErrorOnCycle () Option {
@@ -66,6 +81,13 @@ func SchemaCustomizer(sc SchemaCustomizerFn) Option {
66
81
return func (x * generatorOpt ) { x .schemaCustomizer = sc }
67
82
}
68
83
84
+ // CreateComponents changes the default behavior
85
+ // to add all schemas as components
86
+ // Reduces duplicate schemas in routes
87
+ func CreateComponentSchemas (exso ExportComponentSchemasOptions ) Option {
88
+ return func (x * generatorOpt ) { x .exportComponentSchemas = exso }
89
+ }
90
+
69
91
// NewSchemaRefForValue is a shortcut for NewGenerator(...).NewSchemaRefForValue(...)
70
92
func NewSchemaRefForValue (value interface {}, schemas openapi3.Schemas , opts ... Option ) (* openapi3.SchemaRef , error ) {
71
93
g := NewGenerator (opts ... )
@@ -83,6 +105,7 @@ type Generator struct {
83
105
SchemaRefs map [* openapi3.SchemaRef ]int
84
106
85
107
// componentSchemaRefs is a set of schemas that must be defined in the components to avoid cycles
108
+ // or if we have specified create components schemas
86
109
componentSchemaRefs map [string ]struct {}
87
110
}
88
111
@@ -111,9 +134,16 @@ func (g *Generator) NewSchemaRefForValue(value interface{}, schemas openapi3.Sch
111
134
return nil , err
112
135
}
113
136
for ref := range g .SchemaRefs {
114
- if _ , ok := g .componentSchemaRefs [ref .Ref ]; ok && schemas != nil {
115
- schemas [ref .Ref ] = & openapi3.SchemaRef {
116
- Value : ref .Value ,
137
+ refName := ref .Ref
138
+ if g .opts .exportComponentSchemas .ExportComponentSchemas && strings .HasPrefix (refName , "#/components/schemas/" ) {
139
+ refName = strings .TrimPrefix (refName , "#/components/schemas/" )
140
+ }
141
+
142
+ if _ , ok := g .componentSchemaRefs [refName ]; ok && schemas != nil {
143
+ if ref .Value != nil && ref .Value .Properties != nil {
144
+ schemas [refName ] = & openapi3.SchemaRef {
145
+ Value : ref .Value ,
146
+ }
117
147
}
118
148
}
119
149
if strings .HasPrefix (ref .Ref , "#/components/schemas/" ) {
@@ -298,6 +328,14 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
298
328
schema .Type = & openapi3.Types {"string" }
299
329
schema .Format = "date-time"
300
330
} else {
331
+ typeName := g .generateTypeName (t )
332
+
333
+ if _ , ok := g .componentSchemaRefs [typeName ]; ok && g .opts .exportComponentSchemas .ExportComponentSchemas {
334
+ // Check if we have already parsed this component schema ref based on the name of the struct
335
+ // and use that if so
336
+ return openapi3 .NewSchemaRef (fmt .Sprintf ("#/components/schemas/%s" , typeName ), schema ), nil
337
+ }
338
+
301
339
for _ , fieldInfo := range typeInfo .Fields {
302
340
// Only fields with JSON tag are considered (by default)
303
341
if ! fieldInfo .HasJSONTag && ! g .opts .useAllExportedFields {
@@ -347,6 +385,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
347
385
g .SchemaRefs [ref ]++
348
386
schema .WithPropertyRef (fieldName , ref )
349
387
}
388
+
350
389
}
351
390
352
391
// Object only if it has properties
@@ -362,6 +401,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
362
401
v .SetSchema (schema )
363
402
}
364
403
}
404
+
365
405
}
366
406
367
407
if g .opts .schemaCustomizer != nil {
@@ -370,9 +410,40 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
370
410
}
371
411
}
372
412
413
+ if ! g .opts .exportComponentSchemas .ExportComponentSchemas || t .Kind () != reflect .Struct {
414
+ return openapi3 .NewSchemaRef (t .Name (), schema ), nil
415
+ }
416
+
417
+ // Best way I could find to check that
418
+ // this current type is a generic
419
+ isGeneric , err := regexp .Match (`^.*\[.*\]$` , []byte (t .Name ()))
420
+ if err != nil {
421
+ return nil , err
422
+ }
423
+
424
+ if isGeneric && ! g .opts .exportComponentSchemas .ExportGenerics {
425
+ return openapi3 .NewSchemaRef (t .Name (), schema ), nil
426
+ }
427
+
428
+ // For structs we add the schemas to the component schemas
429
+ if len (parents ) > 1 || g .opts .exportComponentSchemas .ExportTopLevelSchema {
430
+ typeName := g .generateTypeName (t )
431
+
432
+ g .componentSchemaRefs [typeName ] = struct {}{}
433
+ return openapi3 .NewSchemaRef (fmt .Sprintf ("#/components/schemas/%s" , typeName ), schema ), nil
434
+ }
435
+
373
436
return openapi3 .NewSchemaRef (t .Name (), schema ), nil
374
437
}
375
438
439
+ func (g * Generator ) generateTypeName (t reflect.Type ) string {
440
+ if g .opts .typeNameGenerator != nil {
441
+ return g .opts .typeNameGenerator (t )
442
+ }
443
+
444
+ return t .Name ()
445
+ }
446
+
376
447
func (g * Generator ) generateCycleSchemaRef (t reflect.Type , schema * openapi3.Schema ) * openapi3.SchemaRef {
377
448
var typeName string
378
449
switch t .Kind () {
@@ -391,7 +462,7 @@ func (g *Generator) generateCycleSchemaRef(t reflect.Type, schema *openapi3.Sche
391
462
mapSchema .AdditionalProperties = openapi3.AdditionalProperties {Schema : ref }
392
463
return openapi3 .NewSchemaRef ("" , mapSchema )
393
464
default :
394
- typeName = t . Name ( )
465
+ typeName = g . generateTypeName ( t )
395
466
}
396
467
397
468
g .componentSchemaRefs [typeName ] = struct {}{}
0 commit comments