Skip to content

Commit

Permalink
Fix generics used with function scoped types (#1883)
Browse files Browse the repository at this point in the history
  • Loading branch information
berk-karaal authored Sep 5, 2024
1 parent 10030b0 commit 83fe3ca
Show file tree
Hide file tree
Showing 6 changed files with 406 additions and 0 deletions.
16 changes: 16 additions & 0 deletions generics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,22 @@ func TestParseGenericsPackageAlias(t *testing.T) {
assert.Equal(t, string(expected), string(b))
}

func TestParseGenericsFunctionScoped(t *testing.T) {
t.Parallel()

searchDir := "testdata/generics_function_scoped"
expected, err := os.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 TestParametrizeStruct(t *testing.T) {
pd := PackagesDefinitions{
packages: make(map[string]*PackageDefinitions),
Expand Down
4 changes: 4 additions & 0 deletions packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
pkgDefs.uniqueDefinitions[fullName] = nil
anotherTypeDef.NotUnique = true
pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef
anotherTypeDef.SetSchemaName()

typeSpecDef.NotUnique = true
fullName = typeSpecDef.TypeName()
pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
Expand All @@ -261,6 +263,8 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
functionScopedTypes[typeSpec.Name.Name] = typeSpecDef
}

typeSpecDef.SetSchemaName()

if pkgDefs.packages[typeSpecDef.PkgPath] == nil {
pkgDefs.packages[typeSpecDef.PkgPath] = NewPackageDefinitions(astFile.Name.Name, typeSpecDef.PkgPath).AddTypeSpec(fullName, typeSpecDef)
} else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[fullName]; !ok {
Expand Down
75 changes: 75 additions & 0 deletions testdata/generics_function_scoped/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package api

import (
"net/http"

"github.com/swaggo/swag/testdata/generics_function_scoped/types"
)

// @Summary Generic Response
// @Produce json
// @Success 200 {object} types.GenericResponse[api.GetGeneric.User]
// @Success 201 {object} types.GenericResponse[api.GetGeneric.Post]
// @Router / [get]
func GetGeneric(w http.ResponseWriter, r *http.Request) {
type User struct {
Username int `json:"username"`
Email string `json:"email"`
}
type Post struct {
Slug int `json:"slug"`
Title string `json:"title"`
}

_ = types.GenericResponse[any]{}
}

// @Summary Generic Response With Custom Type Names
// @Produce json
// @Success 200 {object} types.GenericResponse[api.GetGenericRenamed.User]
// @Success 201 {object} types.GenericResponse[api.GetGenericRenamed.Post]
// @Router /renamed [get]
func GetGenericRenamed(w http.ResponseWriter, r *http.Request) {
type User struct {
Username int `json:"username"`
Email string `json:"email"`
} // @Name RenamedUserData
type Post struct {
Slug int `json:"slug"`
Title string `json:"title"`
} // @Name RenamedPostData

_ = types.GenericResponse[any]{}
}

// @Summary Multiple Generic Response
// @Produce json
// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructA, api.GetGenericMulti.MyStructB]
// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructB, api.GetGenericMulti.MyStructA]
// @Router /multi [get]
func GetGenericMulti(w http.ResponseWriter, r *http.Request) {
type MyStructA struct {
SomeFieldA string `json:"some_field_a"`
}
type MyStructB struct {
SomeFieldB string `json:"some_field_b"`
}

_ = types.GenericMultiResponse[any, any]{}
}

// @Summary Multiple Generic Response With Custom Type Names
// @Produce json
// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructA, api.GetGenericMultiRenamed.MyStructB]
// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructB, api.GetGenericMultiRenamed.MyStructA]
// @Router /multi-renamed [get]
func GetGenericMultiRenamed(w http.ResponseWriter, r *http.Request) {
type MyStructA struct {
SomeFieldA string `json:"some_field_a"`
} // @Name NameForMyStructA
type MyStructB struct {
SomeFieldB string `json:"some_field_b"`
} // @Name NameForMyStructB

_ = types.GenericMultiResponse[any, any]{}
}
279 changes: 279 additions & 0 deletions testdata/generics_function_scoped/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:8080",
"basePath": "/api",
"paths": {
"/": {
"get": {
"produces": [
"application/json"
],
"summary": "Generic Response",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.GenericResponse-api_GetGeneric_User"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/types.GenericResponse-api_GetGeneric_Post"
}
}
}
}
},
"/multi": {
"get": {
"produces": [
"application/json"
],
"summary": "Multiple Generic Response",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA"
}
}
}
}
},
"/multi-renamed": {
"get": {
"produces": [
"application/json"
],
"summary": "Multiple Generic Response With Custom Type Names",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructA-NameForMyStructB"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructB-NameForMyStructA"
}
}
}
}
},
"/renamed": {
"get": {
"produces": [
"application/json"
],
"summary": "Generic Response With Custom Type Names",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.GenericResponse-RenamedUserData"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/types.GenericResponse-RenamedPostData"
}
}
}
}
}
},
"definitions": {
"NameForMyStructA": {
"type": "object",
"properties": {
"some_field_a": {
"type": "string"
}
}
},
"NameForMyStructB": {
"type": "object",
"properties": {
"some_field_b": {
"type": "string"
}
}
},
"RenamedPostData": {
"type": "object",
"properties": {
"slug": {
"type": "integer"
},
"title": {
"type": "string"
}
}
},
"RenamedUserData": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"username": {
"type": "integer"
}
}
},
"api.GetGeneric.Post": {
"type": "object",
"properties": {
"slug": {
"type": "integer"
},
"title": {
"type": "string"
}
}
},
"api.GetGeneric.User": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"username": {
"type": "integer"
}
}
},
"api.GetGenericMulti.MyStructA": {
"type": "object",
"properties": {
"some_field_a": {
"type": "string"
}
}
},
"api.GetGenericMulti.MyStructB": {
"type": "object",
"properties": {
"some_field_b": {
"type": "string"
}
}
},
"types.GenericMultiResponse-NameForMyStructA-NameForMyStructB": {
"type": "object",
"properties": {
"data_t": {
"$ref": "#/definitions/NameForMyStructA"
},
"data_x": {
"$ref": "#/definitions/NameForMyStructB"
},
"status": {
"type": "string"
}
}
},
"types.GenericMultiResponse-NameForMyStructB-NameForMyStructA": {
"type": "object",
"properties": {
"data_t": {
"$ref": "#/definitions/NameForMyStructB"
},
"data_x": {
"$ref": "#/definitions/NameForMyStructA"
},
"status": {
"type": "string"
}
}
},
"types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB": {
"type": "object",
"properties": {
"data_t": {
"$ref": "#/definitions/api.GetGenericMulti.MyStructA"
},
"data_x": {
"$ref": "#/definitions/api.GetGenericMulti.MyStructB"
},
"status": {
"type": "string"
}
}
},
"types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA": {
"type": "object",
"properties": {
"data_t": {
"$ref": "#/definitions/api.GetGenericMulti.MyStructB"
},
"data_x": {
"$ref": "#/definitions/api.GetGenericMulti.MyStructA"
},
"status": {
"type": "string"
}
}
},
"types.GenericResponse-RenamedPostData": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/RenamedPostData"
},
"status": {
"type": "string"
}
}
},
"types.GenericResponse-RenamedUserData": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/RenamedUserData"
},
"status": {
"type": "string"
}
}
},
"types.GenericResponse-api_GetGeneric_Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/api.GetGeneric.Post"
},
"status": {
"type": "string"
}
}
},
"types.GenericResponse-api_GetGeneric_User": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/api.GetGeneric.User"
},
"status": {
"type": "string"
}
}
}
}
}
Loading

0 comments on commit 83fe3ca

Please sign in to comment.