goctl features of 1.8.4-alpha (#4849)

This commit is contained in:
kesonan
2025-05-15 21:59:48 +08:00
committed by GitHub
parent 5048c350ae
commit 91ab1f6d2b
32 changed files with 11101 additions and 207 deletions

1
tools/goctl/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist

View File

@@ -77,6 +77,7 @@ func init() {
goCmdFlags.StringVar(&gogen.VarStringRemote, "remote") goCmdFlags.StringVar(&gogen.VarStringRemote, "remote")
goCmdFlags.StringVar(&gogen.VarStringBranch, "branch") goCmdFlags.StringVar(&gogen.VarStringBranch, "branch")
goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "test") goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "test")
goCmdFlags.BoolVar(&gogen.VarBoolTypeGroup, "type-group")
goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat) goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat)
javaCmdFlags.StringVar(&javagen.VarStringDir, "dir") javaCmdFlags.StringVar(&javagen.VarStringDir, "dir")

View File

@@ -40,6 +40,8 @@ var (
// VarStringStyle describes the style of output files. // VarStringStyle describes the style of output files.
VarStringStyle string VarStringStyle string
VarBoolWithTest bool VarBoolWithTest bool
// VarBoolTypeGroup describes whether to group types.
VarBoolTypeGroup bool
) )
// GoCommand gen go project files from command line // GoCommand gen go project files from command line

View File

@@ -9,11 +9,11 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/zeromicro/go-zero/core/collection"
"github.com/zeromicro/go-zero/tools/goctl/api/spec" "github.com/zeromicro/go-zero/tools/goctl/api/spec"
apiutil "github.com/zeromicro/go-zero/tools/goctl/api/util" apiutil "github.com/zeromicro/go-zero/tools/goctl/api/util"
"github.com/zeromicro/go-zero/tools/goctl/config" "github.com/zeromicro/go-zero/tools/goctl/config"
"github.com/zeromicro/go-zero/tools/goctl/internal/version" "github.com/zeromicro/go-zero/tools/goctl/internal/version"
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
"github.com/zeromicro/go-zero/tools/goctl/util" "github.com/zeromicro/go-zero/tools/goctl/util"
"github.com/zeromicro/go-zero/tools/goctl/util/format" "github.com/zeromicro/go-zero/tools/goctl/util/format"
) )
@@ -41,53 +41,116 @@ func BuildTypes(types []spec.Type) (string, error) {
return builder.String(), nil return builder.String(), nil
} }
func removeTypeFromDefault(tp spec.Type, group string, groupTypes map[string]map[string]spec.Type) map[string]map[string]spec.Type { func getTypeName(tp spec.Type) string {
if tp == nil {
return ""
}
switch val := tp.(type) { switch val := tp.(type) {
case spec.DefineStruct: case spec.DefineStruct:
typeName := util.Title(tp.Name()) typeName := util.Title(tp.Name())
defaultGroups, ok := groupTypes[groupTypeDefault] return typeName
if ok {
delete(defaultGroups, typeName)
types, ok := groupTypes[group]
if !ok {
types = make(map[string]spec.Type)
}
types[typeName] = tp
groupTypes[group] = types
}
groupTypes[groupTypeDefault] = defaultGroups
case spec.PointerType: case spec.PointerType:
groupTypes = removeTypeFromDefault(val.Type, group, groupTypes) return getTypeName(val.Type)
case spec.ArrayType: case spec.ArrayType:
groupTypes = removeTypeFromDefault(val.Value, group, groupTypes) return getTypeName(val.Value)
} }
return groupTypes return ""
} }
func genTypesWithGroup(dir string, cfg *config.Config, api *spec.ApiSpec) error { func genTypesWithGroup(dir string, cfg *config.Config, api *spec.ApiSpec) error {
groupTypes := make(map[string]map[string]spec.Type) groupTypes := make(map[string]map[string]spec.Type)
for _, v := range api.Types { typesBelongToFiles := make(map[string]*collection.Set)
types, ok := groupTypes[groupTypeDefault]
if !ok {
types = make(map[string]spec.Type)
}
types[util.Title(v.Name())] = v
groupTypes[groupTypeDefault] = types
}
for _, v := range api.Service.Groups { for _, v := range api.Service.Groups {
group := v.GetAnnotation(groupProperty) group := v.GetAnnotation(groupProperty)
if len(group) == 0 { if len(group) == 0 {
group = groupTypeDefault
}
// convert filepath to Identifier name spec.
group = strings.TrimPrefix(group, "/")
group = strings.TrimSuffix(group, "/")
group = util.SafeString(group)
for _, v := range v.Routes {
requestTypeName := getTypeName(v.RequestType)
responseTypeName := getTypeName(v.ResponseType)
requestTypeFileSet, ok := typesBelongToFiles[requestTypeName]
if !ok {
requestTypeFileSet = collection.NewSet()
}
if len(requestTypeName) > 0 {
requestTypeFileSet.AddStr(group)
typesBelongToFiles[requestTypeName] = requestTypeFileSet
}
responseTypeFileSet, ok := typesBelongToFiles[responseTypeName]
if !ok {
responseTypeFileSet = collection.NewSet()
}
if len(responseTypeName) > 0 {
responseTypeFileSet.AddStr(group)
typesBelongToFiles[responseTypeName] = responseTypeFileSet
}
}
}
typesInOneFile := make(map[string]*collection.Set)
for typeName, fileSet := range typesBelongToFiles {
count := fileSet.Count()
switch {
case count == 0: // it means there has no structure type or no request/response body
continue
case count == 1: // it means a structure type used in only one group.
groupName := fileSet.KeysStr()[0]
typeSet, ok := typesInOneFile[groupName]
if !ok {
typeSet = collection.NewSet()
}
typeSet.AddStr(typeName)
typesInOneFile[groupName] = typeSet
default: // it means this type is used in multiple groups.
continue continue
} }
for _, v := range v.Routes { }
if v.RequestType != nil {
groupTypes = removeTypeFromDefault(v.RequestType, group, groupTypes) for _, v := range api.Types {
} typeName := util.Title(v.Name())
if v.ResponseType != nil { groupSet, ok := typesBelongToFiles[typeName]
groupTypes = removeTypeFromDefault(v.ResponseType, group, groupTypes) var typeCount int
} if !ok {
typeCount = 0
} else {
typeCount = groupSet.Count()
} }
if typeCount == 0 { // not belong to any group
types, ok := groupTypes[groupTypeDefault]
if !ok {
types = make(map[string]spec.Type)
}
types[typeName] = v
groupTypes[groupTypeDefault] = types
continue
}
if typeCount == 1 { // belong to one group
groupName := groupSet.KeysStr()[0]
types, ok := groupTypes[groupName]
if !ok {
types = make(map[string]spec.Type)
}
types[typeName] = v
groupTypes[groupName] = types
continue
}
// belong to multiple groups
types, ok := groupTypes[groupTypeDefault]
if !ok {
types = make(map[string]spec.Type)
}
types[typeName] = v
groupTypes[groupTypeDefault] = types
} }
for group, typeGroup := range groupTypes { for group, typeGroup := range groupTypes {
@@ -142,7 +205,7 @@ func writeTypes(dir, baseFilename string, cfg *config.Config, types []spec.Type)
} }
func genTypes(dir string, cfg *config.Config, api *spec.ApiSpec) error { func genTypes(dir string, cfg *config.Config, api *spec.ApiSpec) error {
if env.UseExperimental() { if VarBoolTypeGroup {
return genTypesWithGroup(dir, cfg, api) return genTypesWithGroup(dir, cfg, api)
} }
return writeTypes(dir, typesFile, cfg, api.Types) return writeTypes(dir, typesFile, cfg, api.Types)

View File

@@ -7,15 +7,6 @@ import (
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
) )
func hasKey(properties map[string]string, key string) bool {
if len(properties) == 0 {
return false
}
md := metadata.New(properties)
_, ok := md[key]
return ok
}
func getBoolFromKVOrDefault(properties map[string]string, key string, def bool) bool { func getBoolFromKVOrDefault(properties map[string]string, key string, def bool) bool {
if len(properties) == 0 { if len(properties) == 0 {
return def return def

View File

@@ -42,7 +42,7 @@ func Test_getListFromInfoOrDefault(t *testing.T) {
"empty": `""`, "empty": `""`,
} }
assert.Equal(t, []string{"a", "b", "c"}, getListFromInfoOrDefault(properties, "list", []string{"default"})) assert.Equal(t, []string{"a", " b", " c"}, getListFromInfoOrDefault(properties, "list", []string{"default"}))
assert.Equal(t, []string{"default"}, getListFromInfoOrDefault(properties, "empty", []string{"default"})) assert.Equal(t, []string{"default"}, getListFromInfoOrDefault(properties, "empty", []string{"default"}))
assert.Equal(t, []string{"default"}, getListFromInfoOrDefault(properties, "missing", []string{"default"})) assert.Equal(t, []string{"default"}, getListFromInfoOrDefault(properties, "missing", []string{"default"}))
assert.Equal(t, []string{"default"}, getListFromInfoOrDefault(nil, "nil", []string{"default"})) assert.Equal(t, []string{"default"}, getListFromInfoOrDefault(nil, "nil", []string{"default"}))

View File

@@ -8,10 +8,9 @@ import (
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser" "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
"gopkg.in/yaml.v2"
) )
var ( var (

View File

@@ -1,14 +1,15 @@
package swagger package swagger
const ( const (
tagHeader = "header" tagHeader = "header"
tagPath = "path" tagPath = "path"
tagForm = "form" tagForm = "form"
tagJson = "json" tagJson = "json"
defFlag = "default=" defFlag = "default="
enumFlag = "options=" enumFlag = "options="
rangeFlag = "range=" rangeFlag = "range="
exampleFlag = "example=" exampleFlag = "example="
optionalFlag = "optional"
paramsInHeader = "header" paramsInHeader = "header"
paramsInPath = "path" paramsInPath = "path"
@@ -27,6 +28,36 @@ const (
applicationJson = "application/json" applicationJson = "application/json"
applicationForm = "application/x-www-form-urlencoded" applicationForm = "application/x-www-form-urlencoded"
schemeHttps = "https" schemeHttps = "https"
defaultHost = "127.0.0.1"
defaultBasePath = "/" defaultBasePath = "/"
) )
const (
propertyKeyUseDefinitions = "useDefinitions"
propertyKeyExternalDocsDescription = "externalDocsDescription"
propertyKeyExternalDocsURL = "externalDocsURL"
propertyKeyTitle = "title"
propertyKeyTermsOfService = "termsOfService"
propertyKeyDescription = "description"
propertyKeyVersion = "version"
propertyKeyContactName = "contactName"
propertyKeyContactURL = "contactURL"
propertyKeyContactEmail = "contactEmail"
propertyKeyLicenseName = "licenseName"
propertyKeyLicenseURL = "licenseURL"
propertyKeyProduces = "produces"
propertyKeyConsumes = "consumes"
propertyKeySchemes = "schemes"
propertyKeyTags = "tags"
propertyKeySummary = "summary"
propertyKeyDeprecated = "deprecated"
propertyKeyPrefix = "prefix"
propertyKeyAuthType = "authType"
propertyKeyHost = "host"
propertyKeyBasePath = "basePath"
propertyKeyWrapCodeMsg = "wrapCodeMsg"
propertyKeyBizCodeEnumDescription = "bizCodeEnumDescription"
)
const (
defaultValueOfPropertyUseDefinition = false
)

View File

@@ -7,7 +7,7 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/api/spec" "github.com/zeromicro/go-zero/tools/goctl/api/spec"
) )
func consumesFromTypeOrDef(method string, tp spec.Type) []string { func consumesFromTypeOrDef(ctx Context, method string, tp spec.Type) []string {
if strings.EqualFold(method, http.MethodGet) { if strings.EqualFold(method, http.MethodGet) {
return []string{} return []string{}
} }
@@ -18,7 +18,7 @@ func consumesFromTypeOrDef(method string, tp spec.Type) []string {
if !ok { if !ok {
return []string{} return []string{}
} }
if typeContainsTag(structType, tagJson) { if typeContainsTag(ctx, structType, tagJson) {
return []string{applicationJson} return []string{applicationJson}
} }
return []string{applicationForm} return []string{applicationForm}

View File

@@ -61,7 +61,7 @@ func TestConsumesFromTypeOrDef(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
result := consumesFromTypeOrDef(tt.method, tt.tp) result := consumesFromTypeOrDef(testingContext(t), tt.method, tt.tp)
assert.Equal(t, tt.expected, result) assert.Equal(t, tt.expected, result)
}) })
} }

View File

@@ -0,0 +1,28 @@
package swagger
import (
"testing"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
type Context struct {
UseDefinitions bool
WrapCodeMsg bool
BizCodeEnumDescription string
}
func testingContext(_ *testing.T) Context {
return Context{}
}
func contextFromApi(info spec.Info) Context {
if len(info.Properties) == 0 {
return Context{}
}
return Context{
UseDefinitions: getBoolFromKVOrDefault(info.Properties, propertyKeyUseDefinitions, defaultValueOfPropertyUseDefinition),
WrapCodeMsg: getBoolFromKVOrDefault(info.Properties, propertyKeyWrapCodeMsg, false),
BizCodeEnumDescription: getStringFromKVOrDefault(info.Properties, propertyKeyBizCodeEnumDescription, "business code"),
}
}

View File

@@ -0,0 +1,32 @@
package swagger
import (
"github.com/go-openapi/spec"
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
func definitionsFromTypes(ctx Context, types []apiSpec.Type) spec.Definitions {
if !ctx.UseDefinitions {
return nil
}
definitions := make(spec.Definitions)
for _, tp := range types {
typeName := tp.Name()
definitions[typeName] = schemaFromType(ctx, tp)
}
return definitions
}
func schemaFromType(ctx Context, tp apiSpec.Type) spec.Schema {
p, r := propertiesFromType(ctx, tp)
props := spec.SchemaProps{
Type: typeFromGoType(ctx, tp),
Properties: p,
AdditionalProperties: mapFromGoType(ctx, tp),
Items: itemsFromGoType(ctx, tp),
Required: r,
}
return spec.Schema{
SchemaProps: props,
}
}

View File

@@ -12,15 +12,16 @@ info (
licenseURL: "https://github.com/zeromicro/go-zero" // licenseURL corresponding to Swagger licenseURL: "https://github.com/zeromicro/go-zero" // licenseURL corresponding to Swagger
consumes: "application/json" // consumes corresponding to Swagger,default value is `application/json` consumes: "application/json" // consumes corresponding to Swagger,default value is `application/json`
produces: "application/json" // produces corresponding to Swagger,default value is `application/json` produces: "application/json" // produces corresponding to Swagger,default value is `application/json`
schemes: "https" // schemes corresponding to Swagger,default value is `https`` schemes: "http,https" // schemes corresponding to Swagger,default value is `https``
host: "example.com" // host corresponding to Swagger,default value is `127.0.0.1` host: "example.com" // host corresponding to Swagger,default value is `127.0.0.1`
basePath: "/v1" // basePath corresponding to Swagger,default value is `/` basePath: "/v1" // basePath corresponding to Swagger,default value is `/`
wrapCodeMsg: "true" // to wrap in the universal code-msg structure, like {"code":0,"msg":"OK","data":$data} wrapCodeMsg: true // to wrap in the universal code-msg structure, like {"code":0,"msg":"OK","data":$data}
bizCodeEnumDescription: "1001-User not login<br>1002-User permission denied" // enums of business error codes, in JSON format, with the key being the business error code and the value being the description of that error code. This only takes effect when wrapCodeMsg is set to true. bizCodeEnumDescription: "1001-User not login<br>1002-User permission denied" // enums of business error codes, in JSON format, with the key being the business error code and the value being the description of that error code. This only takes effect when wrapCodeMsg is set to true.
// securityDefinitionsFromJson is a custom authentication configuration, and the JSON content will be directly inserted into the securityDefinitions of Swagger. // securityDefinitionsFromJson is a custom authentication configuration, and the JSON content will be directly inserted into the securityDefinitions of Swagger.
// Format reference: https://swagger.io/specification/v2/#security-definitions-object // Format reference: https://swagger.io/specification/v2/#security-definitions-object
// You can declare authType in the @server of the API to specify the authentication type used for its routes. // You can declare authType in the @server of the API to specify the authentication type used for its routes.
securityDefinitionsFromJson: `{"apiKey":{"description":"apiKey type description","type":"apiKey","name":"x-api-key","in":"header"}}` securityDefinitionsFromJson: `{"apiKey":{"description":"apiKey type description","type":"apiKey","name":"x-api-key","in":"header"}}`
useDefinitions: true // if set true, the definitions will be generated in the swagger.json for response body or json request body file, and the models will be referenced in the API.
) )
type ( type (

File diff suppressed because it is too large Load Diff

View File

@@ -12,15 +12,16 @@ info (
licenseURL: "https://github.com/zeromicro/go-zero" // 对应 swagger 的 licenseURL licenseURL: "https://github.com/zeromicro/go-zero" // 对应 swagger 的 licenseURL
consumes: "application/json" // 对应 swagger 的 consumes,不填默认为 application/json consumes: "application/json" // 对应 swagger 的 consumes,不填默认为 application/json
produces: "application/json" // 对应 swagger 的 produces,不填默认为 application/json produces: "application/json" // 对应 swagger 的 produces,不填默认为 application/json
schemes: "https" // 对应 swagger 的 schemes,不填默认为 https schemes: "http,https" // 对应 swagger 的 schemes,不填默认为 https
host: "example.com" // 对应 swagger 的 host,不填默认为 127.0.0.1 host: "example.com" // 对应 swagger 的 host,不填默认为 127.0.0.1
basePath: "/v1" // 对应 swagger 的 basePath,不填默认为 / basePath: "/v1" // 对应 swagger 的 basePath,不填默认为 /
wrapCodeMsg: "true" // 是否用 code-msg 通用响应体,如果开启,则以格式 {"code":0,"msg":"OK","data":$data} 包括响应体 wrapCodeMsg: true // 是否用 code-msg 通用响应体,如果开启,则以格式 {"code":0,"msg":"OK","data":$data} 包括响应体
bizCodeEnumDescription: "1001-未登录<br>1002-无权限操作" // 全局业务错误码枚举描述json 格式,key 为业务错误码value 为该错误码的描述,仅当 wrapCodeMsg 为 true 时生效 bizCodeEnumDescription: "1001-未登录<br>1002-无权限操作" // 全局业务错误码枚举描述json 格式,key 为业务错误码value 为该错误码的描述,仅当 wrapCodeMsg 为 true 时生效
// securityDefinitionsFromJson 为自定义鉴权配置json 内容将直接放入 swagger 的 securityDefinitions 中, // securityDefinitionsFromJson 为自定义鉴权配置json 内容将直接放入 swagger 的 securityDefinitions 中,
// 格式参考 https://swagger.io/specification/v2/#security-definitions-object // 格式参考 https://swagger.io/specification/v2/#security-definitions-object
// 在 api 的 @server 中可声明 authType 来指定其路由使用的鉴权类型 // 在 api 的 @server 中可声明 authType 来指定其路由使用的鉴权类型
securityDefinitionsFromJson: `{"apiKey":{"description":"apiKey 类型鉴权自定义","type":"apiKey","name":"x-api-key","in":"header"}}` securityDefinitionsFromJson: `{"apiKey":{"description":"apiKey 类型鉴权自定义","type":"apiKey","name":"x-api-key","in":"header"}}`
useDefinitions: true// 开启声明将生成models 进行关联definitions 仅对响应体和 json 请求体生效
) )
type ( type (
@@ -52,7 +53,7 @@ type (
service Swagger { service Swagger {
@doc ( @doc (
description: "query 接口" description: "query 接口"
bizCodeEnumDescription: " 1003-用不存在<br>1004-非法操作" // 接口级别业务错误码枚举描述会覆盖全局的业务错误码json 格式,key 为业务错误码value 为该错误码的描述,仅当 wrapCodeMsg 为 true 时生效 bizCodeEnumDescription: " 1003-用不存在<br>1004-非法操作" // 接口级别业务错误码枚举描述会覆盖全局的业务错误码json 格式,key 为业务错误码value 为该错误码的描述,仅当 wrapCodeMsg 为 true 且 useDefinitions 为 false 时生效
) )
@handler query @handler query
get /query (QueryReq) returns (QueryResp) get /query (QueryReq) returns (QueryResp)

File diff suppressed because it is too large Load Diff

View File

@@ -81,21 +81,21 @@ func enumsValueFromOptions(options []string) []any {
return []any{} return []any{}
} }
func defValueFromOptions(options []string, apiType spec.Type) any { func defValueFromOptions(ctx Context, options []string, apiType spec.Type) any {
tp := sampleTypeFromGoType(apiType) tp := sampleTypeFromGoType(ctx, apiType)
return valueFromOptions(options, defFlag, tp) return valueFromOptions(ctx, options, defFlag, tp)
} }
func exampleValueFromOptions(options []string, apiType spec.Type) any { func exampleValueFromOptions(ctx Context, options []string, apiType spec.Type) any {
tp := sampleTypeFromGoType(apiType) tp := sampleTypeFromGoType(ctx, apiType)
val := valueFromOptions(options, exampleFlag, tp) val := valueFromOptions(ctx, options, exampleFlag, tp)
if val != nil { if val != nil {
return val return val
} }
return defValueFromOptions(options, apiType) return defValueFromOptions(ctx, options, apiType)
} }
func valueFromOptions(options []string, key string, tp string) any { func valueFromOptions(_ Context, options []string, key string, tp string) any {
if len(options) == 0 { if len(options) == 0 {
return nil return nil
} }
@@ -103,16 +103,18 @@ func valueFromOptions(options []string, key string, tp string) any {
if strings.HasPrefix(option, key) { if strings.HasPrefix(option, key) {
s := option[len(key):] s := option[len(key):]
switch tp { switch tp {
case "integer": case swaggerTypeInteger:
val, _ := strconv.ParseInt(s, 10, 64) val, _ := strconv.ParseInt(s, 10, 64)
return val return val
case "boolean": case swaggerTypeBoolean:
val, _ := strconv.ParseBool(s) val, _ := strconv.ParseBool(s)
return val return val
case "number": case swaggerTypeNumber:
val, _ := strconv.ParseFloat(s, 64) val, _ := strconv.ParseFloat(s, 64)
return val return val
case "string": case swaggerTypeArray:
return s
case swaggerTypeString:
return s return s
default: default:
return nil return nil

View File

@@ -161,7 +161,7 @@ func TestDefValueFromOptions(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
result := defValueFromOptions(tt.options, tt.apiType) result := defValueFromOptions(testingContext(t), tt.options, tt.apiType)
assert.Equal(t, tt.expected, result) assert.Equal(t, tt.expected, result)
}) })
} }
@@ -202,7 +202,7 @@ func TestExampleValueFromOptions(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
exampleValueFromOptions(tt.options, tt.apiType) exampleValueFromOptions(testingContext(t), tt.options, tt.apiType)
}) })
} }
} }
@@ -247,7 +247,7 @@ func TestValueFromOptions(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
result := valueFromOptions(tt.options, tt.key, tt.tp) result := valueFromOptions(testingContext(t), tt.options, tt.key, tt.tp)
assert.Equal(t, tt.expected, result) assert.Equal(t, tt.expected, result)
}) })
} }

View File

@@ -8,7 +8,25 @@ import (
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec" apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
) )
func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter { func isPostJson(ctx Context, method string, tp apiSpec.Type) (string, bool) {
if strings.EqualFold(method, http.MethodPost) {
return "", false
}
structType, ok := tp.(apiSpec.DefineStruct)
if !ok {
return "", false
}
var isPostJson bool
rangeMemberAndDo(ctx, structType, func(tag *apiSpec.Tags, required bool, member apiSpec.Member) {
jsonTag, _ := tag.Get(tagJson)
if !isPostJson {
isPostJson = jsonTag != nil
}
})
return structType.RawName, isPostJson
}
func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Parameter {
if tp == nil { if tp == nil {
return []spec.Parameter{} return []spec.Parameter{}
} }
@@ -16,12 +34,30 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
if !ok { if !ok {
return []spec.Parameter{} return []spec.Parameter{}
} }
structName, ok := isPostJson(ctx, method, tp)
if ok {
return []spec.Parameter{
{
ParamProps: spec.ParamProps{
In: paramsInBody,
Name: paramsInBody,
Required: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef(getRefName(structName)),
},
},
},
},
}
}
var ( var (
resp []spec.Parameter resp []spec.Parameter
properties = map[string]spec.Schema{} properties = map[string]spec.Schema{}
requiredFields []string requiredFields []string
) )
rangeMemberAndDo(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
@@ -44,10 +80,9 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Enum: enumsValueFromOptions(headerTag.Options), Enum: enumsValueFromOptions(headerTag.Options),
}, },
SimpleSchema: spec.SimpleSchema{ SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(member.Type), Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(headerTag.Options, member.Type), Default: defValueFromOptions(ctx, headerTag.Options, member.Type),
Example: exampleValueFromOptions(headerTag.Options, member.Type), Items: sampleItemsFromGoType(ctx, member.Type),
Items: sampleItemsFromGoType(member.Type),
}, },
ParamProps: spec.ParamProps{ ParamProps: spec.ParamProps{
In: paramsInHeader, In: paramsInHeader,
@@ -68,10 +103,9 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Enum: enumsValueFromOptions(pathParameterTag.Options), Enum: enumsValueFromOptions(pathParameterTag.Options),
}, },
SimpleSchema: spec.SimpleSchema{ SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(member.Type), Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(pathParameterTag.Options, member.Type), Default: defValueFromOptions(ctx, pathParameterTag.Options, member.Type),
Example: exampleValueFromOptions(pathParameterTag.Options, member.Type), Items: sampleItemsFromGoType(ctx, member.Type),
Items: sampleItemsFromGoType(member.Type),
}, },
ParamProps: spec.ParamProps{ ParamProps: spec.ParamProps{
In: paramsInPath, In: paramsInPath,
@@ -93,10 +127,9 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Enum: enumsValueFromOptions(formTag.Options), Enum: enumsValueFromOptions(formTag.Options),
}, },
SimpleSchema: spec.SimpleSchema{ SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(member.Type), Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(formTag.Options, member.Type), Default: defValueFromOptions(ctx, formTag.Options, member.Type),
Example: exampleValueFromOptions(formTag.Options, member.Type), Items: sampleItemsFromGoType(ctx, member.Type),
Items: sampleItemsFromGoType(member.Type),
}, },
ParamProps: spec.ParamProps{ ParamProps: spec.ParamProps{
In: paramsInQuery, In: paramsInQuery,
@@ -116,10 +149,9 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Enum: enumsValueFromOptions(formTag.Options), Enum: enumsValueFromOptions(formTag.Options),
}, },
SimpleSchema: spec.SimpleSchema{ SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(member.Type), Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(formTag.Options, member.Type), Default: defValueFromOptions(ctx, formTag.Options, member.Type),
Example: exampleValueFromOptions(formTag.Options, member.Type), Items: sampleItemsFromGoType(ctx, member.Type),
Items: sampleItemsFromGoType(member.Type),
}, },
ParamProps: spec.ParamProps{ ParamProps: spec.ParamProps{
In: paramsInForm, In: paramsInForm,
@@ -139,25 +171,25 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
} }
var schema = spec.Schema{ var schema = spec.Schema{
SwaggerSchemaProps: spec.SwaggerSchemaProps{ SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: exampleValueFromOptions(jsonTag.Options, member.Type), Example: exampleValueFromOptions(ctx, jsonTag.Options, member.Type),
}, },
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: formatComment(member.Comment), Description: formatComment(member.Comment),
Type: typeFromGoType(member.Type), Type: typeFromGoType(ctx, member.Type),
Default: defValueFromOptions(jsonTag.Options, member.Type), Default: defValueFromOptions(ctx, jsonTag.Options, member.Type),
Maximum: maximum, Maximum: maximum,
ExclusiveMaximum: exclusiveMaximum, ExclusiveMaximum: exclusiveMaximum,
Minimum: minimum, Minimum: minimum,
ExclusiveMinimum: exclusiveMinimum, ExclusiveMinimum: exclusiveMinimum,
Enum: enumsValueFromOptions(jsonTag.Options), Enum: enumsValueFromOptions(jsonTag.Options),
AdditionalProperties: mapFromGoType(member.Type), AdditionalProperties: mapFromGoType(ctx, member.Type),
}, },
} }
switch sampleTypeFromGoType(member.Type) { switch sampleTypeFromGoType(ctx, member.Type) {
case swaggerTypeArray: case swaggerTypeArray:
schema.Items = itemsFromGoType(member.Type) schema.Items = itemsFromGoType(ctx, member.Type)
case swaggerTypeObject: case swaggerTypeObject:
p, r := propertiesFromType(member.Type) p, r := propertiesFromType(ctx, member.Type)
schema.Properties = p schema.Properties = p
schema.Required = r schema.Required = r
} }
@@ -172,7 +204,7 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Required: true, Required: true,
Schema: &spec.Schema{ Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: typeFromGoType(structType), Type: typeFromGoType(ctx, structType),
Properties: properties, Properties: properties,
Required: requiredFields, Required: requiredFields,
}, },

View File

@@ -9,18 +9,18 @@ import (
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec" apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
) )
func spec2Paths(info apiSpec.Info, srv apiSpec.Service) *spec.Paths { func spec2Paths(ctx Context, srv apiSpec.Service) *spec.Paths {
paths := &spec.Paths{ paths := &spec.Paths{
Paths: make(map[string]spec.PathItem), Paths: make(map[string]spec.PathItem),
} }
for _, group := range srv.Groups { for _, group := range srv.Groups {
prefix := path.Clean(strings.TrimPrefix(group.GetAnnotation("prefix"), "/")) prefix := path.Clean(strings.TrimPrefix(group.GetAnnotation(propertyKeyPrefix), "/"))
for _, route := range group.Routes { for _, route := range group.Routes {
routPath := pathVariable2SwaggerVariable(route.Path) routPath := pathVariable2SwaggerVariable(ctx, route.Path)
if len(prefix) > 0 && prefix != "." { if len(prefix) > 0 && prefix != "." {
routPath = "/" + path.Clean(prefix) + routPath routPath = "/" + path.Clean(prefix) + routPath
} }
pathItem := spec2Path(info, group, route) pathItem := spec2Path(ctx, group, route)
existPathItem, ok := paths.Paths[routPath] existPathItem, ok := paths.Paths[routPath]
if !ok { if !ok {
paths.Paths[routPath] = pathItem paths.Paths[routPath] = pathItem
@@ -60,8 +60,8 @@ func mergePathItem(old, new spec.PathItem) spec.PathItem {
return old return old
} }
func spec2Path(info apiSpec.Info, group apiSpec.Group, route apiSpec.Route) spec.PathItem { func spec2Path(ctx Context, group apiSpec.Group, route apiSpec.Route) spec.PathItem {
authType := getStringFromKVOrDefault(group.Annotation.Properties, "authType", "") authType := getStringFromKVOrDefault(group.Annotation.Properties, propertyKeyAuthType, "")
var security []map[string][]string var security []map[string][]string
if len(authType) > 0 { if len(authType) > 0 {
security = []map[string][]string{ security = []map[string][]string{
@@ -72,20 +72,20 @@ func spec2Path(info apiSpec.Info, group apiSpec.Group, route apiSpec.Route) spec
} }
op := &spec.Operation{ op := &spec.Operation{
OperationProps: spec.OperationProps{ OperationProps: spec.OperationProps{
Description: getStringFromKVOrDefault(route.AtDoc.Properties, "description", ""), Description: getStringFromKVOrDefault(route.AtDoc.Properties, propertyKeyDescription, ""),
Consumes: consumesFromTypeOrDef(route.Method, route.RequestType), Consumes: consumesFromTypeOrDef(ctx, route.Method, route.RequestType),
Produces: getListFromInfoOrDefault(route.AtDoc.Properties, "produces", []string{applicationJson}), Produces: getListFromInfoOrDefault(route.AtDoc.Properties, propertyKeyProduces, []string{applicationJson}),
Schemes: getListFromInfoOrDefault(route.AtDoc.Properties, "schemes", []string{schemeHttps}), Schemes: getListFromInfoOrDefault(route.AtDoc.Properties, propertyKeySchemes, []string{schemeHttps}),
Tags: getListFromInfoOrDefault(group.Annotation.Properties, "tags", []string{""}), Tags: getListFromInfoOrDefault(group.Annotation.Properties, propertyKeyTags, getListFromInfoOrDefault(group.Annotation.Properties, propertyKeySummary, []string{})),
Summary: getStringFromKVOrDefault(route.AtDoc.Properties, "summary", getFirstUsableString(route.AtDoc.Text, route.Handler)), Summary: getStringFromKVOrDefault(route.AtDoc.Properties, propertyKeySummary, getFirstUsableString(route.AtDoc.Text, route.Handler)),
Deprecated: getBoolFromKVOrDefault(route.AtDoc.Properties, "deprecated", false), Deprecated: getBoolFromKVOrDefault(route.AtDoc.Properties, propertyKeyDeprecated, false),
Parameters: parametersFromType(route.Method, route.RequestType), Parameters: parametersFromType(ctx, route.Method, route.RequestType),
Responses: jsonResponseFromType(info, route.AtDoc, route.ResponseType), Responses: jsonResponseFromType(ctx, route.AtDoc, route.ResponseType),
Security: security, Security: security,
}, },
} }
externalDocsDescription := getStringFromKVOrDefault(route.AtDoc.Properties, "externalDocsDescription", "") externalDocsDescription := getStringFromKVOrDefault(route.AtDoc.Properties, propertyKeyExternalDocsDescription, "")
externalDocsURL := getStringFromKVOrDefault(route.AtDoc.Properties, "externalDocsURL", "") externalDocsURL := getStringFromKVOrDefault(route.AtDoc.Properties, propertyKeyExternalDocsURL, "")
if len(externalDocsDescription) > 0 || len(externalDocsURL) > 0 { if len(externalDocsDescription) > 0 || len(externalDocsURL) > 0 {
op.ExternalDocs = &spec.ExternalDocumentation{ op.ExternalDocs = &spec.ExternalDocumentation{
Description: externalDocsDescription, Description: externalDocsDescription,

View File

@@ -5,18 +5,18 @@ import (
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec" apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
) )
func propertiesFromType(tp apiSpec.Type) (spec.SchemaProperties, []string) { func propertiesFromType(ctx Context, tp apiSpec.Type) (spec.SchemaProperties, []string) {
var ( var (
properties = map[string]spec.Schema{} properties = map[string]spec.Schema{}
requiredFields []string requiredFields []string
) )
switch val := tp.(type) { switch val := tp.(type) {
case apiSpec.PointerType: case apiSpec.PointerType:
return propertiesFromType(val.Type) return propertiesFromType(ctx, val.Type)
case apiSpec.ArrayType: case apiSpec.ArrayType:
return propertiesFromType(val.Value) return propertiesFromType(ctx, val.Value)
case apiSpec.DefineStruct, apiSpec.NestedStruct: case apiSpec.DefineStruct, apiSpec.NestedStruct:
rangeMemberAndDo(val, func(tag *apiSpec.Tags, required bool, member apiSpec.Member) { rangeMemberAndDo(ctx, val, func(tag *apiSpec.Tags, required bool, member apiSpec.Member) {
var ( var (
jsonTagString = member.Name jsonTagString = member.Name
minimum, maximum *float64 minimum, maximum *float64
@@ -28,38 +28,46 @@ func propertiesFromType(tp apiSpec.Type) (spec.SchemaProperties, []string) {
if jsonTag != nil { if jsonTag != nil {
jsonTagString = jsonTag.Name jsonTagString = jsonTag.Name
minimum, maximum, exclusiveMinimum, exclusiveMaximum = rangeValueFromOptions(jsonTag.Options) minimum, maximum, exclusiveMinimum, exclusiveMaximum = rangeValueFromOptions(jsonTag.Options)
example = exampleValueFromOptions(jsonTag.Options, member.Type) example = exampleValueFromOptions(ctx, jsonTag.Options, member.Type)
defaultValue = defValueFromOptions(jsonTag.Options, member.Type) defaultValue = defValueFromOptions(ctx, jsonTag.Options, member.Type)
enum = enumsValueFromOptions(jsonTag.Options) enum = enumsValueFromOptions(jsonTag.Options)
} }
if required { if required {
requiredFields = append(requiredFields, jsonTagString) requiredFields = append(requiredFields, jsonTagString)
} }
var schema = spec.Schema{
schema := spec.Schema{
SwaggerSchemaProps: spec.SwaggerSchemaProps{ SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: example, Example: example,
}, },
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: formatComment(member.Comment), Description: formatComment(member.Comment),
Type: typeFromGoType(member.Type), Type: typeFromGoType(ctx, member.Type),
Default: defaultValue, Default: defaultValue,
Maximum: maximum, Maximum: maximum,
ExclusiveMaximum: exclusiveMaximum, ExclusiveMaximum: exclusiveMaximum,
Minimum: minimum, Minimum: minimum,
ExclusiveMinimum: exclusiveMinimum, ExclusiveMinimum: exclusiveMinimum,
Enum: enum, Enum: enum,
AdditionalProperties: mapFromGoType(member.Type), AdditionalProperties: mapFromGoType(ctx, member.Type),
}, },
} }
switch sampleTypeFromGoType(member.Type) {
switch sampleTypeFromGoType(ctx, member.Type) {
case swaggerTypeArray: case swaggerTypeArray:
schema.Items = itemsFromGoType(member.Type) schema.Items = itemsFromGoType(ctx, member.Type)
case swaggerTypeObject: case swaggerTypeObject:
p, r := propertiesFromType(member.Type) p, r := propertiesFromType(ctx, member.Type)
schema.Properties = p schema.Properties = p
schema.Required = r schema.Required = r
} }
if ctx.UseDefinitions {
structName, containsStruct := containsStruct(member.Type)
if containsStruct {
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
}
}
properties[jsonTagString] = schema properties[jsonTagString] = schema
}) })
@@ -67,3 +75,22 @@ func propertiesFromType(tp apiSpec.Type) (spec.SchemaProperties, []string) {
return properties, requiredFields return properties, requiredFields
} }
func containsStruct(tp apiSpec.Type) (string, bool) {
switch val := tp.(type) {
case apiSpec.PointerType:
return containsStruct(val.Type)
case apiSpec.ArrayType:
return containsStruct(val.Value)
case apiSpec.DefineStruct:
return val.RawName, true
case apiSpec.MapType:
return containsStruct(val.Value)
default:
return "", false
}
}
func getRefName(typeName string) string {
return "#/definitions/" + typeName
}

View File

@@ -1,25 +1,62 @@
package swagger package swagger
import ( import (
"net/http"
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec" apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
) )
func jsonResponseFromType(info apiSpec.Info, atDoc apiSpec.AtDoc, tp apiSpec.Type) *spec.Responses { func jsonResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type) *spec.Responses {
p, _ := propertiesFromType(tp) 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{ props := spec.SchemaProps{
Type: typeFromGoType(tp), AdditionalProperties: mapFromGoType(ctx, tp),
Properties: p, Items: itemsFromGoType(ctx, tp),
AdditionalProperties: mapFromGoType(tp), }
Items: itemsFromGoType(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{ return &spec.Responses{
ResponsesProps: spec.ResponsesProps{ ResponsesProps: spec.ResponsesProps{
Default: &spec.Response{ StatusCodeResponses: map[int]spec.Response{
ResponseProps: spec.ResponseProps{ http.StatusOK: {
Schema: &spec.Schema{ ResponseProps: spec.ResponseProps{
SchemaProps: wrapCodeMsgProps(props, info, atDoc), Schema: &spec.Schema{
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
},
}, },
}, },
}, },

View File

@@ -8,12 +8,11 @@ import (
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec" apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
"github.com/zeromicro/go-zero/tools/goctl/internal/version" "github.com/zeromicro/go-zero/tools/goctl/internal/version"
"github.com/zeromicro/go-zero/tools/goctl/util"
) )
func spec2Swagger(api *apiSpec.ApiSpec) (*spec.Swagger, error) { func spec2Swagger(api *apiSpec.ApiSpec) (*spec.Swagger, error) {
ctx := contextFromApi(api.Info)
extensions, info := specExtensions(api.Info) extensions, info := specExtensions(api.Info)
var securityDefinitions spec.SecurityDefinitions var securityDefinitions spec.SecurityDefinitions
securityDefinitionsFromJson := getStringFromKVOrDefault(api.Info.Properties, "securityDefinitionsFromJson", `{}`) securityDefinitionsFromJson := getStringFromKVOrDefault(api.Info.Properties, "securityDefinitionsFromJson", `{}`)
_ = json.Unmarshal([]byte(securityDefinitionsFromJson), &securityDefinitions) _ = json.Unmarshal([]byte(securityDefinitionsFromJson), &securityDefinitions)
@@ -22,14 +21,15 @@ func spec2Swagger(api *apiSpec.ApiSpec) (*spec.Swagger, error) {
Extensions: extensions, Extensions: extensions,
}, },
SwaggerProps: spec.SwaggerProps{ SwaggerProps: spec.SwaggerProps{
Consumes: getListFromInfoOrDefault(api.Info.Properties, "consumes", []string{applicationJson}), Definitions: definitionsFromTypes(ctx, api.Types),
Produces: getListFromInfoOrDefault(api.Info.Properties, "produces", []string{applicationJson}), Consumes: getListFromInfoOrDefault(api.Info.Properties, propertyKeyConsumes, []string{applicationJson}),
Schemes: getListFromInfoOrDefault(api.Info.Properties, "schemes", []string{schemeHttps}), Produces: getListFromInfoOrDefault(api.Info.Properties, propertyKeyProduces, []string{applicationJson}),
Schemes: getListFromInfoOrDefault(api.Info.Properties, propertyKeySchemes, []string{schemeHttps}),
Swagger: swaggerVersion, Swagger: swaggerVersion,
Info: info, Info: info,
Host: getStringFromKVOrDefault(api.Info.Properties, "host", defaultHost), Host: getStringFromKVOrDefault(api.Info.Properties, propertyKeyHost, ""),
BasePath: getStringFromKVOrDefault(api.Info.Properties, "basePath", defaultBasePath), BasePath: getStringFromKVOrDefault(api.Info.Properties, propertyKeyBasePath, defaultBasePath),
Paths: spec2Paths(api.Info, api.Service), Paths: spec2Paths(ctx, api.Service),
SecurityDefinitions: securityDefinitions, SecurityDefinitions: securityDefinitions,
}, },
} }
@@ -42,7 +42,7 @@ func formatComment(comment string) string {
return strings.TrimSpace(s) return strings.TrimSpace(s)
} }
func sampleItemsFromGoType(tp apiSpec.Type) *spec.Items { func sampleItemsFromGoType(ctx Context, tp apiSpec.Type) *spec.Items {
val, ok := tp.(apiSpec.ArrayType) val, ok := tp.(apiSpec.ArrayType)
if !ok { if !ok {
return nil return nil
@@ -52,14 +52,14 @@ func sampleItemsFromGoType(tp apiSpec.Type) *spec.Items {
case apiSpec.PrimitiveType: case apiSpec.PrimitiveType:
return &spec.Items{ return &spec.Items{
SimpleSchema: spec.SimpleSchema{ SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(item), Type: sampleTypeFromGoType(ctx, item),
}, },
} }
case apiSpec.ArrayType: case apiSpec.ArrayType:
return &spec.Items{ return &spec.Items{
SimpleSchema: spec.SimpleSchema{ SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(item), Type: sampleTypeFromGoType(ctx, item),
Items: sampleItemsFromGoType(item), Items: sampleItemsFromGoType(ctx, item),
}, },
} }
default: // unsupported type default: // unsupported type
@@ -68,30 +68,30 @@ func sampleItemsFromGoType(tp apiSpec.Type) *spec.Items {
} }
// itemsFromGoType returns the schema or array of the type, just for non json body parameters. // itemsFromGoType returns the schema or array of the type, just for non json body parameters.
func itemsFromGoType(tp apiSpec.Type) *spec.SchemaOrArray { func itemsFromGoType(ctx Context, tp apiSpec.Type) *spec.SchemaOrArray {
array, ok := tp.(apiSpec.ArrayType) array, ok := tp.(apiSpec.ArrayType)
if !ok { if !ok {
return nil return nil
} }
return itemFromGoType(array.Value) return itemFromGoType(ctx, array.Value)
} }
func mapFromGoType(tp apiSpec.Type) *spec.SchemaOrBool { func mapFromGoType(ctx Context, tp apiSpec.Type) *spec.SchemaOrBool {
mapType, ok := tp.(apiSpec.MapType) mapType, ok := tp.(apiSpec.MapType)
if !ok { if !ok {
return nil return nil
} }
var schema = &spec.Schema{ var schema = &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: typeFromGoType(mapType.Value), Type: typeFromGoType(ctx, mapType.Value),
AdditionalProperties: mapFromGoType(mapType.Value), AdditionalProperties: mapFromGoType(ctx, mapType.Value),
}, },
} }
switch sampleTypeFromGoType(mapType.Value) { switch sampleTypeFromGoType(ctx, mapType.Value) {
case swaggerTypeArray: case swaggerTypeArray:
schema.Items = itemsFromGoType(mapType.Value) schema.Items = itemsFromGoType(ctx, mapType.Value)
case swaggerTypeObject: case swaggerTypeObject:
p, r := propertiesFromType(mapType.Value) p, r := propertiesFromType(ctx, mapType.Value)
schema.Properties = p schema.Properties = p
schema.Required = r schema.Required = r
} }
@@ -102,37 +102,37 @@ func mapFromGoType(tp apiSpec.Type) *spec.SchemaOrBool {
} }
// itemFromGoType returns the schema or array of the type, just for non json body parameters. // itemFromGoType returns the schema or array of the type, just for non json body parameters.
func itemFromGoType(tp apiSpec.Type) *spec.SchemaOrArray { func itemFromGoType(ctx Context, tp apiSpec.Type) *spec.SchemaOrArray {
switch itemType := tp.(type) { switch itemType := tp.(type) {
case apiSpec.PrimitiveType: case apiSpec.PrimitiveType:
return &spec.SchemaOrArray{ return &spec.SchemaOrArray{
Schema: &spec.Schema{ Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: typeFromGoType(tp), Type: typeFromGoType(ctx, tp),
}, },
}, },
} }
case apiSpec.DefineStruct, apiSpec.NestedStruct: case apiSpec.DefineStruct, apiSpec.NestedStruct, apiSpec.MapType:
properties, requiredFields := propertiesFromType(itemType) properties, requiredFields := propertiesFromType(ctx, itemType)
return &spec.SchemaOrArray{ return &spec.SchemaOrArray{
Schema: &spec.Schema{ Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: typeFromGoType(itemType), Type: typeFromGoType(ctx, itemType),
Items: itemsFromGoType(itemType), Items: itemsFromGoType(ctx, itemType),
Properties: properties, Properties: properties,
Required: requiredFields, Required: requiredFields,
AdditionalProperties: mapFromGoType(itemType), AdditionalProperties: mapFromGoType(ctx, itemType),
}, },
}, },
} }
case apiSpec.PointerType: case apiSpec.PointerType:
return itemFromGoType(itemType.Type) return itemFromGoType(ctx, itemType.Type)
case apiSpec.ArrayType: case apiSpec.ArrayType:
return &spec.SchemaOrArray{ return &spec.SchemaOrArray{
Schema: &spec.Schema{ Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: typeFromGoType(itemType), Type: typeFromGoType(ctx, itemType),
Items: itemsFromGoType(itemType), Items: itemsFromGoType(ctx, itemType),
}, },
}, },
} }
@@ -140,7 +140,7 @@ func itemFromGoType(tp apiSpec.Type) *spec.SchemaOrArray {
return nil return nil
} }
func typeFromGoType(tp apiSpec.Type) []string { func typeFromGoType(ctx Context, tp apiSpec.Type) []string {
switch val := tp.(type) { switch val := tp.(type) {
case apiSpec.PrimitiveType: case apiSpec.PrimitiveType:
res, ok := tpMapper[val.RawName] res, ok := tpMapper[val.RawName]
@@ -152,12 +152,12 @@ func typeFromGoType(tp apiSpec.Type) []string {
case apiSpec.DefineStruct, apiSpec.MapType: case apiSpec.DefineStruct, apiSpec.MapType:
return []string{swaggerTypeObject} return []string{swaggerTypeObject}
case apiSpec.PointerType: case apiSpec.PointerType:
return typeFromGoType(val.Type) return typeFromGoType(ctx, val.Type)
} }
return nil return nil
} }
func sampleTypeFromGoType(tp apiSpec.Type) string { func sampleTypeFromGoType(ctx Context, tp apiSpec.Type) string {
switch val := tp.(type) { switch val := tp.(type) {
case apiSpec.PrimitiveType: case apiSpec.PrimitiveType:
return tpMapper[val.RawName] return tpMapper[val.RawName]
@@ -166,13 +166,13 @@ func sampleTypeFromGoType(tp apiSpec.Type) string {
case apiSpec.DefineStruct, apiSpec.MapType, apiSpec.NestedStruct: case apiSpec.DefineStruct, apiSpec.MapType, apiSpec.NestedStruct:
return swaggerTypeObject return swaggerTypeObject
case apiSpec.PointerType: case apiSpec.PointerType:
return sampleTypeFromGoType(val.Type) return sampleTypeFromGoType(ctx, val.Type)
default: default:
return "" return ""
} }
} }
func typeContainsTag(structType apiSpec.DefineStruct, tag string) bool { func typeContainsTag(_ Context, structType apiSpec.DefineStruct, tag string) bool {
for _, field := range structType.Members { for _, field := range structType.Members {
tags, _ := apiSpec.Parse(field.Tag) tags, _ := apiSpec.Parse(field.Tag)
for _, t := range tags.Tags() { for _, t := range tags.Tags() {
@@ -184,13 +184,13 @@ func typeContainsTag(structType apiSpec.DefineStruct, tag string) bool {
return false return false
} }
func expandMembers(tp apiSpec.Type) []apiSpec.Member { func expandMembers(ctx Context, tp apiSpec.Type) []apiSpec.Member {
var members []apiSpec.Member var members []apiSpec.Member
switch val := tp.(type) { switch val := tp.(type) {
case apiSpec.DefineStruct: case apiSpec.DefineStruct:
for _, v := range val.Members { for _, v := range val.Members {
if v.IsInline { if v.IsInline {
members = append(members, expandMembers(v.Type)...) members = append(members, expandMembers(ctx, v.Type)...)
continue continue
} }
members = append(members, v) members = append(members, v)
@@ -198,7 +198,7 @@ func expandMembers(tp apiSpec.Type) []apiSpec.Member {
case apiSpec.NestedStruct: case apiSpec.NestedStruct:
for _, v := range val.Members { for _, v := range val.Members {
if v.IsInline { if v.IsInline {
members = append(members, expandMembers(v.Type)...) members = append(members, expandMembers(ctx, v.Type)...)
continue continue
} }
members = append(members, v) members = append(members, v)
@@ -208,42 +208,42 @@ func expandMembers(tp apiSpec.Type) []apiSpec.Member {
return members return members
} }
func rangeMemberAndDo(structType apiSpec.Type, do func(tag *apiSpec.Tags, required bool, member apiSpec.Member)) { func rangeMemberAndDo(ctx Context, structType apiSpec.Type, do func(tag *apiSpec.Tags, required bool, member apiSpec.Member)) {
var members = expandMembers(structType) var members = expandMembers(ctx, structType)
for _, field := range members { for _, field := range members {
tags, _ := apiSpec.Parse(field.Tag) tags, _ := apiSpec.Parse(field.Tag)
required := isRequired(tags) required := isRequired(ctx, tags)
do(tags, required, field) do(tags, required, field)
} }
} }
func isRequired(tags *apiSpec.Tags) bool { func isRequired(ctx Context, tags *apiSpec.Tags) bool {
tag, err := tags.Get(tagJson) tag, err := tags.Get(tagJson)
if err == nil { if err == nil {
return !isOptional(tag.Options) return !isOptional(ctx, tag.Options)
} }
tag, err = tags.Get(tagForm) tag, err = tags.Get(tagForm)
if err == nil { if err == nil {
return !isOptional(tag.Options) return !isOptional(ctx, tag.Options)
} }
tag, err = tags.Get(tagPath) tag, err = tags.Get(tagPath)
if err == nil { if err == nil {
return !isOptional(tag.Options) return !isOptional(ctx, tag.Options)
} }
return false return false
} }
func isOptional(options []string) bool { func isOptional(_ Context, options []string) bool {
for _, option := range options { for _, option := range options {
if option == "optional" { if option == optionalFlag {
return true return true
} }
} }
return false return false
} }
func pathVariable2SwaggerVariable(path string) string { func pathVariable2SwaggerVariable(_ Context, path string) string {
pathItems := strings.FieldsFunc(path, slashRune) pathItems := strings.FieldsFunc(path, slashRune)
var resp []string var resp []string
for _, v := range pathItems { for _, v := range pathItems {
@@ -256,13 +256,12 @@ func pathVariable2SwaggerVariable(path string) string {
return "/" + strings.Join(resp, "/") return "/" + strings.Join(resp, "/")
} }
func wrapCodeMsgProps(properties spec.SchemaProps, api apiSpec.Info, atDoc apiSpec.AtDoc) spec.SchemaProps { func wrapCodeMsgProps(ctx Context, properties spec.SchemaProps, atDoc apiSpec.AtDoc) spec.SchemaProps {
wrapCodeMsg := getBoolFromKVOrDefault(api.Properties, "wrapCodeMsg", false) if !ctx.WrapCodeMsg {
if !wrapCodeMsg {
return properties return properties
} }
globalCodeDesc := getStringFromKVOrDefault(api.Properties, "bizCodeEnumDescription", "business code") globalCodeDesc := ctx.BizCodeEnumDescription
methodCodeDesc := getStringFromKVOrDefault(atDoc.Properties, "bizCodeEnumDescription", globalCodeDesc) methodCodeDesc := getStringFromKVOrDefault(atDoc.Properties, propertyKeyBizCodeEnumDescription, globalCodeDesc)
return spec.SchemaProps{ return spec.SchemaProps{
Type: []string{swaggerTypeObject}, Type: []string{swaggerTypeObject},
Properties: spec.SchemaProperties{ Properties: spec.SchemaProperties{
@@ -300,22 +299,22 @@ func specExtensions(api apiSpec.Info) (spec.Extensions, *spec.Info) {
ext.Add("x-go-zero-doc", "https://go-zero.dev/") ext.Add("x-go-zero-doc", "https://go-zero.dev/")
info := &spec.Info{} info := &spec.Info{}
info.Description = util.Unquote(api.Properties["description"]) info.Title = getStringFromKVOrDefault(api.Properties, propertyKeyTitle, "")
info.Title = util.Unquote(api.Properties["title"]) info.Description = getStringFromKVOrDefault(api.Properties, propertyKeyDescription, "")
info.TermsOfService = util.Unquote(api.Properties["termsOfService"]) info.TermsOfService = getStringFromKVOrDefault(api.Properties, propertyKeyTermsOfService, "")
info.Version = util.Unquote(api.Properties["version"]) info.Version = getStringFromKVOrDefault(api.Properties, propertyKeyVersion, "1.0")
contactInfo := spec.ContactInfo{} contactInfo := spec.ContactInfo{}
contactInfo.Name = util.Unquote(api.Properties["contactName"]) contactInfo.Name = getStringFromKVOrDefault(api.Properties, propertyKeyContactName, "")
contactInfo.URL = util.Unquote(api.Properties["contactURL"]) contactInfo.URL = getStringFromKVOrDefault(api.Properties, propertyKeyContactURL, "")
contactInfo.Email = util.Unquote(api.Properties["contactEmail"]) contactInfo.Email = getStringFromKVOrDefault(api.Properties, propertyKeyContactEmail, "")
if len(contactInfo.Name) > 0 || len(contactInfo.URL) > 0 || len(contactInfo.Email) > 0 { if len(contactInfo.Name) > 0 || len(contactInfo.URL) > 0 || len(contactInfo.Email) > 0 {
info.Contact = &contactInfo info.Contact = &contactInfo
} }
license := &spec.License{} license := &spec.License{}
license.Name = util.Unquote(api.Properties["licenseName"]) license.Name = getStringFromKVOrDefault(api.Properties, propertyKeyLicenseName, "")
license.URL = util.Unquote(api.Properties["licenseURL"]) license.URL = getStringFromKVOrDefault(api.Properties, propertyKeyLicenseURL, "")
if len(license.Name) > 0 || len(license.URL) > 0 { if len(license.Name) > 0 || len(license.URL) > 0 {
info.License = license info.License = license
} }

View File

@@ -19,7 +19,7 @@ func Test_pathVariable2SwaggerVariable(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
result := pathVariable2SwaggerVariable(tc.input) result := pathVariable2SwaggerVariable(testingContext(t), tc.input)
assert.Equal(t, tc.expected, result) assert.Equal(t, tc.expected, result)
} }
} }

2
tools/goctl/build.env Normal file
View File

@@ -0,0 +1,2 @@
APP_NAME=goctl
APP_VERSION=1.8.4-alpha

50
tools/goctl/build.sh Normal file
View File

@@ -0,0 +1,50 @@
#!/bin/bash
source build.env
APP_NAME=$APP_NAME
VERSION=$APP_VERSION
BUILD_DIR="dist"
ZIP_DIR="${BUILD_DIR}/zips"
PLATFORMS=(
"linux/amd64"
"linux/arm64"
"darwin/amd64"
"darwin/arm64"
"windows/amd64"
"windows/arm64"
)
rm -rf "${BUILD_DIR}"
mkdir -p "${ZIP_DIR}"
for PLATFORM in "${PLATFORMS[@]}"; do
GOOS=${PLATFORM%/*}
GOARCH=${PLATFORM#*/}
OUTPUT="${BUILD_DIR}/${APP_NAME}-${VERSION}-${GOOS}-${GOARCH}"
if [ "${GOOS}" = "windows" ]; then
OUTPUT="${OUTPUT}.exe"
fi
echo "Building for ${GOOS}/${GOARCH}..."
env GOOS="${GOOS}" GOARCH="${GOARCH}" go build -o "${OUTPUT}" goctl.go
if [ $? -ne 0 ]; then
echo "Error building for ${GOOS}/${GOARCH}"
exit 1
fi
ZIP_OUTPUT="${ZIP_DIR}/$(basename "${OUTPUT}")"
if [ "${GOOS}" = "windows" ]; then
zip -j "${ZIP_OUTPUT%.exe}.zip" "${OUTPUT}"
else
zip -j "${ZIP_OUTPUT}.zip" "${OUTPUT}"
fi
echo "Created zip: ${ZIP_OUTPUT}.zip"
done
echo "All builds completed successfully. Zip files are in ${ZIP_DIR}/"

View File

@@ -38,7 +38,8 @@
"remote": "{{.global.remote}}", "remote": "{{.global.remote}}",
"branch": "{{.global.branch}}", "branch": "{{.global.branch}}",
"style": "{{.global.style}}", "style": "{{.global.style}}",
"test": "Generate test files" "test": "Generate test files",
"type-group": "Generate type group files"
}, },
"new": { "new": {
"short": "Fast create api service", "short": "Fast create api service",

View File

@@ -6,7 +6,7 @@ import (
) )
// BuildVersion is the version of goctl. // BuildVersion is the version of goctl.
const BuildVersion = "1.8.3" const BuildVersion = "1.8.4-alpha"
var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5} var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}

View File

@@ -1356,7 +1356,7 @@ func (p *Parser) parseKVExpression() *ast.KVExpr {
expr.Colon = p.curTokenNode() expr.Colon = p.curTokenNode()
// token STRING // token STRING
if !p.advanceIfPeekTokenIs(token.STRING, token.RAW_STRING) { if !p.advanceIfPeekTokenIs(token.STRING, token.RAW_STRING, token.IDENT) {
return nil return nil
} }

View File

@@ -130,6 +130,8 @@ func TestParser_Parse_infoStmt(t *testing.T) {
"author": `"type author here"`, "author": `"type author here"`,
"email": `"type email here"`, "email": `"type email here"`,
"version": `"type version here"`, "version": `"type version here"`,
"enable": `true`,
"disable": `false`,
} }
p := New("foo.api", infoTestAPI) p := New("foo.api", infoTestAPI)
result := p.Parse() result := p.Parse()

View File

@@ -4,4 +4,6 @@ info(
author: "type author here" author: "type author here"
email: "type email here" email: "type email here"
version: "type version here" version: "type version here"
enable: true
disable: false
) )

View File

@@ -10,6 +10,8 @@ info ( // info stmt
author: "type author here" author: "type author here"
email: "type email here" email: "type email here"
version: "type version here" version: "type version here"
enable: true
disable: false
) )
type AliasInt int type AliasInt int