diff --git a/generics.go b/generics.go index c0ec97e80..f95141363 100644 --- a/generics.go +++ b/generics.go @@ -4,10 +4,35 @@ package swag import ( + "fmt" "go/ast" "strings" ) +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 { fullName := typeSpecDef.FullName() @@ -26,29 +51,44 @@ func typeSpecFullName(typeSpecDef *TypeSpecDef) string { return fullName } -func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string) *TypeSpecDef { - genericParams := strings.Split(strings.TrimRight(fullGenericForm, "]"), "[") - if len(genericParams) == 1 { - return nil +func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { + if spec, ok := genericsDefinitions[original][fullGenericForm]; ok { + return spec } - genericParams = strings.Split(genericParams[1], ",") - for i, p := range genericParams { - genericParams[i] = strings.TrimSpace(p) + pkgName := strings.Split(fullGenericForm, ".")[0] + genericTypeName, genericParams := splitStructName(fullGenericForm) + if genericParams == nil { + 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, ok := pkgDefs.uniqueDefinitions[genericParam] - if !ok { - return nil + arrayDepth := 0 + for { + if len(genericParam) <= 2 || genericParam[:2] != "[]" { + break + } + genericParam = genericParam[2:] + arrayDepth++ } - genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = tdef + tdef := pkgDefs.FindTypeSpec(genericParam, original.File, parseDependency) + if tdef == nil { + genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ + ArrayDepth: arrayDepth, + Name: genericParam, + } + } else { + genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ + ArrayDepth: arrayDepth, + TypeSpec: tdef, + } + } } parametrizedTypeSpec := &TypeSpecDef{ @@ -66,16 +106,34 @@ 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(fullTypeName(pkgName, genericTypeName), parametrizedTypeSpec.TypeSpec)} - parametrizedTypeSpec.TypeSpec.Name = ident + for _, def := range original.TypeSpec.TypeParams.List { + if specDef, ok := genericParamTypeDefs[def.Names[0].Name]; ok { + var prefix = "" + if specDef.ArrayDepth > 0 { + prefix = "array_" + if specDef.ArrayDepth > 1 { + prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth) + } + } + typeName = append(typeName, prefix+specDef.TypeDocName()) + } + } + ident.Name = strings.Join(typeName, "-") + 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) newStructTypeDef := &ast.StructType{ @@ -101,18 +159,111 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful } parametrizedTypeSpec.TypeSpec.Type = newStructTypeDef - + if genericsDefinitions[original] == nil { + genericsDefinitions[original] = map[string]*TypeSpecDef{} + } + genericsDefinitions[original][fullGenericForm] = parametrizedTypeSpec return parametrizedTypeSpec } -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 { - return genTypeSpec.TypeSpec.Type +// 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 + 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]*genericTypeSpec) ast.Expr { + 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() + } + case *ast.ArrayType: + return &ast.ArrayType{ + Elt: resolveType(astExpr.Elt, field, genericParamTypeDefs), + Len: astExpr.Len, + Lbrack: astExpr.Lbrack, } - } else if asArray, ok := expr.(*ast.ArrayType); ok { - return &ast.ArrayType{Elt: resolveType(asArray.Elt, field, genericParamTypeDefs), Len: asArray.Len, Lbrack: asArray.Lbrack} } 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 43837063e..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,365 +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/{post_id}": { - "get": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add a new pet to the store", - "parameters": [ - { - "type": "integer", - "format": "int64", - "description": "Some ID", - "name": "post_id", - "in": "path", - "required": true - } - ], - "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.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-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", - "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": { - "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-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) + assert.NoError(t, err) + b, err := json.MarshalIndent(p.swagger, "", " ") + assert.NoError(t, err) + assert.Equal(t, string(expected), string(b)) +} + +func TestParseGenericsNested(t *testing.T) { + t.Parallel() + + 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) + assert.NoError(t, err) + b, err := json.MarshalIndent(p.swagger, "", " ") + assert.NoError(t, err) + assert.Equal(t, string(expected), string(b)) +} + +func TestParseGenericsProperty(t *testing.T) { + t.Parallel() + + 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/operation.go b/operation.go index 29e6bf429..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 { @@ -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/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..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 } @@ -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/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) 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/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/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_arrays/main.go b/testdata/generics_arrays/main.go index cff47d013..1e5423ecd 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,8 @@ import ( // @host localhost:4000 // @basePath /api func main() { - http.HandleFunc("/posts/", api.GetPost) + 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/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_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_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/api/api.go b/testdata/generics_nested/api/api.go new file mode 100644 index 000000000..8d7f967d0 --- /dev/null +++ b/testdata/generics_nested/api/api.go @@ -0,0 +1,39 @@ +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 +// @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.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/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_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..5740be057 --- /dev/null +++ b/testdata/generics_nested/web/handler.go @@ -0,0 +1,75 @@ +package web + +import ( + "time" +) + +// 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 GenericNestedResponse[T any] struct { + // Items from the list response + Items []T + // Status of some other stuff + Status string +} + +// GenericNestedResponseMulti[T, X] +// @Description this contains a few things +type GenericNestedResponseMulti[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 +} 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/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 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"` +}