Closed ivothgle closed 4 weeks ago
GF 目前处理响应只有一个结果 200
See: https://github.com/gogf/gf/blob/6e5ce98d23c65fd7283c84382abfde62a29f9930/net/goai/goai_path.go#L239
无法自定义状态码,在一些 2xx
状态中代表不同含义,参考: 201 Created
需要根据这些状态码响应
201
202
目前 GF 没有支持这个,有一个想法参考:
在 xxxRes.Meta
中设置 successStatusCode
或 errorStatusCode
标签,框架可以解析且设置对应的 HTTP Status Code
文档
server:
address: ":8199"
openapiPath: "/api.json"
swaggerPath: "/swagger"
package main
import (
"context"
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/goai"
)
type HelloCreateReq struct {
g.Meta `path:"/hello" method:"POST"`
Name string `v:"required" dc:"Your name"`
Age int `v:"required|between:1,200" dc:"Your age"`
}
type HelloCreateRes struct {
g.Meta `successStatusCode:"201" errorStatusCode:"400,500"`
ID uint64 `json:"id,string" dc:"ID"`
}
type HelloGetReq struct {
g.Meta `path:"/hello/{id}" method:"GET"`
// ID ID
ID uint64 `json:"id,string" dc:"ID" in:"path" v:"required"`
}
type HelloGetRes struct {
g.Meta `errorStatusCode:"400,500"`
ID uint64 `json:"id,string" dc:"ID"`
Name string `json:"name" dc:"Name"`
Age int `json:"age" dc:"Age"`
}
type Hello struct{}
func (c *Hello) Create(ctx context.Context, r *HelloCreateReq) (*HelloCreateRes, error) {
return &HelloCreateRes{ID: 1}, nil
}
func (c *Hello) Get(ctx context.Context, r *HelloGetReq) (*HelloGetRes, error) {
return &HelloGetRes{
ID: r.ID,
Name: "john",
Age: 18,
}, nil
}
func main() {
s := g.Server()
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(Hello),
)
})
// 设置响应数据结构
oai := s.GetOpenApi()
oai.Config.CommonResponse = ghttp.DefaultHandlerResponse{}
oai.Config.CommonResponseDataField = "Data"
// 错误的响应
dataResp := &goai.Schemas{}
dataResp.Set("code", goai.SchemaRef{
Value: &goai.Schema{
Type: "integer",
Format: "int32",
Title: "业务状态码",
Description: "业务状态码",
},
})
dataResp.Set("message", goai.SchemaRef{
Value: &goai.Schema{
Type: "string",
Title: "业务状态描述",
Description: "业务状态描述",
},
})
dataResp.Set("data", goai.SchemaRef{
Value: &goai.Schema{
Type: "object",
Title: "业务数据",
Description: "业务数据",
Nullable: true,
},
})
oai.Components.Schemas.Set("bizmodel.HTTPResponse", goai.SchemaRef{
Value: &goai.Schema{
Type: "object",
Properties: dataResp,
},
})
// 错误的响应状态码匹配
oai.Components.Responses = goai.Responses{
// 也许我们需要将 key 的空格去掉???
http.StatusText(http.StatusBadRequest): goai.ResponseRef{
Value: &goai.Response{
Description: "BadRequest",
Content: map[string]goai.MediaType{
"application/json": {Schema: &goai.SchemaRef{Ref: "bizmodel.HTTPResponse"}},
},
},
},
http.StatusText(http.StatusNotFound): goai.ResponseRef{
Value: &goai.Response{
Description: "NotFound",
Content: map[string]goai.MediaType{
"application/json": {Schema: &goai.SchemaRef{Ref: "bizmodel.HTTPResponse"}},
},
},
},
http.StatusText(http.StatusInternalServerError): goai.ResponseRef{
Value: &goai.Response{
Description: "InternalServerError",
Content: map[string]goai.MediaType{
"application/json": {Schema: &goai.SchemaRef{Ref: "bizmodel.HTTPResponse"}},
},
},
},
}
s.Run()
}
HelloCreateRes
中设置 successStatusCode
为 201
,那么应该生成 201
文档,也设置了 400
和 500
且这两个 response
的文档可以在文档的 components.responses
中通过 HTTP Status text
匹配到,也应该设置到文档中HelloGetRes
中没有设置 successStatusCode
则应该默认生成 200
的文档 Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
GF currently processes responses with only one result
200
See: https://github.com/gogf/gf/blob/6e5ce98d23c65fd7283c84382abfde62a29f9930/net/goai/goai_path.go#L239
The status code cannot be customized and represents different meanings in some 2xx
states. Reference: 201 Created
Need to respond according to these status codes
201
202
Currently GF does not support this, here is an idea:
Currently GF does not support this. One idea is to set the successStatusCode
or errorStatusCode
tag in xxxRes.Meta
and set the corresponding HTTP Status Code
document
server:
address: ":8199"
openapiPath: "/api.json"
swaggerPath: "/swagger"
package main
import (
"context"
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/goai"
)
type HelloCreateReq struct {
g.Meta `path:"/hello" method:"POST"`
Name string `v:"required" dc:"Your name"`
Age int `v:"required|between:1,200" dc:"Your age"`
}
type HelloCreateRes struct {
g.Meta `successStatusCode:"201" errorStatusCode:"400,500"`
ID uint64 `json:"id,string" dc:"ID"`
}
type HelloGetReq struct {
g.Meta `path:"/hello/{id}" method:"GET"`
// ID ID
ID uint64 `json:"id,string" dc:"ID" in:"path" v:"required"`
}
type HelloGetRes struct {
g.Meta `errorStatusCode:"400,500"`
ID uint64 `json:"id,string" dc:"ID"`
Name string `json:"name" dc:"Name"`
Age int `json:"age" dc:"Age"`
}
type Hello struct{}
func (c *Hello) Create(ctx context.Context, r *HelloCreateReq) (*HelloCreateRes, error) {
return &HelloCreateRes{ID: 1}, nil
}
func (c *Hello) Get(ctx context.Context, r *HelloGetReq) (*HelloGetRes, error) {
return &HelloGetRes{
ID: r.ID,
Name: "john",
Age: 18,
}, nil
}
func main() {
s := g.Server()
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(Hello),
)
})
//Set the response data structure
oai := s.GetOpenApi()
oai.Config.CommonResponse = ghttp.DefaultHandlerResponse{}
oai.Config.CommonResponseDataField = "Data"
//wrong response
dataResp := &goai.Schemas{}
dataResp.Set("code", goai.SchemaRef{
Value: &goai.Schema{
Type: "integer",
Format: "int32",
Title: "Business Status Code",
Description: "Business status code",
},
})
dataResp.Set("message", goai.SchemaRef{
Value: &goai.Schema{
Type: "string",
Title: "Business Status Description",
Description: "Business status description",
},
})
dataResp.Set("data", goai.SchemaRef{
Value: &goai.Schema{
Type: "object",
Title: "Business Data",
Description: "Business data",
Nullable: true,
},
})
oai.Components.Schemas.Set("bizmodel.HTTPResponse", goai.SchemaRef{
Value: &goai.Schema{
Type: "object",
Properties: dataResp,
},
})
// Wrong response status code match
oai.Components.Responses = goai.Responses{
// Maybe we need to remove the spaces from key? ? ?
http.StatusText(http.StatusBadRequest): goai.ResponseRef{
Value: &goai.Response{
Description: "BadRequest",
Content: map[string]goai.MediaType{
"application/json": {Schema: &goai.SchemaRef{Ref: "bizmodel.HTTPResponse"}},
},
},
},
http.StatusText(http.StatusNotFound): goai.ResponseRef{
Value: &goai.Response{
Description: "NotFound",
Content: map[string]goai.MediaType{
"application/json": {Schema: &goai.SchemaRef{Ref: "bizmodel.HTTPResponse"}},
},
},
},
http.StatusText(http.StatusInternalServerError): goai.ResponseRef{
Value: &goai.Response{
Description: "InternalServerError",
Content: map[string]goai.MediaType{
"application/json": {Schema: &goai.SchemaRef{Ref: "bizmodel.HTTPResponse"}},
},
},
},
}
s.Run()
}
successStatusCode
to 201
in HelloCreateRes
, then the 201
document should be generated, 400
and 500
are also set, and the two response
documents can be found in the components.responses' of the document
is matched by HTTP Status text
and should also be set to the documentsuccessStatusCode
is not set in HelloGetRes
, a document of 200
should be generated by default @shuqingzai 厉害,你这个也能实现自定义状态码
但我更想关注的是使用 goai.SchemaRef
不能引用到 #/components/responses/
估计是个问题
@shuqingzai 厉害,你这个也能实现自定义状态码 但我更想关注的是使用
goai.SchemaRef
不能引用到#/components/responses/
估计是个问题
@ivothgle
可以引用啊,需要手动加到 Schemas
中(参考我的示例代码中: oai.Components.Schemas.Set("bizmodel.HTTPResponse", goai.SchemaRef{....
你说的无法引用,是因为 GF 没法手动设置某个接口的多个状态码响应,只有一个 200
响应,需要支持配置多个响应状态码
实际上 Components.Responses
中已经可以引用了
@shuqingzai 我再换一个描述 我知道这样
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error' # 这里引用的是 #/components/schemas/xxxx
但我想实现的是
'404':
$ref: '#/components/responses/NotFound' # 请问怎么生成这个引用链接呢 #/components/responses/xxx
# 其实就比上面少了几行,更高的复用而已
@shuqingzai 我再换一个描述 我知道这样
'404': description: Not found content: application/json: schema: $ref: '#/components/schemas/Error' # 这里引用的是 #/components/schemas/xxxx
但我想实现的是
'404': $ref: '#/components/responses/NotFound' # 请问怎么生成这个引用链接呢 #/components/responses/xxx # 其实就比上面少了几行,更高的复用而已
@ivothgle
懂了~~,这确实是一个 BUG ,schema 的引用前缀被写死了,应该需要判断,如果是根节点开头,不需要拼接前缀
但是问题的核心是 GF 没有对某个接口进行多个 response 的配置,所以还是我一开始说的,需要在 xxxRes
中定义 errorStatusCode
然后在 OpenAPI 文档解析生成时,自动读取自定义的 statusCode
匹配引用公共的 response schema 或者你有更好的实现方案也可以自行实现
因为即使你可以配置进去,也没法使用多个响应,一样没法用
我自己改了一点,可以适配,可以参考下
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
@shuqingzai Let me change the description. I know this
'404': description: Not found content: application/json: schema: $ref: '#/components/schemas/Error' # The reference here is #/components/schemas/xxxx
But what I want to achieve is
'404': $ref: '#/components/responses/NotFound' # How to generate this reference link #/components/responses/xxx # Actually, it’s just a few lines less than the above, just for higher reuse.
@ivothgle
Got it~~, this is indeed a BUG. The reference prefix of the schema is hard-coded. It should be judged. If it starts with the root node, there is no need to splice the prefix.
But the core of the problem is that GF does not configure multiple responses for a certain interface, so as I said at the beginning, you need to define errorStatusCode
in xxxRes
and then automatically read the customized one when the OpenAPI document is parsed and generated. statusCode
matches the public response schema or you can implement it yourself if you have a better implementation solution.
Because even if you can configure it, you can't use multiple responses, it still won't work.
I changed it a bit myself and it can be adapted. You can refer to it below.
这里的核心只有schema 的引用前缀被写死,404,500 本来就是通用状态码 通过重写自定义 /api.json 即可完成高度自定义就行了
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
The core here is that only the reference prefix of the schema is hard-coded. 404 and 500 are originally universal status codes. By rewriting the custom /api.json, a high degree of customization can be achieved.
这里的核心只有schema 的引用前缀被写死,404,500 本来就是通用状态码 通过重写自定义 /api.json 即可完成高度自定义就行了
@ivothgle 你的意思你拿到 /api.json 还要手动改这个文件?? 如果你要手动改那不是因为没法自动设置吗?下次再生成文档,继续手动改? 因为即使你定义了这些 response schema ,你也要在每个 API 路由下引用它,才会有效果,只是定义不引用不会有文档的
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
The core here is that only the reference prefix of the schema is hard-coded. 404 and 500 are originally universal status codes. By rewriting the custom /api.json, a high degree of customization can be achieved. @ivothgle Do you mean you have to manually modify this file after getting /api.json? ? If you want to change it manually, isn't it because it can't be set automatically? Generate the document next time and continue to modify it manually? Because even if you define these response schema, you have to quote it under each API route to have an effect. If you define it without quoting it, there will be no documentation.
千言万语不如代码一贴
func enhanceOpenAPIDoc(s *ghttp.Server) {
s.BindHandler("/api.json", openapiSpec)
s.BindHandler("/swagger-ui/", func(r *ghttp.Request) {
r.Response.Write(MySwaggerUITemplate)
r.ExitAll()
})
openapi := s.GetOpenApi()
openapi.Config.CommonResponse = model.DefaultHandlerResponse{}
openapi.Config.CommonResponseDataField = `Data`
openapi.Config.IgnorePkgPath = true
openapi.Security = &goai.SecurityRequirements{{"bearerAuth": []string{}}}
openapi.Components.SecuritySchemes = goai.SecuritySchemes{
"bearerAuth": goai.SecuritySchemeRef{
Value: &goai.SecurityScheme{
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
},
},
}
}
func genOpenapi(s *ghttp.Server) {
var (
ctx = context.TODO()
err error
methods []string
)
for _, item := range s.GetRoutes() {
switch item.Type {
case ghttp.HandlerTypeMiddleware, ghttp.HandlerTypeHook:
continue
}
if item.Handler.Info.IsStrictRoute {
methods = []string{item.Method}
if gstr.Equal(item.Method, "ALL") {
methods = ghttp.SupportedMethods()
}
for _, method := range methods {
err = s.GetOpenApi().Add(goai.AddInput{
Path: item.Route,
Method: method,
Object: item.Handler.Info.Value.Interface(),
})
if err != nil {
s.Logger().Fatalf(ctx, `%+v`, err)
}
}
}
}
}
func openapiSpec(req *ghttp.Request) {
genOpenapi(req.Server)
// 过滤免登录的接口
si, _ := g.Cfg("secure").Get(req.GetCtx(), "secure.ignore")
unauthenticated := make(map[string]struct{})
for _, url := range si.Strings() {
unauthenticated[url] = struct{}{}
}
for url, path := range req.Server.GetOpenApi().Paths {
tag := "xxxx"
if path.Get != nil {
path.Get.Tags[0] = path.Get.Tags[0] + " - " + tag
if _, ok := unauthenticated[url]; ok {
path.Get.Security = &goai.SecurityRequirements{}
}
}
if path.Post != nil {
path.Post.Tags[0] = path.Post.Tags[0] + " - " + tag
if _, ok := unauthenticated[url]; ok {
path.Post.Security = &goai.SecurityRequirements{}
}
}
if path.Put != nil {
path.Put.Tags[0] = path.Put.Tags[0] + " - " + tag
if _, ok := unauthenticated[url]; ok {
path.Put.Security = &goai.SecurityRequirements{}
}
}
if path.Delete != nil {
path.Delete.Tags[0] = path.Delete.Tags[0] + " - " + tag
if _, ok := unauthenticated[url]; ok {
path.Delete.Security = &goai.SecurityRequirements{}
}
}
}
req.Response.Write(req.Server.GetOpenApi())
}
我都已经在操作 goai.Operation 了,就没必要往 gf 里面增加负担了,把前缀放开就可以了
千言万语不如代码一贴
func enhanceOpenAPIDoc(s *ghttp.Server) { s.BindHandler("/api.json", openapiSpec) s.BindHandler("/swagger-ui/", func(r *ghttp.Request) { r.Response.Write(MySwaggerUITemplate) r.ExitAll() }) openapi := s.GetOpenApi() openapi.Config.CommonResponse = model.DefaultHandlerResponse{} openapi.Config.CommonResponseDataField = `Data` openapi.Config.IgnorePkgPath = true openapi.Security = &goai.SecurityRequirements{{"bearerAuth": []string{}}} openapi.Components.SecuritySchemes = goai.SecuritySchemes{ "bearerAuth": goai.SecuritySchemeRef{ Value: &goai.SecurityScheme{ Type: "http", Scheme: "bearer", BearerFormat: "JWT", }, }, } } func genOpenapi(s *ghttp.Server) { var ( ctx = context.TODO() err error methods []string ) for _, item := range s.GetRoutes() { switch item.Type { case ghttp.HandlerTypeMiddleware, ghttp.HandlerTypeHook: continue } if item.Handler.Info.IsStrictRoute { methods = []string{item.Method} if gstr.Equal(item.Method, "ALL") { methods = ghttp.SupportedMethods() } for _, method := range methods { err = s.GetOpenApi().Add(goai.AddInput{ Path: item.Route, Method: method, Object: item.Handler.Info.Value.Interface(), }) if err != nil { s.Logger().Fatalf(ctx, `%+v`, err) } } } } } func openapiSpec(req *ghttp.Request) { genOpenapi(req.Server) // 过滤免登录的接口 si, _ := g.Cfg("secure").Get(req.GetCtx(), "secure.ignore") unauthenticated := make(map[string]struct{}) for _, url := range si.Strings() { unauthenticated[url] = struct{}{} } for url, path := range req.Server.GetOpenApi().Paths { tag := "xxxx" if path.Get != nil { path.Get.Tags[0] = path.Get.Tags[0] + " - " + tag if _, ok := unauthenticated[url]; ok { path.Get.Security = &goai.SecurityRequirements{} } } if path.Post != nil { path.Post.Tags[0] = path.Post.Tags[0] + " - " + tag if _, ok := unauthenticated[url]; ok { path.Post.Security = &goai.SecurityRequirements{} } } if path.Put != nil { path.Put.Tags[0] = path.Put.Tags[0] + " - " + tag if _, ok := unauthenticated[url]; ok { path.Put.Security = &goai.SecurityRequirements{} } } if path.Delete != nil { path.Delete.Tags[0] = path.Delete.Tags[0] + " - " + tag if _, ok := unauthenticated[url]; ok { path.Delete.Security = &goai.SecurityRequirements{} } } } req.Response.Write(req.Server.GetOpenApi()) }
我都已经在操作 goai.Operation 了,就没必要往 gf 里面增加负担了,把前缀放开就可以了
@ivothgle
你贴的代码都是 GF 已经实现的逻辑,我不明白为啥你还要重写一次
唯一的解释可能是:你需要为他的页面添加简单的 Basic Auth
认证,这其实也是 GF 不完善的点,没法为 OpenAPI 文档添加简单认证或添加中间件,应该是 GF 自身完善,而不是使用者自己实现,这是大部分通用的,而不是定制化需求
Security
的使用,你只需要定义出来,然后在 xxxReq
加上对应标签即可,也不需要像你这样循环为每个路由添加,因为 GF 内部是直接复用标签的,参考: https://github.com/gogf/gf/blob/6e5ce98d23c65fd7283c84382abfde62a29f9930/net/goai/goai_path.go#L164C56-L164C69
类似下面这样:
Paths
然后自己手动添加,不是不行,而是我认为这应该是 GF 内置支持的功能,因为它已经支持 OpenAPI ,只是支持不够完整,需要更加完善而已,观点不同,哈哈 😄你说的都对!
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
Everything you said is right!
@ivothgle @shuqingzai 大家好,关于OpenAPIv3
这块的支持,原本设计的想法是框架层面提供通用的、常用的、易用的实现,大概80%的能力即可。一些扩展的能力可以通过获取到OpenAPIv3
对象后(通过GeOpentAPI
方法)由开发者自己去扩展,也能很容易扩展OpenAPIv3
的实现。如果在扩展这里存在问题,比如扩展能力暴露的接口不足,也欢迎参与社区建设共同来完善❤️。
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
@ivothgle @shuqingzai Hello everyone, regarding the support of
OpenAPIv3
, the original design idea is to provide a universal, commonly used and easy-to-use implementation at the framework level, which can achieve about 80% of the capabilities. Some extended capabilities can be extended by developers themselves by obtaining theOpenAPIv3
object (through theGeOpentAPI
method), and the implementation ofOpenAPIv3
can also be easily extended. If there are problems in the expansion, such as insufficient interfaces exposed by the expansion capabilities, you are welcome to participate in community building to improve it ❤️.
Hello @ivothgle. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it! 你好 @ivothgle。我们喜欢您的提案/反馈,并希望您或其他社区成员通过拉取请求做出贡献。我们提前感谢您的贡献,并期待对其进行审查。
@gqcn 获取到了 GeOpentAPI 对象,却因为代码写死了无法扩展,唉
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
@gqcn obtained the GeOpentAPI object, but it cannot be expanded because the code is written to death, alas.
@gqcn 获取到了 GeOpentAPI 对象,却因为代码写死了无法扩展,唉
你好,是否有个别属性没有公开,请具体描述一下呢?
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
@gqcn obtained the GeOpentAPI object, but it cannot be expanded because the code is hard-coded, alas.
Hello, are there any individual attributes that have not been made public? Please describe them in detail?
@gqcn 强哥好,我个人觉得目前goai模块的主要问题可能还是对于非200状态码的处理不是很理想。诚然在全200状态下文档的支持度已经很好了,但对于非200派的而言实际上还是需要其他状态码管理的,我们非200派维护一些东西会比较拧巴哈哈哈哈。目前添加状态码或者是状态码的具体example都需要进行很长一串结构体的构造,例如下面这个比较丑陋的实现:
func AddResponseStatus(openapi *goai.OpenApiV3, path string, method string, contentType string, object interface{}, status string, description string) {
// Add schema
openapi.Add(goai.AddInput{
Object: object,
})
location := strings.ReplaceAll(reflect.TypeOf(object).PkgPath(), "/", ".")
name := reflect.TypeOf(object).Name()
target := findMethod(openapi.Paths[path], method)
// Prevent duplicate
if _, exist := target.Responses[status]; exist {
return
}
target.Responses[status] = goai.ResponseRef{
Value: &goai.Response{
Content: goai.Content{
contentType: goai.MediaType{
Schema: &goai.SchemaRef{
Ref: location + "." + name,
},
},
},
Description: description,
},
}
}
然后再添加examples的schema,最后的api.json会类似这样:
"/api/v1/auth/login": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/project.api.auth.v1.LoginReq"
}
}
}
},
"responses": {
"200": { some default data },
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/project.api.CommonRes"
},
"examples": {
"Code 401: login failed": {
"value": {
"code": 401,
"message": "incorrect Username or Password",
"data": null
}
}
}
}
},
"description": ""
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/project.api.CommonRes"
},
"examples": {
"Code 1: User not exist": {
"value": {
"code": 1,
"message": "User not exist",
"data": null
}
},
"Code 2: Wrong password": {
"value": {
"code": 2,
"message": "Wrong password",
"data": null
}
},
"Code 3: Not allowed": {
"value": {
"code": 3,
"message": "Not allowed",
"data": null
}
}
}
}
},
"description": ""
}
},
"summary": "User Login",
"tags": [
"Auth"
]
}
},
为了添加非200的状态码往往都需要进行类似这样的操作,并且由于他们和规范路由并不存在强关联,很多时候很容易在文档中漏掉某些状态码,进而导致文档实际上并不完全可用。同时虽然已经封装了添加的函数,但每次添加状态都会调用一次,导致最后实际上还是会在api包里写一大坨东西进去。这块要说是项目本身的问题确实也有一些,但既然GoFrame已经有了相对比较完善的goai文档管理机制,我觉得实际上可以暴露一些类似的方法给到开发者,或者是直接在规范路由的g.Meta
里解决这个问题。
如果暴露方法的话我个人认为对于非200派而言最重要的应该有两个,一个是上面提到的添加状态码schema,另一个是添加examples(对应特定状态码下的error code)。这样的话goai本身不需要太多改动,只需要让开发者自行决定schema是什么样,然后再注入到文档里就可以了。
另一种方案可能是在现有的tag基础上添加一个defaultStatus
和一个errorStatus
,这样可以更改默认响应状态为2xx,或者添加对应的错误状态码列表。对于examples的管理我没有什么好的想法,目前我会用添加一个这样的status list:
var LoginRes403 = map[int]gcode.Code{
1: gcode.New(1, "User not exist", nil),
2: gcode.New(2, "Wrong password", nil),
3: gcode.New(3, "Not allowed", nil),
}
然后通过解析这个变量来生成对应的examples,这样不仅方便,也更容易管理对应api的错误状态。不过这个方法和commen response的耦合比较大,可能对于框架来说不是一个好的解决方案。
GoFrame的规范路由真的是一个很棒的解决方案,我个人会比较重视这块,因为前端调用和后端排障很多时候都需要一份可靠的文档,而在规范路由的情况下大部分时间开发者也都不需要花费过多的精力在api文档的维护上。强哥可以费心看看这块还有没有优化的空间,我go的经验比较少所以上面提到的一些方案可能都比较僵硬。
https://github.com/gogf/gf/issues/3747#issuecomment-2406906280
@UncleChair 框架原本在设计自动API接口文档生成的时候,就没有考虑不同返回状态码的接口文档生成,因为我们的初衷是代码文档一致性维护、自动化生成、只满足大部分的场景。
如果需要实现不同状态码的代码文档一致性维护,我粗略想了一下,其实可以参考g.Meta
的模式,设计专门的文档化数据结构来实现,代码来举个例子,例如:
type XxxRes struct {
g.Meta `path:"/xxx"` // 这里是接口返回的主要描述,针对2xx的状态码
goai.Status403 *Status403Struct // 这个是状态码403时的数据结构描述
goai.Status500 *Status500Struct // 这个是状态码500时的数据结构描述
// 以此类推
}
其中goai.Status*
为goai
包提供的常量预定义。
有更好的建议欢迎一起讨论。
那么文档的example怎么来实现呢?example通常是大块代码,那么可以结合资源管理的能力来实现!资源管理是将静态资源打包到二进制文件中一起发布的能力( https://goframe.org/pages/viewpage.action?pageId=1114671 ),并且在goframe
框架下,该能力是整合了工具链自动化实现的( https://goframe.org/pages/viewpage.action?pageId=1115788 ),开发者几乎不用关心细节。我来举个例子:
type XxxReq struct {
g.Meta `path:"/xxx" example:"resource/example/xxx/200.json"` // 通过example标签指定资源管理器中的具体example代码文件
goai.Status403 Status403Struct `example:"resource/example/xxx/403.json"`
goai.Status500 Status500Struct `example:"resource/example/xxx/500.json"`
// 以此类推
}
并且这个能力在开发时是自动读取的本地文件,在编译发布时是从资源管理器读取的(编译发布即可无需做额外操作),和资源管理器设计的能力一致。
同时,这些能力需要在goai
中进行增强。感兴趣的小伙伴可以一起参与贡献哈。
@ivothgle @shuqingzai
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
https://github.com/gogf/gf/issues/3747#issuecomment-2406906280
@UncleChair When the framework originally designed the automatic API interface document generation, it did not consider the generation of interface documents with different return status codes, because our original intention was to maintain the consistency of code documents and automatically generate them, which only satisfies most scenarios.
If you need to maintain the consistency of code documents for different status codes, I gave it a rough thought. In fact, you can refer to the g.Meta
mode and design a specialized documented data structure to achieve this. Here is an example of the code, for example:
type XxxReq struct {
g.Meta `path:"/xxx"` // Here is the main description of the interface, for the 2xx status code
goai.Status403 Status403Struct // This is the data structure description when status code 403
goai.Status500 Status500Struct // This is the data structure description when the status code is 500
// and so on
}
And this capability is automatically read from local files during development, and is read from the resource manager during compilation and release (no additional operations are required for compilation and release), which is consistent with the capability designed by the resource manager.
So how to implement the example of the document? Example is usually a large block of code, so it can be achieved by combining resource management capabilities! Resource management is the ability to package static resources into binary files and publish them together (https://goframe.org/pages/viewpage.action?pageId=1114671), and under the goframe
framework, this ability is integrated with the tool chain It is implemented automatically (https://goframe.org/pages/viewpage.action?pageId=1115788), and developers hardly need to care about the details. Let me give you an example:
type XxxReq struct {
g.Meta `path:"/xxx" example:"resource/example/xxx/200.json"` // Specify the specific example code file in the resource manager through the example tag
goai.Status403 Status403Struct `example:"resource/example/xxx/403.json"`
goai.Status500 Status500Struct `example:"resource/example/xxx/500.json"`
// and so on
}
At the same time, these capabilities need to be enhanced in goai
. Interested friends can participate and contribute together.
@gqcn 感谢强哥的思路,结合源码的一些内容我这边也想了一个关于不同返回状态码数据结构定义的方案:
status
tag 到gtag里status
tag的字段status
tag的字段status
tag的所有字段解析并添加为特定状态码下的结构定义举个例子就是:
type XXXReq struct {
g.Meta `status:"201" example:"xxx.json"`
Status string `json:"status" des:"server status" eg:"OK!"`
Status404Error *Error404 `status:"404" example:"xxx.json"`
}
这样有一个好处就是goai不需要维护一些保留字段来保证可以解析到对应的结构体,也不会和用户现有的字段产生冲突(因为只将包含status的字段解析为某个状态下的schema),设置状态码也比较方便。同时status字段也可以用于默认返回状态码的设置,解析req就可以拿到(这块尝试写了个简单的实现),也能保证满足用户所有2xx的需求
不知道这样从框架的角度是否是一个好的实现
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
@gqcn Thanks to Brother Qiang for his ideas. Combining some contents of the source code, I also thought of a solution for defining different return status code data structures:
- Add a
status
tag to gtag- Directly declare fields containing the
status
tag when defining the return structure- Ignore fields containing
status
tag when goai structToSchema- Parse and add all fields containing
status
tag as structure definitions under specific status codes
An example is:
type XXXReq struct {
g.Meta `status:"201" example:"xxx.json"`
Status string `json:"status" des:"server status" eg:"OK!"`
Status404Error *Error404 `status:"404" example:"xxx.json"`
}
One advantage of this is that goai does not need to maintain some reserved fields to ensure that the corresponding structure can be parsed, and it will not conflict with the user's existing fields (because only fields containing status are parsed into schema in a certain state) , it is also more convenient to set the status code. At the same time, the status field can also be used to set the default return status code, which can be obtained by parsing req (I tried to write a simple [implementation](https://github.com/gogf/gf/compare/master.. .UncleChair:gf:goai/http_status_enhance)), which can also ensure that all 2xx needs of users are met
I don’t know if this is a good implementation from a framework perspective.
https://github.com/gogf/gf/issues/3747#issuecomment-2408832814
使用status tag
是个不错的主意(另外这个应该写在XxxRes
返回数据结构中,不是XxxReq
请求结构中,我示例写得有问题),至于要不要在返回结构体中通过定义属性来表示不同返回的数据结构,我不确实这是否是一个好的设计,这一点最好大家一起讨论下。
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
https://github.com/gogf/gf/issues/3747#issuecomment-2408832814
It is a good idea to use status tag
(in addition, this should be written in the XxxRes
return data structure, not the XxxReq
request structure. There is something wrong with my example). As for whether to define attributes in the return structure To represent different returned data structures, I am not sure whether this is a good design. It is best for everyone to discuss this together.
定义太多不同的返回数据结构可能确实会造成一些使用上的混乱,其实最好还是在有common response的情况下自动复用,我也是更倾向于直接复用。不过考虑到可能用户的response handler会有定制的错误结构体需求,可能实现一个类似override的流程会更好一些?
目前的构想是当包含 status
标签的字段被解析时,如果该字段本身是空的就直接复用common response;如果包含额外的字段就直接优先将该字段解析并覆盖返回结构。或者和原来的逻辑一样,在包含mime标签时直接忽略common response。这样既能满足一些定制化的需求,也能保证在一般情况下的使用便捷。
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
Defining too many different return data structures may indeed cause some confusion in use. In fact, it is best to automatically reuse when there is a common response. I also prefer direct reuse. However, considering that the user's response handler may have customized error structure requirements, it may be better to implement a process similar to override?
The current idea is that when a field containing the status
tag is parsed, if the field itself is empty, the common response will be reused directly; if it contains additional fields, the field will be parsed first and the return structure will be overwritten. This can not only meet some customized needs, but also ensure the convenience of use under normal circumstances. However, there may not be any way to return a pure status code without any data structure, hahaha.
https://github.com/gogf/gf/issues/3747#issuecomment-2412642714
@UncleChair 我主要担忧的是,定义这些状态码返回数据结构对业务数据结构的侵入性,开发者在编写和使用业务数据结构的时候还需要关心这些非业务相关的数据结构,体验会很不好。但如果是在common response
中定义这些不同状态码的数据结构就没问题了,因为common response
对业务数据结构是无感知的。
我不确定有没有这种脱离common response
公共输出标准数据结构对应的业务场景。如果没有的话事情就更简单了,咱们可以根据不同的状态码定义不同的common response
?
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
https://github.com/gogf/gf/issues/3747#issuecomment-2412642714
@UncleChair My main concern is that defining these status code return data structures is intrusive to business data structures. Developers also need to care about these non-business-related data structures when writing and using business data structures, and the experience will be very bad. . But if you define the data structures of these different status codes in common response
, there will be no problem, because common response
is unaware of the business data structure.
I'm not sure if there is such a business scenario that breaks away from the common response
public output standard data structure. If not, things would be simpler. Can we define different common response
based on different status codes?
@gqcn 在req里定义状态码返回数据结构确实是会对业务理解造成一定的影响,不过我感觉应该也不完全是坏事。目前来说的话我会把req作为返回数据的一个 范例
,也就是这里面包含了所有可能的数据结构和信息,但我不一定需要去填充他,其实gmeta
也比较好的体现了这一点。而对于response结构这基本上就是一个单纯的范例,开发者可以很明确的看到哪些response和哪些api是有关联的,同时再展开一点的话甚至也可以通过tag拿到examples来直接进行错误状态管理(比较类似laravel里的exceptions,某个状态下的某些信息),这样其实对api的整体管理是有好处的。
对于开发者编写和使用业务数据结构这块我能想到的缺点可能就是字段会在scan的时候和某些dao的字段重复,不过我相信这应该是命名规范或者说是设计问题,心智负担确实是有一些,但在项目的一些设计下其实是完全可控的,就比如规定使用statusxxxRes这种字段来命名之类的。更重要的是其实通过这样的方式,返回状态也被api纳入了管理之下,不管是通过tag解析json example还是其它一些方法,在这种情况下一个请求的所有状态和它所在的api是可以有强关联的,这其实无形中减少了开发者的管理负担。
在 common response
里定义错误结构也是一个可以的解决方案,不过还是有用户不使用 common response
的问题,扩展能力感觉可能也不太够。因为基于 common response
去做结构定义的话,首先抛开可能比较edge的脱离标准输出的情况,在req里去引用对应的response也还是会比较困难,如果不想加字段的话可能就会有非常大一坨meta数据丢在那,包括examples的管理,这样感觉也还是会写成类似的方案,只不过是通过tag来标注所有麻烦的信息。
不过话又说回来req的结构比较干净的话看起来确实比较舒服,用起来表面上的心智负担也会更低,不过确实需要一个更有效地可以将错误状态和api关联起来的方案。或者干脆给req加个返回状态和example的方法怎么样?例如这样:
type XXXReq {}
func (r *XXXReq ) Status() map[string]interface{}{
return (状态码和数据结构)
}
解析的时候就可以直接添加了,错误管理也可以用相同的方法拿到数据,同时也保证了业务数据结构的清晰。
Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
@gqcn Defining the status code return data structure in req will indeed have a certain impact on business understanding, but I feel it is not entirely a bad thing. For now, I will use req as an example
of the returned data, which means that it contains all possible data structures and information, but I don't necessarily need to fill it. In fact, gmeta
also reflects it better. at this point. As for the response structure, this is basically a simple example. Developers can clearly see which responses are related to which APIs. At the same time, if you expand it a little more, you can even get examples through tags to directly manage the error status. (Compared to exceptions in Laravel, certain information in a certain state), this is actually good for the overall management of the API.
The disadvantage I can think of for developers writing and using business data structures may be that the fields will be repeated with the fields of some DAOs during scanning. However, I believe this should be a naming convention or a design issue. The mental burden is indeed There are some, but they are actually completely controllable under some designs of the project, such as stipulating the use of fields such as statusxxxRes for naming. More importantly, in this way, the return status is also managed by the API. Whether it is parsing json example through tags or some other methods, in this case all the status of a request and the API where it is located can be If there is a strong correlation, this actually reduces the management burden on developers.
Defining error structures in common response
is also a possible solution, but there are still problems with users not using common response
, and the scalability may not be enough. Because if you define the structure based on common response
, first of all, aside from the possible edge of being separated from the standard output, it will still be difficult to reference the corresponding response in the req. If you don't want to add fields, it may be very large. A bunch of meta data is thrown there, including the management of examples. It feels like a similar solution will still be written, but all the troublesome information will be marked through tags.
But then again, if the structure of req is relatively clean, it does look more comfortable, and the mental load on the surface will be lower when using it. However, a more effective solution is needed to associate error status with the API. Or how about simply adding a method to req that returns status and example? For example:
type XXXReq {}
func (r *XXXReq ) Status() map[string]interface{}{
return (status code and data structure)
}
It can be added directly during parsing, and error management can also use the same method to obtain data, while also ensuring a clear business data structure.
https://github.com/gogf/gf/issues/3747#issuecomment-2414441181
@UncleChair 通过接口实现的方式来定义不同状态码的返回数据结构是个很棒的想法👍🏻!这样既能解决不同状态码扩展的问题,也能避免对业务数据结构的侵入。这里有几个点需要明确一下:
XxxRes
返回数据结构中,而不是XxxReq
请求数据结构中。goai
组件中,由业务侧来实现,接口定义譬如:
type StatusCode = int
type ResponseStatusDefinition interface{
func ResponseStatusMap() map[StatusCode]any
}
example
的关联关系依旧采用咱们之前聊到的使用struct tag
加资源管理来实现。@shuqingzai @ivothgle @hailaz @wln32 @oldme-git 大家一起看看有没更好的建议没有呢?
Go version
go1.22
GoFrame version
2.7.0
Can this bug be reproduced with the latest release?
Option Yes
What did you do?
I want to customize the response 500 content,
output "500": { "$ref": "#/components/schemas/InternalServerError" } He should be "#/components/responses/InternalServerError", see https://swagger.io/docs/specification/describing-responses/
What did you see happen?
It is not possible to customize the reference response structure in OpenAI because its prefix is always fixed
code is: https://github.com/gogf/gf/blob/6e5ce98d23c65fd7283c84382abfde62a29f9930/net/goai/goai.go#L227
What did you expect to see?
He should be "#/components/responses/InternalServerError", see https://swagger.io/docs/specification/describing-responses/