From ac3da0918036ee47d9e5eeea7b1f6f1594b88e22 Mon Sep 17 00:00:00 2001 From: Rob Galanakis Date: Wed, 19 Oct 2022 11:40:02 -0700 Subject: [PATCH] Support maps and slices in operations If an API does not use typed fields (meaningfuls tructs) we can at least throw it into requestBody, or a generic 'object' or 'array' schema. --- builders.go | 20 +++++++++- data_types.go | 4 ++ sashay.go | 14 ++++++- sashay_test.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/builders.go b/builders.go index 6e94b16..69e25af 100644 --- a/builders.go +++ b/builders.go @@ -41,10 +41,14 @@ func (b *baseBuilder) writeNotEmpty(indent int, format string, s string) { func (b *baseBuilder) writeDataType(indent int, f Field) { dataTypeDef, found := b.swagger.dataTypeDefFor(f) if !found { + ts := "(no type)" + if f.Type != nil { + ts = f.Type.String() + } panic(fmt.Sprintf("No dataTypeDef defined for kind %s, type %s. You should either change the type, "+ "or add a custom data type mapper. See Representing Custom Types at "+ "https://godoc.org/github.com/rgalanakis/sashay#hdr-Sashay_Detail__Representing_Custom_Types "+ - "for more information.", f.Kind.String(), f.Type.String())) + "for more information.", f.Kind.String(), ts)) } objectFields := ObjectFields{} dataTypeDef.DataTyper(f, objectFields) @@ -107,7 +111,7 @@ func (b *baseBuilder) writeRefSchema(indent int, f Field) { } else { b.writeLn(indent, "$ref: '%s'", schemaRefLink(f)) } - } else { + } else if f.Kind != reflect.Invalid { b.writeDataType(indent, f) } } @@ -252,6 +256,18 @@ func (b *pathBuilder) writePaths() { } func (b *pathBuilder) writeParams(indent int, f Field) { + if f.Kind != reflect.Struct { + b.base.writeLn(indent, "requestBody:") + b.base.writeLn(indent+1, "content:") + b.base.writeLn(indent+2, "*/*:") + b.base.writeLn(indent+3, "schema:") + if f.Kind == reflect.Map { + b.base.writeLn(indent+4, "type: object") + } else if f.Kind == reflect.Slice { + b.base.writeLn(indent+4, "type: array") + } + return + } writeParams := b.base.writeOnce(indent, "parameters:") for _, field := range enumerateStructFields(f) { tag := field.StructField.Tag diff --git a/data_types.go b/data_types.go index ece9b87..cd8a887 100644 --- a/data_types.go +++ b/data_types.go @@ -99,6 +99,10 @@ func BuiltinDataTyperFor(value interface{}, chained ...DataTyper) DataTyper { dt = SimpleDataTyper("number", "float") case time.Time, *time.Time: dt = SimpleDataTyper("string", "date-time") + case map[string]interface{}: + dt = SimpleDataTyper("object", "") + case []interface{}, []map[string]interface{}: + dt = SimpleDataTyper("array", "") } typers := []DataTyper{dt, defaultDataTyper} typers = append(typers, chained...) diff --git a/sashay.go b/sashay.go index 5cddab7..a01754b 100644 --- a/sashay.go +++ b/sashay.go @@ -56,7 +56,19 @@ func New(title, description, version string) *Sashay { // BuiltinDataTypeValues is a slice of values of all supported data types. // Use it for when you want to define custom DataTypers for the builtin types, // like if you are parsing validations. -var BuiltinDataTypeValues = []interface{}{int(0), int64(0), int32(0), "", false, float64(0), float32(0), time.Time{}} +var BuiltinDataTypeValues = []interface{}{ + int(0), + int64(0), + int32(0), + "", + false, + float64(0), + float32(0), + time.Time{}, + make(map[string]interface{}, 0), + make([]map[string]interface{}, 0), + make([]interface{}, 0), +} // Add registers a Swagger operations and all the associated types. func (sa *Sashay) Add(op Operation) Operation { diff --git a/sashay_test.go b/sashay_test.go index bd76c84..300ce51 100644 --- a/sashay_test.go +++ b/sashay_test.go @@ -1284,4 +1284,105 @@ paths: contents, err := ioutil.ReadFile(f.Name()) Expect(string(contents)).To(ContainSubstring("SwaggerGenAPI")) }) + + It("can handle maps", func() { + sw.Add(sashay.NewOperation( + "GET", + "/users", + "", + map[string]interface{}{}, + map[string]interface{}{}, + map[string]interface{}{}, + )) + Expect(sw.BuildYAML()).To(ContainSubstring(`paths: + /users: + get: + operationId: getUsers + requestBody: + content: + */*: + schema: + type: object + responses: + '200': + description: ok response + content: + application/json: + schema: + type: object + 'default': + description: error response + content: + application/json: + schema: + type: object`)) + }) + It("can handle interface slices", func() { + sw.Add(sashay.NewOperation( + "GET", + "/users", + "", + []interface{}{}, + []interface{}{}, + []interface{}{}, + )) + Expect(sw.BuildYAML()).To(ContainSubstring(`paths: + /users: + get: + operationId: getUsers + requestBody: + content: + */*: + schema: + type: array + responses: + '200': + description: ok response + content: + application/json: + schema: + type: array + items: + 'default': + description: error response + content: + application/json: + schema: + type: array + items:`)) + }) + It("can handle subtypes of maps", func() { + type submap map[string]interface{} + sw.DefineDataType(submap{}, sashay.SimpleDataTyper("object", "")) + sw.Add(sashay.NewOperation( + "GET", + "/users", + "", + submap{}, + submap{}, + submap{}, + )) + Expect(sw.BuildYAML()).To(ContainSubstring(`paths: + /users: + get: + operationId: getUsers + requestBody: + content: + */*: + schema: + type: object + responses: + '200': + description: ok response + content: + application/json: + schema: + type: object + 'default': + description: error response + content: + application/json: + schema: + type: object`)) + }) })