go-restful-openapi icon indicating copy to clipboard operation
go-restful-openapi copied to clipboard

Definition builder fails reference on generic struct

Open tlyons-cs opened this issue 2 years ago • 4 comments

Given a struct created with generics the swagger doc compiles correctly but will fail to reference the definition correctly.

Example:

type Response[T any] struct{
  MetaData MetaData `json:"meta"`
  Objects []T `json:"objects"
}

type MyObject struct{
  Foo string `json:"foo"`
  Bar int `json:"bar"`
}

Passing in a variable of type module.Response[module.MyObject]{} will yield a definition for module.Response[module.MyObject] as well as module.MyObject. But the reference to this definition will use the key #/definitions/module.Response%5Bmodule.MyObject%5D.

The proposed change would be to sanitize the definition keys to be [->%5B and ] ->%5D

tlyons-cs avatar Jul 19 '23 23:07 tlyons-cs

Thank you for reporting this issue. It is the first time I learn about someone using generics with this pkg. I will try to come up with a solution.

On Thu, 20 Jul 2023 at 01:12, tlyons-cs @.***> wrote:

Given a struct created with generics the swagger doc compiles correctly but will fail to reference the definition correctly.

Example:

type Response[T any] struct{ MetaData MetaData json:"meta" Objects []T `json:"objects" }

type MyObject struct{ Foo string json:"foo" Bar int json:"bar" }

Passing in a variable of type module.Response[module.MyObject]{} will yield a definition for module.Response[module.MyObject] as well as module.MyObject. But the reference to this definition will use the key #/definitions/module.Response%5Bmodule.MyObject%5D.

The proposed change would be to sanitize the definition keys to be [->%5B and ] ->%5D

— Reply to this email directly, view it on GitHub https://github.com/emicklei/go-restful-openapi/issues/109, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAFHRDGZ2TTBK54EDY6NU6LXRBSXDANCNFSM6AAAAAA2QTGDIU . You are receiving this because you are subscribed to this thread.Message ID: @.***>

-- Met vriendelijke groet, Kind regards,

Ernest Micklei

Try out my music project Melrōse https://melrōse.org

emicklei avatar Jul 21 '23 06:07 emicklei

@emicklei I have also encountered a similar problem. After using go generics, reflect.Type.Name() displays the passed type object as the full pathname, which causes swagger ui to not resolve "/" when using (schema.$ref)reference.

Example:

package v1
type APIResponse[T any] struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Result  T      `json:"result"`
}

type AuthData struct {
	Roles  map[string]string `json:"roles"`
	Token  string            `json:"token"`
	Status AuthStatus        `json:"status"`
}
package sgxxx

a.Ws.Route(a.Ws.POST("/auth").To(auth.Login).
		Doc("登陆").
		Metadata(restfulspec.KeyOpenAPITags, tags).
		Reads(v1.AuthUserLoginReq{}).
		Returns(200, "ok", v1.APIResponse[v1.AuthData]{}),
	)

build swagger doc result:

paths:
  /v1/apis/auth:
    post:
     ........
        '200':
          description: ok
          schema:
            $ref: '#/definitions/v1.APIResponse%5Bgithub.com/xxx/pkg/v1.AuthData%5D'
definitions:
  v1.APIResponse[github.com/xxx/pkg/v1.AuthData]:

Swagger UI ERROR INFO:

Semantic error at paths./v1/apis/auth.post.responses.200.schema.$ref
$refs must reference a valid location in the document

FIX: I change "/" to "." Swagger UI does not report errors

paths:
  /v1/apis/auth:
   .........
      responses:
        '200':
          description: ok
          schema:
            $ref: '#/definitions/v1.APIResponse%5Bgithub.com.xxx.pkg.v1.AuthData%5D'


definitions:
  v1.APIResponse[github.com.xxx.pkg.v1.AuthData]:

can I submit a PR to fix it, and modify the corresponding content when the keyFrom() is parsed.

HugoWw avatar Mar 29 '24 09:03 HugoWw

thank you for reporting this. Yes, please help get this fixed so that generics are supported too.

emicklei avatar Apr 01 '24 19:04 emicklei

@emicklei I have also encountered a similar problem. After using go generics, reflect.Type.Name() displays the passed type object as the full pathname, which causes swagger ui to not resolve "/" when using (schema.$ref)reference.

Example:

package v1
type APIResponse[T any] struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Result  T      `json:"result"`
}

type AuthData struct {
	Roles  map[string]string `json:"roles"`
	Token  string            `json:"token"`
	Status AuthStatus        `json:"status"`
}
package sgxxx

a.Ws.Route(a.Ws.POST("/auth").To(auth.Login).
		Doc("登陆").
		Metadata(restfulspec.KeyOpenAPITags, tags).
		Reads(v1.AuthUserLoginReq{}).
		Returns(200, "ok", v1.APIResponse[v1.AuthData]{}),
	)

build swagger doc result:

paths:
  /v1/apis/auth:
    post:
     ........
        '200':
          description: ok
          schema:
            $ref: '#/definitions/v1.APIResponse%5Bgithub.com/xxx/pkg/v1.AuthData%5D'
definitions:
  v1.APIResponse[github.com/xxx/pkg/v1.AuthData]:

Swagger UI ERROR INFO:

Semantic error at paths./v1/apis/auth.post.responses.200.schema.$ref
$refs must reference a valid location in the document

FIX: I change "/" to "." Swagger UI does not report errors

paths:
  /v1/apis/auth:
   .........
      responses:
        '200':
          description: ok
          schema:
            $ref: '#/definitions/v1.APIResponse%5Bgithub.com.xxx.pkg.v1.AuthData%5D'


definitions:
  v1.APIResponse[github.com.xxx.pkg.v1.AuthData]:

can I submit a PR to fix it, and modify the corresponding content when the keyFrom() is parsed.

@emicklei

I found that in the library, I can resolve this by customizing the ModelTypeNameHandler method. This is how I addressed it.

config := restfulspec.Config{
		Host:                                "127.0.0.1",
		APIPath:                           "/apidoc.json",
		WebServices:                   resources.Default.RegisteredWebServices(),
		DisableCORS:                   false,
		PostBuildSwaggerObjectHandler: enrichSwaggerObject,
		ModelTypeNameHandler:          enrichModelTypeName,
	}
func enrichModelTypeName(st reflect.Type) (string, bool) {
	var output string
	key := st.String() //v1.APIResponse[github.com.HugoWw/x_apiserver/pkg/resource/v1.AuthData]

	pattern := `\[(.*?)\]`
	re := regexp.MustCompile(pattern)
	match := re.FindStringSubmatch(key)

	if len(match) > 0 {
		// get submatch
		// Example:
		// match[1] = github.com.HugoWw/x_apiserver/pkg/resource/v1.AuthData
		content := match[1]
		end := strings.LastIndex(content, "/")
		replaceContent := content[end+1:]

		output = re.ReplaceAllString(key, "["+replaceContent+"]")
	} else {
		return "", false
	}

	return output, true
}

The generated swag documents are as follows

paths:
  /v1/apis/auth:
    post:
   ...........
      responses:
        '200':
          description: ok
          schema:
            $ref: '#/definitions/v1.APIResponse%5Bv1.AuthData%5D'

definitions:
  v1.APIResponse[v1.AuthData]:
   ........

HugoWw avatar Apr 02 '24 02:04 HugoWw