66"fmt"
77"math"
88"reflect"
9+ "regexp"
910"strings"
1011"time"
1112
@@ -42,10 +43,20 @@ type SetSchemar interface {
4243SetSchema (* 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 {
5465return 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 {
6681return 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 ) {
7193g := NewGenerator (opts ... )
@@ -83,6 +105,7 @@ type Generator struct {
83105SchemaRefs 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
86109componentSchemaRefs map [string ]struct {}
87110}
88111
@@ -111,9 +134,16 @@ func (g *Generator) NewSchemaRefForValue(value interface{}, schemas openapi3.Sch
111134return nil , err
112135}
113136for 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}
119149if strings .HasPrefix (ref .Ref , "#/components/schemas/" ) {
@@ -298,6 +328,14 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
298328schema .Type = & openapi3.Types {"string" }
299329schema .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+
301339for _ , fieldInfo := range typeInfo .Fields {
302340// Only fields with JSON tag are considered (by default)
303341if ! fieldInfo .HasJSONTag && ! g .opts .useAllExportedFields {
@@ -347,6 +385,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
347385g .SchemaRefs [ref ]++
348386schema .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
362401v .SetSchema (schema )
363402}
364403}
404+
365405}
366406
367407if 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+
373436return 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 {
377448var typeName string
378449switch t .Kind () {
@@ -391,7 +462,7 @@ func (g *Generator) generateCycleSchemaRef(t reflect.Type, schema *openapi3.Sche
391462mapSchema .AdditionalProperties = openapi3.AdditionalProperties {Schema : ref }
392463return openapi3 .NewSchemaRef ("" , mapSchema )
393464default :
394- typeName = t . Name ( )
465+ typeName = g . generateTypeName ( t )
395466}
396467
397468g .componentSchemaRefs [typeName ] = struct {}{}
0 commit comments