-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add basic generics support (#1225)
- Loading branch information
Showing
9 changed files
with
444 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
//go:build go1.18 | ||
// +build go1.18 | ||
|
||
package swag | ||
|
||
import ( | ||
"go/ast" | ||
"strings" | ||
) | ||
|
||
func typeSpecFullName(typeSpecDef *TypeSpecDef) string { | ||
fullName := typeSpecDef.FullName() | ||
|
||
if typeSpecDef.TypeSpec.TypeParams != nil { | ||
fullName = fullName + "[" | ||
for i, typeParam := range typeSpecDef.TypeSpec.TypeParams.List { | ||
if i > 0 { | ||
fullName = fullName + "-" | ||
} | ||
|
||
fullName = fullName + typeParam.Names[0].Name | ||
} | ||
fullName = fullName + "]" | ||
} | ||
|
||
return fullName | ||
} | ||
|
||
func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string) *TypeSpecDef { | ||
genericParams := strings.Split(strings.TrimRight(fullGenericForm, "]"), "[") | ||
if len(genericParams) == 1 { | ||
return nil | ||
} | ||
|
||
genericParams = strings.Split(genericParams[1], ",") | ||
for i, p := range genericParams { | ||
genericParams[i] = strings.TrimSpace(p) | ||
} | ||
genericParamTypeDefs := map[string]*TypeSpecDef{} | ||
|
||
if len(genericParams) != len(original.TypeSpec.TypeParams.List) { | ||
return nil | ||
} | ||
|
||
for i, genericParam := range genericParams { | ||
tdef, ok := pkgDefs.uniqueDefinitions[genericParam] | ||
if !ok { | ||
return nil | ||
} | ||
|
||
genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = tdef | ||
} | ||
|
||
parametrizedTypeSpec := &TypeSpecDef{ | ||
File: original.File, | ||
PkgPath: original.PkgPath, | ||
TypeSpec: &ast.TypeSpec{ | ||
Doc: original.TypeSpec.Doc, | ||
Comment: original.TypeSpec.Comment, | ||
Assign: original.TypeSpec.Assign, | ||
}, | ||
} | ||
|
||
ident := &ast.Ident{ | ||
NamePos: original.TypeSpec.Name.NamePos, | ||
Obj: original.TypeSpec.Name.Obj, | ||
} | ||
|
||
genNameParts := strings.Split(fullGenericForm, "[") | ||
if strings.Contains(genNameParts[0], ".") { | ||
genNameParts[0] = strings.Split(genNameParts[0], ".")[1] | ||
} | ||
|
||
ident.Name = genNameParts[0] + "-" + strings.Replace(strings.Join(genericParams, "-"), ".", "_", -1) | ||
ident.Name = strings.Replace(strings.Replace(ident.Name, "\t", "", -1), " ", "", -1) | ||
|
||
parametrizedTypeSpec.TypeSpec.Name = ident | ||
|
||
origStructType := original.TypeSpec.Type.(*ast.StructType) | ||
|
||
newStructTypeDef := &ast.StructType{ | ||
Struct: origStructType.Struct, | ||
Incomplete: origStructType.Incomplete, | ||
Fields: &ast.FieldList{ | ||
Opening: origStructType.Fields.Opening, | ||
Closing: origStructType.Fields.Closing, | ||
}, | ||
} | ||
|
||
for _, field := range origStructType.Fields.List { | ||
newField := &ast.Field{ | ||
Doc: field.Doc, | ||
Names: field.Names, | ||
Tag: field.Tag, | ||
Comment: field.Comment, | ||
} | ||
if genTypeSpec, ok := genericParamTypeDefs[field.Type.(*ast.Ident).Name]; ok { | ||
newField.Type = genTypeSpec.TypeSpec.Type | ||
} else { | ||
newField.Type = field.Type | ||
} | ||
|
||
newStructTypeDef.Fields.List = append(newStructTypeDef.Fields.List, newField) | ||
} | ||
|
||
parametrizedTypeSpec.TypeSpec.Type = newStructTypeDef | ||
|
||
return parametrizedTypeSpec | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//go:build !go1.18 | ||
// +build !go1.18 | ||
|
||
package swag | ||
|
||
func typeSpecFullName(typeSpecDef *TypeSpecDef) string { | ||
return typeSpecDef.FullName() | ||
} | ||
|
||
func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string) *TypeSpecDef { | ||
return original | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
//go:build go1.18 | ||
// +build go1.18 | ||
|
||
package swag | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
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" | ||
p := New() | ||
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) | ||
assert.NoError(t, err) | ||
b, _ := json.MarshalIndent(p.swagger, "", " ") | ||
os.WriteFile("testdata/generics_basic/swagger.json", b, 0644) | ||
assert.Equal(t, expected, string(b)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.