66 "fmt"
77 "math"
88 "reflect"
9+ "regexp"
910 "strings"
1011 "time"
1112
@@ -42,10 +43,20 @@ type SetSchemar interface {
4243 SetSchema (* openapi3.Schema )
4344}
4445
46+ type ExportComponentSchemasOptions struct {
47+ ExportComponentSchemas bool
48+ ExportTopLevelSchema bool
49+ ExportGenerics bool
50+ }
51+
52+ type TypeNameGenerator func (t reflect.Type ) string
53+
4554type 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
4960}
5061
5162// UseAllExportedFields changes the default behavior of only
@@ -54,6 +65,10 @@ func UseAllExportedFields() Option {
5465 return func (x * generatorOpt ) { x .useAllExportedFields = true }
5566}
5667
68+ func CreateTypeNameGenerator (tngnrt TypeNameGenerator ) Option {
69+ return func (x * generatorOpt ) { x .typeNameGenerator = tngnrt }
70+ }
71+
5772// ThrowErrorOnCycle changes the default behavior of creating cycle
5873// refs to instead error if a cycle is detected.
5974func ThrowErrorOnCycle () Option {
@@ -66,6 +81,13 @@ func SchemaCustomizer(sc SchemaCustomizerFn) Option {
6681 return func (x * generatorOpt ) { x .schemaCustomizer = sc }
6782}
6883
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+
6991// NewSchemaRefForValue is a shortcut for NewGenerator(...).NewSchemaRefForValue(...)
7092func NewSchemaRefForValue (value interface {}, schemas openapi3.Schemas , opts ... Option ) (* openapi3.SchemaRef , error ) {
7193 g := NewGenerator (opts ... )
@@ -83,6 +105,7 @@ type Generator struct {
83105 SchemaRefs map [* openapi3.SchemaRef ]int
84106
85107 // 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
86109 componentSchemaRefs map [string ]struct {}
87110}
88111
@@ -111,9 +134,16 @@ func (g *Generator) NewSchemaRefForValue(value interface{}, schemas openapi3.Sch
111134 return nil , err
112135 }
113136 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+ }
117147 }
118148 }
119149 if strings .HasPrefix (ref .Ref , "#/components/schemas/" ) {
@@ -298,6 +328,14 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
298328 schema .Type = & openapi3.Types {"string" }
299329 schema .Format = "date-time"
300330 } 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+
301339 for _ , fieldInfo := range typeInfo .Fields {
302340 // Only fields with JSON tag are considered (by default)
303341 if ! fieldInfo .HasJSONTag && ! g .opts .useAllExportedFields {
@@ -347,6 +385,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
347385 g .SchemaRefs [ref ]++
348386 schema .WithPropertyRef (fieldName , ref )
349387 }
388+
350389 }
351390
352391 // Object only if it has properties
@@ -362,6 +401,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
362401 v .SetSchema (schema )
363402 }
364403 }
404+
365405 }
366406
367407 if g .opts .schemaCustomizer != nil {
@@ -370,9 +410,40 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
370410 }
371411 }
372412
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+
373436 return openapi3 .NewSchemaRef (t .Name (), schema ), nil
374437}
375438
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+
376447func (g * Generator ) generateCycleSchemaRef (t reflect.Type , schema * openapi3.Schema ) * openapi3.SchemaRef {
377448 var typeName string
378449 switch t .Kind () {
@@ -391,7 +462,7 @@ func (g *Generator) generateCycleSchemaRef(t reflect.Type, schema *openapi3.Sche
391462 mapSchema .AdditionalProperties = openapi3.AdditionalProperties {Schema : ref }
392463 return openapi3 .NewSchemaRef ("" , mapSchema )
393464 default :
394- typeName = t . Name ( )
465+ typeName = g . generateTypeName ( t )
395466 }
396467
397468 g .componentSchemaRefs [typeName ] = struct {}{}
0 commit comments