encoding/openapi: make OrderedMap type opaque
The OrderedMap needs to be a pointer, which may be
a bit unintuitive it being a slice. Change it to as struct.
Info is left as a non-pointer to indicate the non-optional
nature of it. The user of pointers is enforced by making
MarshalJSON only work for pointer types.
Change-Id: I8481dd8974825928815bfef3acd1eb5fc0274029
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2460
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index 9adfbe6..9b49b1f 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -56,7 +56,7 @@
type typeFunc func(b *builder, a cue.Value)
-func schemas(g *Generator, inst *cue.Instance) (schemas OrderedMap, err error) {
+func schemas(g *Generator, inst *cue.Instance) (schemas *OrderedMap, err error) {
var fieldFilter *regexp.Regexp
if g.FieldFilter != "" {
fieldFilter, err = regexp.Compile(g.FieldFilter)
@@ -107,7 +107,7 @@
if c.isInternal(label) {
continue
}
- c.schemas.set(c.makeRef(inst, []string{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.
@@ -123,11 +123,11 @@
for _, k := range external {
ext := c.externalRefs[k]
- c.schemas.set(ext.ref, c.build(ext.ref, ext.value.Eval()))
+ c.schemas.Set(ext.ref, c.build(ext.ref, ext.value.Eval()))
}
}
- return *c.schemas, nil
+ return c.schemas, nil
}
func (c *buildContext) build(name string, v cue.Value) *oaSchema {
@@ -459,11 +459,11 @@
b.setFilter("Schema", "required", required)
}
- properties := map[string]*oaSchema{}
+ properties := &OrderedMap{}
for i, _ := v.Fields(cue.Optional(true), cue.Hidden(false)); i.Next(); {
- properties[i.Label()] = b.schema(i.Label(), i.Value())
+ properties.Set(i.Label(), b.schema(i.Label(), i.Value()))
}
- if len(properties) > 0 {
+ if len(properties.kvs) > 0 {
b.set("properties", properties)
}
@@ -738,9 +738,9 @@
func setType(t *oaSchema, b *builder) {
if b.typ != "" {
- t.set("type", b.typ)
+ t.Set("type", b.typ)
if b.format != "" {
- t.set("format", b.format)
+ t.Set("format", b.format)
}
}
}
@@ -762,19 +762,19 @@
b.current = &OrderedMap{}
b.allOf = append(b.allOf, b.current)
}
- b.current.set(key, v)
+ b.current.Set(key, v)
}
func (b *builder) kv(key string, value interface{}) *oaSchema {
constraint := &OrderedMap{}
setType(constraint, b)
- constraint.set(key, value)
+ constraint.Set(key, value)
return constraint
}
func (b *builder) setNot(key string, value interface{}) {
not := &OrderedMap{}
- not.set("not", b.kv(key, value))
+ not.Set("not", b.kv(key, value))
b.add(not)
}
@@ -795,7 +795,7 @@
default:
t := &OrderedMap{}
- t.set("allOf", b.allOf)
+ t.Set("allOf", b.allOf)
return t
}
}
diff --git a/encoding/openapi/openapi.go b/encoding/openapi/openapi.go
index 58cb41f..74cf693 100644
--- a/encoding/openapi/openapi.go
+++ b/encoding/openapi/openapi.go
@@ -75,25 +75,25 @@
// All generates an OpenAPI definition from the given instance.
//
// Note: only a limited number of top-level types are supported so far.
-func (g *Generator) All(inst *cue.Instance) (OrderedMap, error) {
+func (g *Generator) All(inst *cue.Instance) (*OrderedMap, error) {
all, err := g.Schemas(inst)
if err != nil {
return nil, err
}
schemas := &OrderedMap{}
- schemas.set("schema", all)
+ schemas.Set("schema", all)
- top := OrderedMap{}
- top.set("openapi", "3.0.0")
- top.set("info", g.Info)
- top.set("components", schemas)
+ top := &OrderedMap{}
+ top.Set("openapi", "3.0.0")
+ top.Set("info", &g.Info)
+ top.Set("components", schemas)
return top, nil
}
// Schemas extracts component/schemas from the CUE top-level types.
-func (g *Generator) Schemas(inst *cue.Instance) (OrderedMap, error) {
+func (g *Generator) Schemas(inst *cue.Instance) (*OrderedMap, error) {
comps, err := schemas(g, inst)
if err != nil {
return nil, err
diff --git a/encoding/openapi/openapi_test.go b/encoding/openapi/openapi_test.go
index 7bb1b0c..d5eb721 100644
--- a/encoding/openapi/openapi_test.go
+++ b/encoding/openapi/openapi_test.go
@@ -31,7 +31,7 @@
var update *bool = flag.Bool("update", false, "update the test output")
func TestParseDefinitions(t *testing.T) {
- info := OrderedMap{KeyValue{"title", "test"}, KeyValue{"version", "v1"}}
+ info := OrderedMap{kvs: []KeyValue{{"title", "test"}, {"version", "v1"}}}
defaultConfig := &Config{}
resolveRefs := &Config{Info: info, ExpandReferences: true}
diff --git a/encoding/openapi/orderedmap.go b/encoding/openapi/orderedmap.go
index 8d419a8..91a2ec4 100644
--- a/encoding/openapi/orderedmap.go
+++ b/encoding/openapi/orderedmap.go
@@ -18,7 +18,9 @@
// An OrderedMap is a set of key-value pairs that preserves the order in which
// items were added. It marshals to JSON as an object.
-type OrderedMap []KeyValue
+type OrderedMap struct {
+ kvs []KeyValue
+}
// KeyValue associates a value with a key.
type KeyValue struct {
@@ -26,26 +28,37 @@
Value interface{}
}
-func (m *OrderedMap) prepend(key string, value interface{}) {
- *m = append([]KeyValue{{key, value}}, (*m)...)
+// Pairs returns the KeyValue pairs associated with m.
+func (m *OrderedMap) Pairs() []KeyValue {
+ return m.kvs
}
-// set sets a key value pair. If a pair with the same key already existed, it
+func (m *OrderedMap) prepend(key string, value interface{}) {
+ m.kvs = append([]KeyValue{{key, value}}, m.kvs...)
+}
+
+// Set sets a key value pair. If a pair with the same key already existed, it
// will be replaced with the new value. Otherwise, the new value is added to
// the end.
-func (m *OrderedMap) set(key string, value interface{}) {
- for i, v := range *m {
+func (m *OrderedMap) Set(key string, value interface{}) {
+ for i, v := range m.kvs {
if v.Key == key {
- (*m)[i].Value = value
+ m.kvs[i].Value = value
return
}
}
- *m = append(*m, KeyValue{key, value})
+ m.kvs = append(m.kvs, KeyValue{key, value})
+}
+
+// SetAll replaces existing key-value pairs with the given ones. The keys must
+// be unique.
+func (m *OrderedMap) SetAll(kvs []KeyValue) {
+ m.kvs = kvs
}
// exists reports whether a key-value pair exists for the given key.
-func (m OrderedMap) exists(key string) bool {
- for _, v := range m {
+func (m *OrderedMap) exists(key string) bool {
+ for _, v := range m.kvs {
if v.Key == key {
return true
}
@@ -53,10 +66,13 @@
return false
}
-// MarshalJSON implements Marshal
-func (m OrderedMap) MarshalJSON() (b []byte, err error) {
+// MarshalJSON implements json.Marshaler.
+func (m *OrderedMap) MarshalJSON() (b []byte, err error) {
+ // This is a pointer receiever to enforce that we only store pointers to
+ // OrderedMap in the output.
+
b = append(b, '{')
- for i, v := range m {
+ for i, v := range m.kvs {
if i > 0 {
b = append(b, ",\n"...)
}
diff --git a/encoding/openapi/testdata/oneof-funcs.json b/encoding/openapi/testdata/oneof-funcs.json
index 2ca742f..1510628 100644
--- a/encoding/openapi/testdata/oneof-funcs.json
+++ b/encoding/openapi/testdata/oneof-funcs.json
@@ -50,8 +50,8 @@
"count"
],
"properties": {
- "count": {
- "$ref": "#/components/schema/MYINT"
+ "include": {
+ "$ref": "#/components/schema/MYSTRING"
},
"exclude": {
"description": "Randomly picked description from a set of size one.",
@@ -60,8 +60,8 @@
"$ref": "#/components/schema/MYSTRING"
}
},
- "include": {
- "$ref": "#/components/schema/MYSTRING"
+ "count": {
+ "$ref": "#/components/schema/MYINT"
}
}
}
diff --git a/encoding/openapi/testdata/oneof.json b/encoding/openapi/testdata/oneof.json
index 83384fb..f37e664 100644
--- a/encoding/openapi/testdata/oneof.json
+++ b/encoding/openapi/testdata/oneof.json
@@ -42,8 +42,8 @@
"count"
],
"properties": {
- "count": {
- "$ref": "#/components/schema/MyInt"
+ "include": {
+ "$ref": "#/components/schema/MyString"
},
"exclude": {
"type": "array",
@@ -51,8 +51,8 @@
"$ref": "#/components/schema/MyString"
}
},
- "include": {
- "$ref": "#/components/schema/MyString"
+ "count": {
+ "$ref": "#/components/schema/MyInt"
}
}
}
diff --git a/encoding/openapi/testdata/openapi-norefs.json b/encoding/openapi/testdata/openapi-norefs.json
index d6f80b3..425130b 100644
--- a/encoding/openapi/testdata/openapi-norefs.json
+++ b/encoding/openapi/testdata/openapi-norefs.json
@@ -16,18 +16,6 @@
"bar"
],
"properties": {
- "bar": {
- "type": "array",
- "items": {
- "type": "string",
- "format": "string"
- }
- },
- "foo": {
- "type": "number",
- "exclusiveMinimum": 10,
- "exclusiveMaximum": 1000
- },
"port": {
"type": "object",
"required": [
@@ -35,16 +23,28 @@
"obj"
],
"properties": {
+ "port": {
+ "type": "integer"
+ },
"obj": {
"type": "array",
"items": {
"type": "integer"
}
- },
- "port": {
- "type": "integer"
}
}
+ },
+ "foo": {
+ "type": "number",
+ "exclusiveMinimum": 10,
+ "exclusiveMaximum": 1000
+ },
+ "bar": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "string"
+ }
}
}
},
@@ -89,14 +89,14 @@
"obj"
],
"properties": {
+ "port": {
+ "type": "integer"
+ },
"obj": {
"type": "array",
"items": {
"type": "integer"
}
- },
- "port": {
- "type": "integer"
}
}
},
@@ -277,14 +277,14 @@
"obj"
],
"properties": {
+ "port": {
+ "type": "integer"
+ },
"obj": {
"type": "array",
"items": {
"type": "integer"
}
- },
- "port": {
- "type": "integer"
}
}
},
diff --git a/encoding/openapi/testdata/openapi.json b/encoding/openapi/testdata/openapi.json
index d4c6691..d64d270 100644
--- a/encoding/openapi/testdata/openapi.json
+++ b/encoding/openapi/testdata/openapi.json
@@ -13,12 +13,9 @@
"bar"
],
"properties": {
- "bar": {
- "type": "array",
- "items": {
- "type": "string",
- "format": "string"
- }
+ "port": {
+ "$ref": "#/components/schema/Port",
+ "type": "object"
},
"foo": {
"allOf": [
@@ -32,9 +29,12 @@
}
]
},
- "port": {
- "$ref": "#/components/schema/Port",
- "type": "object"
+ "bar": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "string"
+ }
}
}
},
@@ -79,14 +79,14 @@
"obj"
],
"properties": {
+ "port": {
+ "type": "integer"
+ },
"obj": {
"type": "array",
"items": {
"type": "integer"
}
- },
- "port": {
- "type": "integer"
}
}
},
diff --git a/encoding/openapi/testdata/simple-filter.json b/encoding/openapi/testdata/simple-filter.json
index a971c73..8760bcd 100644
--- a/encoding/openapi/testdata/simple-filter.json
+++ b/encoding/openapi/testdata/simple-filter.json
@@ -15,20 +15,20 @@
"double"
],
"properties": {
- "double": {
- "type": "number",
- "format": "double"
- },
- "float": {
- "type": "number",
- "format": "float"
- },
"mediumNum": {
"type": "integer",
"format": "int32"
},
"smallNum": {
"type": "integer"
+ },
+ "float": {
+ "type": "number",
+ "format": "float"
+ },
+ "double": {
+ "type": "number",
+ "format": "double"
}
}
}
diff --git a/encoding/openapi/testdata/simple.json b/encoding/openapi/testdata/simple.json
index ce7675e..ee9aa52 100644
--- a/encoding/openapi/testdata/simple.json
+++ b/encoding/openapi/testdata/simple.json
@@ -15,14 +15,6 @@
"double"
],
"properties": {
- "double": {
- "type": "number",
- "format": "double"
- },
- "float": {
- "type": "number",
- "format": "float"
- },
"mediumNum": {
"type": "integer",
"format": "int32"
@@ -31,6 +23,14 @@
"type": "integer",
"minimum": -128,
"maximum": 127
+ },
+ "float": {
+ "type": "number",
+ "format": "float"
+ },
+ "double": {
+ "type": "number",
+ "format": "double"
}
}
}
diff --git a/encoding/openapi/types.go b/encoding/openapi/types.go
index 5cf481d..3995055 100644
--- a/encoding/openapi/types.go
+++ b/encoding/openapi/types.go
@@ -59,7 +59,7 @@
}
func simplifyNumber(t *OrderedMap, format string) string {
- pairs := *t
+ pairs := t.kvs
k := 0
for i, kv := range pairs {
switch kv.Key {
@@ -75,7 +75,7 @@
pairs[i] = pairs[k]
k++
}
- *t = pairs[:k]
+ t.kvs = pairs[:k]
return format
}