diff --git a/tools/goctl/api/swagger/const.go b/tools/goctl/api/swagger/const.go
index 318ba5638..42dbbcccb 100644
--- a/tools/goctl/api/swagger/const.go
+++ b/tools/goctl/api/swagger/const.go
@@ -54,6 +54,8 @@ const (
propertyKeyDeprecated = "deprecated"
propertyKeyPrefix = "prefix"
propertyKeyAuthType = "authType"
+ propertyKeyRespCode = "respCode"
+ propertyKeyResponses = "responses"
propertyKeyHost = "host"
propertyKeyBasePath = "basePath"
propertyKeyWrapCodeMsg = "wrapCodeMsg"
diff --git a/tools/goctl/api/swagger/example/example.api b/tools/goctl/api/swagger/example/example.api
index 6ac453483..2b8f00573 100644
--- a/tools/goctl/api/swagger/example/example.api
+++ b/tools/goctl/api/swagger/example/example.api
@@ -82,6 +82,7 @@ type (
service Swagger {
@doc (
description: "form demo"
+ respCode: "201" // HTTP status code corresponding to Swagger
)
@handler form
post /form (FormReq) returns (FormResp)
@@ -238,4 +239,3 @@ service Swagger {
@handler jsonComplex
post /json/complex (ComplexJsonReq) returns (ComplexJsonResp)
}
-
diff --git a/tools/goctl/api/swagger/example/example.swagger.json b/tools/goctl/api/swagger/example/example.swagger.json
index dd494fa21..a431755fc 100644
--- a/tools/goctl/api/swagger/example/example.swagger.json
+++ b/tools/goctl/api/swagger/example/example.swagger.json
@@ -60,7 +60,7 @@
}
],
"responses": {
- "200": {
+ "201": {
"description": "",
"schema": {
"type": "object",
diff --git a/tools/goctl/api/swagger/example/example_cn.api b/tools/goctl/api/swagger/example/example_cn.api
index f48168c68..cab950b21 100644
--- a/tools/goctl/api/swagger/example/example_cn.api
+++ b/tools/goctl/api/swagger/example/example_cn.api
@@ -84,6 +84,7 @@ type (
service Swagger {
@doc (
description: "form 接口"
+ respCode: "201" // 对应 Swagger 的 HTTP 状态码
)
@handler form
post /form (FormReq) returns (FormResp)
@@ -244,4 +245,3 @@ service Swagger {
@handler jsonComplex
post /json/complex (ComplexJsonReq) returns (ComplexJsonResp)
}
-
diff --git a/tools/goctl/api/swagger/example/example_cn.swagger.json b/tools/goctl/api/swagger/example/example_cn.swagger.json
index 0d949a9e0..faae89a46 100644
--- a/tools/goctl/api/swagger/example/example_cn.swagger.json
+++ b/tools/goctl/api/swagger/example/example_cn.swagger.json
@@ -60,7 +60,7 @@
}
],
"responses": {
- "200": {
+ "201": {
"description": "",
"schema": {
"type": "object",
diff --git a/tools/goctl/api/swagger/response.go b/tools/goctl/api/swagger/response.go
index 3940a8c20..c57333b1c 100644
--- a/tools/goctl/api/swagger/response.go
+++ b/tools/goctl/api/swagger/response.go
@@ -2,64 +2,108 @@ package swagger
import (
"net/http"
+ "strconv"
+ "strings"
"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 {
+ statusCode := responseStatusCode(atDoc)
+ var response spec.Response
if tp == nil {
- return &spec.Responses{
- ResponsesProps: spec.ResponsesProps{
- StatusCodeResponses: map[int]spec.Response{
- http.StatusOK: {
- ResponseProps: spec.ResponseProps{
- Description: "",
- Schema: &spec.Schema{},
- },
- },
- },
+ response = spec.Response{
+ ResponseProps: spec.ResponseProps{
+ Description: "",
},
}
- }
- 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),
- },
- },
- },
- },
- },
- }
+ } else {
+ props := spec.SchemaProps{
+ AdditionalProperties: mapFromGoType(ctx, tp),
+ Items: itemsFromGoType(ctx, tp),
}
- }
-
- 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: {
+ if ctx.UseDefinitions {
+ structName, ok := containsStruct(tp)
+ if ok {
+ props.Ref = spec.MustCreateRef(getRefName(structName))
+ response = spec.Response{
ResponseProps: spec.ResponseProps{
Schema: &spec.Schema{
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
},
},
+ }
+ return responsesFromStatusCode(atDoc, statusCode, response)
+ }
+ }
+
+ p, _ := propertiesFromType(ctx, tp)
+ props.Type = typeFromGoType(ctx, tp)
+ props.Properties = p
+ response = spec.Response{
+ ResponseProps: spec.ResponseProps{
+ Schema: &spec.Schema{
+ SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
},
},
+ }
+ }
+
+ return responsesFromStatusCode(atDoc, statusCode, response)
+}
+
+func responsesFromStatusCode(atDoc apiSpec.AtDoc, statusCode int, response spec.Response) *spec.Responses {
+ statusCodeResponses := map[int]spec.Response{
+ statusCode: response,
+ }
+ for code, description := range responseDescriptions(atDoc) {
+ if code == statusCode {
+ responseWithDescription := response
+ responseWithDescription.Description = description
+ statusCodeResponses[code] = responseWithDescription
+ } else {
+ statusCodeResponses[code] = spec.Response{
+ ResponseProps: spec.ResponseProps{
+ Description: description,
+ },
+ }
+ }
+ }
+
+ return &spec.Responses{
+ ResponsesProps: spec.ResponsesProps{
+ StatusCodeResponses: statusCodeResponses,
},
}
}
+
+func responseStatusCode(atDoc apiSpec.AtDoc) int {
+ return getOrDefault(atDoc.Properties, propertyKeyRespCode, http.StatusOK, func(str string, def int) int {
+ statusCode, err := strconv.Atoi(str)
+ if err != nil || statusCode < http.StatusContinue || statusCode > 599 {
+ return def
+ }
+
+ return statusCode
+ })
+}
+
+func responseDescriptions(atDoc apiSpec.AtDoc) map[int]string {
+ descriptions := make(map[int]string)
+ for _, item := range strings.Split(getStringFromKVOrDefault(atDoc.Properties, propertyKeyResponses, ""), "
") {
+ codeText, description, ok := strings.Cut(item, "-")
+ if !ok {
+ continue
+ }
+
+ code, err := strconv.Atoi(strings.TrimSpace(codeText))
+ if err != nil || code < http.StatusContinue || code > 599 {
+ continue
+ }
+
+ descriptions[code] = strings.TrimSpace(description)
+ }
+
+ return descriptions
+}
diff --git a/tools/goctl/api/swagger/response_test.go b/tools/goctl/api/swagger/response_test.go
new file mode 100644
index 000000000..d95f4422d
--- /dev/null
+++ b/tools/goctl/api/swagger/response_test.go
@@ -0,0 +1,92 @@
+package swagger
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/zeromicro/go-zero/tools/goctl/api/spec"
+)
+
+func TestJsonResponseFromTypeStatusCode(t *testing.T) {
+ tests := []struct {
+ name string
+ properties map[string]string
+ response spec.Type
+ want int
+ }{
+ {
+ name: "defaults to ok",
+ response: spec.PrimitiveType{RawName: "string"},
+ want: http.StatusOK,
+ },
+ {
+ name: "uses custom status code",
+ properties: map[string]string{
+ propertyKeyRespCode: "201",
+ },
+ response: spec.PrimitiveType{RawName: "string"},
+ want: http.StatusCreated,
+ },
+ {
+ name: "supports quoted custom status code",
+ properties: map[string]string{
+ propertyKeyRespCode: `"204"`,
+ },
+ want: http.StatusNoContent,
+ },
+ {
+ name: "defaults for invalid status code",
+ properties: map[string]string{
+ propertyKeyRespCode: "600",
+ },
+ response: spec.PrimitiveType{RawName: "string"},
+ want: http.StatusOK,
+ },
+ {
+ name: "defaults for non-numeric status code",
+ properties: map[string]string{
+ propertyKeyRespCode: "created",
+ },
+ response: spec.PrimitiveType{RawName: "string"},
+ want: http.StatusOK,
+ },
+ {
+ name: "uses custom status code without response body",
+ properties: map[string]string{
+ propertyKeyRespCode: "204",
+ },
+ want: http.StatusNoContent,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ responses := jsonResponseFromType(testingContext(t), spec.AtDoc{
+ Properties: test.properties,
+ }, test.response)
+
+ assert.Len(t, responses.StatusCodeResponses, 1)
+ assert.Contains(t, responses.StatusCodeResponses, test.want)
+ if test.response == nil {
+ assert.Nil(t, responses.StatusCodeResponses[test.want].Schema)
+ }
+ })
+ }
+}
+
+func TestJsonResponseFromTypeMultipleStatusCodes(t *testing.T) {
+ responses := jsonResponseFromType(testingContext(t), spec.AtDoc{
+ Properties: map[string]string{
+ propertyKeyResponses: "200-OK
401-Unauthorized
404-User not found",
+ },
+ }, spec.PrimitiveType{RawName: "string"})
+
+ assert.Len(t, responses.StatusCodeResponses, 3)
+ assert.Equal(t, "OK", responses.StatusCodeResponses[http.StatusOK].Description)
+ assert.NotNil(t, responses.StatusCodeResponses[http.StatusOK].Schema)
+ assert.Equal(t, "Unauthorized", responses.StatusCodeResponses[http.StatusUnauthorized].Description)
+ assert.Nil(t, responses.StatusCodeResponses[http.StatusUnauthorized].Schema)
+ assert.Equal(t, "User not found", responses.StatusCodeResponses[http.StatusNotFound].Description)
+ assert.Nil(t, responses.StatusCodeResponses[http.StatusNotFound].Schema)
+}