Skip to content

Commit

Permalink
Provide support for generating recursive types into OpenAPI doc #451
Browse files Browse the repository at this point in the history
…+ my touches (#454)

Co-authored-by: Peter Broadhurst <peter.broadhurst@kaleido.io>
  • Loading branch information
fenollp and peterbroadhurst authored Dec 2, 2021
1 parent f7c80fe commit f1075be
Show file tree
Hide file tree
Showing 4 changed files with 378 additions and 99 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ func arrayUniqueItemsChecker(items []interface{}) bool {

## Sub-v0 breaking API changes

### v0.84.0
* The prototype of `openapi3gen.NewSchemaRefForValue` changed:
* It no longer returns a map but that is still accessible under the field `(*Generator).SchemaRefs`.
* It now takes in an additional argument (basically `doc.Components.Schemas`) which gets written to so `$ref` cycles can be properly handled.

### v0.61.0
* Renamed `openapi2.Swagger` to `openapi2.T`.
* Renamed `openapi2conv.FromV3Swagger` to `openapi2conv.FromV3`.
Expand Down
47 changes: 36 additions & 11 deletions openapi3gen/openapi3gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,10 @@ func SchemaCustomizer(sc SchemaCustomizerFn) Option {
return func(x *generatorOpt) { x.schemaCustomizer = sc }
}

// NewSchemaRefForValue uses reflection on the given value to produce a SchemaRef.
func NewSchemaRefForValue(value interface{}, opts ...Option) (*openapi3.SchemaRef, map[*openapi3.SchemaRef]int, error) {
// NewSchemaRefForValue is a shortcut for NewGenerator(...).NewSchemaRefForValue(...)
func NewSchemaRefForValue(value interface{}, schemas openapi3.Schemas, opts ...Option) (*openapi3.SchemaRef, error) {
g := NewGenerator(opts...)
ref, err := g.GenerateSchemaRef(reflect.TypeOf(value))
for ref := range g.SchemaRefs {
ref.Ref = ""
}
return ref, g.SchemaRefs, err
return g.NewSchemaRefForValue(value, schemas)
}

type Generator struct {
Expand All @@ -71,6 +67,9 @@ type Generator struct {
// If count is 1, it's not ne
// An OpenAPI identifier has been assigned to each.
SchemaRefs map[*openapi3.SchemaRef]int

// componentSchemaRefs is a set of schemas that must be defined in the components to avoid cycles
componentSchemaRefs map[string]struct{}
}

func NewGenerator(opts ...Option) *Generator {
Expand All @@ -79,9 +78,10 @@ func NewGenerator(opts ...Option) *Generator {
f(gOpt)
}
return &Generator{
Types: make(map[reflect.Type]*openapi3.SchemaRef),
SchemaRefs: make(map[*openapi3.SchemaRef]int),
opts: *gOpt,
Types: make(map[reflect.Type]*openapi3.SchemaRef),
SchemaRefs: make(map[*openapi3.SchemaRef]int),
componentSchemaRefs: make(map[string]struct{}),
opts: *gOpt,
}
}

Expand All @@ -90,17 +90,41 @@ func (g *Generator) GenerateSchemaRef(t reflect.Type) (*openapi3.SchemaRef, erro
return g.generateSchemaRefFor(nil, t, "_root", "")
}

// NewSchemaRefForValue uses reflection on the given value to produce a SchemaRef, and updates a supplied map with any dependent component schemas if they lead to cycles
func (g *Generator) NewSchemaRefForValue(value interface{}, schemas openapi3.Schemas) (*openapi3.SchemaRef, error) {
ref, err := g.GenerateSchemaRef(reflect.TypeOf(value))
if err != nil {
return nil, err
}
for ref := range g.SchemaRefs {
if _, ok := g.componentSchemaRefs[ref.Ref]; ok && schemas != nil {
schemas[ref.Ref] = &openapi3.SchemaRef{
Value: ref.Value,
}
}
if strings.HasPrefix(ref.Ref, "#/components/schemas/") {
ref.Value = nil
} else {
ref.Ref = ""
}
}
return ref, nil
}

func (g *Generator) generateSchemaRefFor(parents []*jsoninfo.TypeInfo, t reflect.Type, name string, tag reflect.StructTag) (*openapi3.SchemaRef, error) {
if ref := g.Types[t]; ref != nil && g.opts.schemaCustomizer == nil {
g.SchemaRefs[ref]++
return ref, nil
}
ref, err := g.generateWithoutSaving(parents, t, name, tag)
if err != nil {
return nil, err
}
if ref != nil {
g.Types[t] = ref
g.SchemaRefs[ref]++
}
return ref, err
return ref, nil
}

func getStructField(t reflect.Type, fieldInfo jsoninfo.FieldInfo) reflect.StructField {
Expand Down Expand Up @@ -341,6 +365,7 @@ func (g *Generator) generateCycleSchemaRef(t reflect.Type, schema *openapi3.Sche
typeName = t.Name()
}

g.componentSchemaRefs[typeName] = struct{}{}
return openapi3.NewSchemaRef(fmt.Sprintf("#/components/schemas/%s", typeName), schema)
}

Expand Down
Loading

0 comments on commit f1075be

Please sign in to comment.