fix: support PUT, PATCH, DELETE methods for request body definitions in swagger (#5239)

This commit is contained in:
Kevin Wan
2025-10-12 18:24:11 +08:00
committed by GitHub
parent 80c320b46e
commit 2b10a6a223
2 changed files with 36 additions and 14 deletions

View File

@@ -8,28 +8,37 @@ import (
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec" apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
) )
func isPostJson(ctx Context, method string, tp apiSpec.Type) (string, bool) { func isRequestBodyJson(ctx Context, method string, tp apiSpec.Type) (string, bool) {
if !strings.EqualFold(method, http.MethodPost) { // Support HTTP methods that commonly use request bodies with JSON
// POST, PUT, PATCH are standard methods with bodies
// DELETE can also have a body (though less common)
method = strings.ToUpper(method)
if method != http.MethodPost && method != http.MethodPut &&
method != http.MethodPatch && method != http.MethodDelete {
return "", false return "", false
} }
structType, ok := tp.(apiSpec.DefineStruct) structType, ok := tp.(apiSpec.DefineStruct)
if !ok { if !ok {
return "", false return "", false
} }
var isPostJson bool
var hasJsonField bool
rangeMemberAndDo(ctx, structType, func(tag *apiSpec.Tags, required bool, member apiSpec.Member) { rangeMemberAndDo(ctx, structType, func(tag *apiSpec.Tags, required bool, member apiSpec.Member) {
jsonTag, _ := tag.Get(tagJson) jsonTag, _ := tag.Get(tagJson)
if !isPostJson { if !hasJsonField {
isPostJson = jsonTag != nil hasJsonField = jsonTag != nil
} }
}) })
return structType.RawName, isPostJson
return structType.RawName, hasJsonField
} }
func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Parameter { func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Parameter {
if tp == nil { if tp == nil {
return []spec.Parameter{} return []spec.Parameter{}
} }
structType, ok := tp.(apiSpec.DefineStruct) structType, ok := tp.(apiSpec.DefineStruct)
if !ok { if !ok {
return []spec.Parameter{} return []spec.Parameter{}
@@ -43,15 +52,13 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
rangeMemberAndDo(ctx, structType, func(tag *apiSpec.Tags, required bool, member apiSpec.Member) { rangeMemberAndDo(ctx, structType, func(tag *apiSpec.Tags, required bool, member apiSpec.Member) {
headerTag, _ := tag.Get(tagHeader) headerTag, _ := tag.Get(tagHeader)
hasHeader := headerTag != nil hasHeader := headerTag != nil
pathParameterTag, _ := tag.Get(tagPath) pathParameterTag, _ := tag.Get(tagPath)
hasPathParameter := pathParameterTag != nil hasPathParameter := pathParameterTag != nil
formTag, _ := tag.Get(tagForm) formTag, _ := tag.Get(tagForm)
hasForm := formTag != nil hasForm := formTag != nil
jsonTag, _ := tag.Get(tagJson) jsonTag, _ := tag.Get(tagJson)
hasJson := jsonTag != nil hasJson := jsonTag != nil
if hasHeader { if hasHeader {
minimum, maximum, exclusiveMinimum, exclusiveMaximum := rangeValueFromOptions(headerTag.Options) minimum, maximum, exclusiveMinimum, exclusiveMaximum := rangeValueFromOptions(headerTag.Options)
resp = append(resp, spec.Parameter{ resp = append(resp, spec.Parameter{
@@ -75,6 +82,7 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
}, },
}) })
} }
if hasPathParameter { if hasPathParameter {
minimum, maximum, exclusiveMinimum, exclusiveMaximum := rangeValueFromOptions(pathParameterTag.Options) minimum, maximum, exclusiveMinimum, exclusiveMaximum := rangeValueFromOptions(pathParameterTag.Options)
resp = append(resp, spec.Parameter{ resp = append(resp, spec.Parameter{
@@ -98,6 +106,7 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
}, },
}) })
} }
if hasForm { if hasForm {
minimum, maximum, exclusiveMinimum, exclusiveMaximum := rangeValueFromOptions(formTag.Options) minimum, maximum, exclusiveMinimum, exclusiveMaximum := rangeValueFromOptions(formTag.Options)
if strings.EqualFold(method, http.MethodGet) { if strings.EqualFold(method, http.MethodGet) {
@@ -145,8 +154,8 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
}, },
}) })
} }
} }
if hasJson { if hasJson {
minimum, maximum, exclusiveMinimum, exclusiveMaximum := rangeValueFromOptions(jsonTag.Options) minimum, maximum, exclusiveMinimum, exclusiveMaximum := rangeValueFromOptions(jsonTag.Options)
if required { if required {
@@ -179,9 +188,10 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
properties[jsonTag.Name] = schema properties[jsonTag.Name] = schema
} }
}) })
if len(properties) > 0 { if len(properties) > 0 {
if ctx.UseDefinitions { if ctx.UseDefinitions {
structName, ok := isPostJson(ctx, method, tp) structName, ok := isRequestBodyJson(ctx, method, tp)
if ok { if ok {
resp = append(resp, spec.Parameter{ resp = append(resp, spec.Parameter{
ParamProps: spec.ParamProps{ ParamProps: spec.ParamProps{
@@ -213,5 +223,6 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
}) })
} }
} }
return resp return resp
} }

View File

@@ -8,7 +8,7 @@ import (
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec" apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
) )
func TestIsPostJson(t *testing.T) { func TestIsRequestBodyJson(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
method string method string
@@ -18,13 +18,18 @@ func TestIsPostJson(t *testing.T) {
{"POST with JSON", http.MethodPost, true, true}, {"POST with JSON", http.MethodPost, true, true},
{"POST without JSON", http.MethodPost, false, false}, {"POST without JSON", http.MethodPost, false, false},
{"GET with JSON", http.MethodGet, true, false}, {"GET with JSON", http.MethodGet, true, false},
{"PUT with JSON", http.MethodPut, true, false}, {"PUT with JSON", http.MethodPut, true, true},
{"PUT without JSON", http.MethodPut, false, false},
{"PATCH with JSON", http.MethodPatch, true, true},
{"PATCH without JSON", http.MethodPatch, false, false},
{"DELETE with JSON", http.MethodDelete, true, true},
{"DELETE without JSON", http.MethodDelete, false, false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
testStruct := createTestStruct("TestStruct", tt.hasJson) testStruct := createTestStruct("TestStruct", tt.hasJson)
_, result := isPostJson(testingContext(t), tt.method, testStruct) _, result := isRequestBodyJson(testingContext(t), tt.method, testStruct)
assert.Equal(t, tt.expected, result) assert.Equal(t, tt.expected, result)
}) })
} }
@@ -41,6 +46,12 @@ func TestParametersFromType(t *testing.T) {
}{ }{
{"POST JSON with definitions", http.MethodPost, true, true, 1, true}, {"POST JSON with definitions", http.MethodPost, true, true, 1, true},
{"POST JSON without definitions", http.MethodPost, false, true, 1, true}, {"POST JSON without definitions", http.MethodPost, false, true, 1, true},
{"PUT JSON with definitions", http.MethodPut, true, true, 1, true},
{"PUT JSON without definitions", http.MethodPut, false, true, 1, true},
{"PATCH JSON with definitions", http.MethodPatch, true, true, 1, true},
{"PATCH JSON without definitions", http.MethodPatch, false, true, 1, true},
{"DELETE JSON with definitions", http.MethodDelete, true, true, 1, true},
{"DELETE JSON without definitions", http.MethodDelete, false, true, 1, true},
{"GET with form", http.MethodGet, false, false, 1, false}, {"GET with form", http.MethodGet, false, false, 1, false},
{"POST with form", http.MethodPost, false, false, 1, false}, {"POST with form", http.MethodPost, false, false, 1, false},
} }