Skip to content

Commit

Permalink
Feat: Support generic with map params (#1746)
Browse files Browse the repository at this point in the history
* support generic with map params

Signed-off-by: sdghchj <sdghchj@qq.com>
  • Loading branch information
sdghchj authored Jan 29, 2024
1 parent ae7e404 commit 7147984
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 61 deletions.
130 changes: 96 additions & 34 deletions generics.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ import (
)

type genericTypeSpec struct {
ArrayDepth int
TypeSpec *TypeSpecDef
Name string
TypeSpec *TypeSpecDef
Name string
}

type formalParamType struct {
Expand All @@ -31,6 +30,87 @@ func (t *genericTypeSpec) TypeName() string {
return t.Name
}

func normalizeGenericTypeName(name string) string {
return strings.Replace(name, ".", "_", -1)
}

func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string, file *ast.File) (typeSpecDef *TypeSpecDef) {
if strings.HasPrefix(genericParam, "[]") {
typeSpecDef = pkgDefs.getTypeFromGenericParam(genericParam[2:], file)
if typeSpecDef == nil {
return nil
}
var expr ast.Expr
switch typeSpecDef.TypeSpec.Type.(type) {
case *ast.ArrayType, *ast.MapType:
expr = typeSpecDef.TypeSpec.Type
default:
name := typeSpecDef.TypeName()
expr = ast.NewIdent(name)
if _, ok := pkgDefs.uniqueDefinitions[name]; !ok {
pkgDefs.uniqueDefinitions[name] = typeSpecDef
}
}
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(string(IgnoreNameOverridePrefix) + "array_" + typeSpecDef.TypeName()),
Type: &ast.ArrayType{
Elt: expr,
},
},
Enums: typeSpecDef.Enums,
PkgPath: typeSpecDef.PkgPath,
ParentSpec: typeSpecDef.ParentSpec,
NotUnique: false,
}
}

if strings.HasPrefix(genericParam, "map[") {
parts := strings.SplitN(genericParam[4:], "]", 2)
if len(parts) != 2 {
return nil
}
typeSpecDef = pkgDefs.getTypeFromGenericParam(parts[1], file)
if typeSpecDef == nil {
return nil
}
var expr ast.Expr
switch typeSpecDef.TypeSpec.Type.(type) {
case *ast.ArrayType, *ast.MapType:
expr = typeSpecDef.TypeSpec.Type
default:
name := typeSpecDef.TypeName()
expr = ast.NewIdent(name)
if _, ok := pkgDefs.uniqueDefinitions[name]; !ok {
pkgDefs.uniqueDefinitions[name] = typeSpecDef
}
}
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(string(IgnoreNameOverridePrefix) + "map_" + parts[0] + "_" + typeSpecDef.TypeName()),
Type: &ast.MapType{
Key: ast.NewIdent(parts[0]), //assume key is string or integer
Value: expr,
},
},
Enums: typeSpecDef.Enums,
PkgPath: typeSpecDef.PkgPath,
ParentSpec: typeSpecDef.ParentSpec,
NotUnique: false,
}

}
if IsGolangPrimitiveType(genericParam) {
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(genericParam),
Type: ast.NewIdent(genericParam),
},
}
}
return pkgDefs.FindTypeSpec(genericParam, file)
}

func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string) *TypeSpecDef {
if original == nil || original.TypeSpec.TypeParams == nil || len(original.TypeSpec.TypeParams.List) == 0 {
return original
Expand Down Expand Up @@ -58,45 +138,31 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi
genericParamTypeDefs := map[string]*genericTypeSpec{}

for i, genericParam := range genericParams {
arrayDepth := 0
for {
if len(genericParam) <= 2 || genericParam[:2] != "[]" {
break
var typeDef *TypeSpecDef
if !IsGolangPrimitiveType(genericParam) {
typeDef = pkgDefs.getTypeFromGenericParam(genericParam, file)
if typeDef != nil {
genericParam = typeDef.TypeName()
if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok {
pkgDefs.uniqueDefinitions[genericParam] = typeDef
}
}
genericParam = genericParam[2:]
arrayDepth++
}

typeDef := pkgDefs.FindTypeSpec(genericParam, file)
if typeDef != nil {
genericParam = typeDef.TypeName()
if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok {
pkgDefs.uniqueDefinitions[genericParam] = typeDef
}
}

genericParamTypeDefs[formals[i].Name] = &genericTypeSpec{
ArrayDepth: arrayDepth,
TypeSpec: typeDef,
Name: genericParam,
TypeSpec: typeDef,
Name: genericParam,
}
}

name = fmt.Sprintf("%s%s-", string(IgnoreNameOverridePrefix), original.TypeName())
var nameParts []string
for _, def := range formals {
if specDef, ok := genericParamTypeDefs[def.Name]; ok {
var prefix = ""
if specDef.ArrayDepth == 1 {
prefix = "array_"
} else if specDef.ArrayDepth > 1 {
prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth)
}
nameParts = append(nameParts, prefix+specDef.TypeName())
nameParts = append(nameParts, specDef.TypeName())
}
}

name += strings.Replace(strings.Join(nameParts, "-"), ".", "_", -1)
name += normalizeGenericTypeName(strings.Join(nameParts, "-"))

if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok {
return typeSpec
Expand Down Expand Up @@ -180,11 +246,7 @@ func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast.
switch astExpr := expr.(type) {
case *ast.Ident:
if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok {
retType := pkgDefs.getParametrizedType(genTypeSpec)
for i := 0; i < genTypeSpec.ArrayDepth; i++ {
retType = &ast.ArrayType{Elt: retType}
}
return retType
return pkgDefs.getParametrizedType(genTypeSpec)
}
case *ast.ArrayType:
return &ast.ArrayType{
Expand Down
3 changes: 1 addition & 2 deletions generics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"encoding/json"
"fmt"
"go/ast"
"io/fs"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -55,6 +54,7 @@ func TestParseGenericsArrays(t *testing.T) {
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))
}
Expand Down Expand Up @@ -100,7 +100,6 @@ func TestParseGenericsProperty(t *testing.T) {
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
os.WriteFile(searchDir+"/expected.json", b, fs.ModePerm)
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
Expand Down
3 changes: 3 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,9 @@ func fullTypeName(parts ...string) string {
// fillDefinitionDescription additionally fills fields in definition (spec.Schema)
// TODO: If .go file contains many types, it may work for a long time
func fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpecDef *TypeSpecDef) {
if file == nil {
return
}
for _, astDeclaration := range file.Decls {
generalDeclaration, ok := astDeclaration.(*ast.GenDecl)
if !ok || generalDeclaration.Tok != token.TYPE {
Expand Down
1 change: 1 addition & 0 deletions testdata/generics_basic/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Foo = web.GenericResponseMulti[types.Post, types.Post]
// @Success 204 {object} Response[string, types.Field[int]]
// @Success 205 {object} Response[StringStruct, types.Field[int]]
// @Success 206 {object} Response2[string, types.Field[int],string]
// @Success 207 {object} Response[[]map[string]string, map[string][]types.Field[int]]
// @Success 222 {object} web.GenericResponseMulti[types.Post, types.Post]
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
Expand Down
43 changes: 39 additions & 4 deletions testdata/generics_basic/expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericBodyMulti-array_types_Post-array2_types_Post"
"$ref": "#/definitions/web.GenericBodyMulti-array_types_Post-array_array_types_Post"
}
}
],
Expand All @@ -101,7 +101,7 @@
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericResponseMulti-array_types_Post-array2_types_Post"
"$ref": "#/definitions/web.GenericResponseMulti-array_types_Post-array_array_types_Post"
}
}
}
Expand Down Expand Up @@ -171,6 +171,12 @@
"$ref": "#/definitions/api.Response2-string-types_Field-int-string"
}
},
"207": {
"description": "Multi-Status",
"schema": {
"$ref": "#/definitions/api.Response-array_map_string_string-map_string_array_types_Field-int"
}
},
"222": {
"description": "",
"schema": {
Expand Down Expand Up @@ -222,6 +228,26 @@
}
}
},
"api.Response-array_map_string_string-map_string_array_types_Field-int": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"meta": {
"$ref": "#/definitions/map_string_array_types.Field-int"
},
"status": {
"type": "string"
}
}
},
"api.Response-string-types_Field-int": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -258,6 +284,15 @@
}
}
},
"map_string_array_types.Field-int": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Field-int"
}
}
},
"types.Field-int": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -408,7 +443,7 @@
}
}
},
"web.GenericBodyMulti-array_types_Post-array2_types_Post": {
"web.GenericBodyMulti-array_types_Post-array_array_types_Post": {
"type": "object",
"properties": {
"data": {
Expand Down Expand Up @@ -511,7 +546,7 @@
}
}
},
"web.GenericResponseMulti-array_types_Post-array2_types_Post": {
"web.GenericResponseMulti-array_types_Post-array_array_types_Post": {
"type": "object",
"properties": {
"data": {
Expand Down
8 changes: 4 additions & 4 deletions testdata/generics_names/expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/MultiBody-array_Post-array2_Post"
"$ref": "#/definitions/MultiBody-array_Post-array_array_Post"
}
}
],
Expand All @@ -77,7 +77,7 @@
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/MultiResponse-array_Post-array2_Post"
"$ref": "#/definitions/MultiResponse-array_Post-array_array_Post"
}
}
}
Expand Down Expand Up @@ -173,7 +173,7 @@
}
}
},
"MultiBody-array_Post-array2_Post": {
"MultiBody-array_Post-array_array_Post": {
"type": "object",
"properties": {
"data": {
Expand Down Expand Up @@ -207,7 +207,7 @@
}
}
},
"MultiResponse-array_Post-array2_Post": {
"MultiResponse-array_Post-array_array_Post": {
"type": "object",
"properties": {
"data": {
Expand Down
12 changes: 6 additions & 6 deletions testdata/generics_nested/expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
"205": {
"description": "Reset Content",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post"
"$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post"
}
},
"222": {
Expand Down Expand Up @@ -203,7 +203,7 @@
}
}
},
"web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post": {
"web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post": {
"type": "object",
"properties": {
"itemOne": {
Expand All @@ -220,7 +220,7 @@
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerType-array2_types_Post"
"$ref": "#/definitions/web.GenericInnerType-array_array_types_Post"
}
}
}
Expand Down Expand Up @@ -266,7 +266,7 @@
}
}
},
"web.GenericInnerType-array2_types_Post": {
"web.GenericInnerType-array_array_types_Post": {
"type": "object",
"properties": {
"items": {
Expand Down Expand Up @@ -478,7 +478,7 @@
}
}
},
"web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post": {
"web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post": {
"type": "object",
"properties": {
"itemOne": {
Expand All @@ -493,7 +493,7 @@
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post"
"$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post"
}
},
"status": {
Expand Down
Loading

0 comments on commit 7147984

Please sign in to comment.