| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- /*
- 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 spec
- import (
- "github.com/go-openapi/jsonreference"
- "github.com/google/go-cmp/cmp"
- fuzz "github.com/google/gofuzz"
- )
- var SwaggerFuzzFuncs []interface{} = []interface{}{
- func(v *Responses, c fuzz.Continue) {
- c.FuzzNoCustom(v)
- if v.Default != nil {
- // Check if we hit maxDepth and left an incomplete value
- if v.Default.Description == "" {
- v.Default = nil
- v.StatusCodeResponses = nil
- }
- }
- // conversion has no way to discern empty statusCodeResponses from
- // nil, since "default" is always included in the map.
- // So avoid empty responses list
- if len(v.StatusCodeResponses) == 0 {
- v.StatusCodeResponses = nil
- }
- },
- func(v *Operation, c fuzz.Continue) {
- c.FuzzNoCustom(v)
- if v != nil {
- // force non-nil
- v.Responses = &Responses{}
- c.Fuzz(v.Responses)
- v.Schemes = nil
- if c.RandBool() {
- v.Schemes = append(v.Schemes, "http")
- }
- if c.RandBool() {
- v.Schemes = append(v.Schemes, "https")
- }
- if c.RandBool() {
- v.Schemes = append(v.Schemes, "ws")
- }
- if c.RandBool() {
- v.Schemes = append(v.Schemes, "wss")
- }
- // Gnostic unconditionally makes security values non-null
- // So do not fuzz null values into the array.
- for i, val := range v.Security {
- if val == nil {
- v.Security[i] = make(map[string][]string)
- }
- for k, v := range val {
- if v == nil {
- val[k] = make([]string, 0)
- }
- }
- }
- }
- },
- func(v map[int]Response, c fuzz.Continue) {
- n := 0
- c.Fuzz(&n)
- if n == 0 {
- // Test that fuzzer is not at maxDepth so we do not
- // end up with empty elements
- return
- }
- // Prevent negative numbers
- num := c.Intn(4)
- for i := 0; i < num+2; i++ {
- val := Response{}
- c.Fuzz(&val)
- val.Description = c.RandString() + "x"
- v[100*(i+1)+c.Intn(100)] = val
- }
- },
- func(v map[string]PathItem, c fuzz.Continue) {
- n := 0
- c.Fuzz(&n)
- if n == 0 {
- // Test that fuzzer is not at maxDepth so we do not
- // end up with empty elements
- return
- }
- num := c.Intn(5)
- for i := 0; i < num+2; i++ {
- val := PathItem{}
- c.Fuzz(&val)
- // Ref params are only allowed in certain locations, so
- // possibly add a few to PathItems
- numRefsToAdd := c.Intn(5)
- for i := 0; i < numRefsToAdd; i++ {
- theRef := Parameter{}
- c.Fuzz(&theRef.Refable)
- val.Parameters = append(val.Parameters, theRef)
- }
- v["/"+c.RandString()] = val
- }
- },
- func(v *SchemaOrArray, c fuzz.Continue) {
- *v = SchemaOrArray{}
- // gnostic parser just doesn't support more
- // than one Schema here
- v.Schema = &Schema{}
- c.Fuzz(&v.Schema)
- },
- func(v *SchemaOrBool, c fuzz.Continue) {
- *v = SchemaOrBool{}
- if c.RandBool() {
- v.Allows = c.RandBool()
- } else {
- v.Schema = &Schema{}
- v.Allows = true
- c.Fuzz(&v.Schema)
- }
- },
- func(v map[string]Response, c fuzz.Continue) {
- n := 0
- c.Fuzz(&n)
- if n == 0 {
- // Test that fuzzer is not at maxDepth so we do not
- // end up with empty elements
- return
- }
- // Response definitions are not allowed to
- // be refs
- for i := 0; i < c.Intn(5)+1; i++ {
- resp := &Response{}
- c.Fuzz(resp)
- resp.Ref = Ref{}
- resp.Description = c.RandString() + "x"
- // Response refs are not vendor extensible by gnostic
- resp.VendorExtensible.Extensions = nil
- v[c.RandString()+"x"] = *resp
- }
- },
- func(v *Header, c fuzz.Continue) {
- if v != nil {
- c.FuzzNoCustom(v)
- // descendant Items of Header may not be refs
- cur := v.Items
- for cur != nil {
- cur.Ref = Ref{}
- cur = cur.Items
- }
- }
- },
- func(v *Ref, c fuzz.Continue) {
- *v = Ref{}
- v.Ref, _ = jsonreference.New("http://asd.com/" + c.RandString())
- },
- func(v *Response, c fuzz.Continue) {
- *v = Response{}
- if c.RandBool() {
- v.Ref = Ref{}
- v.Ref.Ref, _ = jsonreference.New("http://asd.com/" + c.RandString())
- } else {
- c.Fuzz(&v.VendorExtensible)
- c.Fuzz(&v.Schema)
- c.Fuzz(&v.ResponseProps)
- v.Headers = nil
- v.Ref = Ref{}
- n := 0
- c.Fuzz(&n)
- if n != 0 {
- // Test that fuzzer is not at maxDepth so we do not
- // end up with empty elements
- num := c.Intn(4)
- for i := 0; i < num; i++ {
- if v.Headers == nil {
- v.Headers = make(map[string]Header)
- }
- hdr := Header{}
- c.Fuzz(&hdr)
- if hdr.Type == "" {
- // hit maxDepth, just abort trying to make haders
- v.Headers = nil
- break
- }
- v.Headers[c.RandString()+"x"] = hdr
- }
- } else {
- v.Headers = nil
- }
- }
- v.Description = c.RandString() + "x"
- // Gnostic parses empty as nil, so to keep avoid putting empty
- if len(v.Headers) == 0 {
- v.Headers = nil
- }
- },
- func(v **Info, c fuzz.Continue) {
- // Info is never nil
- *v = &Info{}
- c.FuzzNoCustom(*v)
- (*v).Title = c.RandString() + "x"
- },
- func(v *Extensions, c fuzz.Continue) {
- // gnostic parser only picks up x- vendor extensions
- numChildren := c.Intn(5)
- for i := 0; i < numChildren; i++ {
- if *v == nil {
- *v = Extensions{}
- }
- (*v)["x-"+c.RandString()] = c.RandString()
- }
- },
- func(v *Swagger, c fuzz.Continue) {
- c.FuzzNoCustom(v)
- if v.Paths == nil {
- // Force paths non-nil since it does not have omitempty in json tag.
- // This means a perfect roundtrip (via json) is impossible,
- // since we can't tell the difference between empty/unspecified paths
- v.Paths = &Paths{}
- c.Fuzz(v.Paths)
- }
- v.Swagger = "2.0"
- // Gnostic support serializing ID at all
- // unavoidable data loss
- v.ID = ""
- v.Schemes = nil
- if c.RandUint64()%2 == 1 {
- v.Schemes = append(v.Schemes, "http")
- }
- if c.RandUint64()%2 == 1 {
- v.Schemes = append(v.Schemes, "https")
- }
- if c.RandUint64()%2 == 1 {
- v.Schemes = append(v.Schemes, "ws")
- }
- if c.RandUint64()%2 == 1 {
- v.Schemes = append(v.Schemes, "wss")
- }
- // Gnostic unconditionally makes security values non-null
- // So do not fuzz null values into the array.
- for i, val := range v.Security {
- if val == nil {
- v.Security[i] = make(map[string][]string)
- }
- for k, v := range val {
- if v == nil {
- val[k] = make([]string, 0)
- }
- }
- }
- },
- func(v *SecurityScheme, c fuzz.Continue) {
- v.Description = c.RandString() + "x"
- c.Fuzz(&v.VendorExtensible)
- switch c.Intn(3) {
- case 0:
- v.Type = "basic"
- case 1:
- v.Type = "apiKey"
- switch c.Intn(2) {
- case 0:
- v.In = "header"
- case 1:
- v.In = "query"
- default:
- panic("unreachable")
- }
- v.Name = "x" + c.RandString()
- case 2:
- v.Type = "oauth2"
- switch c.Intn(4) {
- case 0:
- v.Flow = "accessCode"
- v.TokenURL = "https://" + c.RandString()
- v.AuthorizationURL = "https://" + c.RandString()
- case 1:
- v.Flow = "application"
- v.TokenURL = "https://" + c.RandString()
- case 2:
- v.Flow = "implicit"
- v.AuthorizationURL = "https://" + c.RandString()
- case 3:
- v.Flow = "password"
- v.TokenURL = "https://" + c.RandString()
- default:
- panic("unreachable")
- }
- c.Fuzz(&v.Scopes)
- default:
- panic("unreachable")
- }
- },
- func(v *interface{}, c fuzz.Continue) {
- *v = c.RandString() + "x"
- },
- func(v *string, c fuzz.Continue) {
- *v = c.RandString() + "x"
- },
- func(v *ExternalDocumentation, c fuzz.Continue) {
- v.Description = c.RandString() + "x"
- v.URL = c.RandString() + "x"
- },
- func(v *SimpleSchema, c fuzz.Continue) {
- c.FuzzNoCustom(v)
- switch c.Intn(5) {
- case 0:
- v.Type = "string"
- case 1:
- v.Type = "number"
- case 2:
- v.Type = "boolean"
- case 3:
- v.Type = "integer"
- case 4:
- v.Type = "array"
- default:
- panic("unreachable")
- }
- switch c.Intn(5) {
- case 0:
- v.CollectionFormat = "csv"
- case 1:
- v.CollectionFormat = "ssv"
- case 2:
- v.CollectionFormat = "tsv"
- case 3:
- v.CollectionFormat = "pipes"
- case 4:
- v.CollectionFormat = ""
- default:
- panic("unreachable")
- }
- // None of the types which include SimpleSchema in our definitions
- // actually support "example" in the official spec
- v.Example = nil
- // unsupported by openapi
- v.Nullable = false
- },
- func(v *int64, c fuzz.Continue) {
- c.Fuzz(v)
- // Gnostic does not differentiate between 0 and non-specified
- // so avoid using 0 for fuzzer
- if *v == 0 {
- *v = 1
- }
- },
- func(v *float64, c fuzz.Continue) {
- c.Fuzz(v)
- // Gnostic does not differentiate between 0 and non-specified
- // so avoid using 0 for fuzzer
- if *v == 0.0 {
- *v = 1.0
- }
- },
- func(v *Parameter, c fuzz.Continue) {
- if v == nil {
- return
- }
- c.Fuzz(&v.VendorExtensible)
- if c.RandBool() {
- // body param
- v.Description = c.RandString() + "x"
- v.Name = c.RandString() + "x"
- v.In = "body"
- c.Fuzz(&v.Description)
- c.Fuzz(&v.Required)
- v.Schema = &Schema{}
- c.Fuzz(&v.Schema)
- } else {
- c.Fuzz(&v.SimpleSchema)
- c.Fuzz(&v.CommonValidations)
- v.AllowEmptyValue = false
- v.Description = c.RandString() + "x"
- v.Name = c.RandString() + "x"
- switch c.Intn(4) {
- case 0:
- // Header param
- v.In = "header"
- case 1:
- // Form data param
- v.In = "formData"
- v.AllowEmptyValue = c.RandBool()
- case 2:
- // Query param
- v.In = "query"
- v.AllowEmptyValue = c.RandBool()
- case 3:
- // Path param
- v.In = "path"
- v.Required = true
- default:
- panic("unreachable")
- }
- // descendant Items of Parameter may not be refs
- cur := v.Items
- for cur != nil {
- cur.Ref = Ref{}
- cur = cur.Items
- }
- }
- },
- func(v *Schema, c fuzz.Continue) {
- if c.RandBool() {
- // file schema
- c.Fuzz(&v.Default)
- c.Fuzz(&v.Description)
- c.Fuzz(&v.Example)
- c.Fuzz(&v.ExternalDocs)
- c.Fuzz(&v.Format)
- c.Fuzz(&v.ReadOnly)
- c.Fuzz(&v.Required)
- c.Fuzz(&v.Title)
- v.Type = StringOrArray{"file"}
- } else {
- // normal schema
- c.Fuzz(&v.SchemaProps)
- c.Fuzz(&v.SwaggerSchemaProps)
- c.Fuzz(&v.VendorExtensible)
- // c.Fuzz(&v.ExtraProps)
- // ExtraProps will not roundtrip - gnostic throws out
- // unrecognized keys
- }
- // Not supported by official openapi v2 spec
- // and stripped by k8s apiserver
- v.ID = ""
- v.AnyOf = nil
- v.OneOf = nil
- v.Not = nil
- v.Nullable = false
- v.AdditionalItems = nil
- v.Schema = ""
- v.PatternProperties = nil
- v.Definitions = nil
- v.Dependencies = nil
- },
- }
- var SwaggerDiffOptions = []cmp.Option{
- // cmp.Diff panics on Ref since jsonreference.Ref uses unexported fields
- cmp.Comparer(func(a Ref, b Ref) bool {
- return a.String() == b.String()
- }),
- }
|