Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide support for generating recursive types into OpenAPI doc #451 + my touches #454

Merged
merged 13 commits into from
Dec 2, 2021
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