Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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