Path: blob/dev/pkg/input/formats/openapi/examples.go
2070 views
package openapi12import (3"fmt"4"maps"5"slices"67"github.com/getkin/kin-openapi/openapi3"8"github.com/pkg/errors"9)1011// From: https://github.com/danielgtaylor/apisprout/blob/master/example.go1213func getSchemaExample(schema *openapi3.Schema) (interface{}, bool) {14if schema.Example != nil {15return schema.Example, true16}1718if schema.Default != nil {19return schema.Default, true20}2122if len(schema.Enum) > 0 {23return schema.Enum[0], true24}25return nil, false26}2728// stringFormatExample returns an example string based on the given format.29// http://json-schema.org/latest/json-schema-validation.html#rfc.section.7.330func stringFormatExample(format string) string {31switch format {32case "date":33// https://tools.ietf.org/html/rfc333934return "2018-07-23"35case "date-time":36// This is the date/time of API Sprout's first commit! :-)37return "2018-07-23T22:58:00-07:00"38case "time":39return "22:58:00-07:00"40case "email":41return "[email protected]"42case "hostname":43// https://tools.ietf.org/html/rfc2606#page-244return "example.com"45case "ipv4":46// https://tools.ietf.org/html/rfc573747return "198.51.100.0"48case "ipv6":49// https://tools.ietf.org/html/rfc384950return "2001:0db8:85a3:0000:0000:8a2e:0370:7334"51case "uri":52return "https://tools.ietf.org/html/rfc3986"53case "uri-template":54// https://tools.ietf.org/html/rfc657055return "http://example.com/dictionary/{term:1}/{term}"56case "json-pointer":57// https://tools.ietf.org/html/rfc690158return "#/components/parameters/term"59case "regex":60// https://stackoverflow.com/q/3296050/16426861return "/^1?$|^(11+?)\\1+$/"62case "uuid":63// https://www.ietf.org/rfc/rfc4122.txt64return "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"65case "password":66return "********"67case "binary":68return "sagefuzzertest"69}70return ""71}7273// excludeFromMode will exclude a schema if the mode is request and the schema74// is read-only75func excludeFromMode(schema *openapi3.Schema) bool {76if schema == nil {77return true78}7980if schema.ReadOnly {81return true82}83return false84}8586// isRequired checks whether a key is actually required.87func isRequired(schema *openapi3.Schema, key string) bool {88return slices.Contains(schema.Required, key)89}9091type cachedSchema struct {92pending bool93out interface{}94}9596var (97// ErrRecursive is when a schema is impossible to represent because it infinitely recurses.98ErrRecursive = errors.New("Recursive schema")99100// ErrNoExample is sent when no example was found for an operation.101ErrNoExample = errors.New("No example found")102)103104func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedSchema) (out interface{}, err error) {105if ex, ok := getSchemaExample(schema); ok {106return ex, nil107}108109cached, ok := cache[schema]110if !ok {111cached = &cachedSchema{112pending: true,113}114cache[schema] = cached115} else if cached.pending {116return nil, ErrRecursive117} else {118return cached.out, nil119}120121defer func() {122cached.pending = false123cached.out = out124}()125126// Handle combining keywords127if len(schema.OneOf) > 0 {128var ex interface{}129var err error130131for _, candidate := range schema.OneOf {132ex, err = openAPIExample(candidate.Value, cache)133if err == nil {134break135}136}137return ex, err138}139if len(schema.AnyOf) > 0 {140var ex interface{}141var err error142143for _, candidate := range schema.AnyOf {144ex, err = openAPIExample(candidate.Value, cache)145if err == nil {146break147}148}149return ex, err150}151if len(schema.AllOf) > 0 {152example := map[string]interface{}{}153154for _, allOf := range schema.AllOf {155candidate, err := openAPIExample(allOf.Value, cache)156if err != nil {157return nil, err158}159160value, ok := candidate.(map[string]interface{})161if !ok {162return nil, ErrNoExample163}164165maps.Copy(example, value)166}167return example, nil168}169170switch {171case schema.Type.Is("boolean"):172return true, nil173case schema.Type.Is("number"), schema.Type.Is("integer"):174value := 0.0175176if schema.Min != nil && *schema.Min > value {177value = *schema.Min178if schema.ExclusiveMin {179if schema.Max != nil {180// Make the value half way.181value = (*schema.Min + *schema.Max) / 2.0182} else {183value++184}185}186}187188if schema.Max != nil && *schema.Max < value {189value = *schema.Max190if schema.ExclusiveMax {191if schema.Min != nil {192// Make the value half way.193value = (*schema.Min + *schema.Max) / 2.0194} else {195value--196}197}198}199200if schema.MultipleOf != nil && int(value)%int(*schema.MultipleOf) != 0 {201value += float64(int(*schema.MultipleOf) - (int(value) % int(*schema.MultipleOf)))202}203204if schema.Type.Is("integer") {205return int(value), nil206}207return value, nil208case schema.Type.Is("string"):209if ex := stringFormatExample(schema.Format); ex != "" {210return ex, nil211}212example := "string"213214for schema.MinLength > uint64(len(example)) {215example += example216}217218if schema.MaxLength != nil && *schema.MaxLength < uint64(len(example)) {219example = example[:*schema.MaxLength]220}221return example, nil222case schema.Type.Is("array"), schema.Items != nil:223example := []interface{}{}224225if schema.Items != nil && schema.Items.Value != nil {226ex, err := openAPIExample(schema.Items.Value, cache)227if err != nil {228return nil, fmt.Errorf("can't get example for array item: %+v", err)229}230231example = append(example, ex)232233for uint64(len(example)) < schema.MinItems {234example = append(example, ex)235}236}237return example, nil238case schema.Type.Is("object"), len(schema.Properties) > 0:239example := map[string]interface{}{}240241for k, v := range schema.Properties {242if excludeFromMode(v.Value) {243continue244}245246ex, err := openAPIExample(v.Value, cache)247if err == ErrRecursive {248if isRequired(schema, k) {249return nil, fmt.Errorf("can't get example for '%s': %+v", k, err)250}251} else if err != nil {252return nil, fmt.Errorf("can't get example for '%s': %+v", k, err)253} else {254example[k] = ex255}256}257258if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil {259addl := schema.AdditionalProperties.Schema.Value260261if !excludeFromMode(addl) {262ex, err := openAPIExample(addl, cache)263if err == ErrRecursive {264// We just won't add this if it's recursive.265} else if err != nil {266return nil, fmt.Errorf("can't get example for additional properties: %+v", err)267} else {268example["additionalPropertyName"] = ex269}270}271}272return example, nil273}274return nil, ErrNoExample275}276277// generateExampleFromSchema creates an example structure from an OpenAPI 3 schema278// object, which is an extended subset of JSON Schema.279//280// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#schemaObject281func generateExampleFromSchema(schema *openapi3.Schema) (interface{}, error) {282return openAPIExample(schema, make(map[*openapi3.Schema]*cachedSchema)) // TODO: Use caching283}284285func generateEmptySchemaValue(contentType string) *openapi3.Schema {286schema := &openapi3.Schema{}287objectType := &openapi3.Types{"object"}288stringType := &openapi3.Types{"string"}289290switch contentType {291case "application/json":292schema.Type = objectType293schema.Properties = make(map[string]*openapi3.SchemaRef)294case "application/xml":295schema.Type = stringType296schema.Format = "xml"297schema.Example = "<?xml version=\"1.0\"?><root/>"298case "text/plain":299schema.Type = stringType300case "application/x-www-form-urlencoded":301schema.Type = objectType302schema.Properties = make(map[string]*openapi3.SchemaRef)303case "multipart/form-data":304schema.Type = objectType305schema.Properties = make(map[string]*openapi3.SchemaRef)306case "application/octet-stream":307default:308schema.Type = stringType309schema.Format = "binary"310}311312return schema313}314315316