Update code-generator to v0.26.0

This commit is contained in:
Raunak Pradip Shah
2023-02-28 12:33:04 +05:30
parent 0f5bcc4ff3
commit e231c81e52
296 changed files with 7612 additions and 15553 deletions

View File

@@ -246,38 +246,42 @@ var schemaTypeFormatMap = map[string]typeInfo{
// the spec does not need to be simple type,format) or can even return a simple type,format (e.g. IntOrString). For simple
// type formats, the benefit of adding OpenAPIDefinitionGetter interface is to keep both type and property documentation.
// Example:
// type Sample struct {
// ...
// // port of the server
// port IntOrString
// ...
// }
//
// type Sample struct {
// ...
// // port of the server
// port IntOrString
// ...
// }
//
// // IntOrString documentation...
// type IntOrString { ... }
//
// Adding IntOrString to this function:
// "port" : {
// format: "string",
// type: "int-or-string",
// Description: "port of the server"
// }
//
// "port" : {
// format: "string",
// type: "int-or-string",
// Description: "port of the server"
// }
//
// Implement OpenAPIDefinitionGetter for IntOrString:
//
// "port" : {
// $Ref: "#/definitions/IntOrString"
// Description: "port of the server"
// }
// "port" : {
// $Ref: "#/definitions/IntOrString"
// Description: "port of the server"
// }
//
// ...
// definitions:
// {
// "IntOrString": {
// format: "string",
// type: "int-or-string",
// Description: "IntOrString documentation..." // new
// }
// }
//
// {
// "IntOrString": {
// format: "string",
// type: "int-or-string",
// Description: "IntOrString documentation..." // new
// }
// }
func OpenAPITypeFormat(typeName string) (string, string) {
mapped, ok := schemaTypeFormatMap[typeName]
if !ok {

View File

@@ -21,7 +21,6 @@ import (
"crypto/sha512"
"encoding/json"
"fmt"
"mime"
"net/http"
"net/url"
"path"
@@ -41,15 +40,9 @@ import (
)
const (
jsonExt = ".json"
mimeJson = "application/json"
// TODO(mehdy): change @68f4ded to a version tag when gnostic add version tags.
mimePb = "application/com.github.googleapis.gnostic.OpenAPIv3@68f4ded+protobuf"
mimePbGz = "application/x-gzip"
subTypeProtobuf = "com.github.proto-openapi.spec.v3@v1.0+protobuf"
subTypeJSON = "json"
subTypeProtobufDeprecated = "com.github.proto-openapi.spec.v3@v1.0+protobuf"
subTypeProtobuf = "com.github.proto-openapi.spec.v3.v1.0+protobuf"
subTypeJSON = "json"
)
// OpenAPIV3Discovery is the format of the Discovery document for OpenAPI V3
@@ -84,12 +77,6 @@ type OpenAPIV3Group struct {
etagCache handler.HandlerCache
}
func init() {
mime.AddExtensionType(".json", mimeJson)
mime.AddExtensionType(".pb-v1", mimePb)
mime.AddExtensionType(".gz", mimePbGz)
}
func computeETag(data []byte) string {
if data == nil {
return ""
@@ -154,7 +141,7 @@ func (o *OpenAPIService) getSingleGroupBytes(getType string, group string) ([]by
}
etagBytes, err := v.etagCache.Get()
return specBytes, string(etagBytes), v.lastModified, err
} else if getType == subTypeProtobuf {
} else if getType == subTypeProtobuf || getType == subTypeProtobufDeprecated {
specPb, err := v.pbCache.Get()
if err != nil {
return nil, "", v.lastModified, err
@@ -191,6 +178,8 @@ func ToV3ProtoBinary(json []byte) ([]byte, error) {
func (o *OpenAPIService) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
data, _ := o.getGroupBytes()
w.Header().Set("Etag", strconv.Quote(computeETag(data)))
w.Header().Set("Content-Type", "application/json")
http.ServeContent(w, r, "/openapi/v3", time.Now(), bytes.NewReader(data))
}
@@ -210,11 +199,13 @@ func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Reque
}
accepted := []struct {
Type string
SubType string
Type string
SubType string
ReturnedContentType string
}{
{"application", subTypeJSON},
{"application", subTypeProtobuf},
{"application", subTypeJSON, "application/" + subTypeJSON},
{"application", subTypeProtobuf, "application/" + subTypeProtobuf},
{"application", subTypeProtobufDeprecated, "application/" + subTypeProtobuf},
}
for _, clause := range clauses {
@@ -229,6 +220,9 @@ func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Reque
if err != nil {
return
}
// Set Content-Type header in the reponse
w.Header().Set("Content-Type", accepts.ReturnedContentType)
// ETag must be enclosed in double quotes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
w.Header().Set("Etag", strconv.Quote(etag))

View File

@@ -18,3 +18,7 @@ package internal
// Used by tests to selectively disable experimental JSON unmarshaler
var UseOptimizedJSONUnmarshaling bool = true
var UseOptimizedJSONUnmarshalingV3 bool = false
// Used by tests to selectively disable experimental JSON marshaler
var UseOptimizedJSONMarshaling bool = true

View File

@@ -0,0 +1,65 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"github.com/go-openapi/jsonreference"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)
// DeterministicMarshal calls the jsonv2 library with the deterministic
// flag in order to have stable marshaling.
func DeterministicMarshal(in any) ([]byte, error) {
return jsonv2.MarshalOptions{Deterministic: true}.Marshal(jsonv2.EncodeOptions{}, in)
}
// JSONRefFromMap populates a json reference object if the map v contains a $ref key.
func JSONRefFromMap(jsonRef *jsonreference.Ref, v map[string]interface{}) error {
if v == nil {
return nil
}
if vv, ok := v["$ref"]; ok {
if str, ok := vv.(string); ok {
ref, err := jsonreference.New(str)
if err != nil {
return err
}
*jsonRef = ref
}
}
return nil
}
// SanitizeExtensions sanitizes the input map such that non extension
// keys (non x-*, X-*) keys are dropped from the map. Returns the new
// modified map, or nil if the map is now empty.
func SanitizeExtensions(e map[string]interface{}) map[string]interface{} {
for k := range e {
if !IsExtensionKey(k) {
delete(e, k)
}
}
if len(e) == 0 {
e = nil
}
return e
}
// IsExtensionKey returns true if the input string is of format x-* or X-*
func IsExtensionKey(k string) bool {
return len(k) > 1 && (k[0] == 'x' || k[0] == 'X') && k[1] == '-'
}

View File

@@ -34,6 +34,13 @@ type MarshalOptions struct {
// unknown JSON object members.
DiscardUnknownMembers bool
// Deterministic specifies that the same input value will be serialized
// as the exact same output bytes. Different processes of
// the same program will serialize equal values to the same bytes,
// but different versions of the same program are not guaranteed
// to produce the exact same sequence of bytes.
Deterministic bool
// formatDepth is the depth at which we respect the format flag.
formatDepth int
// format is custom formatting for the value at the specified depth.

View File

@@ -62,7 +62,7 @@ func unmarshalValueAny(uo UnmarshalOptions, dec *Decoder) (any, error) {
}
return dec.stringCache.make(val), nil
case '0':
fv, _ := parseFloat(val, 64) // ignore error since readValue gaurantees val is valid
fv, _ := parseFloat(val, 64) // ignore error since readValue guarantees val is valid
return fv, nil
default:
panic("BUG: invalid kind: " + k.String())
@@ -99,13 +99,32 @@ func marshalObjectAny(mo MarshalOptions, enc *Encoder, obj map[string]any) error
if !enc.options.AllowInvalidUTF8 {
enc.tokens.last.disableNamespace()
}
for name, val := range obj {
if err := enc.WriteToken(String(name)); err != nil {
return err
if !mo.Deterministic || len(obj) <= 1 {
for name, val := range obj {
if err := enc.WriteToken(String(name)); err != nil {
return err
}
if err := marshalValueAny(mo, enc, val); err != nil {
return err
}
}
if err := marshalValueAny(mo, enc, val); err != nil {
return err
} else {
names := getStrings(len(obj))
var i int
for name := range obj {
(*names)[i] = name
i++
}
names.Sort()
for _, name := range *names {
if err := enc.WriteToken(String(name)); err != nil {
return err
}
if err := marshalValueAny(mo, enc, obj[name]); err != nil {
return err
}
}
putStrings(names)
}
if err := enc.WriteToken(ObjectEnd); err != nil {
return err

View File

@@ -5,6 +5,7 @@
package json
import (
"bytes"
"encoding/base32"
"encoding/base64"
"encoding/hex"
@@ -12,6 +13,7 @@ import (
"fmt"
"math"
"reflect"
"sort"
"strconv"
"sync"
)
@@ -228,13 +230,7 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
}
}
val := enc.UnusedBuffer()
var b []byte
if va.Kind() == reflect.Array {
// TODO(https://go.dev/issue/47066): Avoid reflect.Value.Slice.
b = va.Slice(0, va.Len()).Bytes()
} else {
b = va.Bytes()
}
b := va.Bytes()
n := len(`"`) + encodedLen(len(b)) + len(`"`)
if cap(val) < n {
val = make([]byte, n)
@@ -248,19 +244,19 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
}
unmarshalDefault := fncs.unmarshal
fncs.unmarshal = func(uo UnmarshalOptions, dec *Decoder, va addressableValue) error {
decode, decodedLen := decodeBase64, decodedLenBase64
decode, decodedLen, encodedLen := decodeBase64, decodedLenBase64, encodedLenBase64
if uo.format != "" && uo.formatDepth == dec.tokens.depth() {
switch uo.format {
case "base64":
decode, decodedLen = decodeBase64, decodedLenBase64
decode, decodedLen, encodedLen = decodeBase64, decodedLenBase64, encodedLenBase64
case "base64url":
decode, decodedLen = decodeBase64URL, decodedLenBase64URL
decode, decodedLen, encodedLen = decodeBase64URL, decodedLenBase64URL, encodedLenBase64URL
case "base32":
decode, decodedLen = decodeBase32, decodedLenBase32
decode, decodedLen, encodedLen = decodeBase32, decodedLenBase32, encodedLenBase32
case "base32hex":
decode, decodedLen = decodeBase32Hex, decodedLenBase32Hex
decode, decodedLen, encodedLen = decodeBase32Hex, decodedLenBase32Hex, encodedLenBase32Hex
case "base16", "hex":
decode, decodedLen = decodeBase16, decodedLenBase16
decode, decodedLen, encodedLen = decodeBase16, decodedLenBase16, encodedLenBase16
case "array":
uo.format = ""
return unmarshalDefault(uo, dec, va)
@@ -290,23 +286,28 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
n--
}
n = decodedLen(n)
var b []byte
b := va.Bytes()
if va.Kind() == reflect.Array {
// TODO(https://go.dev/issue/47066): Avoid reflect.Value.Slice.
b = va.Slice(0, va.Len()).Bytes()
if n != len(b) {
err := fmt.Errorf("decoded base64 length of %d mismatches array length of %d", n, len(b))
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
}
} else {
b = va.Bytes()
if b == nil || cap(b) < n {
b = make([]byte, n)
} else {
b = b[:n]
}
}
if _, err := decode(b, val); err != nil {
n2, err := decode(b, val)
if err == nil && len(val) != encodedLen(n2) {
// TODO(https://go.dev/issue/53845): RFC 4648, section 3.3,
// specifies that non-alphabet characters must be rejected.
// Unfortunately, the "base32" and "base64" packages allow
// '\r' and '\n' characters by default.
err = errors.New("illegal data at input byte " + strconv.Itoa(bytes.IndexAny(val, "\r\n")))
}
if err != nil {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
}
if va.Kind() == reflect.Slice {
@@ -412,7 +413,7 @@ func makeUintArshaler(t reflect.Type) *arshaler {
return nil
}
x := math.Float64frombits(uint64(va.Uint()))
x := math.Float64frombits(va.Uint())
return enc.writeNumber(x, rawUintNumber, mo.StringifyNumbers)
}
fncs.unmarshal = func(uo UnmarshalOptions, dec *Decoder, va addressableValue) error {
@@ -450,7 +451,7 @@ func makeUintArshaler(t reflect.Type) *arshaler {
err := fmt.Errorf("cannot parse %q as unsigned integer: %w", val, strconv.ErrRange)
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
}
va.SetUint(uint64(n))
va.SetUint(n)
return nil
}
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t}
@@ -549,23 +550,9 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
return &fncs
}
var mapIterPool = sync.Pool{
New: func() any { return new(reflect.MapIter) },
}
func getMapIter(mv reflect.Value) *reflect.MapIter {
iter := mapIterPool.Get().(*reflect.MapIter)
iter.Reset(mv)
return iter
}
func putMapIter(iter *reflect.MapIter) {
iter.Reset(reflect.Value{}) // allow underlying map to be garbage collected
mapIterPool.Put(iter)
}
func makeMapArshaler(t reflect.Type) *arshaler {
// NOTE: The logic below disables namespaces for tracking duplicate names
// when handling map keys with a unique represention.
// when handling map keys with a unique representation.
// NOTE: Values retrieved from a map are not addressable,
// so we shallow copy the values to make them addressable and
@@ -641,24 +628,76 @@ func makeMapArshaler(t reflect.Type) *arshaler {
enc.tokens.last.disableNamespace()
}
// NOTE: Map entries are serialized in a non-deterministic order.
// Users that need stable output should call RawValue.Canonicalize.
// TODO(go1.19): Remove use of a sync.Pool with reflect.MapIter.
// Calling reflect.Value.MapRange no longer allocates.
// See https://go.dev/cl/400675.
iter := getMapIter(va.Value)
defer putMapIter(iter)
for iter.Next() {
k.SetIterKey(iter)
if err := marshalKey(mko, enc, k); err != nil {
// TODO: If err is errMissingName, then wrap it as a
// SemanticError since this key type cannot be serialized
// as a JSON string.
return err
switch {
case !mo.Deterministic || n <= 1:
for iter := va.Value.MapRange(); iter.Next(); {
k.SetIterKey(iter)
if err := marshalKey(mko, enc, k); err != nil {
// TODO: If err is errMissingName, then wrap it as a
// SemanticError since this key type cannot be serialized
// as a JSON string.
return err
}
v.SetIterValue(iter)
if err := marshalVal(mo, enc, v); err != nil {
return err
}
}
v.SetIterValue(iter)
if err := marshalVal(mo, enc, v); err != nil {
return err
case !nonDefaultKey && t.Key().Kind() == reflect.String:
names := getStrings(n)
for i, iter := 0, va.Value.MapRange(); i < n && iter.Next(); i++ {
k.SetIterKey(iter)
(*names)[i] = k.String()
}
names.Sort()
for _, name := range *names {
if err := enc.WriteToken(String(name)); err != nil {
return err
}
// TODO(https://go.dev/issue/57061): Use v.SetMapIndexOf.
k.SetString(name)
v.Set(va.MapIndex(k.Value))
if err := marshalVal(mo, enc, v); err != nil {
return err
}
}
putStrings(names)
default:
type member struct {
name string // unquoted name
key addressableValue
}
members := make([]member, n)
keys := reflect.MakeSlice(reflect.SliceOf(t.Key()), n, n)
for i, iter := 0, va.Value.MapRange(); i < n && iter.Next(); i++ {
// Marshal the member name.
k := addressableValue{keys.Index(i)} // indexed slice element is always addressable
k.SetIterKey(iter)
if err := marshalKey(mko, enc, k); err != nil {
// TODO: If err is errMissingName, then wrap it as a
// SemanticError since this key type cannot be serialized
// as a JSON string.
return err
}
name := enc.unwriteOnlyObjectMemberName()
members[i] = member{name, k}
}
// TODO: If AllowDuplicateNames is enabled, then sort according
// to reflect.Value as well if the names are equal.
// See internal/fmtsort.
// TODO(https://go.dev/issue/47619): Use slices.SortFunc instead.
sort.Slice(members, func(i, j int) bool {
return lessUTF16(members[i].name, members[j].name)
})
for _, member := range members {
if err := enc.WriteToken(String(member.name)); err != nil {
return err
}
// TODO(https://go.dev/issue/57061): Use v.SetMapIndexOf.
v.Set(va.MapIndex(member.key.Value))
if err := marshalVal(mo, enc, v); err != nil {
return err
}
}
}
}
@@ -856,7 +895,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
// 2. The object namespace is guaranteed to be disabled.
// 3. The object name is guaranteed to be valid and pre-escaped.
// 4. There is no need to flush the buffer (for unwrite purposes).
// 5. There is no possibility of an error occuring.
// 5. There is no possibility of an error occurring.
if optimizeCommon {
// Append any delimiters or optional whitespace.
if enc.tokens.last.length() > 0 {
@@ -996,7 +1035,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
if fields.inlinedFallback == nil {
// Skip unknown value since we have no place to store it.
if err := dec.skipValue(); err != nil {
if err := dec.SkipValue(); err != nil {
return err
}
} else {

View File

@@ -5,6 +5,7 @@
package json
import (
"bytes"
"errors"
"reflect"
)
@@ -89,35 +90,61 @@ func marshalInlinedFallbackAll(mo MarshalOptions, enc *Encoder, va addressableVa
}
return nil
} else {
if v.Len() == 0 {
m := v // must be a map[string]V
n := m.Len()
if n == 0 {
return nil
}
m := v
mk := newAddressableValue(stringType)
mv := newAddressableValue(m.Type().Elem())
for iter := m.MapRange(); iter.Next(); {
b, err := appendString(enc.UnusedBuffer(), iter.Key().String(), !enc.options.AllowInvalidUTF8, nil)
marshalKey := func(mk addressableValue) error {
b, err := appendString(enc.UnusedBuffer(), mk.String(), !enc.options.AllowInvalidUTF8, nil)
if err != nil {
return err
}
if insertUnquotedName != nil {
isVerbatim := consumeSimpleString(b) == len(b)
isVerbatim := bytes.IndexByte(b, '\\') < 0
name := unescapeStringMayCopy(b, isVerbatim)
if !insertUnquotedName(name) {
return &SyntacticError{str: "duplicate name " + string(b) + " in object"}
}
}
if err := enc.WriteValue(b); err != nil {
return err
return enc.WriteValue(b)
}
marshalVal := f.fncs.marshal
if mo.Marshalers != nil {
marshalVal, _ = mo.Marshalers.lookup(marshalVal, mv.Type())
}
if !mo.Deterministic || n <= 1 {
for iter := m.MapRange(); iter.Next(); {
mk.SetIterKey(iter)
if err := marshalKey(mk); err != nil {
return err
}
mv.Set(iter.Value())
if err := marshalVal(mo, enc, mv); err != nil {
return err
}
}
mv.Set(iter.Value())
marshal := f.fncs.marshal
if mo.Marshalers != nil {
marshal, _ = mo.Marshalers.lookup(marshal, mv.Type())
} else {
names := getStrings(n)
for i, iter := 0, m.Value.MapRange(); i < n && iter.Next(); i++ {
mk.SetIterKey(iter)
(*names)[i] = mk.String()
}
if err := marshal(mo, enc, mv); err != nil {
return err
names.Sort()
for _, name := range *names {
mk.SetString(name)
if err := marshalKey(mk); err != nil {
return err
}
// TODO(https://go.dev/issue/57061): Use mv.SetMapIndexOf.
mv.Set(m.MapIndex(mk.Value))
if err := marshalVal(mo, enc, mv); err != nil {
return err
}
}
putStrings(names)
}
return nil
}
@@ -162,7 +189,7 @@ func unmarshalInlinedFallbackNext(uo UnmarshalOptions, dec *Decoder, va addressa
} else {
name := string(unquotedName) // TODO: Intern this?
m := v
m := v // must be a map[string]V
if m.IsNil() {
m.Set(reflect.MakeMap(m.Type()))
}

View File

@@ -21,8 +21,8 @@ var (
)
// MarshalerV1 is implemented by types that can marshal themselves.
// It is recommended that types implement MarshalerV2 unless
// the implementation is trying to avoid a hard dependency on this package.
// It is recommended that types implement MarshalerV2 unless the implementation
// is trying to avoid a hard dependency on the "jsontext" package.
//
// It is recommended that implementations return a buffer that is safe
// for the caller to retain and potentially mutate.

View File

@@ -5,6 +5,7 @@
package json
import (
"errors"
"fmt"
"reflect"
"strings"
@@ -85,25 +86,39 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
fncs.nonDefault = true
fncs.marshal = func(mo MarshalOptions, enc *Encoder, va addressableValue) error {
format := time.RFC3339Nano
isRFC3339 := true
if mo.format != "" && mo.formatDepth == enc.tokens.depth() {
var err error
format, err = checkTimeFormat(mo.format)
format, isRFC3339, err = checkTimeFormat(mo.format)
if err != nil {
return &SemanticError{action: "marshal", GoType: t, Err: err}
}
}
tt := va.Interface().(time.Time)
if y := tt.Year(); y < 0 || y >= 10000 {
// RFC 3339 is clear that years are 4 digits exactly.
// See https://go.dev/issue/4556#c15 for more discussion.
err := fmt.Errorf("year %d outside of range [0,9999]", y)
return &SemanticError{action: "marshal", GoType: t, Err: err}
}
b := enc.UnusedBuffer()
b = append(b, '"')
b = tt.AppendFormat(b, format)
b = append(b, '"')
if isRFC3339 {
// Not all Go timestamps can be represented as valid RFC 3339.
// Explicitly check for these edge cases.
// See https://go.dev/issue/4556 and https://go.dev/issue/54580.
var err error
switch b := b[len(`"`) : len(b)-len(`"`)]; {
case b[len("9999")] != '-': // year must be exactly 4 digits wide
err = errors.New("year outside of range [0,9999]")
case b[len(b)-1] != 'Z':
c := b[len(b)-len("Z07:00")]
if ('0' <= c && c <= '9') || parseDec2(b[len(b)-len("07:00"):]) >= 24 {
err = errors.New("timezone hour outside of range [0,23]")
}
}
if err != nil {
return &SemanticError{action: "marshal", GoType: t, Err: err}
}
return enc.WriteValue(b) // RFC 3339 never needs JSON escaping
}
// The format may contain special characters that need escaping.
// Verify that the result is a valid JSON string (common case),
// otherwise escape the string correctly (slower case).
@@ -113,10 +128,11 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
return enc.WriteValue(b)
}
fncs.unmarshal = func(uo UnmarshalOptions, dec *Decoder, va addressableValue) error {
format := time.RFC3339Nano
format := time.RFC3339
isRFC3339 := true
if uo.format != "" && uo.formatDepth == dec.tokens.depth() {
var err error
format, err = checkTimeFormat(uo.format)
format, isRFC3339, err = checkTimeFormat(uo.format)
if err != nil {
return &SemanticError{action: "unmarshal", GoType: t, Err: err}
}
@@ -136,6 +152,29 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
case '"':
val = unescapeStringMayCopy(val, flags.isVerbatim())
tt2, err := time.Parse(format, string(val))
if isRFC3339 && err == nil {
// TODO(https://go.dev/issue/54580): RFC 3339 specifies
// the exact grammar of a valid timestamp. However,
// the parsing functionality in "time" is too loose and
// incorrectly accepts invalid timestamps as valid.
// Remove these manual checks when "time" checks it for us.
newParseError := func(layout, value, layoutElem, valueElem, message string) error {
return &time.ParseError{Layout: layout, Value: value, LayoutElem: layoutElem, ValueElem: valueElem, Message: message}
}
switch {
case val[len("2006-01-02T")+1] == ':': // hour must be two digits
err = newParseError(format, string(val), "15", string(val[len("2006-01-02T"):][:1]), "")
case val[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
err = newParseError(format, string(val), ".", ",", "")
case val[len(val)-1] != 'Z':
switch {
case parseDec2(val[len(val)-len("07:00"):]) >= 24: // timezone hour must be in range
err = newParseError(format, string(val), "Z07:00", string(val[len(val)-len("Z07:00"):]), ": timezone hour out of range")
case parseDec2(val[len(val)-len("00"):]) >= 60: // timezone minute must be in range
err = newParseError(format, string(val), "Z07:00", string(val[len(val)-len("Z07:00"):]), ": timezone minute out of range")
}
}
}
if err != nil {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
}
@@ -149,48 +188,54 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
return fncs
}
func checkTimeFormat(format string) (string, error) {
func checkTimeFormat(format string) (string, bool, error) {
// We assume that an exported constant in the time package will
// always start with an uppercase ASCII letter.
if len(format) > 0 && 'A' <= format[0] && format[0] <= 'Z' {
switch format {
case "ANSIC":
return time.ANSIC, nil
return time.ANSIC, false, nil
case "UnixDate":
return time.UnixDate, nil
return time.UnixDate, false, nil
case "RubyDate":
return time.RubyDate, nil
return time.RubyDate, false, nil
case "RFC822":
return time.RFC822, nil
return time.RFC822, false, nil
case "RFC822Z":
return time.RFC822Z, nil
return time.RFC822Z, false, nil
case "RFC850":
return time.RFC850, nil
return time.RFC850, false, nil
case "RFC1123":
return time.RFC1123, nil
return time.RFC1123, false, nil
case "RFC1123Z":
return time.RFC1123Z, nil
return time.RFC1123Z, false, nil
case "RFC3339":
return time.RFC3339, nil
return time.RFC3339, true, nil
case "RFC3339Nano":
return time.RFC3339Nano, nil
return time.RFC3339Nano, true, nil
case "Kitchen":
return time.Kitchen, nil
return time.Kitchen, false, nil
case "Stamp":
return time.Stamp, nil
return time.Stamp, false, nil
case "StampMilli":
return time.StampMilli, nil
return time.StampMilli, false, nil
case "StampMicro":
return time.StampMicro, nil
return time.StampMicro, false, nil
case "StampNano":
return time.StampNano, nil
return time.StampNano, false, nil
default:
// Reject any format that is an exported Go identifier in case
// new format constants are added to the time package.
if strings.TrimFunc(format, isLetterOrDigit) == "" {
return "", fmt.Errorf("undefined format layout: %v", format)
return "", false, fmt.Errorf("undefined format layout: %v", format)
}
}
}
return format, nil
return format, false, nil
}
// parseDec2 parses b as an unsigned, base-10, 2-digit number.
// It panics if len(b) < 2. The result is undefined if digits are not base-10.
func parseDec2(b []byte) byte {
return 10*(b[0]-'0') + (b[1] - '0')
}

View File

@@ -347,9 +347,9 @@ func (d *Decoder) PeekKind() Kind {
return next
}
// skipValue is semantically equivalent to calling ReadValue and discarding
// SkipValue is semantically equivalent to calling ReadValue and discarding
// the result except that memory is not wasted trying to hold the entire result.
func (d *Decoder) skipValue() error {
func (d *Decoder) SkipValue() error {
switch d.PeekKind() {
case '{', '[':
// For JSON objects and arrays, keep skipping all tokens
@@ -374,7 +374,7 @@ func (d *Decoder) skipValue() error {
}
// ReadToken reads the next Token, advancing the read offset.
// The returned token is only valid until the next Peek or Read call.
// The returned token is only valid until the next Peek, Read, or Skip call.
// It returns io.EOF if there are no more tokens.
func (d *Decoder) ReadToken() (Token, error) {
// Determine the next kind.
@@ -585,7 +585,7 @@ func (f valueFlags) isCanonical() bool { return f&stringNonCanonical == 0 }
// ReadValue returns the next raw JSON value, advancing the read offset.
// The value is stripped of any leading or trailing whitespace.
// The returned value is only valid until the next Peek or Read call and
// The returned value is only valid until the next Peek, Read, or Skip call and
// may not be mutated while the Decoder remains in use.
// If the decoder is currently at the end token for an object or array,
// then it reports a SyntacticError and the internal state remains unchanged.
@@ -1013,7 +1013,7 @@ func (d *Decoder) InputOffset() int64 {
// UnreadBuffer returns the data remaining in the unread buffer,
// which may contain zero or more bytes.
// The returned buffer must not be mutated while Decoder continues to be used.
// The buffer contents are valid until the next Peek or Read call.
// The buffer contents are valid until the next Peek, Read, or Skip call.
func (d *Decoder) UnreadBuffer() []byte {
return d.unreadBuffer()
}
@@ -1213,7 +1213,7 @@ func consumeStringResumable(flags *valueFlags, b []byte, resumeOffset int, valid
return n, &SyntacticError{str: "invalid escape sequence " + strconv.Quote(string(b[n:n+6])) + " within string"}
}
// Only certain control characters can use the \uFFFF notation
// for canonical formating (per RFC 8785, section 3.2.2.2.).
// for canonical formatting (per RFC 8785, section 3.2.2.2.).
switch v1 {
// \uFFFF notation not permitted for these characters.
case '\b', '\f', '\n', '\r', '\t':

View File

@@ -8,8 +8,7 @@
// primitive data types such as booleans, strings, and numbers,
// in addition to structured data types such as objects and arrays.
//
//
// Terminology
// # Terminology
//
// This package uses the terms "encode" and "decode" for syntactic functionality
// that is concerned with processing JSON based on its grammar, and
@@ -32,8 +31,7 @@
//
// See RFC 8259 for more information.
//
//
// Specifications
// # Specifications
//
// Relevant specifications include RFC 4627, RFC 7159, RFC 7493, RFC 8259,
// and RFC 8785. Each RFC is generally a stricter subset of another RFC.
@@ -60,8 +58,7 @@
// In particular, it makes specific choices about behavior that RFC 8259
// leaves as undefined in order to ensure greater interoperability.
//
//
// JSON Representation of Go structs
// # JSON Representation of Go structs
//
// A Go struct is naturally represented as a JSON object,
// where each Go struct field corresponds with a JSON object member.

View File

@@ -347,6 +347,30 @@ func (e *Encoder) unwriteEmptyObjectMember(prevName *string) bool {
return true
}
// unwriteOnlyObjectMemberName unwrites the only object member name
// and returns the unquoted name.
func (e *Encoder) unwriteOnlyObjectMemberName() string {
if last := e.tokens.last; !last.isObject() || last.length() != 1 {
panic("BUG: must be called on an object after writing first name")
}
// Unwrite the name and whitespace.
b := trimSuffixString(e.buf)
isVerbatim := bytes.IndexByte(e.buf[len(b):], '\\') < 0
name := string(unescapeStringMayCopy(e.buf[len(b):], isVerbatim))
e.buf = trimSuffixWhitespace(b)
// Undo state changes.
e.tokens.last.decrement()
if !e.options.AllowDuplicateNames {
if e.tokens.last.isActiveNamespace() {
e.namespaces.last().removeLast()
}
e.names.clearLast()
}
return name
}
func trimSuffixWhitespace(b []byte) []byte {
// NOTE: The arguments and logic are kept simple to keep this inlineable.
n := len(b) - 1

View File

@@ -8,6 +8,7 @@ import (
"bytes"
"io"
"math/bits"
"sort"
"sync"
)
@@ -148,3 +149,34 @@ func putStreamingDecoder(d *Decoder) {
streamingDecoderPool.Put(d)
}
}
var stringsPools = &sync.Pool{New: func() any { return new(stringSlice) }}
type stringSlice []string
// getStrings returns a non-nil pointer to a slice with length n.
func getStrings(n int) *stringSlice {
s := stringsPools.Get().(*stringSlice)
if cap(*s) < n {
*s = make([]string, n)
}
*s = (*s)[:n]
return s
}
func putStrings(s *stringSlice) {
if cap(*s) > 1<<10 {
*s = nil // avoid pinning arbitrarily large amounts of memory
}
stringsPools.Put(s)
}
// Sort sorts the string slice according to RFC 8785, section 3.2.3.
func (ss *stringSlice) Sort() {
// TODO(https://go.dev/issue/47619): Use slices.SortFunc instead.
sort.Sort(ss)
}
func (ss *stringSlice) Len() int { return len(*ss) }
func (ss *stringSlice) Less(i, j int) bool { return lessUTF16((*ss)[i], (*ss)[j]) }
func (ss *stringSlice) Swap(i, j int) { (*ss)[i], (*ss)[j] = (*ss)[j], (*ss)[i] }

View File

@@ -721,7 +721,7 @@ func (s *uintSet) has(i uint) bool {
return s.lo.has(i)
} else {
i -= 64
iHi, iLo := int(i/64), uint(i%64)
iHi, iLo := int(i/64), i%64
return iHi < len(s.hi) && s.hi[iHi].has(iLo)
}
}
@@ -735,7 +735,7 @@ func (s *uintSet) insert(i uint) bool {
return !has
} else {
i -= 64
iHi, iLo := int(i/64), uint(i%64)
iHi, iLo := int(i/64), i%64
if iHi >= len(s.hi) {
s.hi = append(s.hi, make([]uintSet64, iHi+1-len(s.hi))...)
s.hi = s.hi[:cap(s.hi)]

View File

@@ -112,7 +112,7 @@ func Bool(b bool) Token {
return False
}
// String construct a Token representing a JSON string.
// String constructs a Token representing a JSON string.
// The provided string should contain valid UTF-8, otherwise invalid characters
// may be mangled as the Unicode replacement character.
func String(s string) Token {
@@ -225,7 +225,7 @@ func (t Token) appendString(dst []byte, validateUTF8, preserveRaw bool, escapeRu
}
// String returns the unescaped string value for a JSON string.
// For other JSON kinds, this returns the raw JSON represention.
// For other JSON kinds, this returns the raw JSON representation.
func (t Token) String() string {
// This is inlinable to take advantage of "function outlining".
// This avoids an allocation for the string(b) conversion
@@ -373,10 +373,10 @@ func (t Token) Int() int64 {
case 'i':
return int64(t.num)
case 'u':
if uint64(t.num) > maxInt64 {
if t.num > maxInt64 {
return maxInt64
}
return int64(uint64(t.num))
return int64(t.num)
}
}
@@ -425,7 +425,7 @@ func (t Token) Uint() uint64 {
// Handle exact integer value.
switch t.str[0] {
case 'u':
return uint64(t.num)
return t.num
case 'i':
if int64(t.num) < minUint64 {
return minUint64

View File

@@ -263,7 +263,7 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
afterValue := d.InputOffset()
if isSorted && len(*members) > 0 {
isSorted = lessUTF16(prevName, name)
isSorted = lessUTF16(prevName, []byte(name))
}
*members = append(*members, memberName{name, beforeName, afterValue})
prevName = name
@@ -317,7 +317,7 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
// to the UTF-16 codepoints of the UTF-8 encoded input strings.
// This implements the ordering specified in RFC 8785, section 3.2.3.
// The inputs must be valid UTF-8, otherwise this may panic.
func lessUTF16(x, y []byte) bool {
func lessUTF16[Bytes []byte | string](x, y Bytes) bool {
// NOTE: This is an optimized, allocation-free implementation
// of lessUTF16Simple in fuzz_test.go. FuzzLessUTF16 verifies that the
// two implementations agree on the result of comparing any two strings.
@@ -326,8 +326,13 @@ func lessUTF16(x, y []byte) bool {
return ('\u0000' <= r && r <= '\uD7FF') || ('\uE000' <= r && r <= '\uFFFF')
}
var invalidUTF8 bool
x0, y0 := x, y
for {
if len(x) == 0 || len(y) == 0 {
if len(x) == len(y) && invalidUTF8 {
return string(x0) < string(y0)
}
return len(x) < len(y)
}
@@ -341,35 +346,36 @@ func lessUTF16(x, y []byte) bool {
}
// Decode next pair of runes as UTF-8.
rx, nx := utf8.DecodeRune(x)
ry, ny := utf8.DecodeRune(y)
// TODO(https://go.dev/issue/56948): Use a generic implementation
// of utf8.DecodeRune, or rely on a compiler optimization to statically
// hide the cost of a type switch (https://go.dev/issue/57072).
var rx, ry rune
var nx, ny int
switch any(x).(type) {
case string:
rx, nx = utf8.DecodeRuneInString(string(x))
ry, ny = utf8.DecodeRuneInString(string(y))
case []byte:
rx, nx = utf8.DecodeRune([]byte(x))
ry, ny = utf8.DecodeRune([]byte(y))
}
selfx := isUTF16Self(rx)
selfy := isUTF16Self(ry)
switch {
// Both runes encode as either a single or surrogate pair
// of UTF-16 codepoints.
case isUTF16Self(rx) == isUTF16Self(ry):
if rx != ry {
return rx < ry
}
// The x rune is a single UTF-16 codepoint, while
// the y rune is a surrogate pair of UTF-16 codepoints.
case isUTF16Self(rx):
ry, _ := utf16.EncodeRune(ry)
if rx != ry {
return rx < ry
}
panic("BUG: invalid UTF-8") // implies rx is an unpaired surrogate half
case selfx && !selfy:
ry, _ = utf16.EncodeRune(ry)
// The y rune is a single UTF-16 codepoint, while
// the x rune is a surrogate pair of UTF-16 codepoints.
case isUTF16Self(ry):
rx, _ := utf16.EncodeRune(rx)
if rx != ry {
return rx < ry
}
panic("BUG: invalid UTF-8") // implies ry is an unpaired surrogate half
case selfy && !selfx:
rx, _ = utf16.EncodeRune(rx)
}
if rx != ry {
return rx < ry
}
invalidUTF8 = invalidUTF8 || (rx == utf8.RuneError && nx == 1) || (ry == utf8.RuneError && ny == 1)
x, y = x[nx:], y[ny:]
}
}

260
vendor/k8s.io/kube-openapi/pkg/schemaconv/openapi.go generated vendored Normal file
View File

@@ -0,0 +1,260 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package schemaconv
import (
"errors"
"path"
"strings"
"k8s.io/kube-openapi/pkg/validation/spec"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
// ToSchemaFromOpenAPI converts a directory of OpenAPI schemas to an smd Schema.
// - models: a map from definition name to OpenAPI V3 structural schema for each definition.
// Key in map is used to resolve references in the schema.
// - preserveUnknownFields: flag indicating whether unknown fields in all schemas should be preserved.
// - returns: nil and an error if there is a parse error, or if schema does not satisfy a
// required structural schema invariant for conversion. If no error, returns
// a new smd schema.
//
// Schema should be validated as structural before using with this function, or
// there may be information lost.
func ToSchemaFromOpenAPI(models map[string]*spec.Schema, preserveUnknownFields bool) (*schema.Schema, error) {
c := convert{
preserveUnknownFields: preserveUnknownFields,
output: &schema.Schema{},
}
for name, spec := range models {
// Skip/Ignore top-level references
if len(spec.Ref.String()) > 0 {
continue
}
var a schema.Atom
// Hard-coded schemas for now as proto_models implementation functions.
// https://github.com/kubernetes/kube-openapi/issues/364
if name == quantityResource {
a = schema.Atom{
Scalar: untypedDef.Atom.Scalar,
}
} else if name == rawExtensionResource {
a = untypedDef.Atom
} else {
c2 := c.push(name, &a)
c2.visitSpec(spec)
c.pop(c2)
}
c.insertTypeDef(name, a)
}
if len(c.errorMessages) > 0 {
return nil, errors.New(strings.Join(c.errorMessages, "\n"))
}
c.addCommonTypes()
return c.output, nil
}
func (c *convert) visitSpec(m *spec.Schema) {
// Check if this schema opts its descendants into preserve-unknown-fields
if p, ok := m.Extensions["x-kubernetes-preserve-unknown-fields"]; ok && p == true {
c.preserveUnknownFields = true
}
a := c.top()
*a = c.parseSchema(m)
}
func (c *convert) parseSchema(m *spec.Schema) schema.Atom {
// k8s-generated OpenAPI specs have historically used only one value for
// type and starting with OpenAPIV3 it is only allowed to be
// a single string.
typ := ""
if len(m.Type) > 0 {
typ = m.Type[0]
}
// Structural Schemas produced by kubernetes follow very specific rules which
// we can use to infer the SMD type:
switch typ {
case "":
// According to Swagger docs:
// https://swagger.io/docs/specification/data-models/data-types/#any
//
// If no type is specified, it is equivalent to accepting any type.
return schema.Atom{
Scalar: ptr(schema.Scalar("untyped")),
List: c.parseList(m),
Map: c.parseObject(m),
}
case "object":
return schema.Atom{
Map: c.parseObject(m),
}
case "array":
return schema.Atom{
List: c.parseList(m),
}
case "integer", "boolean", "number", "string":
return convertPrimitive(typ, m.Format)
default:
c.reportError("unrecognized type: '%v'", typ)
return schema.Atom{
Scalar: ptr(schema.Scalar("untyped")),
}
}
}
func (c *convert) makeOpenAPIRef(specSchema *spec.Schema) schema.TypeRef {
refString := specSchema.Ref.String()
// Special-case handling for $ref stored inside a single-element allOf
if len(refString) == 0 && len(specSchema.AllOf) == 1 && len(specSchema.AllOf[0].Ref.String()) > 0 {
refString = specSchema.AllOf[0].Ref.String()
}
if _, n := path.Split(refString); len(n) > 0 {
//!TODO: Refactor the field ElementRelationship override
// we can generate the types with overrides ahead of time rather than
// requiring the hacky runtime support
// (could just create a normalized key struct containing all customizations
// to deduplicate)
mapRelationship, err := getMapElementRelationship(specSchema.Extensions)
if err != nil {
c.reportError(err.Error())
}
if len(mapRelationship) > 0 {
return schema.TypeRef{
NamedType: &n,
ElementRelationship: &mapRelationship,
}
}
return schema.TypeRef{
NamedType: &n,
}
}
var inlined schema.Atom
// compute the type inline
c2 := c.push("inlined in "+c.currentName, &inlined)
c2.preserveUnknownFields = c.preserveUnknownFields
c2.visitSpec(specSchema)
c.pop(c2)
return schema.TypeRef{
Inlined: inlined,
}
}
func (c *convert) parseObject(s *spec.Schema) *schema.Map {
var fields []schema.StructField
for name, member := range s.Properties {
fields = append(fields, schema.StructField{
Name: name,
Type: c.makeOpenAPIRef(&member),
Default: member.Default,
})
}
// AdditionalProperties informs the schema of any "unknown" keys
// Unknown keys are enforced by the ElementType field.
elementType := func() schema.TypeRef {
if s.AdditionalProperties == nil {
// According to openAPI spec, an object without properties and without
// additionalProperties is assumed to be a free-form object.
if c.preserveUnknownFields || len(s.Properties) == 0 {
return schema.TypeRef{
NamedType: &deducedName,
}
}
// If properties are specified, do not implicitly allow unknown
// fields
return schema.TypeRef{}
} else if s.AdditionalProperties.Schema != nil {
// Unknown fields use the referred schema
return c.makeOpenAPIRef(s.AdditionalProperties.Schema)
} else if s.AdditionalProperties.Allows {
// A boolean instead of a schema was provided. Deduce the
// type from the value provided at runtime.
return schema.TypeRef{
NamedType: &deducedName,
}
} else {
// Additional Properties are explicitly disallowed by the user.
// Ensure element type is empty.
return schema.TypeRef{}
}
}()
relationship, err := getMapElementRelationship(s.Extensions)
if err != nil {
c.reportError(err.Error())
}
return &schema.Map{
Fields: fields,
ElementRelationship: relationship,
ElementType: elementType,
}
}
func (c *convert) parseList(s *spec.Schema) *schema.List {
relationship, mapKeys, err := getListElementRelationship(s.Extensions)
if err != nil {
c.reportError(err.Error())
}
elementType := func() schema.TypeRef {
if s.Items != nil {
if s.Items.Schema == nil || s.Items.Len() != 1 {
c.reportError("structural schema arrays must have exactly one member subtype")
return schema.TypeRef{
NamedType: &deducedName,
}
}
subSchema := s.Items.Schema
if subSchema == nil {
subSchema = &s.Items.Schemas[0]
}
return c.makeOpenAPIRef(subSchema)
} else if len(s.Type) > 0 && len(s.Type[0]) > 0 {
c.reportError("`items` must be specified on arrays")
}
// A list with no items specified is treated as "untyped".
return schema.TypeRef{
NamedType: &untypedName,
}
}()
return &schema.List{
ElementRelationship: relationship,
Keys: mapKeys,
ElementType: elementType,
}
}

View File

@@ -0,0 +1,178 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package schemaconv
import (
"errors"
"path"
"strings"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
// ToSchema converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2).
func ToSchema(models proto.Models) (*schema.Schema, error) {
return ToSchemaWithPreserveUnknownFields(models, false)
}
// ToSchemaWithPreserveUnknownFields converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2), it will preserve unknown fields if specified.
func ToSchemaWithPreserveUnknownFields(models proto.Models, preserveUnknownFields bool) (*schema.Schema, error) {
c := convert{
preserveUnknownFields: preserveUnknownFields,
output: &schema.Schema{},
}
for _, name := range models.ListModels() {
model := models.LookupModel(name)
var a schema.Atom
c2 := c.push(name, &a)
model.Accept(c2)
c.pop(c2)
c.insertTypeDef(name, a)
}
if len(c.errorMessages) > 0 {
return nil, errors.New(strings.Join(c.errorMessages, "\n"))
}
c.addCommonTypes()
return c.output, nil
}
func (c *convert) makeRef(model proto.Schema, preserveUnknownFields bool) schema.TypeRef {
var tr schema.TypeRef
if r, ok := model.(*proto.Ref); ok {
if r.Reference() == "io.k8s.apimachinery.pkg.runtime.RawExtension" {
return schema.TypeRef{
NamedType: &untypedName,
}
}
// reference a named type
_, n := path.Split(r.Reference())
tr.NamedType = &n
mapRelationship, err := getMapElementRelationship(model.GetExtensions())
if err != nil {
c.reportError(err.Error())
}
// empty string means unset.
if len(mapRelationship) > 0 {
tr.ElementRelationship = &mapRelationship
}
} else {
// compute the type inline
c2 := c.push("inlined in "+c.currentName, &tr.Inlined)
c2.preserveUnknownFields = preserveUnknownFields
model.Accept(c2)
c.pop(c2)
if tr == (schema.TypeRef{}) {
// emit warning?
tr.NamedType = &untypedName
}
}
return tr
}
func (c *convert) VisitKind(k *proto.Kind) {
preserveUnknownFields := c.preserveUnknownFields
if p, ok := k.GetExtensions()["x-kubernetes-preserve-unknown-fields"]; ok && p == true {
preserveUnknownFields = true
}
a := c.top()
a.Map = &schema.Map{}
for _, name := range k.FieldOrder {
member := k.Fields[name]
tr := c.makeRef(member, preserveUnknownFields)
a.Map.Fields = append(a.Map.Fields, schema.StructField{
Name: name,
Type: tr,
Default: member.GetDefault(),
})
}
unions, err := makeUnions(k.GetExtensions())
if err != nil {
c.reportError(err.Error())
return
}
// TODO: We should check that the fields and discriminator
// specified in the union are actual fields in the struct.
a.Map.Unions = unions
if preserveUnknownFields {
a.Map.ElementType = schema.TypeRef{
NamedType: &deducedName,
}
}
a.Map.ElementRelationship, err = getMapElementRelationship(k.GetExtensions())
if err != nil {
c.reportError(err.Error())
}
}
func (c *convert) VisitArray(a *proto.Array) {
relationship, mapKeys, err := getListElementRelationship(a.GetExtensions())
if err != nil {
c.reportError(err.Error())
}
atom := c.top()
atom.List = &schema.List{
ElementType: c.makeRef(a.SubType, c.preserveUnknownFields),
ElementRelationship: relationship,
Keys: mapKeys,
}
}
func (c *convert) VisitMap(m *proto.Map) {
relationship, err := getMapElementRelationship(m.GetExtensions())
if err != nil {
c.reportError(err.Error())
}
a := c.top()
a.Map = &schema.Map{
ElementType: c.makeRef(m.SubType, c.preserveUnknownFields),
ElementRelationship: relationship,
}
}
func (c *convert) VisitPrimitive(p *proto.Primitive) {
a := c.top()
if c.currentName == quantityResource {
a.Scalar = ptr(schema.Scalar("untyped"))
} else {
*a = convertPrimitive(p.Type, p.Format)
}
}
func (c *convert) VisitArbitrary(a *proto.Arbitrary) {
*c.top() = deducedDef.Atom
}
func (c *convert) VisitReference(proto.Reference) {
// Do nothing, we handle references specially
}

View File

@@ -17,43 +17,18 @@ limitations under the License.
package schemaconv
import (
"errors"
"fmt"
"path"
"sort"
"strings"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
const (
quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
rawExtensionResource = "io.k8s.apimachinery.pkg.runtime.RawExtension"
)
// ToSchema converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2).
func ToSchema(models proto.Models) (*schema.Schema, error) {
return ToSchemaWithPreserveUnknownFields(models, false)
}
// ToSchemaWithPreserveUnknownFields converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2), it will preserve unknown fields if specified.
func ToSchemaWithPreserveUnknownFields(models proto.Models, preserveUnknownFields bool) (*schema.Schema, error) {
c := convert{
input: models,
preserveUnknownFields: preserveUnknownFields,
output: &schema.Schema{},
}
if err := c.convertAll(); err != nil {
return nil, err
}
c.addCommonTypes()
return c.output, nil
}
type convert struct {
input proto.Models
preserveUnknownFields bool
output *schema.Schema
@@ -64,7 +39,6 @@ type convert struct {
func (c *convert) push(name string, a *schema.Atom) *convert {
return &convert{
input: c.input,
preserveUnknownFields: c.preserveUnknownFields,
output: c.output,
currentName: name,
@@ -78,30 +52,17 @@ func (c *convert) pop(c2 *convert) {
c.errorMessages = append(c.errorMessages, c2.errorMessages...)
}
func (c *convert) convertAll() error {
for _, name := range c.input.ListModels() {
model := c.input.LookupModel(name)
c.insertTypeDef(name, model)
}
if len(c.errorMessages) > 0 {
return errors.New(strings.Join(c.errorMessages, "\n"))
}
return nil
}
func (c *convert) reportError(format string, args ...interface{}) {
c.errorMessages = append(c.errorMessages,
c.currentName+": "+fmt.Sprintf(format, args...),
)
}
func (c *convert) insertTypeDef(name string, model proto.Schema) {
func (c *convert) insertTypeDef(name string, atom schema.Atom) {
def := schema.TypeDef{
Name: name,
Atom: atom,
}
c2 := c.push(name, &def.Atom)
model.Accept(c2)
c.pop(c2)
if def.Atom == (schema.Atom{}) {
// This could happen if there were a top-level reference.
return
@@ -156,46 +117,6 @@ var deducedDef schema.TypeDef = schema.TypeDef{
},
}
func (c *convert) makeRef(model proto.Schema, preserveUnknownFields bool) schema.TypeRef {
var tr schema.TypeRef
if r, ok := model.(*proto.Ref); ok {
if r.Reference() == "io.k8s.apimachinery.pkg.runtime.RawExtension" {
return schema.TypeRef{
NamedType: &untypedName,
}
}
// reference a named type
_, n := path.Split(r.Reference())
tr.NamedType = &n
ext := model.GetExtensions()
if val, ok := ext["x-kubernetes-map-type"]; ok {
switch val {
case "atomic":
relationship := schema.Atomic
tr.ElementRelationship = &relationship
case "granular":
relationship := schema.Separable
tr.ElementRelationship = &relationship
default:
c.reportError("unknown map type %v", val)
}
}
} else {
// compute the type inline
c2 := c.push("inlined in "+c.currentName, &tr.Inlined)
c2.preserveUnknownFields = preserveUnknownFields
model.Accept(c2)
c.pop(c2)
if tr == (schema.TypeRef{}) {
// emit warning?
tr.NamedType = &untypedName
}
}
return tr
}
func makeUnions(extensions map[string]interface{}) ([]schema.Union, error) {
schemaUnions := []schema.Union{}
if iunions, ok := extensions["x-kubernetes-unions"]; ok {
@@ -299,52 +220,6 @@ func makeUnion(extensions map[string]interface{}) (schema.Union, error) {
return union, nil
}
func (c *convert) VisitKind(k *proto.Kind) {
preserveUnknownFields := c.preserveUnknownFields
if p, ok := k.GetExtensions()["x-kubernetes-preserve-unknown-fields"]; ok && p == true {
preserveUnknownFields = true
}
a := c.top()
a.Map = &schema.Map{}
for _, name := range k.FieldOrder {
member := k.Fields[name]
tr := c.makeRef(member, preserveUnknownFields)
a.Map.Fields = append(a.Map.Fields, schema.StructField{
Name: name,
Type: tr,
Default: member.GetDefault(),
})
}
unions, err := makeUnions(k.GetExtensions())
if err != nil {
c.reportError(err.Error())
return
}
// TODO: We should check that the fields and discriminator
// specified in the union are actual fields in the struct.
a.Map.Unions = unions
if preserveUnknownFields {
a.Map.ElementType = schema.TypeRef{
NamedType: &deducedName,
}
}
ext := k.GetExtensions()
if val, ok := ext["x-kubernetes-map-type"]; ok {
switch val {
case "atomic":
a.Map.ElementRelationship = schema.Atomic
case "granular":
a.Map.ElementRelationship = schema.Separable
default:
c.reportError("unknown map type %v", val)
}
}
}
func toStringSlice(o interface{}) (out []string, ok bool) {
switch t := o.(type) {
case []interface{}:
@@ -355,117 +230,108 @@ func toStringSlice(o interface{}) (out []string, ok bool) {
}
}
return out, true
case []string:
return t, true
}
return nil, false
}
func (c *convert) VisitArray(a *proto.Array) {
atom := c.top()
atom.List = &schema.List{
ElementRelationship: schema.Atomic,
}
l := atom.List
l.ElementType = c.makeRef(a.SubType, c.preserveUnknownFields)
ext := a.GetExtensions()
if val, ok := ext["x-kubernetes-list-type"]; ok {
if val == "atomic" {
l.ElementRelationship = schema.Atomic
} else if val == "set" {
l.ElementRelationship = schema.Associative
} else if val == "map" {
l.ElementRelationship = schema.Associative
if keys, ok := ext["x-kubernetes-list-map-keys"]; ok {
if keyNames, ok := toStringSlice(keys); ok {
l.Keys = keyNames
} else {
c.reportError("uninterpreted map keys: %#v", keys)
}
} else {
c.reportError("missing map keys")
}
} else {
c.reportError("unknown list type %v", val)
l.ElementRelationship = schema.Atomic
}
} else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
if val == "merge" || val == "merge,retainKeys" {
l.ElementRelationship = schema.Associative
if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
if keyName, ok := key.(string); ok {
l.Keys = []string{keyName}
} else {
c.reportError("uninterpreted merge key: %#v", key)
}
} else {
// It's not an error for this to be absent, it
// means it's a set.
}
} else if val == "retainKeys" {
} else {
c.reportError("unknown patch strategy %v", val)
l.ElementRelationship = schema.Atomic
}
}
}
func (c *convert) VisitMap(m *proto.Map) {
a := c.top()
a.Map = &schema.Map{}
a.Map.ElementType = c.makeRef(m.SubType, c.preserveUnknownFields)
ext := m.GetExtensions()
if val, ok := ext["x-kubernetes-map-type"]; ok {
switch val {
case "atomic":
a.Map.ElementRelationship = schema.Atomic
case "granular":
a.Map.ElementRelationship = schema.Separable
default:
c.reportError("unknown map type %v", val)
}
}
}
func ptr(s schema.Scalar) *schema.Scalar { return &s }
func (c *convert) VisitPrimitive(p *proto.Primitive) {
a := c.top()
if c.currentName == quantityResource {
a.Scalar = ptr(schema.Scalar("untyped"))
} else {
switch p.Type {
case proto.Integer:
a.Scalar = ptr(schema.Numeric)
case proto.Number:
a.Scalar = ptr(schema.Numeric)
case proto.String:
switch p.Format {
case "":
a.Scalar = ptr(schema.String)
case "byte":
// byte really means []byte and is encoded as a string.
a.Scalar = ptr(schema.String)
case "int-or-string":
a.Scalar = ptr(schema.Scalar("untyped"))
case "date-time":
a.Scalar = ptr(schema.Scalar("untyped"))
default:
a.Scalar = ptr(schema.Scalar("untyped"))
}
case proto.Boolean:
a.Scalar = ptr(schema.Boolean)
// Basic conversion functions to convert OpenAPI schema definitions to
// SMD Schema atoms
func convertPrimitive(typ string, format string) (a schema.Atom) {
switch typ {
case "integer":
a.Scalar = ptr(schema.Numeric)
case "number":
a.Scalar = ptr(schema.Numeric)
case "string":
switch format {
case "":
a.Scalar = ptr(schema.String)
case "byte":
// byte really means []byte and is encoded as a string.
a.Scalar = ptr(schema.String)
case "int-or-string":
a.Scalar = ptr(schema.Scalar("untyped"))
case "date-time":
a.Scalar = ptr(schema.Scalar("untyped"))
default:
a.Scalar = ptr(schema.Scalar("untyped"))
}
case "boolean":
a.Scalar = ptr(schema.Boolean)
default:
a.Scalar = ptr(schema.Scalar("untyped"))
}
return a
}
func getListElementRelationship(ext map[string]any) (schema.ElementRelationship, []string, error) {
if val, ok := ext["x-kubernetes-list-type"]; ok {
switch val {
case "atomic":
return schema.Atomic, nil, nil
case "set":
return schema.Associative, nil, nil
case "map":
keys, ok := ext["x-kubernetes-list-map-keys"]
if !ok {
return schema.Associative, nil, fmt.Errorf("missing map keys")
}
keyNames, ok := toStringSlice(keys)
if !ok {
return schema.Associative, nil, fmt.Errorf("uninterpreted map keys: %#v", keys)
}
return schema.Associative, keyNames, nil
default:
return schema.Atomic, nil, fmt.Errorf("unknown list type %v", val)
}
} else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
switch val {
case "merge", "merge,retainKeys":
if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
keyName, ok := key.(string)
if !ok {
return schema.Associative, nil, fmt.Errorf("uninterpreted merge key: %#v", key)
}
return schema.Associative, []string{keyName}, nil
}
// It's not an error for x-kubernetes-patch-merge-key to be absent,
// it means it's a set
return schema.Associative, nil, nil
case "retainKeys":
return schema.Atomic, nil, nil
default:
return schema.Atomic, nil, fmt.Errorf("unknown patch strategy %v", val)
}
}
// Treat as atomic by default
return schema.Atomic, nil, nil
}
// Returns map element relationship if specified, or empty string if unspecified
func getMapElementRelationship(ext map[string]any) (schema.ElementRelationship, error) {
val, ok := ext["x-kubernetes-map-type"]
if !ok {
// unset Map element relationship
return "", nil
}
switch val {
case "atomic":
return schema.Atomic, nil
case "granular":
return schema.Separable, nil
default:
return "", fmt.Errorf("unknown map type %v", val)
}
}
func (c *convert) VisitArbitrary(a *proto.Arbitrary) {
*c.top() = deducedDef.Atom
}
func (c *convert) VisitReference(proto.Reference) {
// Do nothing, we handle references specially
}

View File

@@ -58,7 +58,7 @@ type EncodingProps struct {
// Describes how a specific property value will be serialized depending on its type
Style string `json:"style,omitempty"`
// When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map. For other types of properties this property has no effect
Explode string `json:"explode,omitempty"`
Explode bool `json:"explode,omitempty"`
// AllowReserved determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986
AllowReserved bool `json:"allowReserved,omitempty"`
}

View File

@@ -19,8 +19,8 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Example https://swagger.io/specification/#example-object

View File

@@ -18,8 +18,8 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
type ExternalDocumentation struct {

250
vendor/k8s.io/kube-openapi/pkg/spec3/fuzz.go generated vendored Normal file
View File

@@ -0,0 +1,250 @@
package spec3
import (
fuzz "github.com/google/gofuzz"
"math/rand"
"strings"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// refChance is the chance that a particular component will use a $ref
// instead of fuzzed. Expressed as a fraction 1/n, currently there is
// a 1/3 chance that a ref will be used.
const refChance = 3
const alphaNumChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randAlphanumString() string {
arr := make([]string, rand.Intn(10)+5)
for i := 0; i < len(arr); i++ {
arr[i] = string(alphaNumChars[rand.Intn(len(alphaNumChars))])
}
return strings.Join(arr, "")
}
var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
func(s *string, c fuzz.Continue) {
// All OpenAPI V3 map keys must follow the corresponding
// regex. Note that this restricts the range for all other
// string values as well.
str := randAlphanumString()
*s = str
},
func(o *OpenAPI, c fuzz.Continue) {
c.FuzzNoCustom(o)
o.Version = "3.0.0"
},
func(r *interface{}, c fuzz.Continue) {
switch c.Intn(3) {
case 0:
*r = nil
case 1:
n := c.RandString() + "x"
*r = n
case 2:
n := c.Float64()
*r = n
}
},
func(v **spec.Info, c fuzz.Continue) {
// Info is never nil
*v = &spec.Info{}
c.FuzzNoCustom(*v)
(*v).Title = c.RandString() + "x"
},
func(v *Paths, c fuzz.Continue) {
c.Fuzz(&v.VendorExtensible)
num := c.Intn(5)
if num > 0 {
v.Paths = make(map[string]*Path)
}
for i := 0; i < num; i++ {
val := Path{}
c.Fuzz(&val)
v.Paths["/"+c.RandString()] = &val
}
},
func(v *SecurityScheme, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
switch c.Intn(4) {
case 0:
v.Type = "apiKey"
v.Name = c.RandString() + "x"
switch c.Intn(3) {
case 0:
v.In = "query"
case 1:
v.In = "header"
case 2:
v.In = "cookie"
}
case 1:
v.Type = "http"
case 2:
v.Type = "oauth2"
v.Flows = make(map[string]*OAuthFlow)
flow := OAuthFlow{}
flow.AuthorizationUrl = c.RandString() + "x"
v.Flows["implicit"] = &flow
flow.Scopes = make(map[string]string)
flow.Scopes["foo"] = "bar"
case 3:
v.Type = "openIdConnect"
v.OpenIdConnectUrl = "https://" + c.RandString()
}
v.Scheme = "basic"
},
func(v *spec.Ref, c fuzz.Continue) {
switch c.Intn(7) {
case 0:
*v = spec.MustCreateRef("#/components/schemas/" + randAlphanumString())
case 1:
*v = spec.MustCreateRef("#/components/responses/" + randAlphanumString())
case 2:
*v = spec.MustCreateRef("#/components/headers/" + randAlphanumString())
case 3:
*v = spec.MustCreateRef("#/components/securitySchemes/" + randAlphanumString())
case 5:
*v = spec.MustCreateRef("#/components/parameters/" + randAlphanumString())
case 6:
*v = spec.MustCreateRef("#/components/requestBodies/" + randAlphanumString())
}
},
func(v *Parameter, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
c.Fuzz(&v.ParameterProps)
c.Fuzz(&v.VendorExtensible)
switch c.Intn(3) {
case 0:
// Header param
v.In = "query"
case 1:
v.In = "header"
case 2:
v.In = "cookie"
}
},
func(v *RequestBody, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
c.Fuzz(&v.RequestBodyProps)
c.Fuzz(&v.VendorExtensible)
},
func(v *Header, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
c.Fuzz(&v.HeaderProps)
c.Fuzz(&v.VendorExtensible)
},
func(v *ResponsesProps, c fuzz.Continue) {
c.Fuzz(&v.Default)
n := c.Intn(5)
for i := 0; i < n; i++ {
r2 := Response{}
c.Fuzz(&r2)
// HTTP Status code in 100-599 Range
code := c.Intn(500) + 100
v.StatusCodeResponses = make(map[int]*Response)
v.StatusCodeResponses[code] = &r2
}
},
func(v *Response, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
c.Fuzz(&v.ResponseProps)
c.Fuzz(&v.VendorExtensible)
},
func(v *spec.Extensions, c fuzz.Continue) {
*v = spec.Extensions{}
numChildren := c.Intn(5)
if numChildren == 0 {
*v = nil
}
for i := 0; i < numChildren; i++ {
v.Add("x-"+randAlphanumString(), c.RandString()+"x")
}
},
func(v *spec.ExternalDocumentation, c fuzz.Continue) {
c.Fuzz(&v.Description)
v.URL = "https://" + randAlphanumString()
},
func(v *spec.Schema, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Ref)
return
}
c.Fuzz(&v.VendorExtensible)
c.Fuzz(&v.Description)
c.Fuzz(&v.Nullable)
c.Fuzz(&v.Title)
c.Fuzz(&v.Required)
c.Fuzz(&v.ExternalDocs)
n := c.Intn(8)
switch n {
case 0:
// To prevent exponential growth from recursively generating properties, only allow the schema to be an object with low frequency
if c.Intn(5) == 0 {
c.Fuzz(&v.Properties)
c.Fuzz(&v.MinProperties)
c.Fuzz(&v.MaxProperties)
} else {
v.Type = spec.StringOrArray{"integer"}
switch c.Intn(3) {
case 0:
v.Format = "int32"
case 1:
v.Format = "int64"
}
c.Fuzz(&v.MultipleOf)
c.Fuzz(&v.Minimum)
c.Fuzz(&v.Maximum)
c.Fuzz(&v.ExclusiveMaximum)
c.Fuzz(&v.ExclusiveMinimum)
}
case 1:
v.Type = spec.StringOrArray{"number"}
switch c.Intn(3) {
case 0:
v.Format = "float"
case 1:
v.Format = "double"
}
c.Fuzz(&v.MultipleOf)
c.Fuzz(&v.ExclusiveMaximum)
c.Fuzz(&v.ExclusiveMinimum)
c.Fuzz(&v.Minimum)
c.Fuzz(&v.Maximum)
case 2:
v.Type = spec.StringOrArray{"string"}
c.Fuzz(&v.MinLength)
c.Fuzz(&v.MaxLength)
case 3:
v.Type = spec.StringOrArray{"boolean"}
case 4:
v.Type = spec.StringOrArray{"array"}
s := spec.Schema{}
c.Fuzz(&s)
v.Items = &spec.SchemaOrArray{Schema: &s}
case 5:
c.Fuzz(&v.AnyOf)
case 6:
c.Fuzz(&v.AllOf)
case 7:
c.Fuzz(&v.OneOf)
}
},
}

View File

@@ -56,7 +56,7 @@ func (m *MediaType) UnmarshalJSON(data []byte) error {
// MediaTypeProps a struct that allows you to specify content format, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#mediaTypeObject
type MediaTypeProps struct {
// Schema holds the schema defining the type used for the media type
Schema *spec.Schema `json:"schema,omitempty"`
Schema *spec.Schema `json:"schema,omitempty"`
// Example of the media type
Example interface{} `json:"example,omitempty"`
// Examples of the media type. Each example object should match the media type and specific schema if present

View File

@@ -19,8 +19,8 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Operation describes a single API operation on a path, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operationObject
@@ -73,7 +73,7 @@ type OperationProps struct {
// Deprecated declares this operation to be deprecated
Deprecated bool `json:"deprecated,omitempty"`
// SecurityRequirement holds a declaration of which security mechanisms can be used for this operation
SecurityRequirement []*SecurityRequirement `json:"security,omitempty"`
SecurityRequirement []map[string][]string `json:"security,omitempty"`
// Servers contains an alternative server array to service this operation
Servers []*Server `json:"servers,omitempty"`
}

View File

@@ -20,8 +20,8 @@ import (
"encoding/json"
"strings"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Paths describes the available paths and operations for the API, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#pathsObject

View File

@@ -19,8 +19,8 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// RequestBody describes a single request body, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#requestBodyObject

View File

@@ -20,8 +20,8 @@ import (
"encoding/json"
"strconv"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Responses holds the list of possible responses as they are returned from executing this operation
@@ -78,20 +78,29 @@ func (r ResponsesProps) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals responses from JSON
func (r *ResponsesProps) UnmarshalJSON(data []byte) error {
var res map[string]*Response
var res map[string]json.RawMessage
if err := json.Unmarshal(data, &res); err != nil {
return nil
return err
}
if v, ok := res["default"]; ok {
r.Default = v
value := Response{}
if err := json.Unmarshal(v, &value); err != nil {
return err
}
r.Default = &value
delete(res, "default")
}
for k, v := range res {
// Take all integral keys
if nk, err := strconv.Atoi(k); err == nil {
if r.StatusCodeResponses == nil {
r.StatusCodeResponses = map[int]*Response{}
}
r.StatusCodeResponses[nk] = v
value := Response{}
if err := json.Unmarshal(v, &value); err != nil {
return err
}
r.StatusCodeResponses[nk] = &value
}
}
return nil
@@ -149,7 +158,6 @@ type ResponseProps struct {
Links map[string]*Link `json:"links,omitempty"`
}
// Link represents a possible design-time link for a response, more at https://swagger.io/specification/#link-object
type Link struct {
spec.Refable

View File

@@ -1,56 +0,0 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
)
// SecurityRequirementProps describes the required security schemes to execute an operation, more at https://swagger.io/specification/#security-requirement-object
//
// Note that this struct is actually a thin wrapper around SecurityRequirementProps to make it referable and extensible
type SecurityRequirement struct {
SecurityRequirementProps
spec.VendorExtensible
}
// MarshalJSON is a custom marshal function that knows how to encode SecurityRequirement as JSON
func (s *SecurityRequirement) MarshalJSON() ([]byte, error) {
b1, err := json.Marshal(s.SecurityRequirementProps)
if err != nil {
return nil, err
}
b2, err := json.Marshal(s.VendorExtensible)
if err != nil {
return nil, err
}
return swag.ConcatJSON(b1, b2), nil
}
// UnmarshalJSON hydrates this items instance with the data from JSON
func (s *SecurityRequirement) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &s.SecurityRequirementProps); err != nil {
return err
}
return json.Unmarshal(data, &s.VendorExtensible)
}
// SecurityRequirementProps describes the required security schemes to execute an operation, more at https://swagger.io/specification/#security-requirement-object
type SecurityRequirementProps map[string][]string

View File

@@ -19,8 +19,8 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// SecurityScheme defines reusable Security Scheme Object, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject

View File

@@ -18,9 +18,8 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
type Server struct {

View File

@@ -120,7 +120,7 @@ func (d *Definitions) ParseSchemaV3(s *openapi_v3.Schema, path *Path) (Schema, e
switch s.GetType() {
case object:
for _, extension := range s.GetSpecificationExtension() {
if extension.Name == "x-kuberentes-group-version-kind" {
if extension.Name == "x-kubernetes-group-version-kind" {
// Objects with x-kubernetes-group-version-kind are always top
// level types.
return d.parseV3Kind(s, path)
@@ -285,7 +285,7 @@ func parseV3Interface(def *yaml.Node) (interface{}, error) {
func (d *Definitions) parseV3BaseSchema(s *openapi_v3.Schema, path *Path) (*BaseSchema, error) {
if s == nil {
return nil, fmt.Errorf("cannot initializae BaseSchema from nil")
return nil, fmt.Errorf("cannot initialize BaseSchema from nil")
}
def, err := parseV3Interface(s.GetDefault().ToRawInfo())

View File

@@ -43,6 +43,9 @@ type Header struct {
// MarshalJSON marshal this to JSON
func (h Header) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(h)
}
b1, err := json.Marshal(h.CommonValidations)
if err != nil {
return nil, err
@@ -62,6 +65,20 @@ func (h Header) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3, b4), nil
}
func (h Header) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
CommonValidations commonValidationsOmitZero `json:",inline"`
SimpleSchema simpleSchemaOmitZero `json:",inline"`
Extensions
HeaderProps
}
x.CommonValidations = commonValidationsOmitZero(h.CommonValidations)
x.SimpleSchema = simpleSchemaOmitZero(h.SimpleSchema)
x.Extensions = internal.SanitizeExtensions(h.Extensions)
x.HeaderProps = h.HeaderProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON unmarshals this header from JSON
func (h *Header) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@@ -94,12 +111,8 @@ func (h *Header) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Dec
h.CommonValidations = x.CommonValidations
h.SimpleSchema = x.SimpleSchema
h.Extensions = x.Extensions
h.Extensions = internal.SanitizeExtensions(x.Extensions)
h.HeaderProps = x.HeaderProps
h.Extensions.sanitize()
if len(h.Extensions) == 0 {
h.Extensions = nil
}
return nil
}

View File

@@ -89,17 +89,9 @@ func (e Extensions) GetObject(key string, out interface{}) error {
return nil
}
func (e Extensions) sanitize() {
for k := range e {
if !isExtensionKey(k) {
delete(e, k)
}
}
}
func (e Extensions) sanitizeWithExtra() (extra map[string]any) {
for k, v := range e {
if !isExtensionKey(k) {
if !internal.IsExtensionKey(k) {
if extra == nil {
extra = make(map[string]any)
}
@@ -110,10 +102,6 @@ func (e Extensions) sanitizeWithExtra() (extra map[string]any) {
return extra
}
func isExtensionKey(k string) bool {
return len(k) > 1 && (k[0] == 'x' || k[0] == 'X') && k[1] == '-'
}
// VendorExtensible composition block.
type VendorExtensible struct {
Extensions Extensions
@@ -181,6 +169,9 @@ type Info struct {
// MarshalJSON marshal this to JSON
func (i Info) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(i)
}
b1, err := json.Marshal(i.InfoProps)
if err != nil {
return nil, err
@@ -192,6 +183,16 @@ func (i Info) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (i Info) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
InfoProps
}
x.Extensions = i.Extensions
x.InfoProps = i.InfoProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON marshal this from JSON
func (i *Info) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@@ -212,11 +213,7 @@ func (i *Info) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decod
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
i.VendorExtensible.Extensions = x.Extensions
i.Extensions = internal.SanitizeExtensions(x.Extensions)
i.InfoProps = x.InfoProps
return nil
}

View File

@@ -37,6 +37,18 @@ type SimpleSchema struct {
Example interface{} `json:"example,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type simpleSchemaOmitZero struct {
Type string `json:"type,omitempty"`
Nullable bool `json:"nullable,omitzero"`
Format string `json:"format,omitempty"`
Items *Items `json:"items,omitzero"`
CollectionFormat string `json:"collectionFormat,omitempty"`
Default interface{} `json:"default,omitempty"`
Example interface{} `json:"example,omitempty"`
}
// CommonValidations describe common JSON-schema validations
type CommonValidations struct {
Maximum *float64 `json:"maximum,omitempty"`
@@ -53,6 +65,23 @@ type CommonValidations struct {
Enum []interface{} `json:"enum,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type commonValidationsOmitZero struct {
Maximum *float64 `json:"maximum,omitempty"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
Minimum *float64 `json:"minimum,omitempty"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
MaxLength *int64 `json:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitzero"`
MultipleOf *float64 `json:"multipleOf,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
}
// Items a limited subset of JSON-Schema's items object.
// It is used by parameter definitions that are not located in "body".
//
@@ -105,18 +134,18 @@ func (i *Items) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Deco
if err := i.Refable.Ref.fromMap(x.Extensions); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
i.CommonValidations = x.CommonValidations
i.SimpleSchema = x.SimpleSchema
i.VendorExtensible.Extensions = x.Extensions
i.Extensions = internal.SanitizeExtensions(x.Extensions)
return nil
}
// MarshalJSON converts this items object to JSON
func (i Items) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(i)
}
b1, err := json.Marshal(i.CommonValidations)
if err != nil {
return nil, err
@@ -135,3 +164,17 @@ func (i Items) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b4, b3, b1, b2), nil
}
func (i Items) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
CommonValidations commonValidationsOmitZero `json:",inline"`
SimpleSchema simpleSchemaOmitZero `json:",inline"`
Ref string `json:"$ref,omitempty"`
Extensions
}
x.CommonValidations = commonValidationsOmitZero(i.CommonValidations)
x.SimpleSchema = simpleSchemaOmitZero(i.SimpleSchema)
x.Ref = i.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(i.Extensions)
return opts.MarshalNext(enc, x)
}

View File

@@ -42,6 +42,23 @@ type OperationProps struct {
Responses *Responses `json:"responses,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type operationPropsOmitZero struct {
Description string `json:"description,omitempty"`
Consumes []string `json:"consumes,omitempty"`
Produces []string `json:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty"`
Tags []string `json:"tags,omitempty"`
Summary string `json:"summary,omitempty"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
ID string `json:"operationId,omitempty"`
Deprecated bool `json:"deprecated,omitempty,omitzero"`
Security []map[string][]string `json:"security,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
Responses *Responses `json:"responses,omitzero"`
}
// MarshalJSON takes care of serializing operation properties to JSON
//
// We use a custom marhaller here to handle a special cases related to
@@ -96,17 +113,16 @@ func (o *Operation) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
o.VendorExtensible.Extensions = x.Extensions
o.Extensions = internal.SanitizeExtensions(x.Extensions)
o.OperationProps = OperationProps(x.OperationPropsNoMethods)
return nil
}
// MarshalJSON converts this items object to JSON
func (o Operation) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(o)
}
b1, err := json.Marshal(o.OperationProps)
if err != nil {
return nil, err
@@ -118,3 +134,13 @@ func (o Operation) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b1, b2)
return concated, nil
}
func (o Operation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
OperationProps operationPropsOmitZero `json:",inline"`
}
x.Extensions = internal.SanitizeExtensions(o.Extensions)
x.OperationProps = operationPropsOmitZero(o.OperationProps)
return opts.MarshalNext(enc, x)
}

View File

@@ -36,6 +36,17 @@ type ParamProps struct {
AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type paramPropsOmitZero struct {
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
In string `json:"in,omitempty"`
Required bool `json:"required,omitzero"`
Schema *Schema `json:"schema,omitzero"`
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
}
// Parameter a unique parameter is defined by a combination of a [name](#parameterName) and [location](#parameterIn).
//
// There are five possible parameter types.
@@ -109,19 +120,18 @@ func (p *Parameter) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.
if err := p.Refable.Ref.fromMap(x.Extensions); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
p.CommonValidations = x.CommonValidations
p.SimpleSchema = x.SimpleSchema
p.VendorExtensible.Extensions = x.Extensions
p.Extensions = internal.SanitizeExtensions(x.Extensions)
p.ParamProps = x.ParamProps
return nil
}
// MarshalJSON converts this items object to JSON
func (p Parameter) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.CommonValidations)
if err != nil {
return nil, err
@@ -144,3 +154,19 @@ func (p Parameter) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b3, b1, b2, b4, b5), nil
}
func (p Parameter) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
CommonValidations commonValidationsOmitZero `json:",inline"`
SimpleSchema simpleSchemaOmitZero `json:",inline"`
ParamProps paramPropsOmitZero `json:",inline"`
Ref string `json:"$ref,omitempty"`
Extensions
}
x.CommonValidations = commonValidationsOmitZero(p.CommonValidations)
x.SimpleSchema = simpleSchemaOmitZero(p.SimpleSchema)
x.Extensions = internal.SanitizeExtensions(p.Extensions)
x.ParamProps = paramPropsOmitZero(p.ParamProps)
x.Ref = p.Refable.Ref.String()
return opts.MarshalNext(enc, x)
}

View File

@@ -70,24 +70,20 @@ func (p *PathItem) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.D
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
p.Extensions = x.Extensions
p.PathItemProps = x.PathItemProps
if err := p.Refable.Ref.fromMap(p.Extensions); err != nil {
if err := p.Refable.Ref.fromMap(x.Extensions); err != nil {
return err
}
p.Extensions.sanitize()
if len(p.Extensions) == 0 {
p.Extensions = nil
}
p.Extensions = internal.SanitizeExtensions(x.Extensions)
p.PathItemProps = x.PathItemProps
return nil
}
// MarshalJSON converts this items object to JSON
func (p PathItem) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(p)
}
b3, err := json.Marshal(p.Refable)
if err != nil {
return nil, err
@@ -103,3 +99,15 @@ func (p PathItem) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b3, b4, b5)
return concated, nil
}
func (p PathItem) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
Extensions
PathItemProps
}
x.Ref = p.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(p.Extensions)
x.PathItemProps = p.PathItemProps
return opts.MarshalNext(enc, x)
}

View File

@@ -92,7 +92,7 @@ func (p *Paths) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Deco
}
switch k := tok.String(); {
case isExtensionKey(k):
case internal.IsExtensionKey(k):
ext = nil
if err := opts.UnmarshalNext(dec, &ext); err != nil {
return err
@@ -114,7 +114,9 @@ func (p *Paths) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Deco
p.Paths[k] = pi
default:
_, err := dec.ReadValue() // skip value
return err
if err != nil {
return err
}
}
}
default:
@@ -124,6 +126,9 @@ func (p *Paths) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Deco
// MarshalJSON converts this items object to JSON
func (p Paths) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.VendorExtensible)
if err != nil {
return nil, err
@@ -142,3 +147,18 @@ func (p Paths) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b1, b2)
return concated, nil
}
func (p Paths) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
m := make(map[string]any, len(p.Extensions)+len(p.Paths))
for k, v := range p.Extensions {
if internal.IsExtensionKey(k) {
m[k] = v
}
}
for k, v := range p.Paths {
if strings.HasPrefix(k, "/") {
m[k] = v
}
}
return opts.MarshalNext(enc, m)
}

View File

@@ -21,6 +21,8 @@ import (
"path/filepath"
"github.com/go-openapi/jsonreference"
"k8s.io/kube-openapi/pkg/internal"
)
// Refable is a struct for things that accept a $ref property
@@ -149,19 +151,5 @@ func (r *Ref) UnmarshalJSON(d []byte) error {
}
func (r *Ref) fromMap(v map[string]interface{}) error {
if v == nil {
return nil
}
if vv, ok := v["$ref"]; ok {
if str, ok := vv.(string); ok {
ref, err := jsonreference.New(str)
if err != nil {
return err
}
*r = Ref{Ref: ref}
}
}
return nil
return internal.JSONRefFromMap(&r.Ref, v)
}

View File

@@ -30,6 +30,15 @@ type ResponseProps struct {
Examples map[string]interface{} `json:"examples,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type responsePropsOmitZero struct {
Description string `json:"description,omitempty"`
Schema *Schema `json:"schema,omitzero"`
Headers map[string]Header `json:"headers,omitempty"`
Examples map[string]interface{} `json:"examples,omitempty"`
}
// Response describes a single response from an API Operation.
//
// For more information: http://goo.gl/8us55a#responseObject
@@ -68,23 +77,20 @@ func (r *Response) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.D
return err
}
r.Extensions = x.Extensions
r.ResponseProps = x.ResponseProps
if err := r.Refable.Ref.fromMap(r.Extensions); err != nil {
if err := r.Refable.Ref.fromMap(x.Extensions); err != nil {
return err
}
r.Extensions.sanitize()
if len(r.Extensions) == 0 {
r.Extensions = nil
}
r.Extensions = internal.SanitizeExtensions(x.Extensions)
r.ResponseProps = x.ResponseProps
return nil
}
// MarshalJSON converts this items object to JSON
func (r Response) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.ResponseProps)
if err != nil {
return nil, err
@@ -100,6 +106,18 @@ func (r Response) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (r Response) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
Extensions
ResponseProps responsePropsOmitZero `json:",inline"`
}
x.Ref = r.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(r.Extensions)
x.ResponseProps = responsePropsOmitZero(r.ResponseProps)
return opts.MarshalNext(enc, x)
}
// NewResponse creates a new response instance
func NewResponse() *Response {
return new(Response)

View File

@@ -63,6 +63,9 @@ func (r *Responses) UnmarshalJSON(data []byte) error {
// MarshalJSON converts this items object to JSON
func (r Responses) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.ResponsesProps)
if err != nil {
return nil, err
@@ -75,6 +78,25 @@ func (r Responses) MarshalJSON() ([]byte, error) {
return concated, nil
}
func (r Responses) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type ArbitraryKeys map[string]interface{}
var x struct {
ArbitraryKeys
Default *Response `json:"default,omitempty"`
}
x.ArbitraryKeys = make(map[string]any, len(r.Extensions)+len(r.StatusCodeResponses))
for k, v := range r.Extensions {
if internal.IsExtensionKey(k) {
x.ArbitraryKeys[k] = v
}
}
for k, v := range r.StatusCodeResponses {
x.ArbitraryKeys[strconv.Itoa(k)] = v
}
x.Default = r.Default
return opts.MarshalNext(enc, x)
}
// ResponsesProps describes all responses for an operation.
// It tells what is the default response and maps all responses with a
// HTTP status code.
@@ -148,7 +170,7 @@ func (r *Responses) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.
return nil
}
switch k := tok.String(); {
case isExtensionKey(k):
case internal.IsExtensionKey(k):
ext = nil
if err := opts.UnmarshalNext(dec, &ext); err != nil {
return err

View File

@@ -196,6 +196,46 @@ type SchemaProps struct {
Definitions Definitions `json:"definitions,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type schemaPropsOmitZero struct {
ID string `json:"id,omitempty"`
Ref Ref `json:"-"`
Schema SchemaURL `json:"-"`
Description string `json:"description,omitempty"`
Type StringOrArray `json:"type,omitzero"`
Nullable bool `json:"nullable,omitzero"`
Format string `json:"format,omitempty"`
Title string `json:"title,omitempty"`
Default interface{} `json:"default,omitzero"`
Maximum *float64 `json:"maximum,omitempty"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
Minimum *float64 `json:"minimum,omitempty"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
MaxLength *int64 `json:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitzero"`
MultipleOf *float64 `json:"multipleOf,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
MaxProperties *int64 `json:"maxProperties,omitempty"`
MinProperties *int64 `json:"minProperties,omitempty"`
Required []string `json:"required,omitempty"`
Items *SchemaOrArray `json:"items,omitzero"`
AllOf []Schema `json:"allOf,omitempty"`
OneOf []Schema `json:"oneOf,omitempty"`
AnyOf []Schema `json:"anyOf,omitempty"`
Not *Schema `json:"not,omitzero"`
Properties map[string]Schema `json:"properties,omitempty"`
AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitzero"`
PatternProperties map[string]Schema `json:"patternProperties,omitempty"`
Dependencies Dependencies `json:"dependencies,omitempty"`
AdditionalItems *SchemaOrBool `json:"additionalItems,omitzero"`
Definitions Definitions `json:"definitions,omitempty"`
}
// SwaggerSchemaProps are additional properties supported by swagger schemas, but not JSON-schema (draft 4)
type SwaggerSchemaProps struct {
Discriminator string `json:"discriminator,omitempty"`
@@ -204,6 +244,15 @@ type SwaggerSchemaProps struct {
Example interface{} `json:"example,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type swaggerSchemaPropsOmitZero struct {
Discriminator string `json:"discriminator,omitempty"`
ReadOnly bool `json:"readOnly,omitzero"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
Example interface{} `json:"example,omitempty"`
}
// Schema the schema object allows the definition of input and output data types.
// These types can be objects, but also primitives and arrays.
// This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/)
@@ -434,6 +483,9 @@ func (s *Schema) WithExternalDocs(description, url string) *Schema {
// MarshalJSON marshal this to JSON
func (s Schema) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.SchemaProps)
if err != nil {
return nil, fmt.Errorf("schema props %v", err)
@@ -465,6 +517,29 @@ func (s Schema) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil
}
func (s Schema) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type ArbitraryKeys map[string]interface{}
var x struct {
ArbitraryKeys
SchemaProps schemaPropsOmitZero `json:",inline"`
SwaggerSchemaProps swaggerSchemaPropsOmitZero `json:",inline"`
Ref string `json:"$ref,omitempty"`
}
x.ArbitraryKeys = make(map[string]any, len(s.Extensions)+len(s.ExtraProps))
for k, v := range s.Extensions {
if internal.IsExtensionKey(k) {
x.ArbitraryKeys[k] = v
}
}
for k, v := range s.ExtraProps {
x.ArbitraryKeys[k] = v
}
x.SchemaProps = schemaPropsOmitZero(s.SchemaProps)
x.SwaggerSchemaProps = swaggerSchemaPropsOmitZero(s.SwaggerSchemaProps)
x.Ref = s.Ref.String()
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON marshal this from JSON
func (s *Schema) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {

View File

@@ -18,6 +18,7 @@ import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)
@@ -45,6 +46,9 @@ type SecurityScheme struct {
// MarshalJSON marshal this to JSON
func (s SecurityScheme) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.SecuritySchemeProps)
if err != nil {
return nil, err
@@ -56,6 +60,16 @@ func (s SecurityScheme) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (s SecurityScheme) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
SecuritySchemeProps
}
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.SecuritySchemeProps = s.SecuritySchemeProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON marshal this from JSON
func (s *SecurityScheme) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &s.SecuritySchemeProps); err != nil {
@@ -72,11 +86,7 @@ func (s *SecurityScheme) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *js
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
s.VendorExtensible.Extensions = x.Extensions
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.SecuritySchemeProps = x.SecuritySchemeProps
return nil
}

View File

@@ -35,6 +35,9 @@ type Swagger struct {
// MarshalJSON marshals this swagger structure to json
func (s Swagger) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.SwaggerProps)
if err != nil {
return nil, err
@@ -46,12 +49,22 @@ func (s Swagger) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
// MarshalJSON marshals this swagger structure to json
func (s Swagger) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
SwaggerProps
}
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.SwaggerProps = s.SwaggerProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON unmarshals a swagger spec from json
func (s *Swagger) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
return jsonv2.Unmarshal(data, s)
}
var sw Swagger
if err := json.Unmarshal(data, &sw.SwaggerProps); err != nil {
return err
@@ -75,15 +88,8 @@ func (s *Swagger) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.De
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
s.Extensions = x.Extensions
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.SwaggerProps = x.SwaggerProps
s.Extensions.sanitize()
if len(s.Extensions) == 0 {
s.Extensions = nil
}
return nil
}
@@ -126,6 +132,9 @@ var jsFalse = []byte("false")
// MarshalJSON convert this object to JSON
func (s SchemaOrBool) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
if s.Schema != nil {
return json.Marshal(s.Schema)
}
@@ -136,6 +145,18 @@ func (s SchemaOrBool) MarshalJSON() ([]byte, error) {
return jsTrue, nil
}
// MarshalJSON convert this object to JSON
func (s SchemaOrBool) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
if s.Schema != nil {
return opts.MarshalNext(enc, s.Schema)
}
if s.Schema == nil && !s.Allows {
return enc.WriteToken(jsonv2.False)
}
return enc.WriteToken(jsonv2.True)
}
// UnmarshalJSON converts this bool or schema object from a JSON structure
func (s *SchemaOrBool) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@@ -185,6 +206,9 @@ type SchemaOrStringArray struct {
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
if len(s.Property) > 0 {
return json.Marshal(s.Property)
}
@@ -194,6 +218,17 @@ func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) {
return []byte("null"), nil
}
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrStringArray) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
if len(s.Property) > 0 {
return opts.MarshalNext(enc, s.Property)
}
if s.Schema != nil {
return opts.MarshalNext(enc, s.Schema)
}
return enc.WriteToken(jsonv2.Null)
}
// UnmarshalJSON converts this schema object or array from a JSON structure
func (s *SchemaOrStringArray) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@@ -347,12 +382,23 @@ func (s *SchemaOrArray) ContainsType(name string) bool {
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrArray) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
if len(s.Schemas) > 0 {
return json.Marshal(s.Schemas)
}
return json.Marshal(s.Schema)
}
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrArray) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
if len(s.Schemas) > 0 {
return opts.MarshalNext(enc, s.Schemas)
}
return opts.MarshalNext(enc, s.Schema)
}
// UnmarshalJSON converts this schema object or array from a JSON structure
func (s *SchemaOrArray) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {

View File

@@ -41,6 +41,9 @@ type Tag struct {
// MarshalJSON marshal this to JSON
func (t Tag) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(t)
}
b1, err := json.Marshal(t.TagProps)
if err != nil {
return nil, err
@@ -52,6 +55,16 @@ func (t Tag) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (t Tag) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
TagProps
}
x.Extensions = internal.SanitizeExtensions(t.Extensions)
x.TagProps = t.TagProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON marshal this from JSON
func (t *Tag) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@@ -72,11 +85,7 @@ func (t *Tag) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decode
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
t.VendorExtensible.Extensions = x.Extensions
t.Extensions = internal.SanitizeExtensions(x.Extensions)
t.TagProps = x.TagProps
return nil
}