From 1b5767913843be67bc2e46fc294d8812647c19bf Mon Sep 17 00:00:00 2001 From: berk-karaal Date: Thu, 5 Sep 2024 18:27:17 +0300 Subject: [PATCH 1/2] fix(packages): set SchemaName fields of function scoped types --- packages.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages.go b/packages.go index 479b26396..74439d442 100644 --- a/packages.go +++ b/packages.go @@ -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 @@ -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 { From 3505efeedb2ebe9c1c5c5ecd79f2aa4f617d9b51 Mon Sep 17 00:00:00 2001 From: berk-karaal Date: Thu, 5 Sep 2024 18:32:56 +0300 Subject: [PATCH 2/2] test: add tests for generics used with function scoped types --- generics_test.go | 16 + testdata/generics_function_scoped/api/api.go | 75 +++++ .../generics_function_scoped/expected.json | 279 ++++++++++++++++++ testdata/generics_function_scoped/main.go | 20 ++ .../types/response.go | 12 + 5 files changed, 402 insertions(+) create mode 100644 testdata/generics_function_scoped/api/api.go create mode 100644 testdata/generics_function_scoped/expected.json create mode 100644 testdata/generics_function_scoped/main.go create mode 100644 testdata/generics_function_scoped/types/response.go diff --git a/generics_test.go b/generics_test.go index 033bcf546..fecaa4d4e 100644 --- a/generics_test.go +++ b/generics_test.go @@ -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), diff --git a/testdata/generics_function_scoped/api/api.go b/testdata/generics_function_scoped/api/api.go new file mode 100644 index 000000000..904bce33f --- /dev/null +++ b/testdata/generics_function_scoped/api/api.go @@ -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]{} +} diff --git a/testdata/generics_function_scoped/expected.json b/testdata/generics_function_scoped/expected.json new file mode 100644 index 000000000..3fb99fa01 --- /dev/null +++ b/testdata/generics_function_scoped/expected.json @@ -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" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/generics_function_scoped/main.go b/testdata/generics_function_scoped/main.go new file mode 100644 index 000000000..7dd44c323 --- /dev/null +++ b/testdata/generics_function_scoped/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "net/http" + + "github.com/swaggo/swag/testdata/generics_function_scoped/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server. +// @host localhost:8080 +// @basePath /api +func main() { + http.HandleFunc("/", api.GetGeneric) + http.HandleFunc("/renamed", api.GetGenericRenamed) + http.HandleFunc("/multi", api.GetGenericMulti) + http.HandleFunc("/multi-renamed", api.GetGenericMulti) + http.ListenAndServe(":8080", nil) +} diff --git a/testdata/generics_function_scoped/types/response.go b/testdata/generics_function_scoped/types/response.go new file mode 100644 index 000000000..d12e5324c --- /dev/null +++ b/testdata/generics_function_scoped/types/response.go @@ -0,0 +1,12 @@ +package types + +type GenericResponse[T any] struct { + Status string `json:"status"` + Data T `json:"data"` +} + +type GenericMultiResponse[T any, X any] struct { + Status string `json:"status"` + DataT T `json:"data_t"` + DataX X `json:"data_x"` +}