encoding/openapi: implement structural schema
See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/
This is needed to make generated schema compliant with CRDs.
Structural schema are momentarily enabled by requesting to
expand references. Even when not expanding, the generator
will strive to normalize the schema somewhat, however.
Change-Id: I36fc8bc0d0e41d1b47b8bed55462ab9d07cfc26f
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2803
Reviewed-by: Jason Wang <jasonwzm@google.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/openapi/testdata/array.json b/encoding/openapi/testdata/array.json
index 570d659..5a9136a 100644
--- a/encoding/openapi/testdata/array.json
+++ b/encoding/openapi/testdata/array.json
@@ -9,12 +9,13 @@
"bar": {
"type": "array",
"items": {
- "default": "1",
+ "type": "string",
"enum": [
"1",
"2",
"3"
- ]
+ ],
+ "default": "1"
}
},
"foo": {
@@ -28,12 +29,13 @@
"e": {
"type": "array",
"items": {
- "default": "1",
+ "type": "string",
"enum": [
"1",
"2",
"3"
- ]
+ ],
+ "default": "1"
}
}
}
@@ -52,12 +54,13 @@
},
"MyEnum": {
"description": "MyEnum",
- "default": "1",
+ "type": "string",
"enum": [
"1",
"2",
"3"
- ]
+ ],
+ "default": "1"
},
"MyStruct": {
"description": "MyStruct",
@@ -69,12 +72,13 @@
"e": {
"type": "array",
"items": {
- "default": "1",
+ "type": "string",
"enum": [
"1",
"2",
"3"
- ]
+ ],
+ "default": "1"
}
}
}
diff --git a/encoding/openapi/testdata/nums.json b/encoding/openapi/testdata/nums.json
index fb6efba..69874fe 100644
--- a/encoding/openapi/testdata/nums.json
+++ b/encoding/openapi/testdata/nums.json
@@ -10,14 +10,11 @@
"neq": {
"type": "number",
"not": {
- "type": "number",
"allOff": [
{
- "type": "number",
"minimum": 4
},
{
- "type": "number",
"maximum": 4
}
]
diff --git a/encoding/openapi/testdata/oneof-funcs.json b/encoding/openapi/testdata/oneof-funcs.json
index dd0755e..157b53c 100644
--- a/encoding/openapi/testdata/oneof-funcs.json
+++ b/encoding/openapi/testdata/oneof-funcs.json
@@ -8,9 +8,9 @@
"schemas": {
"MYSTRING": {
"description": "Randomly picked description from a set of size one.",
+ "type": "object",
"oneOf": [
{
- "type": "object",
"required": [
"exact"
],
@@ -23,7 +23,6 @@
}
},
{
- "type": "object",
"required": [
"regex"
],
diff --git a/encoding/openapi/testdata/oneof-resolve.json b/encoding/openapi/testdata/oneof-resolve.json
index 217dc9f..629e0dd 100644
--- a/encoding/openapi/testdata/oneof-resolve.json
+++ b/encoding/openapi/testdata/oneof-resolve.json
@@ -7,30 +7,27 @@
"components": {
"schemas": {
"MyString": {
+ "type": "object",
+ "properties": {
+ "exact": {
+ "type": "string",
+ "format": "string"
+ },
+ "regex": {
+ "type": "string",
+ "format": "string"
+ }
+ },
"oneOf": [
{
- "type": "object",
"required": [
"exact"
- ],
- "properties": {
- "exact": {
- "type": "string",
- "format": "string"
- }
- }
+ ]
},
{
- "type": "object",
"required": [
"regex"
- ],
- "properties": {
- "regex": {
- "type": "string",
- "format": "string"
- }
- }
+ ]
}
]
},
@@ -46,60 +43,54 @@
],
"properties": {
"include": {
+ "type": "object",
+ "properties": {
+ "exact": {
+ "type": "string",
+ "format": "string"
+ },
+ "regex": {
+ "type": "string",
+ "format": "string"
+ }
+ },
"oneOf": [
{
- "type": "object",
"required": [
"exact"
- ],
- "properties": {
- "exact": {
- "type": "string",
- "format": "string"
- }
- }
+ ]
},
{
- "type": "object",
"required": [
"regex"
- ],
- "properties": {
- "regex": {
- "type": "string",
- "format": "string"
- }
- }
+ ]
}
]
},
"exclude": {
"type": "array",
"items": {
+ "type": "object",
+ "properties": {
+ "exact": {
+ "type": "string",
+ "format": "string"
+ },
+ "regex": {
+ "type": "string",
+ "format": "string"
+ }
+ },
"oneOf": [
{
- "type": "object",
"required": [
"exact"
- ],
- "properties": {
- "exact": {
- "type": "string",
- "format": "string"
- }
- }
+ ]
},
{
- "type": "object",
"required": [
"regex"
- ],
- "properties": {
- "regex": {
- "type": "string",
- "format": "string"
- }
- }
+ ]
}
]
}
diff --git a/encoding/openapi/testdata/oneof.json b/encoding/openapi/testdata/oneof.json
index 2781b7b..b2c0c97 100644
--- a/encoding/openapi/testdata/oneof.json
+++ b/encoding/openapi/testdata/oneof.json
@@ -4,9 +4,9 @@
"components": {
"schemas": {
"MyString": {
+ "type": "object",
"oneOf": [
{
- "type": "object",
"required": [
"exact"
],
@@ -18,7 +18,6 @@
}
},
{
- "type": "object",
"required": [
"regex"
],
diff --git a/encoding/openapi/testdata/openapi-norefs.json b/encoding/openapi/testdata/openapi-norefs.json
index 658290e..0c7e922 100644
--- a/encoding/openapi/testdata/openapi-norefs.json
+++ b/encoding/openapi/testdata/openapi-norefs.json
@@ -8,76 +8,63 @@
"schemas": {
"MyMessage": {
"description": "MyMessage is my message.",
- "allOf": [
- {
+ "type": "object",
+ "required": [
+ "foo",
+ "bar"
+ ],
+ "properties": {
+ "port": {
"type": "object",
"required": [
- "foo",
- "bar"
+ "port",
+ "obj"
],
"properties": {
"port": {
- "type": "object",
- "required": [
- "port",
- "obj"
- ],
- "properties": {
- "port": {
- "type": "integer"
- },
- "obj": {
- "type": "array",
- "items": {
- "type": "integer"
- }
- }
- }
+ "type": "integer"
},
- "foo": {
- "type": "number",
- "exclusiveMinimum": 10,
- "exclusiveMaximum": 1000
- },
- "bar": {
+ "obj": {
"type": "array",
"items": {
- "type": "string",
- "format": "string"
+ "type": "integer"
}
}
}
},
+ "foo": {
+ "type": "number",
+ "exclusiveMinimum": 10,
+ "exclusiveMaximum": 1000
+ },
+ "bar": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "string"
+ }
+ },
+ "a": {
+ "description": "Field a.",
+ "type": "integer",
+ "enum": [
+ 1
+ ]
+ },
+ "b": {
+ "type": "string",
+ "format": "string"
+ }
+ },
+ "oneOf": [
{
- "type": "object",
- "oneOf": [
- {
- "type": "object",
- "required": [
- "a"
- ],
- "properties": {
- "a": {
- "description": "Field a.",
- "type": "integer",
- "enum": [
- 1
- ]
- }
- }
- },
- {
- "type": "object",
- "required": [
- "b"
- ],
- "properties": {
- "b": {
- "type": "string",
- "format": "string"
- }
- }
- }
+ "required": [
+ "a"
+ ]
+ },
+ {
+ "required": [
+ "b"
]
}
]
@@ -105,150 +92,122 @@
"format": "int32"
},
"YourMessage": {
+ "type": "object",
+ "properties": {
+ "a": {
+ "type": "string",
+ "format": "string"
+ },
+ "b": {
+ "format": "string"
+ }
+ },
"oneOf": [
{
- "type": "object",
"required": [
"b"
- ],
- "properties": {
- "a": {
- "type": "string",
- "format": "string"
- },
- "b": {
- "type": "string",
- "format": "string"
- }
- }
+ ]
},
{
- "type": "object",
"required": [
"b"
- ],
- "properties": {
- "a": {
- "type": "string",
- "format": "string"
- },
- "b": {
- "type": "number"
- }
- }
+ ]
}
]
},
"YourMessage2": {
+ "type": "object",
+ "properties": {
+ "a": {
+ "type": "number"
+ },
+ "c": {
+ "type": "number"
+ },
+ "e": {
+ "type": "number"
+ },
+ "f": {
+ "type": "number"
+ },
+ "d": {
+ "type": "number"
+ },
+ "b": {
+ "type": "number"
+ }
+ },
"allOf": [
{
"oneOf": [
{
- "type": "object",
"required": [
"a"
- ],
- "properties": {
- "a": {
- "type": "number"
- }
- }
+ ]
},
{
- "type": "object",
"required": [
"b"
- ],
- "properties": {
- "b": {
- "type": "number"
- }
- }
+ ]
}
]
},
{
"oneOf": [
{
- "type": "object",
"required": [
"c"
- ],
- "properties": {
- "c": {
- "type": "number"
- }
- }
+ ]
},
{
- "type": "object",
"required": [
"d"
- ],
- "properties": {
- "d": {
- "type": "number"
- }
- }
+ ]
}
]
},
{
"oneOf": [
{
- "type": "object",
"required": [
"e"
- ],
- "properties": {
- "e": {
- "type": "number"
- }
- }
+ ]
},
{
- "type": "object",
"required": [
"f"
- ],
- "properties": {
- "f": {
- "type": "number"
- }
- }
+ ]
}
]
}
]
},
"Msg2": {
+ "type": "object",
+ "properties": {
+ "b": {
+ "type": "number"
+ },
+ "a": {
+ "type": "string",
+ "format": "string"
+ }
+ },
"oneOf": [
{
- "type": "object",
"required": [
"b"
- ],
- "properties": {
- "b": {
- "type": "number"
- }
- }
+ ]
},
{
- "type": "object",
"required": [
"a"
- ],
- "properties": {
- "a": {
- "type": "string",
- "format": "string"
- }
- }
+ ]
}
]
},
"Enum": {
+ "type": "string",
"enum": [
"foo",
"bar",
@@ -267,47 +226,30 @@
]
},
"DefaultStruct": {
- "allOf": [
+ "type": "object",
+ "properties": {
+ "port": {},
+ "obj": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "default": {
+ "port": 1
+ },
+ "oneOf": [
{
- "oneOf": [
- {
- "type": "object",
- "required": [
- "port",
- "obj"
- ],
- "properties": {
- "port": {
- "type": "integer"
- },
- "obj": {
- "type": "array",
- "items": {
- "type": "integer"
- }
- }
- }
- },
- {
- "type": "object",
- "required": [
- "port"
- ],
- "properties": {
- "port": {
- "type": "integer",
- "enum": [
- 1
- ]
- }
- }
- }
+ "required": [
+ "port",
+ "obj"
]
},
{
- "default": {
- "port": 1
- }
+ "required": [
+ "port"
+ ]
}
]
}
diff --git a/encoding/openapi/testdata/openapi.json b/encoding/openapi/testdata/openapi.json
index c60e19e..a8b888a 100644
--- a/encoding/openapi/testdata/openapi.json
+++ b/encoding/openapi/testdata/openapi.json
@@ -5,70 +5,61 @@
"schemas": {
"MyMessage": {
"description": "MyMessage is my message.",
- "allOf": [
- {
+ "type": "object",
+ "required": [
+ "foo",
+ "bar"
+ ],
+ "properties": {
+ "port": {
"type": "object",
+ "$ref": "#/components/schemas/Port"
+ },
+ "foo": {
+ "type": "number",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Int32"
+ },
+ {
+ "exclusiveMinimum": 10,
+ "exclusiveMaximum": 1000
+ }
+ ]
+ },
+ "bar": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "string"
+ }
+ }
+ },
+ "oneOf": [
+ {
"required": [
- "foo",
- "bar"
+ "a"
],
"properties": {
- "port": {
- "type": "object",
- "$ref": "#/components/schemas/Port"
- },
- "foo": {
- "allOf": [
- {
- "$ref": "#/components/schemas/Int32"
- },
- {
- "type": "number",
- "exclusiveMinimum": 10,
- "exclusiveMaximum": 1000
- }
+ "a": {
+ "description": "Field a.",
+ "type": "integer",
+ "enum": [
+ 1
]
- },
- "bar": {
- "type": "array",
- "items": {
- "type": "string",
- "format": "string"
- }
}
}
},
{
- "type": "object",
- "oneOf": [
- {
- "type": "object",
- "required": [
- "a"
- ],
- "properties": {
- "a": {
- "description": "Field a.",
- "type": "integer",
- "enum": [
- 1
- ]
- }
- }
- },
- {
- "type": "object",
- "required": [
- "b"
- ],
- "properties": {
- "b": {
- "type": "string",
- "format": "string"
- }
- }
+ "required": [
+ "b"
+ ],
+ "properties": {
+ "b": {
+ "type": "string",
+ "format": "string"
}
- ]
+ }
}
]
},
@@ -95,9 +86,9 @@
"format": "int32"
},
"YourMessage": {
+ "type": "object",
"oneOf": [
{
- "type": "object",
"required": [
"b"
],
@@ -113,7 +104,6 @@
}
},
{
- "type": "object",
"required": [
"b"
],
@@ -130,11 +120,11 @@
]
},
"YourMessage2": {
+ "type": "object",
"allOf": [
{
"oneOf": [
{
- "type": "object",
"required": [
"a"
],
@@ -145,7 +135,6 @@
}
},
{
- "type": "object",
"required": [
"b"
],
@@ -160,7 +149,6 @@
{
"oneOf": [
{
- "type": "object",
"required": [
"c"
],
@@ -171,7 +159,6 @@
}
},
{
- "type": "object",
"required": [
"d"
],
@@ -186,7 +173,6 @@
{
"oneOf": [
{
- "type": "object",
"required": [
"e"
],
@@ -197,7 +183,6 @@
}
},
{
- "type": "object",
"required": [
"f"
],
@@ -212,9 +197,9 @@
]
},
"Msg2": {
+ "type": "object",
"oneOf": [
{
- "type": "object",
"required": [
"b"
],
@@ -225,7 +210,6 @@
}
},
{
- "type": "object",
"required": [
"a"
],
@@ -239,6 +223,7 @@
]
},
"Enum": {
+ "type": "string",
"enum": [
"foo",
"bar",
@@ -257,31 +242,25 @@
]
},
"DefaultStruct": {
- "allOf": [
+ "type": "object",
+ "default": {
+ "port": 1
+ },
+ "oneOf": [
{
- "oneOf": [
- {
- "$ref": "#/components/schemas/Port"
- },
- {
- "type": "object",
- "required": [
- "port"
- ],
- "properties": {
- "port": {
- "type": "integer",
- "enum": [
- 1
- ]
- }
- }
- }
- ]
+ "$ref": "#/components/schemas/Port"
},
{
- "default": {
- "port": 1
+ "required": [
+ "port"
+ ],
+ "properties": {
+ "port": {
+ "type": "integer",
+ "enum": [
+ 1
+ ]
+ }
}
}
]
diff --git a/encoding/openapi/testdata/strings.json b/encoding/openapi/testdata/strings.json
index d62bfff..4b8123b 100644
--- a/encoding/openapi/testdata/strings.json
+++ b/encoding/openapi/testdata/strings.json
@@ -23,7 +23,6 @@
"myAntiPattern": {
"type": "string",
"not": {
- "type": "string",
"pattern": "foo.*bar"
}
}
diff --git a/encoding/openapi/testdata/structural.cue b/encoding/openapi/testdata/structural.cue
new file mode 100644
index 0000000..f1bb1c6
--- /dev/null
+++ b/encoding/openapi/testdata/structural.cue
@@ -0,0 +1,44 @@
+import "time"
+
+Attributes: {
+ // A map of attribute name to its value.
+ attributes: {
+ <_>: AttrValue
+ }
+}
+
+// The attribute value.
+AttrValue: {}
+
+AttrValue: {
+ // Used for values of type STRING, DNS_NAME, EMAIL_ADDRESS, and URI
+ stringValue: string @protobuf(2,name=string_value)
+} | {
+ // Used for values of type INT64
+ int64Value: int64 @protobuf(3,name=int64_value)
+} | {
+ // Used for values of type DOUBLE
+ doubleValue: float64 @protobuf(4,type=double,name=double_value)
+} | {
+ // Used for values of type BOOL
+ boolValue: bool @protobuf(5,name=bool_value)
+} | {
+ // Used for values of type BYTES
+ bytesValue: bytes @protobuf(6,name=bytes_value)
+} | {
+ // Used for values of type TIMESTAMP
+ timestampValue: time.Time @protobuf(7,type=google.protobuf.Timestamp,name=timestamp_value)
+} | {
+ // Used for values of type DURATION
+ durationValue: time.Duration @protobuf(8,type=google.protobuf.Duration,name=duration_value)
+} | {
+ // Used for values of type STRING_MAP
+ stringMapValue: Attributes_StringMap @protobuf(9,type=StringMap,name=string_map_value)
+}
+
+Attributes_StringMap: {
+ // Holds a set of name/value pairs.
+ entries: {
+ <_>: string
+ } @protobuf(1,type=map<string,string>)
+}
diff --git a/encoding/openapi/testdata/structural.json b/encoding/openapi/testdata/structural.json
new file mode 100644
index 0000000..b1beee2
--- /dev/null
+++ b/encoding/openapi/testdata/structural.json
@@ -0,0 +1,234 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "test",
+ "version": "v1"
+ },
+ "components": {
+ "schemas": {
+ "Attributes": {
+ "type": "object",
+ "required": [
+ "attributes"
+ ],
+ "properties": {
+ "attributes": {
+ "description": "A map of attribute name to its value.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "stringValue": {
+ "description": "Used for values of type STRING, DNS_NAME, EMAIL_ADDRESS, and URI",
+ "type": "string",
+ "format": "string"
+ },
+ "int64Value": {
+ "description": "Used for values of type INT64",
+ "type": "integer",
+ "format": "int64"
+ },
+ "doubleValue": {
+ "description": "Used for values of type DOUBLE",
+ "type": "number",
+ "format": "double"
+ },
+ "boolValue": {
+ "description": "Used for values of type BOOL",
+ "type": "boolean"
+ },
+ "bytesValue": {
+ "description": "Used for values of type BYTES",
+ "type": "string",
+ "format": "binary"
+ },
+ "timestampValue": {
+ "description": "Used for values of type TIMESTAMP",
+ "type": "string",
+ "format": "dateTime"
+ },
+ "durationValue": {
+ "description": "Used for values of type DURATION",
+ "type": "string"
+ },
+ "stringMapValue": {
+ "description": "Used for values of type STRING_MAP",
+ "type": "object",
+ "required": [
+ "entries"
+ ],
+ "properties": {
+ "entries": {
+ "description": "Holds a set of name/value pairs.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string",
+ "format": "string"
+ }
+ }
+ }
+ }
+ },
+ "oneOf": [
+ {
+ "required": [
+ "stringValue"
+ ]
+ },
+ {
+ "required": [
+ "int64Value"
+ ]
+ },
+ {
+ "required": [
+ "doubleValue"
+ ]
+ },
+ {
+ "required": [
+ "boolValue"
+ ]
+ },
+ {
+ "required": [
+ "bytesValue"
+ ]
+ },
+ {
+ "required": [
+ "timestampValue"
+ ]
+ },
+ {
+ "required": [
+ "durationValue"
+ ]
+ },
+ {
+ "required": [
+ "stringMapValue"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "AttrValue": {
+ "description": "The attribute value.",
+ "type": "object",
+ "properties": {
+ "stringValue": {
+ "description": "Used for values of type STRING, DNS_NAME, EMAIL_ADDRESS, and URI",
+ "type": "string",
+ "format": "string"
+ },
+ "int64Value": {
+ "description": "Used for values of type INT64",
+ "type": "integer",
+ "format": "int64"
+ },
+ "doubleValue": {
+ "description": "Used for values of type DOUBLE",
+ "type": "number",
+ "format": "double"
+ },
+ "boolValue": {
+ "description": "Used for values of type BOOL",
+ "type": "boolean"
+ },
+ "bytesValue": {
+ "description": "Used for values of type BYTES",
+ "type": "string",
+ "format": "binary"
+ },
+ "timestampValue": {
+ "description": "Used for values of type TIMESTAMP",
+ "type": "string",
+ "format": "dateTime"
+ },
+ "durationValue": {
+ "description": "Used for values of type DURATION",
+ "type": "string"
+ },
+ "stringMapValue": {
+ "description": "Used for values of type STRING_MAP",
+ "type": "object",
+ "required": [
+ "entries"
+ ],
+ "properties": {
+ "entries": {
+ "description": "Holds a set of name/value pairs.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string",
+ "format": "string"
+ }
+ }
+ }
+ }
+ },
+ "oneOf": [
+ {
+ "required": [
+ "stringValue"
+ ]
+ },
+ {
+ "required": [
+ "int64Value"
+ ]
+ },
+ {
+ "required": [
+ "doubleValue"
+ ]
+ },
+ {
+ "required": [
+ "boolValue"
+ ]
+ },
+ {
+ "required": [
+ "bytesValue"
+ ]
+ },
+ {
+ "required": [
+ "timestampValue"
+ ]
+ },
+ {
+ "required": [
+ "durationValue"
+ ]
+ },
+ {
+ "required": [
+ "stringMapValue"
+ ]
+ }
+ ]
+ },
+ "Attributes_StringMap": {
+ "type": "object",
+ "required": [
+ "entries"
+ ],
+ "properties": {
+ "entries": {
+ "description": "Holds a set of name/value pairs.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string",
+ "format": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file