Skip to content

Commit 7147984

Browse files
authoredJan 29, 2024
Feat: Support generic with map params (#1746)
* support generic with map params Signed-off-by: sdghchj <sdghchj@qq.com>
1 parent ae7e404 commit 7147984

File tree

9 files changed

+178
-61
lines changed

9 files changed

+178
-61
lines changed
 

‎generics.go

+96-34
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ import (
1414
)
1515

1616
type genericTypeSpec struct {
17-
ArrayDepth int
18-
TypeSpec *TypeSpecDef
19-
Name string
17+
TypeSpec *TypeSpecDef
18+
Name string
2019
}
2120

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

33+
func normalizeGenericTypeName(name string) string {
34+
return strings.Replace(name, ".", "_", -1)
35+
}
36+
37+
func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string, file *ast.File) (typeSpecDef *TypeSpecDef) {
38+
if strings.HasPrefix(genericParam, "[]") {
39+
typeSpecDef = pkgDefs.getTypeFromGenericParam(genericParam[2:], file)
40+
if typeSpecDef == nil {
41+
return nil
42+
}
43+
var expr ast.Expr
44+
switch typeSpecDef.TypeSpec.Type.(type) {
45+
case *ast.ArrayType, *ast.MapType:
46+
expr = typeSpecDef.TypeSpec.Type
47+
default:
48+
name := typeSpecDef.TypeName()
49+
expr = ast.NewIdent(name)
50+
if _, ok := pkgDefs.uniqueDefinitions[name]; !ok {
51+
pkgDefs.uniqueDefinitions[name] = typeSpecDef
52+
}
53+
}
54+
return &TypeSpecDef{
55+
TypeSpec: &ast.TypeSpec{
56+
Name: ast.NewIdent(string(IgnoreNameOverridePrefix) + "array_" + typeSpecDef.TypeName()),
57+
Type: &ast.ArrayType{
58+
Elt: expr,
59+
},
60+
},
61+
Enums: typeSpecDef.Enums,
62+
PkgPath: typeSpecDef.PkgPath,
63+
ParentSpec: typeSpecDef.ParentSpec,
64+
NotUnique: false,
65+
}
66+
}
67+
68+
if strings.HasPrefix(genericParam, "map[") {
69+
parts := strings.SplitN(genericParam[4:], "]", 2)
70+
if len(parts) != 2 {
71+
return nil
72+
}
73+
typeSpecDef = pkgDefs.getTypeFromGenericParam(parts[1], file)
74+
if typeSpecDef == nil {
75+
return nil
76+
}
77+
var expr ast.Expr
78+
switch typeSpecDef.TypeSpec.Type.(type) {
79+
case *ast.ArrayType, *ast.MapType:
80+
expr = typeSpecDef.TypeSpec.Type
81+
default:
82+
name := typeSpecDef.TypeName()
83+
expr = ast.NewIdent(name)
84+
if _, ok := pkgDefs.uniqueDefinitions[name]; !ok {
85+
pkgDefs.uniqueDefinitions[name] = typeSpecDef
86+
}
87+
}
88+
return &TypeSpecDef{
89+
TypeSpec: &ast.TypeSpec{
90+
Name: ast.NewIdent(string(IgnoreNameOverridePrefix) + "map_" + parts[0] + "_" + typeSpecDef.TypeName()),
91+
Type: &ast.MapType{
92+
Key: ast.NewIdent(parts[0]), //assume key is string or integer
93+
Value: expr,
94+
},
95+
},
96+
Enums: typeSpecDef.Enums,
97+
PkgPath: typeSpecDef.PkgPath,
98+
ParentSpec: typeSpecDef.ParentSpec,
99+
NotUnique: false,
100+
}
101+
102+
}
103+
if IsGolangPrimitiveType(genericParam) {
104+
return &TypeSpecDef{
105+
TypeSpec: &ast.TypeSpec{
106+
Name: ast.NewIdent(genericParam),
107+
Type: ast.NewIdent(genericParam),
108+
},
109+
}
110+
}
111+
return pkgDefs.FindTypeSpec(genericParam, file)
112+
}
113+
34114
func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string) *TypeSpecDef {
35115
if original == nil || original.TypeSpec.TypeParams == nil || len(original.TypeSpec.TypeParams.List) == 0 {
36116
return original
@@ -58,45 +138,31 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi
58138
genericParamTypeDefs := map[string]*genericTypeSpec{}
59139

60140
for i, genericParam := range genericParams {
61-
arrayDepth := 0
62-
for {
63-
if len(genericParam) <= 2 || genericParam[:2] != "[]" {
64-
break
141+
var typeDef *TypeSpecDef
142+
if !IsGolangPrimitiveType(genericParam) {
143+
typeDef = pkgDefs.getTypeFromGenericParam(genericParam, file)
144+
if typeDef != nil {
145+
genericParam = typeDef.TypeName()
146+
if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok {
147+
pkgDefs.uniqueDefinitions[genericParam] = typeDef
148+
}
65149
}
66-
genericParam = genericParam[2:]
67-
arrayDepth++
68150
}
69-
70-
typeDef := pkgDefs.FindTypeSpec(genericParam, file)
71-
if typeDef != nil {
72-
genericParam = typeDef.TypeName()
73-
if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok {
74-
pkgDefs.uniqueDefinitions[genericParam] = typeDef
75-
}
76-
}
77-
78151
genericParamTypeDefs[formals[i].Name] = &genericTypeSpec{
79-
ArrayDepth: arrayDepth,
80-
TypeSpec: typeDef,
81-
Name: genericParam,
152+
TypeSpec: typeDef,
153+
Name: genericParam,
82154
}
83155
}
84156

85157
name = fmt.Sprintf("%s%s-", string(IgnoreNameOverridePrefix), original.TypeName())
86158
var nameParts []string
87159
for _, def := range formals {
88160
if specDef, ok := genericParamTypeDefs[def.Name]; ok {
89-
var prefix = ""
90-
if specDef.ArrayDepth == 1 {
91-
prefix = "array_"
92-
} else if specDef.ArrayDepth > 1 {
93-
prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth)
94-
}
95-
nameParts = append(nameParts, prefix+specDef.TypeName())
161+
nameParts = append(nameParts, specDef.TypeName())
96162
}
97163
}
98164

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

101167
if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok {
102168
return typeSpec
@@ -180,11 +246,7 @@ func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast.
180246
switch astExpr := expr.(type) {
181247
case *ast.Ident:
182248
if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok {
183-
retType := pkgDefs.getParametrizedType(genTypeSpec)
184-
for i := 0; i < genTypeSpec.ArrayDepth; i++ {
185-
retType = &ast.ArrayType{Elt: retType}
186-
}
187-
return retType
249+
return pkgDefs.getParametrizedType(genTypeSpec)
188250
}
189251
case *ast.ArrayType:
190252
return &ast.ArrayType{

‎generics_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"encoding/json"
88
"fmt"
99
"go/ast"
10-
"io/fs"
1110
"os"
1211
"path/filepath"
1312
"testing"
@@ -55,6 +54,7 @@ func TestParseGenericsArrays(t *testing.T) {
5554
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
5655
assert.NoError(t, err)
5756
b, err := json.MarshalIndent(p.swagger, "", " ")
57+
5858
assert.NoError(t, err)
5959
assert.Equal(t, string(expected), string(b))
6060
}
@@ -100,7 +100,6 @@ func TestParseGenericsProperty(t *testing.T) {
100100
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
101101
assert.NoError(t, err)
102102
b, err := json.MarshalIndent(p.swagger, "", " ")
103-
os.WriteFile(searchDir+"/expected.json", b, fs.ModePerm)
104103
assert.NoError(t, err)
105104
assert.Equal(t, string(expected), string(b))
106105
}

‎parser.go

+3
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,9 @@ func fullTypeName(parts ...string) string {
12831283
// fillDefinitionDescription additionally fills fields in definition (spec.Schema)
12841284
// TODO: If .go file contains many types, it may work for a long time
12851285
func fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpecDef *TypeSpecDef) {
1286+
if file == nil {
1287+
return
1288+
}
12861289
for _, astDeclaration := range file.Decls {
12871290
generalDeclaration, ok := astDeclaration.(*ast.GenDecl)
12881291
if !ok || generalDeclaration.Tok != token.TYPE {

‎testdata/generics_basic/api/api.go

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Foo = web.GenericResponseMulti[types.Post, types.Post]
3939
// @Success 204 {object} Response[string, types.Field[int]]
4040
// @Success 205 {object} Response[StringStruct, types.Field[int]]
4141
// @Success 206 {object} Response2[string, types.Field[int],string]
42+
// @Success 207 {object} Response[[]map[string]string, map[string][]types.Field[int]]
4243
// @Success 222 {object} web.GenericResponseMulti[types.Post, types.Post]
4344
// @Failure 400 {object} web.APIError "We need ID!!"
4445
// @Failure 404 {object} web.APIError "Can not find ID"

‎testdata/generics_basic/expected.json

+39-4
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"in": "body",
8282
"required": true,
8383
"schema": {
84-
"$ref": "#/definitions/web.GenericBodyMulti-array_types_Post-array2_types_Post"
84+
"$ref": "#/definitions/web.GenericBodyMulti-array_types_Post-array_array_types_Post"
8585
}
8686
}
8787
],
@@ -101,7 +101,7 @@
101101
"222": {
102102
"description": "",
103103
"schema": {
104-
"$ref": "#/definitions/web.GenericResponseMulti-array_types_Post-array2_types_Post"
104+
"$ref": "#/definitions/web.GenericResponseMulti-array_types_Post-array_array_types_Post"
105105
}
106106
}
107107
}
@@ -171,6 +171,12 @@
171171
"$ref": "#/definitions/api.Response2-string-types_Field-int-string"
172172
}
173173
},
174+
"207": {
175+
"description": "Multi-Status",
176+
"schema": {
177+
"$ref": "#/definitions/api.Response-array_map_string_string-map_string_array_types_Field-int"
178+
}
179+
},
174180
"222": {
175181
"description": "",
176182
"schema": {
@@ -222,6 +228,26 @@
222228
}
223229
}
224230
},
231+
"api.Response-array_map_string_string-map_string_array_types_Field-int": {
232+
"type": "object",
233+
"properties": {
234+
"data": {
235+
"type": "array",
236+
"items": {
237+
"type": "object",
238+
"additionalProperties": {
239+
"type": "string"
240+
}
241+
}
242+
},
243+
"meta": {
244+
"$ref": "#/definitions/map_string_array_types.Field-int"
245+
},
246+
"status": {
247+
"type": "string"
248+
}
249+
}
250+
},
225251
"api.Response-string-types_Field-int": {
226252
"type": "object",
227253
"properties": {
@@ -258,6 +284,15 @@
258284
}
259285
}
260286
},
287+
"map_string_array_types.Field-int": {
288+
"type": "object",
289+
"additionalProperties": {
290+
"type": "array",
291+
"items": {
292+
"$ref": "#/definitions/types.Field-int"
293+
}
294+
}
295+
},
261296
"types.Field-int": {
262297
"type": "object",
263298
"properties": {
@@ -408,7 +443,7 @@
408443
}
409444
}
410445
},
411-
"web.GenericBodyMulti-array_types_Post-array2_types_Post": {
446+
"web.GenericBodyMulti-array_types_Post-array_array_types_Post": {
412447
"type": "object",
413448
"properties": {
414449
"data": {
@@ -511,7 +546,7 @@
511546
}
512547
}
513548
},
514-
"web.GenericResponseMulti-array_types_Post-array2_types_Post": {
549+
"web.GenericResponseMulti-array_types_Post-array_array_types_Post": {
515550
"type": "object",
516551
"properties": {
517552
"data": {

‎testdata/generics_names/expected.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"in": "body",
6464
"required": true,
6565
"schema": {
66-
"$ref": "#/definitions/MultiBody-array_Post-array2_Post"
66+
"$ref": "#/definitions/MultiBody-array_Post-array_array_Post"
6767
}
6868
}
6969
],
@@ -77,7 +77,7 @@
7777
"222": {
7878
"description": "",
7979
"schema": {
80-
"$ref": "#/definitions/MultiResponse-array_Post-array2_Post"
80+
"$ref": "#/definitions/MultiResponse-array_Post-array_array_Post"
8181
}
8282
}
8383
}
@@ -173,7 +173,7 @@
173173
}
174174
}
175175
},
176-
"MultiBody-array_Post-array2_Post": {
176+
"MultiBody-array_Post-array_array_Post": {
177177
"type": "object",
178178
"properties": {
179179
"data": {
@@ -207,7 +207,7 @@
207207
}
208208
}
209209
},
210-
"MultiResponse-array_Post-array2_Post": {
210+
"MultiResponse-array_Post-array_array_Post": {
211211
"type": "object",
212212
"properties": {
213213
"data": {

‎testdata/generics_nested/expected.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
"205": {
120120
"description": "Reset Content",
121121
"schema": {
122-
"$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post"
122+
"$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post"
123123
}
124124
},
125125
"222": {
@@ -203,7 +203,7 @@
203203
}
204204
}
205205
},
206-
"web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post": {
206+
"web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post": {
207207
"type": "object",
208208
"properties": {
209209
"itemOne": {
@@ -220,7 +220,7 @@
220220
"items": {
221221
"type": "array",
222222
"items": {
223-
"$ref": "#/definitions/web.GenericInnerType-array2_types_Post"
223+
"$ref": "#/definitions/web.GenericInnerType-array_array_types_Post"
224224
}
225225
}
226226
}
@@ -266,7 +266,7 @@
266266
}
267267
}
268268
},
269-
"web.GenericInnerType-array2_types_Post": {
269+
"web.GenericInnerType-array_array_types_Post": {
270270
"type": "object",
271271
"properties": {
272272
"items": {
@@ -478,7 +478,7 @@
478478
}
479479
}
480480
},
481-
"web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post": {
481+
"web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post": {
482482
"type": "object",
483483
"properties": {
484484
"itemOne": {
@@ -493,7 +493,7 @@
493493
"description": "ItemsTwo is the second thing",
494494
"type": "array",
495495
"items": {
496-
"$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post"
496+
"$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post"
497497
}
498498
},
499499
"status": {

‎testdata/generics_property/expected.json

+26-9
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@
213213
}
214214
},
215215
"value4": {
216-
"$ref": "#/definitions/types.SubField1-api_Person-string"
216+
"$ref": "#/definitions/types.SubField1-array_api_Person-string"
217217
}
218218
}
219219
},
@@ -242,7 +242,7 @@
242242
}
243243
},
244244
"value4": {
245-
"$ref": "#/definitions/types.SubField1-types_Post-string"
245+
"$ref": "#/definitions/types.SubField1-array_types_Post-string"
246246
}
247247
}
248248
},
@@ -348,44 +348,61 @@
348348
}
349349
}
350350
},
351-
"types.SubField1-string-string": {
351+
"types.SubField1-array_api_Person-string": {
352352
"type": "object",
353353
"properties": {
354354
"subValue1": {
355+
"type": "array",
356+
"items": {
357+
"$ref": "#/definitions/api.Person"
358+
}
359+
},
360+
"subValue2": {
355361
"type": "string"
362+
}
363+
}
364+
},
365+
"types.SubField1-array_types_Post-string": {
366+
"type": "object",
367+
"properties": {
368+
"subValue1": {
369+
"type": "array",
370+
"items": {
371+
"$ref": "#/definitions/types.Post"
372+
}
356373
},
357374
"subValue2": {
358375
"type": "string"
359376
}
360377
}
361378
},
362-
"types.SubField1-types_Field-api_Person-string": {
379+
"types.SubField1-string-string": {
363380
"type": "object",
364381
"properties": {
365382
"subValue1": {
366-
"$ref": "#/definitions/types.Field-api_Person"
383+
"type": "string"
367384
},
368385
"subValue2": {
369386
"type": "string"
370387
}
371388
}
372389
},
373-
"types.SubField1-types_Field-string-string": {
390+
"types.SubField1-types_Field-api_Person-string": {
374391
"type": "object",
375392
"properties": {
376393
"subValue1": {
377-
"$ref": "#/definitions/types.Field-string"
394+
"$ref": "#/definitions/types.Field-api_Person"
378395
},
379396
"subValue2": {
380397
"type": "string"
381398
}
382399
}
383400
},
384-
"types.SubField1-types_Post-string": {
401+
"types.SubField1-types_Field-string-string": {
385402
"type": "object",
386403
"properties": {
387404
"subValue1": {
388-
"$ref": "#/definitions/types.Post"
405+
"$ref": "#/definitions/types.Field-string"
389406
},
390407
"subValue2": {
391408
"type": "string"

‎types.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type TypeSpecDef struct {
3535

3636
// Name the name of the typeSpec.
3737
func (t *TypeSpecDef) Name() string {
38-
if t.TypeSpec != nil {
38+
if t.TypeSpec != nil && t.TypeSpec.Name != nil {
3939
return t.TypeSpec.Name.Name
4040
}
4141

@@ -71,7 +71,7 @@ func (t *TypeSpecDef) TypeName() string {
7171
return r
7272
}, t.PkgPath)
7373
names = append(names, pkgPath)
74-
} else {
74+
} else if t.File != nil {
7575
names = append(names, t.File.Name.Name)
7676
}
7777
if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil {

0 commit comments

Comments
 (0)
Please sign in to comment.