Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/internal/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ var UseOptimizedJSONUnmarshalingV3 bool = true

// Used by tests to selectively disable experimental JSON marshaler
var UseOptimizedJSONMarshaling bool = true
var UseOptimizedJSONMarshalingV3 bool = true
148 changes: 148 additions & 0 deletions pkg/spec3/benchmark_serialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/google/go-cmp/cmp"
fuzz "github.com/google/gofuzz"
"k8s.io/kube-openapi/pkg/internal"
jsontesting "k8s.io/kube-openapi/pkg/util/jsontesting"
"k8s.io/kube-openapi/pkg/validation/spec"
)

Expand Down Expand Up @@ -71,6 +72,95 @@ func TestOpenAPIV3Deserialize(t *testing.T) {
}
}

func TestOpenAPIV3Serialize(t *testing.T) {
swagFile, err := os.Open("./testdata/appsv1spec.json")
if err != nil {
t.Fatal(err)
}
defer swagFile.Close()
originalJSON, err := io.ReadAll(swagFile)
if err != nil {
t.Fatal(err)
}
var openapi *OpenAPI
if err := json.Unmarshal(originalJSON, &openapi); err != nil {
t.Fatal(err)
}

internal.UseOptimizedJSONUnmarshalingV3 = false
want, err := json.Marshal(openapi)
if err != nil {
t.Fatal(err)
}
internal.UseOptimizedJSONUnmarshalingV3 = true
got, err := openapi.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if err := jsontesting.JsonCompare(want, got); err != nil {
t.Errorf("marshal doesn't match: %v", err)
}
}

func TestOpenAPIV3SerializeFuzzed(t *testing.T) {
var fuzzer *fuzz.Fuzzer
fuzzer = fuzz.NewWithSeed(1646791953)
fuzzer.MaxDepth(13).NilChance(0.075).NumElements(1, 2)
fuzzer.Funcs(OpenAPIV3FuzzFuncs...)

for i := 0; i < 100; i++ {
openapi := &OpenAPI{}
fuzzer.Fuzz(openapi)

internal.UseOptimizedJSONUnmarshalingV3 = false
want, err := json.Marshal(openapi)
if err != nil {
t.Fatal(err)
}
internal.UseOptimizedJSONUnmarshalingV3 = true
got, err := openapi.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if err := jsontesting.JsonCompare(want, got); err != nil {
t.Errorf("fuzzed marshal doesn't match: %v", err)
}
}
}

func TestOpenAPIV3SerializeStable(t *testing.T) {
swagFile, err := os.Open("./testdata/appsv1spec.json")
if err != nil {
t.Fatal(err)
}
defer swagFile.Close()
originalJSON, err := io.ReadAll(swagFile)
if err != nil {
t.Fatal(err)
}
var openapi *OpenAPI
if err := json.Unmarshal(originalJSON, &openapi); err != nil {
t.Fatal(err)
}

internal.UseOptimizedJSONUnmarshalingV3 = true
for i := 0; i < 5; i++ {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
want, err := openapi.MarshalJSON()
if err != nil {
t.Fatal(err)
}
got, err := openapi.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if err := jsontesting.JsonCompare(want, got); err != nil {
t.Errorf("marshal doesn't match: %v", err)
}
})
}
}

func BenchmarkOpenAPIV3Deserialize(b *testing.B) {
benchcases := []struct {
file string
Expand Down Expand Up @@ -142,3 +232,61 @@ func BenchmarkOpenAPIV3Deserialize(b *testing.B) {
})
}
}

func BenchmarkOpenAPIV3Serialize(b *testing.B) {
benchcases := []struct {
file string
}{
{
file: "appsv1spec.json",
},
{
file: "authorizationv1spec.json",
},
}
for _, bc := range benchcases {
swagFile, err := os.Open("./testdata/" + bc.file)
if err != nil {
b.Fatal(err)
}
defer swagFile.Close()
originalJSON, err := io.ReadAll(swagFile)
if err != nil {
b.Fatal(err)
}
var openapi *OpenAPI
if err := json.Unmarshal(originalJSON, &openapi); err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.Run(fmt.Sprintf("%s jsonv1", bc.file), func(b2 *testing.B) {
b2.ReportAllocs()
internal.UseOptimizedJSONMarshalingV3 = false
for i := 0; i < b2.N; i++ {
if _, err := json.Marshal(openapi); err != nil {
b2.Fatal(err)
}
}
})

b.Run(fmt.Sprintf("%s jsonv2 via jsonv1 full spec", bc.file), func(b2 *testing.B) {
b2.ReportAllocs()
internal.UseOptimizedJSONMarshalingV3 = true
for i := 0; i < b2.N; i++ {
if _, err := json.Marshal(openapi); err != nil {
b2.Fatal(err)
}
}
})

b.Run("jsonv2", func(b2 *testing.B) {
b2.ReportAllocs()
internal.UseOptimizedJSONMarshalingV3 = true
for i := 0; i < b2.N; i++ {
if _, err := openapi.MarshalJSON(); err != nil {
b2.Fatal(err)
}
}
})
}
}
21 changes: 21 additions & 0 deletions pkg/spec3/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type Encoding struct {

// MarshalJSON is a custom marshal function that knows how to encode Encoding as JSON
func (e *Encoding) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.EncodingProps)
if err != nil {
return nil, err
Expand All @@ -43,6 +46,16 @@ func (e *Encoding) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}

func (e *Encoding) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
EncodingProps encodingPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.EncodingProps = encodingPropsOmitZero(e.EncodingProps)
return opts.MarshalNext(enc, x)
}

func (e *Encoding) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
Expand Down Expand Up @@ -82,3 +95,11 @@ type EncodingProps struct {
// AllowReserved determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986
AllowReserved bool `json:"allowReserved,omitempty"`
}

type encodingPropsOmitZero struct {
ContentType string `json:"contentType,omitempty"`
Headers map[string]*Header `json:"headers,omitempty"`
Style string `json:"style,omitempty"`
Explode bool `json:"explode,omitzero"`
AllowReserved bool `json:"allowReserved,omitzero"`
}
14 changes: 14 additions & 0 deletions pkg/spec3/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type Example struct {

// MarshalJSON is a custom marshal function that knows how to encode RequestBody as JSON
func (e *Example) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.Refable)
if err != nil {
return nil, err
Expand All @@ -50,6 +53,17 @@ func (e *Example) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b1, b2, b3), nil
}
func (e *Example) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
ExampleProps `json:",inline"`
spec.Extensions
}
x.Ref = e.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.ExampleProps = e.ExampleProps
return opts.MarshalNext(enc, x)
}

func (e *Example) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
Expand Down
13 changes: 13 additions & 0 deletions pkg/spec3/external_documentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type ExternalDocumentationProps struct {

// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (e *ExternalDocumentation) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.ExternalDocumentationProps)
if err != nil {
return nil, err
Expand All @@ -50,6 +53,16 @@ func (e *ExternalDocumentation) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}

func (e *ExternalDocumentation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
ExternalDocumentationProps `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.ExternalDocumentationProps = e.ExternalDocumentationProps
return opts.MarshalNext(enc, x)
}

func (e *ExternalDocumentation) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
Expand Down
15 changes: 15 additions & 0 deletions pkg/spec3/fuzz.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,21 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
c.Fuzz(&v.ResponseProps)
c.Fuzz(&v.VendorExtensible)
},
func(v *Operation, c fuzz.Continue) {
c.FuzzNoCustom(v)
// Do not fuzz null values into the array.
for i, val := range v.SecurityRequirement {
if val == nil {
v.SecurityRequirement[i] = make(map[string][]string)
}

for k, v := range val {
if v == nil {
val[k] = make([]string, 0)
}
}
}
},
func(v *spec.Extensions, c fuzz.Continue) {
numChildren := c.Intn(5)
for i := 0; i < numChildren; i++ {
Expand Down
31 changes: 31 additions & 0 deletions pkg/spec3/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type Header struct {

// MarshalJSON is a custom marshal function that knows how to encode Header as JSON
func (h *Header) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(h)
}
b1, err := json.Marshal(h.Refable)
if err != nil {
return nil, err
Expand All @@ -51,6 +54,18 @@ func (h *Header) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}

func (h *Header) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
HeaderProps headerPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Ref = h.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(h.Extensions)
x.HeaderProps = headerPropsOmitZero(h.HeaderProps)
return opts.MarshalNext(enc, x)
}

func (h *Header) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, h)
Expand Down Expand Up @@ -109,3 +124,19 @@ type HeaderProps struct {
// Examples of the header
Examples map[string]*Example `json:"examples,omitempty"`
}

// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type headerPropsOmitZero struct {
Description string `json:"description,omitempty"`
Required bool `json:"required,omitzero"`
Deprecated bool `json:"deprecated,omitzero"`
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
Style string `json:"style,omitempty"`
Explode bool `json:"explode,omitzero"`
AllowReserved bool `json:"allowReserved,omitzero"`
Schema *spec.Schema `json:"schema,omitzero"`
Content map[string]*MediaType `json:"content,omitempty"`
Example interface{} `json:"example,omitempty"`
Examples map[string]*Example `json:"examples,omitempty"`
}
20 changes: 20 additions & 0 deletions pkg/spec3/media_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type MediaType struct {

// MarshalJSON is a custom marshal function that knows how to encode MediaType as JSON
func (m *MediaType) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(m)
}
b1, err := json.Marshal(m.MediaTypeProps)
if err != nil {
return nil, err
Expand All @@ -46,6 +49,16 @@ func (m *MediaType) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}

func (e *MediaType) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
MediaTypeProps mediaTypePropsOmitZero `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.MediaTypeProps = mediaTypePropsOmitZero(e.MediaTypeProps)
return opts.MarshalNext(enc, x)
}

func (m *MediaType) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, m)
Expand Down Expand Up @@ -84,3 +97,10 @@ type MediaTypeProps struct {
// A map between a property name and its encoding information. The key, being the property name, MUST exist in the schema as a property. The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded
Encoding map[string]*Encoding `json:"encoding,omitempty"`
}

type mediaTypePropsOmitZero struct {
Schema *spec.Schema `json:"schema,omitzero"`
Example interface{} `json:"example,omitempty"`
Examples map[string]*Example `json:"examples,omitempty"`
Encoding map[string]*Encoding `json:"encoding,omitempty"`
}
Loading