2025-09-30 08:16:44 +00:00
|
|
|
|
package swagger
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/http"
|
2025-09-30 09:33:29 +00:00
|
|
|
|
"strings"
|
2025-09-30 08:16:44 +00:00
|
|
|
|
|
|
|
|
|
"github.com/go-openapi/spec"
|
|
|
|
|
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func jsonResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type) *spec.Responses {
|
2025-09-30 09:33:29 +00:00
|
|
|
|
return jsonResponseFromTypeWithDocs(ctx, atDoc, tp, nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func jsonResponseFromTypeWithDocs(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type, handlerDoc apiSpec.Doc) *spec.Responses {
|
|
|
|
|
// 首先檢查是否有 @respdoc 註解(從 handlerDoc 或 atDoc)
|
|
|
|
|
var respDocs []RespDoc
|
|
|
|
|
if len(handlerDoc) > 0 {
|
|
|
|
|
respDocs = parseRespDocsFromDoc(handlerDoc)
|
|
|
|
|
}
|
|
|
|
|
if len(respDocs) == 0 {
|
|
|
|
|
respDocs = parseRespDocs(atDoc)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(respDocs) > 0 {
|
|
|
|
|
return jsonResponseFromRespDocs(ctx, atDoc, respDocs, tp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 原有邏輯:使用默認的 200 回應
|
2025-09-30 08:16:44 +00:00
|
|
|
|
if tp == nil {
|
|
|
|
|
return &spec.Responses{
|
|
|
|
|
ResponsesProps: spec.ResponsesProps{
|
|
|
|
|
StatusCodeResponses: map[int]spec.Response{
|
|
|
|
|
http.StatusOK: {
|
|
|
|
|
ResponseProps: spec.ResponseProps{
|
|
|
|
|
Description: "",
|
|
|
|
|
Schema: &spec.Schema{},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
props := spec.SchemaProps{
|
|
|
|
|
AdditionalProperties: mapFromGoType(ctx, tp),
|
|
|
|
|
Items: itemsFromGoType(ctx, tp),
|
|
|
|
|
}
|
|
|
|
|
if ctx.UseDefinitions {
|
|
|
|
|
structName, ok := containsStruct(tp)
|
|
|
|
|
if ok {
|
|
|
|
|
props.Ref = spec.MustCreateRef(getRefName(structName))
|
|
|
|
|
return &spec.Responses{
|
|
|
|
|
ResponsesProps: spec.ResponsesProps{
|
|
|
|
|
StatusCodeResponses: map[int]spec.Response{
|
|
|
|
|
http.StatusOK: {
|
|
|
|
|
ResponseProps: spec.ResponseProps{
|
|
|
|
|
Schema: &spec.Schema{
|
|
|
|
|
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p, _ := propertiesFromType(ctx, tp)
|
|
|
|
|
props.Type = typeFromGoType(ctx, tp)
|
|
|
|
|
props.Properties = p
|
|
|
|
|
return &spec.Responses{
|
|
|
|
|
ResponsesProps: spec.ResponsesProps{
|
|
|
|
|
StatusCodeResponses: map[int]spec.Response{
|
|
|
|
|
http.StatusOK: {
|
|
|
|
|
ResponseProps: spec.ResponseProps{
|
|
|
|
|
Schema: &spec.Schema{
|
|
|
|
|
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-30 09:33:29 +00:00
|
|
|
|
|
|
|
|
|
// jsonResponseFromRespDocs 從 @respdoc 註解生成多狀態碼回應
|
|
|
|
|
func jsonResponseFromRespDocs(ctx Context, atDoc apiSpec.AtDoc, respDocs []RespDoc, defaultType apiSpec.Type) *spec.Responses {
|
|
|
|
|
responses := &spec.Responses{
|
|
|
|
|
ResponsesProps: spec.ResponsesProps{
|
|
|
|
|
StatusCodeResponses: make(map[int]spec.Response),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, respDoc := range respDocs {
|
|
|
|
|
// 如果有單一 Schema,直接使用
|
|
|
|
|
if respDoc.Schema != "" {
|
|
|
|
|
tp := findTypeByName(ctx.Api, respDoc.Schema)
|
|
|
|
|
if tp == nil && respDoc.StatusCode == http.StatusOK && defaultType != nil {
|
|
|
|
|
// 如果找不到類型且是 200,使用默認類型
|
|
|
|
|
tp = defaultType
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response := createResponseFromType(ctx, atDoc, tp, respDoc.Description)
|
|
|
|
|
responses.StatusCodeResponses[respDoc.StatusCode] = response
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果有多個業務錯誤碼(BizCodes),為 Swagger 2.0 創建一個通用回應
|
|
|
|
|
// OpenAPI 3.0 會在 convertSwagger2ToOpenAPI3 中特殊處理
|
|
|
|
|
if len(respDoc.BizCodes) > 0 {
|
|
|
|
|
// 對於 Swagger 2.0,我們使用 oneOf/anyOf 概念的註釋
|
|
|
|
|
// 但 Swagger 2.0 不支持 oneOf,所以使用第一個類型作為示例
|
|
|
|
|
// 並在描述中列出所有可能的類型
|
|
|
|
|
|
|
|
|
|
var firstType apiSpec.Type
|
|
|
|
|
descriptions := []string{}
|
|
|
|
|
|
|
|
|
|
for code, bizCode := range respDoc.BizCodes {
|
|
|
|
|
tp := findTypeByName(ctx.Api, bizCode.Schema)
|
|
|
|
|
if firstType == nil && tp != nil {
|
|
|
|
|
firstType = tp
|
|
|
|
|
}
|
|
|
|
|
desc := code + ": " + bizCode.Schema
|
|
|
|
|
if bizCode.Description != "" {
|
|
|
|
|
desc += " - " + bizCode.Description
|
|
|
|
|
}
|
|
|
|
|
descriptions = append(descriptions, desc)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
description := respDoc.Description
|
|
|
|
|
if len(descriptions) > 0 {
|
|
|
|
|
if description != "" {
|
|
|
|
|
description += "\n\n"
|
|
|
|
|
}
|
|
|
|
|
description += "Possible errors:\n" + strings.Join(descriptions, "\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response := createResponseFromType(ctx, atDoc, firstType, description)
|
|
|
|
|
|
|
|
|
|
// 在 VendorExtensible 中存儲業務錯誤碼信息,供 OpenAPI 3.0 使用
|
|
|
|
|
if response.Extensions == nil {
|
|
|
|
|
response.Extensions = make(spec.Extensions)
|
|
|
|
|
}
|
|
|
|
|
response.Extensions["x-biz-codes"] = respDoc.BizCodes
|
|
|
|
|
|
|
|
|
|
responses.StatusCodeResponses[respDoc.StatusCode] = response
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果沒有定義 200 回應,添加默認的
|
|
|
|
|
if _, ok := responses.StatusCodeResponses[http.StatusOK]; !ok && defaultType != nil {
|
|
|
|
|
responses.StatusCodeResponses[http.StatusOK] = createResponseFromType(ctx, atDoc, defaultType, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return responses
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// createResponseFromType 從類型創建回應對象
|
|
|
|
|
func createResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type, description string) spec.Response {
|
|
|
|
|
if tp == nil {
|
|
|
|
|
return spec.Response{
|
|
|
|
|
ResponseProps: spec.ResponseProps{
|
|
|
|
|
Description: description,
|
|
|
|
|
Schema: &spec.Schema{},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
props := spec.SchemaProps{
|
|
|
|
|
AdditionalProperties: mapFromGoType(ctx, tp),
|
|
|
|
|
Items: itemsFromGoType(ctx, tp),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ctx.UseDefinitions {
|
|
|
|
|
structName, ok := containsStruct(tp)
|
|
|
|
|
if ok {
|
|
|
|
|
props.Ref = spec.MustCreateRef(getRefName(structName))
|
|
|
|
|
return spec.Response{
|
|
|
|
|
ResponseProps: spec.ResponseProps{
|
|
|
|
|
Description: description,
|
|
|
|
|
Schema: &spec.Schema{
|
|
|
|
|
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p, _ := propertiesFromType(ctx, tp)
|
|
|
|
|
props.Type = typeFromGoType(ctx, tp)
|
|
|
|
|
props.Properties = p
|
|
|
|
|
|
|
|
|
|
return spec.Response{
|
|
|
|
|
ResponseProps: spec.ResponseProps{
|
|
|
|
|
Description: description,
|
|
|
|
|
Schema: &spec.Schema{
|
|
|
|
|
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|