Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Commit

Permalink
encoding/openapi: add option to close over imported types
Browse files Browse the repository at this point in the history
To suppor this, two options are included:

SelfContained: to enable the feature.

NameFunc to allow user-defined fully qualified names
to disambiguate imported types.

Issue #56

Change-Id: I372f35c5fd71d204a60c8c26d4efce21f9457a27
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2375
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
  • Loading branch information
mpvl committed Jul 1, 2019
1 parent f9bd63e commit ef90d5c
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 15 deletions.
96 changes: 81 additions & 15 deletions encoding/openapi/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"math"
"path"
"sort"
"strconv"
"strings"

Expand All @@ -26,12 +27,24 @@ import (
)

type buildContext struct {
inst *cue.Instance
refPrefix string
path []string

expandRefs bool
nameFunc func(inst *cue.Instance, path []string) string

schemas *orderedMap

// Track external schemas.
externalRefs map[string]*externalType
}

type externalType struct {
ref string
inst *cue.Instance
path []string
value cue.Value
}

type oaSchema = orderedMap
Expand All @@ -40,9 +53,12 @@ type typeFunc func(b *builder, a cue.Value)

func components(inst *cue.Instance, cfg *Config) (comps *orderedMap, err error) {
c := buildContext{
refPrefix: "components/schema",
expandRefs: cfg.ExpandReferences,
schemas: &orderedMap{},
inst: inst,
refPrefix: "components/schema",
expandRefs: cfg.ExpandReferences,
nameFunc: cfg.ReferenceFunc,
schemas: &orderedMap{},
externalRefs: map[string]*externalType{},
}

defer func() {
Expand Down Expand Up @@ -75,7 +91,24 @@ func components(inst *cue.Instance, cfg *Config) (comps *orderedMap, err error)
if c.isInternal(label) {
continue
}
c.schemas.Set(label, c.build(label, i.Value()))
c.schemas.Set(c.makeRef(inst, []string{label}), c.build(label, i.Value()))
}

// keep looping until a fixed point is reached.
for done := 0; len(c.externalRefs) != done; {
done = len(c.externalRefs)

// From now on, all references need to be expanded
external := []string{}
for k := range c.externalRefs {
external = append(external, k)
}
sort.Strings(external)

for _, k := range external {
ext := c.externalRefs[k]
c.schemas.Set(ext.ref, c.build(ext.ref, ext.value.Eval()))
}
}
return comps, nil
}
Expand Down Expand Up @@ -110,20 +143,23 @@ func (b *builder) schema(name string, v cue.Value) *oaSchema {
defer func() { b.ctx.path = oldPath }()

c := newRootBuilder(b.ctx)
c.value(v, nil)
isRef := c.value(v, nil)
schema := c.finish()
doc := []string{}
for _, d := range v.Doc() {
doc = append(doc, d.Text())
}
if len(doc) > 0 {
str := strings.TrimSpace(strings.Join(doc, "\n"))
schema.Prepend("description", str)

if !isRef {
doc := []string{}
for _, d := range v.Doc() {
doc = append(doc, d.Text())
}
if len(doc) > 0 {
str := strings.TrimSpace(strings.Join(doc, "\n"))
schema.Prepend("description", str)
}
}
return schema
}

func (b *builder) value(v cue.Value, f typeFunc) {
func (b *builder) value(v cue.Value, f typeFunc) (isRef bool) {
count := 0
disallowDefault := false
var values cue.Value
Expand All @@ -132,18 +168,28 @@ func (b *builder) value(v cue.Value, f typeFunc) {
values = v
count = 1
} else {
dedup := map[string]bool{}
hasNoRef := false
for _, v := range appendSplit(nil, cue.AndOp, v) {
// This may be a reference to an enum. So we need to check references before
// dissecting them.
switch p, r := v.Reference(); {
case len(r) > 0:
ref := b.ctx.makeRef(p, r)
if dedup[ref] {
continue
}
dedup[ref] = true

b.addRef(v, p, r)
disallowDefault = true
default:
hasNoRef = true
count++
values = values.Unify(v)
}
}
isRef = !hasNoRef && len(dedup) == 1
}

if count > 0 { // TODO: implement IsAny.
Expand Down Expand Up @@ -193,6 +239,7 @@ func (b *builder) value(v cue.Value, f typeFunc) {
b.set("default", v)
}
}
return isRef
}

func appendSplit(a []cue.Value, splitBy cue.Op, v cue.Value) []cue.Value {
Expand Down Expand Up @@ -729,10 +776,29 @@ func (b *builder) addConjunct(f func(*builder)) {
}

func (b *builder) addRef(v cue.Value, inst *cue.Instance, ref []string) {
name := b.ctx.makeRef(inst, ref)
b.addConjunct(func(b *builder) {
a := append([]string{"#", b.ctx.refPrefix}, ref...)
b.set("$ref", path.Join(a...))
b.set("$ref", path.Join("#", b.ctx.refPrefix, name))
})

if b.ctx.inst != inst {
b.ctx.externalRefs[name] = &externalType{
ref: name,
inst: inst,
path: ref,
value: v,
}
}
}

func (b *buildContext) makeRef(inst *cue.Instance, ref []string) string {
a := make([]string, 0, len(ref)+3)
if b.nameFunc != nil {
a = append(a, b.nameFunc(inst, ref))
} else {
a = append(a, ref...)
}
return path.Join(a...)
}

func (b *builder) int(v cue.Value) int64 {
Expand Down
8 changes: 8 additions & 0 deletions encoding/openapi/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ import (

// A Config defines options for mapping CUE to and from OpenAPI.
type Config struct {
// ReferenceFunc allows users to specify an alternative representation
// for references.
ReferenceFunc func(inst *cue.Instance, path []string) string

// SelfContained causes all non-expanded external references to be included
// in this document.
SelfContained bool

// ExpandReferences replaces references with actual objects when generating
// OpenAPI Schema. It is an error for an CUE value to refer to itself
// when this object is used.
Expand Down
7 changes: 7 additions & 0 deletions encoding/openapi/testdata/simple.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
MyStruct: {
mediumNum: int32
smallNum: int8

float: float32
double: float64
}
35 changes: 35 additions & 0 deletions encoding/openapi/testdata/simple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"openapi": "3.0.0",
"components": {
"schema": {
"MyStruct": {
"type": "object",
"required": [
"mediumNum",
"smallNum",
"float",
"double"
],
"properties": {
"double": {
"type": "number",
"format": "double"
},
"float": {
"type": "number",
"format": "float"
},
"mediumNum": {
"type": "integer",
"format": "int32"
},
"smallNum": {
"type": "integer",
"minimum": -128,
"maximum": 127
}
}
}
}
}
}

0 comments on commit ef90d5c

Please sign in to comment.