From 9fbd2640f808500433cf45cdfbc446cb1825ece7 Mon Sep 17 00:00:00 2001 From: Fabian Martin Date: Sat, 16 Jul 2022 10:20:34 +0200 Subject: [PATCH 1/7] feat: add support for nested generics nested generics support and related tests added --- generics.go | 52 ++-- generics_test.go | 327 ++++++++++++++++++++++++ operation.go | 6 +- testdata/generics_arrays/main.go | 4 +- testdata/generics_nested/api/api.go | 20 ++ testdata/generics_nested/main.go | 17 ++ testdata/generics_nested/web/handler.go | 50 ++++ 7 files changed, 455 insertions(+), 21 deletions(-) create mode 100644 testdata/generics_nested/api/api.go create mode 100644 testdata/generics_nested/main.go create mode 100644 testdata/generics_nested/web/handler.go diff --git a/generics.go b/generics.go index c0ec97e80..807705c55 100644 --- a/generics.go +++ b/generics.go @@ -27,24 +27,19 @@ func typeSpecFullName(typeSpecDef *TypeSpecDef) string { } func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string) *TypeSpecDef { - genericParams := strings.Split(strings.TrimRight(fullGenericForm, "]"), "[") - if len(genericParams) == 1 { + genericTypeName, genericParams := splitStructName(fullGenericForm) + if genericParams == nil { return nil } - genericParams = strings.Split(genericParams[1], ",") - for i, p := range genericParams { - genericParams[i] = strings.TrimSpace(p) - } genericParamTypeDefs := map[string]*TypeSpecDef{} - if len(genericParams) != len(original.TypeSpec.TypeParams.List) { return nil } for i, genericParam := range genericParams { - tdef, ok := pkgDefs.uniqueDefinitions[genericParam] - if !ok { + tdef := pkgDefs.FindTypeSpec(genericParam, original.File, false) + if tdef == nil { return nil } @@ -66,16 +61,22 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful Obj: original.TypeSpec.Name.Obj, } - genNameParts := strings.Split(fullGenericForm, "[") - if strings.Contains(genNameParts[0], ".") { - genNameParts[0] = strings.Split(genNameParts[0], ".")[1] + if strings.Contains(genericTypeName, ".") { + genericTypeName = strings.Split(genericTypeName, ".")[1] } - ident.Name = genNameParts[0] + "-" + strings.Replace(strings.Join(genericParams, "-"), ".", "_", -1) - ident.Name = strings.Replace(strings.Replace(ident.Name, "\t", "", -1), " ", "", -1) + var typeName = []string{TypeDocName(genericTypeName, parametrizedTypeSpec.TypeSpec)} - parametrizedTypeSpec.TypeSpec.Name = ident + for _, def := range original.TypeSpec.TypeParams.List { + if specDef, ok := genericParamTypeDefs[def.Names[0].Name]; ok { + typeName = append(typeName, strings.Replace(TypeDocName(specDef.FullName(), specDef.TypeSpec), "-", "_", -1)) + } + } + ident.Name = strings.Join(typeName, "-") + ident.Name = strings.Replace(ident.Name, ".", "_", -1) + + parametrizedTypeSpec.TypeSpec.Name = ident origStructType := original.TypeSpec.Type.(*ast.StructType) newStructTypeDef := &ast.StructType{ @@ -101,10 +102,29 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful } parametrizedTypeSpec.TypeSpec.Type = newStructTypeDef - return parametrizedTypeSpec } +// splitStructName splits a generic struct name in his parts +func splitStructName(fullGenericForm string) (string, []string) { + // split only at the first '[' and remove the last ']' + genericParams := strings.SplitN(strings.TrimSpace(fullGenericForm)[:len(fullGenericForm)-1], "[", 2) + if len(genericParams) == 1 { + return "", nil + } + + // generic type name + genericTypeName := genericParams[0] + + // generic params + genericParams = strings.Split(genericParams[1], ",") + for i, p := range genericParams { + genericParams[i] = strings.TrimSpace(p) + } + + return genericTypeName, genericParams +} + func resolveType(expr ast.Expr, field *ast.Field, genericParamTypeDefs map[string]*TypeSpecDef) ast.Expr { if asIdent, ok := expr.(*ast.Ident); ok { if genTypeSpec, ok := genericParamTypeDefs[asIdent.Name]; ok { diff --git a/generics_test.go b/generics_test.go index 43837063e..cca56f5bc 100644 --- a/generics_test.go +++ b/generics_test.go @@ -375,3 +375,330 @@ func TestParseGenericsArrays(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expected, string(b)) } + +func TestParseGenericsNested(t *testing.T) { + t.Parallel() + + expected := `{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts": { + "get": { + "description": "Get All of the Posts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Posts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericListResponse-web_Post" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/web.GenericListResponse-web_GenericListResponse_web_Post" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_GenericListResponse_web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericListResponseMulti-web_GenericListResponse_web_Post-web_Post" + } + } + } + } + } + }, + "definitions": { + "web.GenericListResponse-web_GenericListResponse_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericListResponse-web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericListResponseMulti-web_GenericListResponse_web_Post-web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericListResponseMulti-web_Post-web_GenericListResponse_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + } + } +}` + + searchDir := "testdata/generics_nested" + p := New() + err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + assert.NoError(t, err) + b, err := json.MarshalIndent(p.swagger, "", " ") + assert.NoError(t, err) + assert.Equal(t, expected, string(b)) +} diff --git a/operation.go b/operation.go index 29e6bf429..9a1a70774 100644 --- a/operation.go +++ b/operation.go @@ -818,7 +818,7 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { return nil, fmt.Errorf("type spec not found") } -var responsePattern = regexp.MustCompile(`^([\w,]+)\s+([\w{}]+)\s+([\w\-.\\{}=,\[\]]+)[^"]*(.*)?`) +var responsePattern = regexp.MustCompile(`^([\w,]+)\s+([\w{}]+)\s+([\w\-.\\{}=,\[\s\]]+)\s*(".*)?`) // ResponseType{data1=Type1,data2=Type2}. var combinedPattern = regexp.MustCompile(`^([\w\-./\[\]]+){(.*)}$`) @@ -978,7 +978,7 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as description := strings.Trim(matches[4], "\"") - schema, err := operation.parseAPIObjectSchema(commentLine, strings.Trim(matches[2], "{}"), matches[3], astFile) + schema, err := operation.parseAPIObjectSchema(commentLine, strings.Trim(matches[2], "{}"), strings.TrimSpace(matches[3]), astFile) if err != nil { return err } @@ -1043,7 +1043,7 @@ func (operation *Operation) ParseResponseHeaderComment(commentLine string, _ *as header := newHeaderSpec(strings.Trim(matches[2], "{}"), strings.Trim(matches[4], "\"")) - headerKey := matches[3] + headerKey := strings.TrimSpace(matches[3]) if strings.EqualFold(matches[1], "all") { if operation.Responses.Default != nil { diff --git a/testdata/generics_arrays/main.go b/testdata/generics_arrays/main.go index cff47d013..8d79868fc 100644 --- a/testdata/generics_arrays/main.go +++ b/testdata/generics_arrays/main.go @@ -3,7 +3,7 @@ package main import ( "net/http" - "github.com/swaggo/swag/testdata/generics_basic/api" + "github.com/swaggo/swag/testdata/generics_arrays/api" ) // @title Swagger Example API @@ -12,6 +12,6 @@ import ( // @host localhost:4000 // @basePath /api func main() { - http.HandleFunc("/posts/", api.GetPost) + http.HandleFunc("/posts/", api.GetPosts) http.ListenAndServe(":8080", nil) } diff --git a/testdata/generics_nested/api/api.go b/testdata/generics_nested/api/api.go new file mode 100644 index 000000000..71a2ff50d --- /dev/null +++ b/testdata/generics_nested/api/api.go @@ -0,0 +1,20 @@ +package api + +import ( + "net/http" + + "github.com/swaggo/swag/testdata/generics_nested/web" +) + +// @Summary List Posts +// @Description Get All of the Posts +// @Accept json +// @Produce json +// @Success 200 {object} web.GenericListResponse[web.Post] +// @Success 201 {object} web.GenericListResponse[web.GenericListResponse[web.Post]] +// @Success 202 {object} web.GenericListResponseMulti[web.Post, web.GenericListResponse[web.Post]] +// @Success 222 {object} web.GenericListResponseMulti[web.GenericListResponse[web.Post], web.Post] +// @Router /posts [get] +func GetPosts(w http.ResponseWriter, r *http.Request) { + _ = web.GenericListResponse[web.Post]{} +} diff --git a/testdata/generics_nested/main.go b/testdata/generics_nested/main.go new file mode 100644 index 000000000..4817e03cb --- /dev/null +++ b/testdata/generics_nested/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "net/http" + + "github.com/swaggo/swag/testdata/generics_nested/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @host localhost:4000 +// @basePath /api +func main() { + http.HandleFunc("/posts/", api.GetPosts) + http.ListenAndServe(":8080", nil) +} diff --git a/testdata/generics_nested/web/handler.go b/testdata/generics_nested/web/handler.go new file mode 100644 index 000000000..a92ea8bad --- /dev/null +++ b/testdata/generics_nested/web/handler.go @@ -0,0 +1,50 @@ +package web + +import ( + "time" +) + +// GenericListResponse[T] +// @Description Some Generic List Response +type GenericListResponse[T any] struct { + // Items from the list response + Items []T + // Status of some other stuff + Status string +} + +// GenericListResponseMulti[T, X] +// @Description this contains a few things +type GenericListResponseMulti[T any, X any] struct { + // ItemsOne is the first thing + ItemOne T + // ItemsTwo is the second thing + ItemsTwo []X + + // Status of the things + Status string +} + +type Post struct { + ID int `json:"id" example:"1" format:"int64"` + // Post name + Name string `json:"name" example:"poti"` + // Post data + Data struct { + // Post tag + Tag []string `json:"name"` + } `json:"data"` +} + +// APIError +// @Description API error +// @Description with information about it +// Other some summary +type APIError struct { + // Error an Api error + Error string // Error this is Line comment + // Error `number` tick comment + ErrorNo int64 + ErrorCtx string // Error `context` tick comment + CreatedAt time.Time // Error time +} From 42eea37c7be0858a3b40421e9de03deaa4bb7cca Mon Sep 17 00:00:00 2001 From: Fabian Martin Date: Mon, 18 Jul 2022 12:37:19 +0200 Subject: [PATCH 2/7] fix: Multiple usage of same generic generate different definition paths cache generic definitions by full name --- generics.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/generics.go b/generics.go index 807705c55..9100941bd 100644 --- a/generics.go +++ b/generics.go @@ -8,6 +8,8 @@ import ( "strings" ) +var genericsDefinitions = map[string]*TypeSpecDef{} + func typeSpecFullName(typeSpecDef *TypeSpecDef) string { fullName := typeSpecDef.FullName() @@ -27,6 +29,10 @@ func typeSpecFullName(typeSpecDef *TypeSpecDef) string { } func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string) *TypeSpecDef { + if spec, ok := genericsDefinitions[fullGenericForm]; ok { + return spec + } + genericTypeName, genericParams := splitStructName(fullGenericForm) if genericParams == nil { return nil @@ -102,6 +108,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful } parametrizedTypeSpec.TypeSpec.Type = newStructTypeDef + genericsDefinitions[fullGenericForm] = parametrizedTypeSpec return parametrizedTypeSpec } From e6065e6c8435b02b82dd0283f1e3266d7bd31da9 Mon Sep 17 00:00:00 2001 From: Fabian Martin Date: Sun, 31 Jul 2022 20:47:55 +0200 Subject: [PATCH 3/7] feat: add support for generic array parameter - allow usage of arrays as parameter definitions - tests extended and new body param added to tests --- generics.go | 74 +- generics_test.go | 1985 ++++++++++++++++++++--- operation.go | 2 +- testdata/generics_arrays/api/api.go | 29 +- testdata/generics_arrays/main.go | 2 + testdata/generics_arrays/web/handler.go | 9 + testdata/generics_basic/api/api.go | 30 +- testdata/generics_basic/main.go | 2 + testdata/generics_basic/web/handler.go | 9 + testdata/generics_nested/api/api.go | 29 +- testdata/generics_nested/web/handler.go | 33 +- 11 files changed, 1992 insertions(+), 212 deletions(-) diff --git a/generics.go b/generics.go index 9100941bd..eb2ad2f9e 100644 --- a/generics.go +++ b/generics.go @@ -4,11 +4,17 @@ package swag import ( + "fmt" "go/ast" "strings" ) -var genericsDefinitions = map[string]*TypeSpecDef{} +var genericsDefinitions = map[*TypeSpecDef]map[string]*TypeSpecDef{} + +type genericTypeSpec struct { + ArrayDepth int + TypeSpec *TypeSpecDef +} func typeSpecFullName(typeSpecDef *TypeSpecDef) string { fullName := typeSpecDef.FullName() @@ -29,7 +35,7 @@ func typeSpecFullName(typeSpecDef *TypeSpecDef) string { } func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string) *TypeSpecDef { - if spec, ok := genericsDefinitions[fullGenericForm]; ok { + if spec, ok := genericsDefinitions[original][fullGenericForm]; ok { return spec } @@ -38,18 +44,32 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful return nil } - genericParamTypeDefs := map[string]*TypeSpecDef{} + genericParamTypeDefs := map[string]*genericTypeSpec{} if len(genericParams) != len(original.TypeSpec.TypeParams.List) { return nil } for i, genericParam := range genericParams { - tdef := pkgDefs.FindTypeSpec(genericParam, original.File, false) + arrayDepth := 0 + for { + var isArray = len(genericParam) > 2 && genericParam[:2] == "[]" + if isArray { + genericParam = genericParam[2:] + arrayDepth++ + } else { + break + } + } + + tdef := pkgDefs.FindTypeSpec(genericParam, original.File, true) if tdef == nil { return nil } - genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = tdef + genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ + ArrayDepth: arrayDepth, + TypeSpec: tdef, + } } parametrizedTypeSpec := &TypeSpecDef{ @@ -75,7 +95,14 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful for _, def := range original.TypeSpec.TypeParams.List { if specDef, ok := genericParamTypeDefs[def.Names[0].Name]; ok { - typeName = append(typeName, strings.Replace(TypeDocName(specDef.FullName(), specDef.TypeSpec), "-", "_", -1)) + var prefix = "" + if specDef.ArrayDepth > 0 { + prefix = "array_" + if specDef.ArrayDepth > 1 { + prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth) + } + } + typeName = append(typeName, prefix+strings.Replace(TypeDocName(specDef.TypeSpec.FullName(), specDef.TypeSpec.TypeSpec), "-", "_", -1)) } } @@ -108,7 +135,10 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful } parametrizedTypeSpec.TypeSpec.Type = newStructTypeDef - genericsDefinitions[fullGenericForm] = parametrizedTypeSpec + if genericsDefinitions[original] == nil { + genericsDefinitions[original] = map[string]*TypeSpecDef{} + } + genericsDefinitions[original][fullGenericForm] = parametrizedTypeSpec return parametrizedTypeSpec } @@ -124,18 +154,38 @@ func splitStructName(fullGenericForm string) (string, []string) { genericTypeName := genericParams[0] // generic params - genericParams = strings.Split(genericParams[1], ",") - for i, p := range genericParams { - genericParams[i] = strings.TrimSpace(p) + insideBrackets := 0 + lastParam := "" + params := strings.Split(genericParams[1], ",") + genericParams = []string{} + for _, p := range params { + numOpened := strings.Count(p, "[") + numClosed := strings.Count(p, "]") + if numOpened == numClosed && insideBrackets == 0 { + genericParams = append(genericParams, strings.TrimSpace(p)) + continue + } + + insideBrackets += numOpened - numClosed + lastParam += p + "," + + if insideBrackets == 0 { + genericParams = append(genericParams, strings.TrimSpace(strings.TrimRight(lastParam, ","))) + lastParam = "" + } } return genericTypeName, genericParams } -func resolveType(expr ast.Expr, field *ast.Field, genericParamTypeDefs map[string]*TypeSpecDef) ast.Expr { +func resolveType(expr ast.Expr, field *ast.Field, genericParamTypeDefs map[string]*genericTypeSpec) ast.Expr { if asIdent, ok := expr.(*ast.Ident); ok { if genTypeSpec, ok := genericParamTypeDefs[asIdent.Name]; ok { - return genTypeSpec.TypeSpec.Type + if genTypeSpec.ArrayDepth > 0 { + genTypeSpec.ArrayDepth-- + return &ast.ArrayType{Elt: resolveType(expr, field, genericParamTypeDefs)} + } + return genTypeSpec.TypeSpec.TypeSpec.Type } } else if asArray, ok := expr.(*ast.ArrayType); ok { return &ast.ArrayType{Elt: resolveType(asArray.Elt, field, genericParamTypeDefs), Len: asArray.Len, Lbrack: asArray.Lbrack} diff --git a/generics_test.go b/generics_test.go index cca56f5bc..d1ab0e756 100644 --- a/generics_test.go +++ b/generics_test.go @@ -24,8 +24,82 @@ func TestParseGenericsBasic(t *testing.T) { "host": "localhost:4000", "basePath": "/api", "paths": { - "/posts/{post_id}": { - "get": { + "/posts-multi/": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericBodyMulti-web_Post-web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericResponse-web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericResponseMulti-web_Post-web_Post" + } + } + } + } + }, + "/posts-multis/": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericBodyMulti-array_web_Post-array2_web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericResponse-array_web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericResponseMulti-array_web_Post-array2_web_Post" + } + } + } + } + }, + "/posts/": { + "post": { "description": "get string by ID", "consumes": [ "application/json" @@ -36,12 +110,13 @@ func TestParseGenericsBasic(t *testing.T) { "summary": "Add a new pet to the store", "parameters": [ { - "type": "integer", - "format": "int64", "description": "Some ID", - "name": "post_id", - "in": "path", - "required": true + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericBody-web_Post" + } } ], "responses": { @@ -96,7 +171,7 @@ func TestParseGenericsBasic(t *testing.T) { } } }, - "web.GenericResponse-web_Post": { + "web.GenericBody-web_Post": { "type": "object", "properties": { "data": { @@ -126,13 +201,80 @@ func TestParseGenericsBasic(t *testing.T) { "example": "poti" } } + } + } + }, + "web.GenericBodyMulti-array_web_Post-array2_web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } }, - "status": { - "type": "string" + "meta": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } } } }, - "web.GenericResponseMulti-web_Post-web_Post": { + "web.GenericBodyMulti-web_Post-web_Post": { "type": "object", "properties": { "data": { @@ -190,71 +332,13 @@ func TestParseGenericsBasic(t *testing.T) { "example": "poti" } } - }, - "status": { - "type": "string" - } - } - } - } -}` - - searchDir := "testdata/generics_basic" - p := New() - err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) - assert.NoError(t, err) - b, err := json.MarshalIndent(p.swagger, "", " ") - assert.NoError(t, err) - assert.Equal(t, expected, string(b)) -} - -func TestParseGenericsArrays(t *testing.T) { - t.Parallel() - - expected := `{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "contact": {}, - "version": "1.0" - }, - "host": "localhost:4000", - "basePath": "/api", - "paths": { - "/posts": { - "get": { - "description": "Get All of the Posts", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "List Posts", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericListResponse-web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_Post" - } - } } } - } - }, - "definitions": { - "web.GenericListResponse-web_Post": { + }, + "web.GenericResponse-array_web_Post": { "type": "object", "properties": { - "items": { - "description": "Items from the list response", + "data": { "type": "array", "items": { "type": "object", @@ -286,16 +370,50 @@ func TestParseGenericsArrays(t *testing.T) { } }, "status": { - "description": "Status of some other stuff", "type": "string" } } }, - "web.GenericListResponseMulti-web_Post-web_Post": { + "web.GenericResponse-web_Post": { "type": "object", "properties": { - "itemsOne": { - "description": "ItemsOne is the first thing", + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "status": { + "type": "string" + } + } + }, + "web.GenericResponseMulti-array_web_Post-array2_web_Post": { + "type": "object", + "properties": { + "data": { "type": "array", "items": { "type": "object", @@ -326,48 +444,113 @@ func TestParseGenericsArrays(t *testing.T) { } } }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", + "meta": { "type": "array", "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } } } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" } } } }, "status": { - "description": "Status of the things", "type": "string" } } - } - } + }, + "web.GenericResponseMulti-web_Post-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "meta": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "status": { + "type": "string" + } + } + } + } }` - searchDir := "testdata/generics_arrays" + searchDir := "testdata/generics_basic" p := New() err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) assert.NoError(t, err) @@ -376,7 +559,7 @@ func TestParseGenericsArrays(t *testing.T) { assert.Equal(t, expected, string(b)) } -func TestParseGenericsNested(t *testing.T) { +func TestParseGenericsArrays(t *testing.T) { t.Parallel() expected := `{ @@ -400,6 +583,17 @@ func TestParseGenericsNested(t *testing.T) { "application/json" ], "summary": "List Posts", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericListBody-web_Post" + } + } + ], "responses": { "200": { "description": "OK", @@ -407,88 +601,1326 @@ func TestParseGenericsNested(t *testing.T) { "$ref": "#/definitions/web.GenericListResponse-web_Post" } }, - "201": { - "description": "Created", + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_Post" + } + } + } + } + }, + "/posts-multi": { + "get": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericListBodyMulti-web_Post-web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/web.GenericListResponse-web_GenericListResponse_web_Post" + "$ref": "#/definitions/web.GenericListResponse-web_Post" } }, - "202": { - "description": "Accepted", + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_Post" + } + } + } + } + }, + "/posts-multis": { + "get": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericListBodyMulti-web_Post-array_web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_GenericListResponse_web_Post" + "$ref": "#/definitions/web.GenericListResponse-array_web_Post" } }, "222": { "description": "", "schema": { - "$ref": "#/definitions/web.GenericListResponseMulti-web_GenericListResponse_web_Post-web_Post" + "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-array_web_Post" + } + } + } + } + } + }, + "definitions": { + "web.GenericListBody-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "web.GenericListBodyMulti-web_Post-array_web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "meta": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "web.GenericListBodyMulti-web_Post-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "meta": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "web.GenericListResponse-array_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericListResponse-web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericListResponseMulti-web_Post-array_web_Post": { + "type": "object", + "properties": { + "itemsOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericListResponseMulti-web_Post-web_Post": { + "type": "object", + "properties": { + "itemsOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + } + } +}` + + searchDir := "testdata/generics_arrays" + p := New() + err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + assert.NoError(t, err) + b, err := json.MarshalIndent(p.swagger, "", " ") + assert.NoError(t, err) + assert.Equal(t, expected, string(b)) +} + +func TestParseGenericsNested(t *testing.T) { + t.Parallel() + expected := `{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts": { + "get": { + "description": "Get All of the Posts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Posts", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-web_Post" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-web_GenericInnerType_web_Post" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_Post" + } + }, + "203": { + "description": "Non-Authoritative Information", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_GenericInnerType_web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_web_Post-web_Post" + } + } + } + } + }, + "/posts-multis/": { + "get": { + "description": "Get All of the Posts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Posts", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_array_web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-array_web_Post" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_web_Post" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_array_web_Post" + } + }, + "203": { + "description": "Non-Authoritative Information", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-array_web_Post-web_GenericInnerMultiType_array_web_Post_web_Post" + } + }, + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-array_web_Post-array_web_GenericInnerMultiType_array_web_Post_web_Post" + } + }, + "205": { + "description": "Reset Content", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_array_web_GenericInnerType_array2_web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_array_web_Post-array_web_Post" + } + } + } + } + } + }, + "definitions": { + "web.GenericNestedBody-web_GenericInnerType_array_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedBody-web_GenericInnerType_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-array_web_GenericInnerType_array_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-array_web_GenericInnerType_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-array_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-web_GenericInnerType_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-array_web_Post-array_web_GenericInnerMultiType_array_web_Post_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-array_web_Post-web_GenericInnerMultiType_array_web_Post_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-web_GenericInnerType_array_web_Post-array_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } } } - } - } - } - }, - "definitions": { - "web.GenericListResponse-web_GenericListResponse_web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", "type": "array", "items": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", "type": "object", "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, "name": { - "description": "Post name", - "type": "string", - "example": "poti" + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } } } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" } } } }, "status": { - "description": "Status of some other stuff", + "description": "Status of the things", "type": "string" } } }, - "web.GenericListResponse-web_Post": { + "web.GenericNestedResponseMulti-web_GenericInnerType_web_Post-web_Post": { "type": "object", "properties": { - "items": { - "description": "Items from the list response", + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", "type": "array", "items": { "type": "object", @@ -520,22 +1952,51 @@ func TestParseGenericsNested(t *testing.T) { } }, "status": { - "description": "Status of some other stuff", + "description": "Status of the things", "type": "string" } } }, - "web.GenericListResponseMulti-web_GenericListResponse_web_Post-web_Post": { + "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_array_web_GenericInnerType_array2_web_Post": { "type": "object", "properties": { "itemOne": { "description": "ItemsOne is the first thing", "type": "object", "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", "type": "object", "properties": { "data": { @@ -562,11 +2023,92 @@ func TestParseGenericsNested(t *testing.T) { "example": "poti" } } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + } + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_GenericInnerType_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } } }, - "status": { - "description": "Status of some other stuff", - "type": "string" + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" } } }, @@ -576,28 +2118,72 @@ func TestParseGenericsNested(t *testing.T) { "items": { "type": "object", "properties": { - "data": { - "description": "Post data", + "itemOne": { + "description": "ItemsOne is the first thing", "type": "object", "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, "name": { - "description": "Post tag", - "type": "array", + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { "items": { - "type": "string" + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } } } } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" } } } @@ -608,7 +2194,7 @@ func TestParseGenericsNested(t *testing.T) { } } }, - "web.GenericListResponseMulti-web_Post-web_GenericListResponse_web_Post": { + "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_Post": { "type": "object", "properties": { "itemOne": { @@ -646,8 +2232,37 @@ func TestParseGenericsNested(t *testing.T) { "items": { "type": "object", "properties": { - "items": { - "description": "Items from the list response", + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", "type": "array", "items": { "type": "object", @@ -677,10 +2292,6 @@ func TestParseGenericsNested(t *testing.T) { } } } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" } } } diff --git a/operation.go b/operation.go index 9a1a70774..71c4e47ca 100644 --- a/operation.go +++ b/operation.go @@ -213,7 +213,7 @@ func (operation *Operation) ParseMetadata(attribute, lowerAttribute, lineRemaind return nil } -var paramPattern = regexp.MustCompile(`(\S+)\s+(\w+)\s+([\S.]+)\s+(\w+)\s+"([^"]+)"`) +var paramPattern = regexp.MustCompile(`(\S+)\s+(\w+)\s+([\S. ]+?)\s+(\w+)\s+"([^"]+)"`) func findInSlice(arr []string, target string) bool { for _, str := range arr { diff --git a/testdata/generics_arrays/api/api.go b/testdata/generics_arrays/api/api.go index 20cca94f3..42a80b993 100644 --- a/testdata/generics_arrays/api/api.go +++ b/testdata/generics_arrays/api/api.go @@ -10,9 +10,36 @@ import ( // @Description Get All of the Posts // @Accept json // @Produce json +// @Param data body web.GenericListBody[web.Post] true "Some ID" // @Success 200 {object} web.GenericListResponse[web.Post] // @Success 222 {object} web.GenericListResponseMulti[web.Post, web.Post] // @Router /posts [get] func GetPosts(w http.ResponseWriter, r *http.Request) { - _ = web.GenericListResponse[web.Post]{} + _ = web.GenericListResponseMulti[web.Post, web.Post]{} +} + +// @Summary Add new pets to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Param data body web.GenericListBodyMulti[web.Post, web.Post] true "Some ID" +// @Success 200 {object} web.GenericListResponse[web.Post] +// @Success 222 {object} web.GenericListResponseMulti[web.Post, web.Post] +// @Router /posts-multi [get] +func GetPostMulti(w http.ResponseWriter, r *http.Request) { + //write your code + _ = web.GenericListResponseMulti[web.Post, web.Post]{} +} + +// @Summary Add new pets to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Param data body web.GenericListBodyMulti[web.Post, []web.Post] true "Some ID" +// @Success 200 {object} web.GenericListResponse[[]web.Post] +// @Success 222 {object} web.GenericListResponseMulti[web.Post, []web.Post] +// @Router /posts-multis [get] +func GetPostArray(w http.ResponseWriter, r *http.Request) { + //write your code + _ = web.GenericListResponseMulti[web.Post, []web.Post]{} } diff --git a/testdata/generics_arrays/main.go b/testdata/generics_arrays/main.go index 8d79868fc..1e5423ecd 100644 --- a/testdata/generics_arrays/main.go +++ b/testdata/generics_arrays/main.go @@ -13,5 +13,7 @@ import ( // @basePath /api func main() { http.HandleFunc("/posts/", api.GetPosts) + http.HandleFunc("/posts-multi/", api.GetPostMulti) + http.HandleFunc("/posts-multis/", api.GetPostArray) http.ListenAndServe(":8080", nil) } diff --git a/testdata/generics_arrays/web/handler.go b/testdata/generics_arrays/web/handler.go index 88bd8b035..9c18316f4 100644 --- a/testdata/generics_arrays/web/handler.go +++ b/testdata/generics_arrays/web/handler.go @@ -4,6 +4,15 @@ import ( "time" ) +type GenericListBody[T any] struct { + Data []T +} + +type GenericListBodyMulti[T any, X any] struct { + Data []T + Meta []X +} + // GenericListResponse[T] // @Description Some Generic List Response type GenericListResponse[T any] struct { diff --git a/testdata/generics_basic/api/api.go b/testdata/generics_basic/api/api.go index 3881f2230..c27fc2729 100644 --- a/testdata/generics_basic/api/api.go +++ b/testdata/generics_basic/api/api.go @@ -10,13 +10,39 @@ import ( // @Description get string by ID // @Accept json // @Produce json -// @Param post_id path int true "Some ID" Format(int64) +// @Param data body web.GenericBody[web.Post] true "Some ID" // @Success 200 {object} web.GenericResponse[web.Post] // @Success 222 {object} web.GenericResponseMulti[web.Post, web.Post] // @Failure 400 {object} web.APIError "We need ID!!" // @Failure 404 {object} web.APIError "Can not find ID" -// @Router /posts/{post_id} [get] +// @Router /posts/ [post] func GetPost(w http.ResponseWriter, r *http.Request) { //write your code _ = web.GenericResponse[web.Post]{} } + +// @Summary Add new pets to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Param data body web.GenericBodyMulti[web.Post, web.Post] true "Some ID" +// @Success 200 {object} web.GenericResponse[web.Post] +// @Success 222 {object} web.GenericResponseMulti[web.Post, web.Post] +// @Router /posts-multi/ [post] +func GetPostMulti(w http.ResponseWriter, r *http.Request) { + //write your code + _ = web.GenericResponse[web.Post]{} +} + +// @Summary Add new pets to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Param data body web.GenericBodyMulti[[]web.Post, [][]web.Post] true "Some ID" +// @Success 200 {object} web.GenericResponse[[]web.Post] +// @Success 222 {object} web.GenericResponseMulti[[]web.Post, [][]web.Post] +// @Router /posts-multis/ [post] +func GetPostArray(w http.ResponseWriter, r *http.Request) { + //write your code + _ = web.GenericResponse[web.Post]{} +} diff --git a/testdata/generics_basic/main.go b/testdata/generics_basic/main.go index cff47d013..99845ab89 100644 --- a/testdata/generics_basic/main.go +++ b/testdata/generics_basic/main.go @@ -13,5 +13,7 @@ import ( // @basePath /api func main() { http.HandleFunc("/posts/", api.GetPost) + http.HandleFunc("/posts-multi/", api.GetPostMulti) + http.HandleFunc("/posts-multis/", api.GetPostArray) http.ListenAndServe(":8080", nil) } diff --git a/testdata/generics_basic/web/handler.go b/testdata/generics_basic/web/handler.go index 011910708..958ffffec 100644 --- a/testdata/generics_basic/web/handler.go +++ b/testdata/generics_basic/web/handler.go @@ -4,6 +4,15 @@ import ( "time" ) +type GenericBody[T any] struct { + Data T +} + +type GenericBodyMulti[T any, X any] struct { + Data T + Meta X +} + type GenericResponse[T any] struct { Data T diff --git a/testdata/generics_nested/api/api.go b/testdata/generics_nested/api/api.go index 71a2ff50d..8d7f967d0 100644 --- a/testdata/generics_nested/api/api.go +++ b/testdata/generics_nested/api/api.go @@ -10,11 +10,30 @@ import ( // @Description Get All of the Posts // @Accept json // @Produce json -// @Success 200 {object} web.GenericListResponse[web.Post] -// @Success 201 {object} web.GenericListResponse[web.GenericListResponse[web.Post]] -// @Success 202 {object} web.GenericListResponseMulti[web.Post, web.GenericListResponse[web.Post]] -// @Success 222 {object} web.GenericListResponseMulti[web.GenericListResponse[web.Post], web.Post] +// @Param data body web.GenericNestedBody[web.GenericInnerType[web.Post]] true "Some ID" +// @Success 200 {object} web.GenericNestedResponse[web.Post] +// @Success 201 {object} web.GenericNestedResponse[web.GenericInnerType[web.Post]] +// @Success 202 {object} web.GenericNestedResponseMulti[web.Post, web.GenericInnerMultiType[web.Post, web.Post]] +// @Success 203 {object} web.GenericNestedResponseMulti[web.Post, web.GenericInnerMultiType[web.Post, web.GenericInnerType[web.Post]]] +// @Success 222 {object} web.GenericNestedResponseMulti[web.GenericInnerType[web.Post], web.Post] // @Router /posts [get] func GetPosts(w http.ResponseWriter, r *http.Request) { - _ = web.GenericListResponse[web.Post]{} + _ = web.GenericNestedResponse[web.Post]{} +} + +// @Summary List Posts +// @Description Get All of the Posts +// @Accept json +// @Produce json +// @Param data body web.GenericNestedBody[web.GenericInnerType[[]web.Post]] true "Some ID" +// @Success 200 {object} web.GenericNestedResponse[[]web.Post] +// @Success 201 {object} web.GenericNestedResponse[[]web.GenericInnerType[web.Post]] +// @Success 202 {object} web.GenericNestedResponse[[]web.GenericInnerType[[]web.Post]] +// @Success 203 {object} web.GenericNestedResponseMulti[[]web.Post, web.GenericInnerMultiType[[]web.Post, web.Post]] +// @Success 204 {object} web.GenericNestedResponseMulti[[]web.Post, []web.GenericInnerMultiType[[]web.Post, web.Post]] +// @Success 205 {object} web.GenericNestedResponseMulti[web.Post, web.GenericInnerMultiType[web.Post, []web.GenericInnerType[[][]web.Post]]] +// @Success 222 {object} web.GenericNestedResponseMulti[web.GenericInnerType[[]web.Post], []web.Post] +// @Router /posts-multis/ [get] +func GetPostArray(w http.ResponseWriter, r *http.Request) { + _ = web.GenericNestedResponse[web.Post]{} } diff --git a/testdata/generics_nested/web/handler.go b/testdata/generics_nested/web/handler.go index a92ea8bad..5740be057 100644 --- a/testdata/generics_nested/web/handler.go +++ b/testdata/generics_nested/web/handler.go @@ -4,18 +4,43 @@ import ( "time" ) -// GenericListResponse[T] +// GenericNestedBody[T] +// @Description Some Generic Body +type GenericNestedBody[T any] struct { + // Items from the list response + Items T + // Status of some other stuff + Status string +} + +// GenericInnerType[T] +// @Description Some Generic Body +type GenericInnerType[T any] struct { + // Items from the list response + Items T +} + +// GenericInnerMultiType[T, X] +// @Description Some Generic Body +type GenericInnerMultiType[T any, X any] struct { + // ItemsOne is the first thing + ItemOne T + // ItemsTwo is the second thing + ItemsTwo []X +} + +// GenericNestedResponse[T] // @Description Some Generic List Response -type GenericListResponse[T any] struct { +type GenericNestedResponse[T any] struct { // Items from the list response Items []T // Status of some other stuff Status string } -// GenericListResponseMulti[T, X] +// GenericNestedResponseMulti[T, X] // @Description this contains a few things -type GenericListResponseMulti[T any, X any] struct { +type GenericNestedResponseMulti[T any, X any] struct { // ItemsOne is the first thing ItemOne T // ItemsTwo is the second thing From fff4b9fb852a670ae55391134665734c8b554fc6 Mon Sep 17 00:00:00 2001 From: Fabian Martin Date: Sun, 31 Jul 2022 23:20:34 +0200 Subject: [PATCH 4/7] feat: Add support for generic properties - get generic field type - support built in types in structs refs https://github.com/swaggo/swag/issues/1213 --- generics.go | 86 ++++++++- generics_other.go | 11 +- generics_test.go | 225 ++++++++++++++++++++++ packages.go | 2 +- parser.go | 12 +- parser_test.go | 14 +- testdata/generics_property/api/api.go | 17 ++ testdata/generics_property/main.go | 17 ++ testdata/generics_property/web/handler.go | 58 ++++++ 9 files changed, 417 insertions(+), 25 deletions(-) create mode 100644 testdata/generics_property/api/api.go create mode 100644 testdata/generics_property/main.go create mode 100644 testdata/generics_property/web/handler.go diff --git a/generics.go b/generics.go index eb2ad2f9e..d17cfd4ff 100644 --- a/generics.go +++ b/generics.go @@ -14,6 +14,23 @@ var genericsDefinitions = map[*TypeSpecDef]map[string]*TypeSpecDef{} type genericTypeSpec struct { ArrayDepth int TypeSpec *TypeSpecDef + Name string +} + +func (s *genericTypeSpec) Type() ast.Expr { + if s.TypeSpec != nil { + return s.TypeSpec.TypeSpec.Type + } + + return &ast.Ident{Name: s.Name} +} + +func (s *genericTypeSpec) TypeDocName() string { + if s.TypeSpec != nil { + return strings.Replace(TypeDocName(s.TypeSpec.FullName(), s.TypeSpec.TypeSpec), "-", "_", -1) + } + + return s.Name } func typeSpecFullName(typeSpecDef *TypeSpecDef) string { @@ -34,7 +51,7 @@ func typeSpecFullName(typeSpecDef *TypeSpecDef) string { return fullName } -func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string) *TypeSpecDef { +func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { if spec, ok := genericsDefinitions[original][fullGenericForm]; ok { return spec } @@ -61,14 +78,18 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful } } - tdef := pkgDefs.FindTypeSpec(genericParam, original.File, true) + tdef := pkgDefs.FindTypeSpec(genericParam, original.File, parseDependency) if tdef == nil { - return nil - } - - genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ - ArrayDepth: arrayDepth, - TypeSpec: tdef, + genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ + ArrayDepth: arrayDepth, + TypeSpec: nil, + Name: genericParam, + } + } else { + genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ + ArrayDepth: arrayDepth, + TypeSpec: tdef, + } } } @@ -102,7 +123,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth) } } - typeName = append(typeName, prefix+strings.Replace(TypeDocName(specDef.TypeSpec.FullName(), specDef.TypeSpec.TypeSpec), "-", "_", -1)) + typeName = append(typeName, prefix+specDef.TypeDocName()) } } @@ -185,7 +206,7 @@ func resolveType(expr ast.Expr, field *ast.Field, genericParamTypeDefs map[strin genTypeSpec.ArrayDepth-- return &ast.ArrayType{Elt: resolveType(expr, field, genericParamTypeDefs)} } - return genTypeSpec.TypeSpec.TypeSpec.Type + return genTypeSpec.Type() } } else if asArray, ok := expr.(*ast.ArrayType); ok { return &ast.ArrayType{Elt: resolveType(asArray.Elt, field, genericParamTypeDefs), Len: asArray.Len, Lbrack: asArray.Lbrack} @@ -193,3 +214,48 @@ func resolveType(expr ast.Expr, field *ast.Field, genericParamTypeDefs map[strin return field.Type } + +func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { + switch fieldType := field.(type) { + case *ast.IndexListExpr: + spec := &TypeSpecDef{ + File: file, + TypeSpec: getGenericTypeSpec(fieldType.X), + PkgPath: file.Name.Name, + } + fullName := spec.FullName() + "[" + + for _, index := range fieldType.Indices { + var fieldName string + var err error + + switch item := index.(type) { + case *ast.ArrayType: + fieldName, err = getFieldType(file, item.Elt) + fieldName = "[]" + fieldName + default: + fieldName, err = getFieldType(file, index) + } + + if err != nil { + return "", err + } + + fullName += fieldName + ", " + } + + return strings.TrimRight(fullName, ", ") + "]", nil + } + + return "", fmt.Errorf("unknown field type %#v", field) +} + +func getGenericTypeSpec(field ast.Expr) *ast.TypeSpec { + switch indexType := field.(type) { + case *ast.Ident: + return indexType.Obj.Decl.(*ast.TypeSpec) + case *ast.ArrayType: + return indexType.Elt.(*ast.Ident).Obj.Decl.(*ast.TypeSpec) + } + return nil +} diff --git a/generics_other.go b/generics_other.go index a695023b1..9d6aa142b 100644 --- a/generics_other.go +++ b/generics_other.go @@ -3,10 +3,19 @@ package swag +import ( + "fmt" + "go/ast" +) + func typeSpecFullName(typeSpecDef *TypeSpecDef) string { return typeSpecDef.FullName() } -func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string) *TypeSpecDef { +func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { return original } + +func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { + return "", fmt.Errorf("unknown field type %#v", field) +} diff --git a/generics_test.go b/generics_test.go index d1ab0e756..bba300cd4 100644 --- a/generics_test.go +++ b/generics_test.go @@ -2313,3 +2313,228 @@ func TestParseGenericsNested(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expected, string(b)) } + +func TestParseGenericsProperty(t *testing.T) { + t.Parallel() + expected := `{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts": { + "get": { + "description": "Get All of the Posts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Posts", + "parameters": [ + { + "type": "string", + "name": "next_id", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "string", + "name": "prev_id", + "in": "query" + }, + { + "type": "integer", + "name": "rows", + "in": "query" + }, + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/web.PostResponse" + } + }, + "201": { + "description": "ok", + "schema": { + "$ref": "#/definitions/web.PostResponses" + } + }, + "202": { + "description": "ok", + "schema": { + "$ref": "#/definitions/web.StringResponse" + } + } + } + } + } + }, + "definitions": { + "web.PostResponse": { + "type": "object", + "properties": { + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "items2": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "web.PostResponses": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "items2": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "web.StringResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "string" + } + }, + "items2": { + "type": "integer" + } + } + } + } +}` + + searchDir := "testdata/generics_property" + p := New() + err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + assert.NoError(t, err) + b, err := json.MarshalIndent(p.swagger, "", " ") + assert.NoError(t, err) + assert.Equal(t, expected, string(b)) +} diff --git a/packages.go b/packages.go index 445d09576..3750c858f 100644 --- a/packages.go +++ b/packages.go @@ -338,7 +338,7 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File } if strings.Contains(tName, joinedParts) { - if parametrized := pkgDefs.parametrizeStruct(tSpec, typeName); parametrized != nil { + if parametrized := pkgDefs.parametrizeStruct(tSpec, typeName, parseDependency); parametrized != nil { return parametrized } } diff --git a/parser.go b/parser.go index 3e2e353eb..8520721f0 100644 --- a/parser.go +++ b/parser.go @@ -1225,7 +1225,7 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st } } - typeName, err := getFieldType(field.Type) + typeName, err := getFieldType(file, field.Type) if err != nil { return nil, nil, err } @@ -1269,7 +1269,7 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st } if schema == nil { - typeName, err := getFieldType(field.Type) + typeName, err := getFieldType(file, field.Type) if err == nil { // named type schema, err = parser.getTypeSchema(typeName, file, true) @@ -1302,26 +1302,26 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st return map[string]spec.Schema{fieldName: *schema}, tagRequired, nil } -func getFieldType(field ast.Expr) (string, error) { +func getFieldType(file *ast.File, field ast.Expr) (string, error) { switch fieldType := field.(type) { case *ast.Ident: return fieldType.Name, nil case *ast.SelectorExpr: - packageName, err := getFieldType(fieldType.X) + packageName, err := getFieldType(file, fieldType.X) if err != nil { return "", err } return fullTypeName(packageName, fieldType.Sel.Name), nil case *ast.StarExpr: - fullName, err := getFieldType(fieldType.X) + fullName, err := getFieldType(file, fieldType.X) if err != nil { return "", err } return fullName, nil default: - return "", fmt.Errorf("unknown field type %#v", field) + return getGenericFieldType(file, field) } } diff --git a/parser_test.go b/parser_test.go index 2aaad9ab3..d5eb14a2d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3569,28 +3569,28 @@ func TestParser_Skip(t *testing.T) { func TestGetFieldType(t *testing.T) { t.Parallel() - field, err := getFieldType(&ast.Ident{Name: "User"}) + field, err := getFieldType(&ast.File{}, &ast.Ident{Name: "User"}) assert.NoError(t, err) assert.Equal(t, "User", field) - _, err = getFieldType(&ast.FuncType{}) + _, err = getFieldType(&ast.File{}, &ast.FuncType{}) assert.Error(t, err) - field, err = getFieldType(&ast.SelectorExpr{X: &ast.Ident{Name: "models"}, Sel: &ast.Ident{Name: "User"}}) + field, err = getFieldType(&ast.File{}, &ast.SelectorExpr{X: &ast.Ident{Name: "models"}, Sel: &ast.Ident{Name: "User"}}) assert.NoError(t, err) assert.Equal(t, "models.User", field) - _, err = getFieldType(&ast.SelectorExpr{X: &ast.FuncType{}, Sel: &ast.Ident{Name: "User"}}) + _, err = getFieldType(&ast.File{}, &ast.SelectorExpr{X: &ast.FuncType{}, Sel: &ast.Ident{Name: "User"}}) assert.Error(t, err) - field, err = getFieldType(&ast.StarExpr{X: &ast.Ident{Name: "User"}}) + field, err = getFieldType(&ast.File{}, &ast.StarExpr{X: &ast.Ident{Name: "User"}}) assert.NoError(t, err) assert.Equal(t, "User", field) - field, err = getFieldType(&ast.StarExpr{X: &ast.FuncType{}}) + field, err = getFieldType(&ast.File{}, &ast.StarExpr{X: &ast.FuncType{}}) assert.Error(t, err) - field, err = getFieldType(&ast.StarExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "models"}, Sel: &ast.Ident{Name: "User"}}}) + field, err = getFieldType(&ast.File{}, &ast.StarExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "models"}, Sel: &ast.Ident{Name: "User"}}}) assert.NoError(t, err) assert.Equal(t, "models.User", field) } diff --git a/testdata/generics_property/api/api.go b/testdata/generics_property/api/api.go new file mode 100644 index 000000000..43206653e --- /dev/null +++ b/testdata/generics_property/api/api.go @@ -0,0 +1,17 @@ +package api + +import ( + "net/http" +) + +// @Summary List Posts +// @Description Get All of the Posts +// @Accept json +// @Produce json +// @Param data query web.PostPager true "1" +// @Success 200 {object} web.PostResponse "ok" +// @Success 201 {object} web.PostResponses "ok" +// @Success 202 {object} web.StringResponse "ok" +// @Router /posts [get] +func GetPosts(w http.ResponseWriter, r *http.Request) { +} diff --git a/testdata/generics_property/main.go b/testdata/generics_property/main.go new file mode 100644 index 000000000..8d79868fc --- /dev/null +++ b/testdata/generics_property/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "net/http" + + "github.com/swaggo/swag/testdata/generics_arrays/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @host localhost:4000 +// @basePath /api +func main() { + http.HandleFunc("/posts/", api.GetPosts) + http.ListenAndServe(":8080", nil) +} diff --git a/testdata/generics_property/web/handler.go b/testdata/generics_property/web/handler.go new file mode 100644 index 000000000..2c7c1020e --- /dev/null +++ b/testdata/generics_property/web/handler.go @@ -0,0 +1,58 @@ +package web + +type PostSelector func(selector func()) + +type Filter interface { + ~func(selector func()) +} + +type query[T any, F Filter] interface { + Where(ps ...F) T +} + +type Pager[T query[T, F], F Filter] struct { + Rows uint8 `json:"rows" form:"rows"` + Page int `json:"page" form:"page"` + NextID *string `json:"next_id" form:"next_id"` + PrevID *string `json:"prev_id" form:"prev_id"` + query T +} + +type String string + +func (String) Where(ps ...PostSelector) String { + return "" +} + +type PostPager struct { + Pager[String, PostSelector] + Search string `json:"search" form:"search"` +} + +type PostResponse struct { + GenericResponse[Post, Post] +} + +type PostResponses struct { + GenericResponse[[]Post, Post] +} + +type StringResponse struct { + GenericResponse[[]string, *uint8] +} + +type GenericResponse[T any, T2 any] struct { + Items T + Items2 T2 +} + +type Post struct { + ID int `json:"id" example:"1" format:"int64"` + // Post name + Name string `json:"name" example:"poti"` + // Post data + Data struct { + // Post tag + Tag []string `json:"name"` + } `json:"data"` +} From 9c5441322392b64e43f28dd59f8e1a55e909bba1 Mon Sep 17 00:00:00 2001 From: Fabian Martin Date: Mon, 1 Aug 2022 13:35:22 +0200 Subject: [PATCH 5/7] feat: Support custom model names for generics add prefix to generic model names, to prevent renaming, if name annotation exists --- generics.go | 5 +++-- parser.go | 2 +- schema.go | 13 ++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/generics.go b/generics.go index d17cfd4ff..6eefb33a8 100644 --- a/generics.go +++ b/generics.go @@ -56,6 +56,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful return spec } + pkgName := strings.Split(fullGenericForm, ".")[0] genericTypeName, genericParams := splitStructName(fullGenericForm) if genericParams == nil { return nil @@ -112,7 +113,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful genericTypeName = strings.Split(genericTypeName, ".")[1] } - var typeName = []string{TypeDocName(genericTypeName, parametrizedTypeSpec.TypeSpec)} + var typeName = []string{TypeDocName(fullTypeName(pkgName, genericTypeName), parametrizedTypeSpec.TypeSpec)} for _, def := range original.TypeSpec.TypeParams.List { if specDef, ok := genericParamTypeDefs[def.Names[0].Name]; ok { @@ -128,7 +129,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful } ident.Name = strings.Join(typeName, "-") - ident.Name = strings.Replace(ident.Name, ".", "_", -1) + ident.Name = string(IgnoreNameOverridePrefix) + strings.Replace(strings.Replace(ident.Name, ".", "_", -1), "_", ".", 1) parametrizedTypeSpec.TypeSpec.Name = ident origStructType := original.TypeSpec.Type.(*ast.StructType) diff --git a/parser.go b/parser.go index 8520721f0..ec78bb55b 100644 --- a/parser.go +++ b/parser.go @@ -1066,7 +1066,7 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) } func fullTypeName(pkgName, typeName string) string { - if pkgName != "" { + if pkgName != "" && !ignoreNameOverride(typeName) { return pkgName + "." + typeName } diff --git a/schema.go b/schema.go index c7f2ec416..59dbc6fe6 100644 --- a/schema.go +++ b/schema.go @@ -34,6 +34,9 @@ const ( ANY = "any" // NIL represent a empty value. NIL = "nil" + + // IgnoreNameOverridePrefix Prepend to model to avoid renaming based on comment. + IgnoreNameOverridePrefix = '$' ) // CheckSchemaType checks if typeName is not a name of primitive type. @@ -132,7 +135,7 @@ func TransToValidCollectionFormat(format string) string { // TypeDocName get alias from comment '// @name ', otherwise the original type name to display in doc. func TypeDocName(pkgName string, spec *ast.TypeSpec) string { - if spec != nil { + if spec != nil && !ignoreNameOverride(pkgName) { if spec.Comment != nil { for _, comment := range spec.Comment.List { texts := strings.Split(strings.TrimSpace(strings.TrimLeft(comment.Text, "/")), " ") @@ -147,9 +150,17 @@ func TypeDocName(pkgName string, spec *ast.TypeSpec) string { } } + if ignoreNameOverride(pkgName) { + return pkgName[1:] + } + return pkgName } +func ignoreNameOverride(name string) bool { + return len(name) != 0 && name[0] == IgnoreNameOverridePrefix +} + // RefSchema build a reference schema. func RefSchema(refType string) *spec.Schema { return spec.RefSchema("#/definitions/" + refType) From 84cb8536edce19b280f56d4940c5778e5977d246 Mon Sep 17 00:00:00 2001 From: Fabian Martin Date: Mon, 1 Aug 2022 23:49:13 +0200 Subject: [PATCH 6/7] fix: Check if generic name starts with pkgName - The first underscore was replaced instead of checking if the generated name even starts with the package name. - New Tests added to test the name generation - schema test extended to test the new behavior --- generics.go | 7 +- generics_test.go | 2526 +--------------------- schema_test.go | 10 + testdata/generics_arrays/expected.json | 533 +++++ testdata/generics_basic/expected.json | 536 +++++ testdata/generics_names/api/api.go | 48 + testdata/generics_names/expected.json | 536 +++++ testdata/generics_names/main.go | 19 + testdata/generics_names/web/handler.go | 51 + testdata/generics_nested/expected.json | 1197 ++++++++++ testdata/generics_property/expected.json | 213 ++ 11 files changed, 3186 insertions(+), 2490 deletions(-) create mode 100644 testdata/generics_arrays/expected.json create mode 100644 testdata/generics_basic/expected.json create mode 100644 testdata/generics_names/api/api.go create mode 100644 testdata/generics_names/expected.json create mode 100644 testdata/generics_names/main.go create mode 100644 testdata/generics_names/web/handler.go create mode 100644 testdata/generics_nested/expected.json create mode 100644 testdata/generics_property/expected.json diff --git a/generics.go b/generics.go index 6eefb33a8..60016b3c6 100644 --- a/generics.go +++ b/generics.go @@ -129,7 +129,12 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful } ident.Name = strings.Join(typeName, "-") - ident.Name = string(IgnoreNameOverridePrefix) + strings.Replace(strings.Replace(ident.Name, ".", "_", -1), "_", ".", 1) + ident.Name = strings.Replace(ident.Name, ".", "_", -1) + pkgNamePrefix := pkgName + "_" + if strings.HasPrefix(ident.Name, pkgNamePrefix) { + ident.Name = fullTypeName(pkgName, ident.Name[len(pkgNamePrefix):]) + } + ident.Name = string(IgnoreNameOverridePrefix) + ident.Name parametrizedTypeSpec.TypeSpec.Name = ident origStructType := original.TypeSpec.Type.(*ast.StructType) diff --git a/generics_test.go b/generics_test.go index bba300cd4..81b55c27a 100644 --- a/generics_test.go +++ b/generics_test.go @@ -5,6 +5,8 @@ package swag import ( "encoding/json" + "io/ioutil" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -13,2528 +15,74 @@ import ( func TestParseGenericsBasic(t *testing.T) { t.Parallel() - expected := `{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "contact": {}, - "version": "1.0" - }, - "host": "localhost:4000", - "basePath": "/api", - "paths": { - "/posts-multi/": { - "post": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add new pets to the store", - "parameters": [ - { - "description": "Some ID", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/web.GenericBodyMulti-web_Post-web_Post" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericResponse-web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericResponseMulti-web_Post-web_Post" - } - } - } - } - }, - "/posts-multis/": { - "post": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add new pets to the store", - "parameters": [ - { - "description": "Some ID", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/web.GenericBodyMulti-array_web_Post-array2_web_Post" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericResponse-array_web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericResponseMulti-array_web_Post-array2_web_Post" - } - } - } - } - }, - "/posts/": { - "post": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add a new pet to the store", - "parameters": [ - { - "description": "Some ID", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/web.GenericBody-web_Post" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericResponse-web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericResponseMulti-web_Post-web_Post" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "$ref": "#/definitions/web.APIError" - } - } - } - } - } - }, - "definitions": { - "web.APIError": { - "description": "API error with information about it", - "type": "object", - "properties": { - "createdAt": { - "description": "Error time", - "type": "string" - }, - "error": { - "description": "Error an Api error", - "type": "string" - }, - "errorCtx": { - "description": "Error ` + "`context`" + ` tick comment", - "type": "string" - }, - "errorNo": { - "description": "Error ` + "`number`" + ` tick comment", - "type": "integer" - } - } - }, - "web.GenericBody-web_Post": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "web.GenericBodyMulti-array_web_Post-array2_web_Post": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "meta": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - }, - "web.GenericBodyMulti-web_Post-web_Post": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "meta": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "web.GenericResponse-array_web_Post": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "status": { - "type": "string" - } - } - }, - "web.GenericResponse-web_Post": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "status": { - "type": "string" - } - } - }, - "web.GenericResponseMulti-array_web_Post-array2_web_Post": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "meta": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "status": { - "type": "string" - } - } - }, - "web.GenericResponseMulti-web_Post-web_Post": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "meta": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "status": { - "type": "string" - } - } - } - } -}` - searchDir := "testdata/generics_basic" + expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + p := New() - err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) assert.NoError(t, err) b, err := json.MarshalIndent(p.swagger, "", " ") assert.NoError(t, err) - assert.Equal(t, expected, string(b)) + assert.Equal(t, string(expected), string(b)) } func TestParseGenericsArrays(t *testing.T) { t.Parallel() - expected := `{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "contact": {}, - "version": "1.0" - }, - "host": "localhost:4000", - "basePath": "/api", - "paths": { - "/posts": { - "get": { - "description": "Get All of the Posts", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "List Posts", - "parameters": [ - { - "description": "Some ID", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/web.GenericListBody-web_Post" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericListResponse-web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_Post" - } - } - } - } - }, - "/posts-multi": { - "get": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add new pets to the store", - "parameters": [ - { - "description": "Some ID", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/web.GenericListBodyMulti-web_Post-web_Post" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericListResponse-web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_Post" - } - } - } - } - }, - "/posts-multis": { - "get": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add new pets to the store", - "parameters": [ - { - "description": "Some ID", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/web.GenericListBodyMulti-web_Post-array_web_Post" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericListResponse-array_web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-array_web_Post" - } - } - } - } - } - }, - "definitions": { - "web.GenericListBody-web_Post": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - }, - "web.GenericListBodyMulti-web_Post-array_web_Post": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "meta": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - }, - "web.GenericListBodyMulti-web_Post-web_Post": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "meta": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - }, - "web.GenericListResponse-array_web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericListResponse-web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericListResponseMulti-web_Post-array_web_Post": { - "type": "object", - "properties": { - "itemsOne": { - "description": "ItemsOne is the first thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - }, - "web.GenericListResponseMulti-web_Post-web_Post": { - "type": "object", - "properties": { - "itemsOne": { - "description": "ItemsOne is the first thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - } - } -}` - searchDir := "testdata/generics_arrays" + expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + p := New() - err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) assert.NoError(t, err) b, err := json.MarshalIndent(p.swagger, "", " ") assert.NoError(t, err) - assert.Equal(t, expected, string(b)) + assert.Equal(t, string(expected), string(b)) } func TestParseGenericsNested(t *testing.T) { t.Parallel() - expected := `{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "contact": {}, - "version": "1.0" - }, - "host": "localhost:4000", - "basePath": "/api", - "paths": { - "/posts": { - "get": { - "description": "Get All of the Posts", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "List Posts", - "parameters": [ - { - "description": "Some ID", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_web_Post" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-web_Post" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-web_GenericInnerType_web_Post" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_Post" - } - }, - "203": { - "description": "Non-Authoritative Information", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_GenericInnerType_web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_web_Post-web_Post" - } - } - } - } - }, - "/posts-multis/": { - "get": { - "description": "Get All of the Posts", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "List Posts", - "parameters": [ - { - "description": "Some ID", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_array_web_Post" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-array_web_Post" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_web_Post" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_array_web_Post" - } - }, - "203": { - "description": "Non-Authoritative Information", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-array_web_Post-web_GenericInnerMultiType_array_web_Post_web_Post" - } - }, - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-array_web_Post-array_web_GenericInnerMultiType_array_web_Post_web_Post" - } - }, - "205": { - "description": "Reset Content", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_array_web_GenericInnerType_array2_web_Post" - } - }, - "222": { - "description": "", - "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_array_web_Post-array_web_Post" - } - } - } - } - } - }, - "definitions": { - "web.GenericNestedBody-web_GenericInnerType_array_web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericNestedBody-web_GenericInnerType_web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericNestedResponse-array_web_GenericInnerType_array_web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericNestedResponse-array_web_GenericInnerType_web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericNestedResponse-array_web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericNestedResponse-web_GenericInnerType_web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericNestedResponse-web_Post": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "status": { - "description": "Status of some other stuff", - "type": "string" - } - } - }, - "web.GenericNestedResponseMulti-array_web_Post-array_web_GenericInnerMultiType_array_web_Post_web_Post": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - }, - "web.GenericNestedResponseMulti-array_web_Post-web_GenericInnerMultiType_array_web_Post_web_Post": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - }, - "web.GenericNestedResponseMulti-web_GenericInnerType_array_web_Post-array_web_Post": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - }, - "web.GenericNestedResponseMulti-web_GenericInnerType_web_Post-web_Post": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - }, - "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_array_web_GenericInnerType_array2_web_Post": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "array", - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - } - } - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - }, - "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_GenericInnerType_web_Post": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "items": { - "description": "Items from the list response", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - }, - "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_Post": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "itemOne": { - "description": "ItemsOne is the first thing", - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "itemsTwo": { - "description": "ItemsTwo is the second thing", - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - } - } - }, - "status": { - "description": "Status of the things", - "type": "string" - } - } - } - } -}` searchDir := "testdata/generics_nested" + expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + p := New() - err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) assert.NoError(t, err) b, err := json.MarshalIndent(p.swagger, "", " ") assert.NoError(t, err) - assert.Equal(t, expected, string(b)) + assert.Equal(t, string(expected), string(b)) } func TestParseGenericsProperty(t *testing.T) { t.Parallel() - expected := `{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "contact": {}, - "version": "1.0" - }, - "host": "localhost:4000", - "basePath": "/api", - "paths": { - "/posts": { - "get": { - "description": "Get All of the Posts", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "List Posts", - "parameters": [ - { - "type": "string", - "name": "next_id", - "in": "query" - }, - { - "type": "integer", - "name": "page", - "in": "query" - }, - { - "type": "string", - "name": "prev_id", - "in": "query" - }, - { - "type": "integer", - "name": "rows", - "in": "query" - }, - { - "type": "string", - "name": "search", - "in": "query" - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "$ref": "#/definitions/web.PostResponse" - } - }, - "201": { - "description": "ok", - "schema": { - "$ref": "#/definitions/web.PostResponses" - } - }, - "202": { - "description": "ok", - "schema": { - "$ref": "#/definitions/web.StringResponse" - } - } - } - } - } - }, - "definitions": { - "web.PostResponse": { - "type": "object", - "properties": { - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - }, - "items2": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "web.PostResponses": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - }, - "items2": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } - } - } - }, - "web.StringResponse": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "type": "string" - } - }, - "items2": { - "type": "integer" - } - } - } - } -}` searchDir := "testdata/generics_property" + expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + + p := New() + err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + assert.NoError(t, err) + b, err := json.MarshalIndent(p.swagger, "", " ") + assert.NoError(t, err) + assert.Equal(t, string(expected), string(b)) +} + +func TestParseGenericsNames(t *testing.T) { + t.Parallel() + + searchDir := "testdata/generics_names" + expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + p := New() - err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) assert.NoError(t, err) b, err := json.MarshalIndent(p.swagger, "", " ") assert.NoError(t, err) - assert.Equal(t, expected, string(b)) + assert.Equal(t, string(expected), string(b)) } diff --git a/schema_test.go b/schema_test.go index 58135b81a..b5b724818 100644 --- a/schema_test.go +++ b/schema_test.go @@ -166,4 +166,14 @@ func TestTypeDocName(t *testing.T) { List: []*ast.Comment{{Text: "// @name Model"}}, }, })) + + expected = "package.ModelName" + assert.Equal(t, expected, TypeDocName("$package.ModelName", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}})) + + expected = "Model" + assert.Equal(t, expected, TypeDocName("$Model", &ast.TypeSpec{ + Comment: &ast.CommentGroup{ + List: []*ast.Comment{{Text: "// @name ModelName"}}, + }, + })) } diff --git a/testdata/generics_arrays/expected.json b/testdata/generics_arrays/expected.json new file mode 100644 index 000000000..25dda1fd8 --- /dev/null +++ b/testdata/generics_arrays/expected.json @@ -0,0 +1,533 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts": { + "get": { + "description": "Get All of the Posts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Posts", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericListBody-web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericListResponse-web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_Post" + } + } + } + } + }, + "/posts-multi": { + "get": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericListBodyMulti-web_Post-web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericListResponse-web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-web_Post" + } + } + } + } + }, + "/posts-multis": { + "get": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericListBodyMulti-web_Post-array_web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericListResponse-array_web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericListResponseMulti-web_Post-array_web_Post" + } + } + } + } + } + }, + "definitions": { + "web.GenericListBody-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "web.GenericListBodyMulti-web_Post-array_web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "meta": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "web.GenericListBodyMulti-web_Post-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "meta": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "web.GenericListResponse-array_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericListResponse-web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericListResponseMulti-web_Post-array_web_Post": { + "type": "object", + "properties": { + "itemsOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericListResponseMulti-web_Post-web_Post": { + "type": "object", + "properties": { + "itemsOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/generics_basic/expected.json b/testdata/generics_basic/expected.json new file mode 100644 index 000000000..ac61b4f78 --- /dev/null +++ b/testdata/generics_basic/expected.json @@ -0,0 +1,536 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts-multi/": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericBodyMulti-web_Post-web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericResponse-web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericResponseMulti-web_Post-web_Post" + } + } + } + } + }, + "/posts-multis/": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericBodyMulti-array_web_Post-array2_web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericResponse-array_web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericResponseMulti-array_web_Post-array2_web_Post" + } + } + } + } + }, + "/posts/": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add a new pet to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericBody-web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericResponse-web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericResponseMulti-web_Post-web_Post" + } + }, + "400": { + "description": "We need ID!!", + "schema": { + "$ref": "#/definitions/web.APIError" + } + }, + "404": { + "description": "Can not find ID", + "schema": { + "$ref": "#/definitions/web.APIError" + } + } + } + } + } + }, + "definitions": { + "web.APIError": { + "description": "API error with information about it", + "type": "object", + "properties": { + "createdAt": { + "description": "Error time", + "type": "string" + }, + "error": { + "description": "Error an Api error", + "type": "string" + }, + "errorCtx": { + "description": "Error `context` tick comment", + "type": "string" + }, + "errorNo": { + "description": "Error `number` tick comment", + "type": "integer" + } + } + }, + "web.GenericBody-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "web.GenericBodyMulti-array_web_Post-array2_web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "meta": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "web.GenericBodyMulti-web_Post-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "meta": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "web.GenericResponse-array_web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "type": "string" + } + } + }, + "web.GenericResponse-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "status": { + "type": "string" + } + } + }, + "web.GenericResponseMulti-array_web_Post-array2_web_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "meta": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "type": "string" + } + } + }, + "web.GenericResponseMulti-web_Post-web_Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "meta": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "status": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/generics_names/api/api.go b/testdata/generics_names/api/api.go new file mode 100644 index 000000000..ef0c9c415 --- /dev/null +++ b/testdata/generics_names/api/api.go @@ -0,0 +1,48 @@ +package api + +import ( + "net/http" + + "github.com/swaggo/swag/testdata/generics_names/web" +) + +// @Summary Add a new pet to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Param data body web.GenericBody[web.Post] true "Some ID" +// @Success 200 {object} web.GenericResponse[web.Post] +// @Success 222 {object} web.GenericResponseMulti[web.Post, web.Post] +// @Failure 400 {object} web.APIError "We need ID!!" +// @Failure 404 {object} web.APIError "Can not find ID" +// @Router /posts/ [post] +func GetPost(w http.ResponseWriter, r *http.Request) { + //write your code + _ = web.GenericResponse[web.Post]{} +} + +// @Summary Add new pets to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Param data body web.GenericBodyMulti[web.Post, web.Post] true "Some ID" +// @Success 200 {object} web.GenericResponse[web.Post] +// @Success 222 {object} web.GenericResponseMulti[web.Post, web.Post] +// @Router /posts-multi/ [post] +func GetPostMulti(w http.ResponseWriter, r *http.Request) { + //write your code + _ = web.GenericResponse[web.Post]{} +} + +// @Summary Add new pets to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Param data body web.GenericBodyMulti[[]web.Post, [][]web.Post] true "Some ID" +// @Success 200 {object} web.GenericResponse[[]web.Post] +// @Success 222 {object} web.GenericResponseMulti[[]web.Post, [][]web.Post] +// @Router /posts-multis/ [post] +func GetPostArray(w http.ResponseWriter, r *http.Request) { + //write your code + _ = web.GenericResponse[web.Post]{} +} diff --git a/testdata/generics_names/expected.json b/testdata/generics_names/expected.json new file mode 100644 index 000000000..175ab1c12 --- /dev/null +++ b/testdata/generics_names/expected.json @@ -0,0 +1,536 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts-multi/": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/MultiBody-Post-Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response-Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/MultiResponse-Post-Post" + } + } + } + } + }, + "/posts-multis/": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add new pets to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/MultiBody-array_Post-array2_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response-array_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/MultiResponse-array_Post-array2_Post" + } + } + } + } + }, + "/posts/": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add a new pet to the store", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Body-Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response-Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/MultiResponse-Post-Post" + } + }, + "400": { + "description": "We need ID!!", + "schema": { + "$ref": "#/definitions/web.APIError" + } + }, + "404": { + "description": "Can not find ID", + "schema": { + "$ref": "#/definitions/web.APIError" + } + } + } + } + } + }, + "definitions": { + "Body-Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "MultiBody-Post-Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "meta": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "MultiBody-array_Post-array2_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "meta": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "MultiResponse-Post-Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "meta": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "status": { + "type": "string" + } + } + }, + "MultiResponse-array_Post-array2_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "meta": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "type": "string" + } + } + }, + "Response-Post": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "status": { + "type": "string" + } + } + }, + "Response-array_Post": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "type": "string" + } + } + }, + "web.APIError": { + "description": "API error with information about it", + "type": "object", + "properties": { + "createdAt": { + "description": "Error time", + "type": "string" + }, + "error": { + "description": "Error an Api error", + "type": "string" + }, + "errorCtx": { + "description": "Error `context` tick comment", + "type": "string" + }, + "errorNo": { + "description": "Error `number` tick comment", + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/generics_names/main.go b/testdata/generics_names/main.go new file mode 100644 index 000000000..bf7307b9d --- /dev/null +++ b/testdata/generics_names/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "net/http" + + "github.com/swaggo/swag/testdata/generics_names/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @host localhost:4000 +// @basePath /api +func main() { + http.HandleFunc("/posts/", api.GetPost) + http.HandleFunc("/posts-multi/", api.GetPostMulti) + http.HandleFunc("/posts-multis/", api.GetPostArray) + http.ListenAndServe(":8080", nil) +} diff --git a/testdata/generics_names/web/handler.go b/testdata/generics_names/web/handler.go new file mode 100644 index 000000000..09aa7f1de --- /dev/null +++ b/testdata/generics_names/web/handler.go @@ -0,0 +1,51 @@ +package web + +import ( + "time" +) + +type GenericBody[T any] struct { + Data T +} // @name Body + +type GenericBodyMulti[T any, X any] struct { + Data T + Meta X +} // @name MultiBody + +type GenericResponse[T any] struct { + Data T + + Status string +} // @name Response + +type GenericResponseMulti[T any, X any] struct { + Data T + Meta X + + Status string +} // @name MultiResponse + +type Post struct { + ID int `json:"id" example:"1" format:"int64"` + // Post name + Name string `json:"name" example:"poti"` + // Post data + Data struct { + // Post tag + Tag []string `json:"name"` + } `json:"data"` +} // @name Post + +// APIError +// @Description API error +// @Description with information about it +// Other some summary +type APIError struct { + // Error an Api error + Error string // Error this is Line comment + // Error `number` tick comment + ErrorNo int64 + ErrorCtx string // Error `context` tick comment + CreatedAt time.Time // Error time +} diff --git a/testdata/generics_nested/expected.json b/testdata/generics_nested/expected.json new file mode 100644 index 000000000..b1bbaffc6 --- /dev/null +++ b/testdata/generics_nested/expected.json @@ -0,0 +1,1197 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts": { + "get": { + "description": "Get All of the Posts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Posts", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-web_Post" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-web_GenericInnerType_web_Post" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_Post" + } + }, + "203": { + "description": "Non-Authoritative Information", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_GenericInnerType_web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_web_Post-web_Post" + } + } + } + } + }, + "/posts-multis/": { + "get": { + "description": "Get All of the Posts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Posts", + "parameters": [ + { + "description": "Some ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_array_web_Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-array_web_Post" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_web_Post" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_array_web_Post" + } + }, + "203": { + "description": "Non-Authoritative Information", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-array_web_Post-web_GenericInnerMultiType_array_web_Post_web_Post" + } + }, + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-array_web_Post-array_web_GenericInnerMultiType_array_web_Post_web_Post" + } + }, + "205": { + "description": "Reset Content", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_array_web_GenericInnerType_array2_web_Post" + } + }, + "222": { + "description": "", + "schema": { + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_array_web_Post-array_web_Post" + } + } + } + } + } + }, + "definitions": { + "web.GenericNestedBody-web_GenericInnerType_array_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedBody-web_GenericInnerType_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-array_web_GenericInnerType_array_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-array_web_GenericInnerType_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-array_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-web_GenericInnerType_web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponse-web_Post": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of some other stuff", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-array_web_Post-array_web_GenericInnerMultiType_array_web_Post_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-array_web_Post-web_GenericInnerMultiType_array_web_Post_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-web_GenericInnerType_array_web_Post-array_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-web_GenericInnerType_web_Post-web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_array_web_GenericInnerType_array2_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + } + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_GenericInnerType_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "items": { + "description": "Items from the list response", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + }, + "web.GenericNestedResponseMulti-web_Post-web_GenericInnerMultiType_web_Post_web_Post": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "itemOne": { + "description": "ItemsOne is the first thing", + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "itemsTwo": { + "description": "ItemsTwo is the second thing", + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + } + } + }, + "status": { + "description": "Status of the things", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/generics_property/expected.json b/testdata/generics_property/expected.json new file mode 100644 index 000000000..6927fb14e --- /dev/null +++ b/testdata/generics_property/expected.json @@ -0,0 +1,213 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts": { + "get": { + "description": "Get All of the Posts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Posts", + "parameters": [ + { + "type": "string", + "name": "next_id", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "string", + "name": "prev_id", + "in": "query" + }, + { + "type": "integer", + "name": "rows", + "in": "query" + }, + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/web.PostResponse" + } + }, + "201": { + "description": "ok", + "schema": { + "$ref": "#/definitions/web.PostResponses" + } + }, + "202": { + "description": "ok", + "schema": { + "$ref": "#/definitions/web.StringResponse" + } + } + } + } + } + }, + "definitions": { + "web.PostResponse": { + "type": "object", + "properties": { + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "items2": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "web.PostResponses": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + }, + "items2": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } + }, + "web.StringResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "string" + } + }, + "items2": { + "type": "integer" + } + } + } + } +} \ No newline at end of file From 68fbadb9586863c2fdee60942aa03a0175adf6c5 Mon Sep 17 00:00:00 2001 From: Fabian Martin Date: Tue, 2 Aug 2022 00:00:33 +0200 Subject: [PATCH 7/7] refactor: Apply suggested changes from PR Suggested changes applied --- generics.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/generics.go b/generics.go index 60016b3c6..f95141363 100644 --- a/generics.go +++ b/generics.go @@ -70,20 +70,17 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful for i, genericParam := range genericParams { arrayDepth := 0 for { - var isArray = len(genericParam) > 2 && genericParam[:2] == "[]" - if isArray { - genericParam = genericParam[2:] - arrayDepth++ - } else { + if len(genericParam) <= 2 || genericParam[:2] != "[]" { break } + genericParam = genericParam[2:] + arrayDepth++ } tdef := pkgDefs.FindTypeSpec(genericParam, original.File, parseDependency) if tdef == nil { genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ ArrayDepth: arrayDepth, - TypeSpec: nil, Name: genericParam, } } else { @@ -206,16 +203,21 @@ func splitStructName(fullGenericForm string) (string, []string) { } func resolveType(expr ast.Expr, field *ast.Field, genericParamTypeDefs map[string]*genericTypeSpec) ast.Expr { - if asIdent, ok := expr.(*ast.Ident); ok { - if genTypeSpec, ok := genericParamTypeDefs[asIdent.Name]; ok { + switch astExpr := expr.(type) { + case *ast.Ident: + if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok { if genTypeSpec.ArrayDepth > 0 { genTypeSpec.ArrayDepth-- return &ast.ArrayType{Elt: resolveType(expr, field, genericParamTypeDefs)} } return genTypeSpec.Type() } - } else if asArray, ok := expr.(*ast.ArrayType); ok { - return &ast.ArrayType{Elt: resolveType(asArray.Elt, field, genericParamTypeDefs), Len: asArray.Len, Lbrack: asArray.Lbrack} + case *ast.ArrayType: + return &ast.ArrayType{ + Elt: resolveType(astExpr.Elt, field, genericParamTypeDefs), + Len: astExpr.Len, + Lbrack: astExpr.Lbrack, + } } return field.Type