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.VarStringBranch, "branch")
goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "test")
goCmdFlags.BoolVar(&gogen.VarBoolTypeGroup, "type-group")
goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat)
javaCmdFlags.StringVar(&javagen.VarStringDir, "dir")

View File

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

View File

@@ -9,11 +9,11 @@ import (
"sort"
"strings"
"github.com/zeromicro/go-zero/core/collection"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
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/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/format"
)
@@ -41,53 +41,116 @@ func BuildTypes(types []spec.Type) (string, error) {
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) {
case spec.DefineStruct:
typeName := util.Title(tp.Name())
defaultGroups, ok := groupTypes[groupTypeDefault]
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
return typeName
case spec.PointerType:
groupTypes = removeTypeFromDefault(val.Type, group, groupTypes)
return getTypeName(val.Type)
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 {
groupTypes := make(map[string]map[string]spec.Type)
for _, v := range api.Types {
types, ok := groupTypes[groupTypeDefault]
if !ok {
types = make(map[string]spec.Type)
}
types[util.Title(v.Name())] = v
groupTypes[groupTypeDefault] = types
}
typesBelongToFiles := make(map[string]*collection.Set)
for _, v := range api.Service.Groups {
group := v.GetAnnotation(groupProperty)
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
}
for _, v := range v.Routes {
if v.RequestType != nil {
groupTypes = removeTypeFromDefault(v.RequestType, group, groupTypes)
}
if v.ResponseType != nil {
groupTypes = removeTypeFromDefault(v.ResponseType, group, groupTypes)
}
}
for _, v := range api.Types {
typeName := util.Title(v.Name())
groupSet, ok := typesBelongToFiles[typeName]
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 {
@@ -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 {
if env.UseExperimental() {
if VarBoolTypeGroup {
return genTypesWithGroup(dir, cfg, api)
}
return writeTypes(dir, typesFile, cfg, api.Types)

View File

@@ -7,15 +7,6 @@ import (
"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 {
if len(properties) == 0 {
return def

View File

@@ -42,7 +42,7 @@ func Test_getListFromInfoOrDefault(t *testing.T) {
"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, "missing", []string{"default"}))
assert.Equal(t, []string{"default"}, getListFromInfoOrDefault(nil, "nil", []string{"default"}))

View File

@@ -8,10 +8,9 @@ import (
"strings"
"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/util/pathx"
"gopkg.in/yaml.v2"
)
var (

View File

@@ -1,14 +1,15 @@
package swagger
const (
tagHeader = "header"
tagPath = "path"
tagForm = "form"
tagJson = "json"
defFlag = "default="
enumFlag = "options="
rangeFlag = "range="
exampleFlag = "example="
tagHeader = "header"
tagPath = "path"
tagForm = "form"
tagJson = "json"
defFlag = "default="
enumFlag = "options="
rangeFlag = "range="
exampleFlag = "example="
optionalFlag = "optional"
paramsInHeader = "header"
paramsInPath = "path"
@@ -27,6 +28,36 @@ const (
applicationJson = "application/json"
applicationForm = "application/x-www-form-urlencoded"
schemeHttps = "https"
defaultHost = "127.0.0.1"
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"
)
func consumesFromTypeOrDef(method string, tp spec.Type) []string {
func consumesFromTypeOrDef(ctx Context, method string, tp spec.Type) []string {
if strings.EqualFold(method, http.MethodGet) {
return []string{}
}
@@ -18,7 +18,7 @@ func consumesFromTypeOrDef(method string, tp spec.Type) []string {
if !ok {
return []string{}
}
if typeContainsTag(structType, tagJson) {
if typeContainsTag(ctx, structType, tagJson) {
return []string{applicationJson}
}
return []string{applicationForm}

View File

@@ -61,7 +61,7 @@ func TestConsumesFromTypeOrDef(t *testing.T) {
for _, tt := range tests {
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)
})
}

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
consumes: "application/json" // consumes 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`
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.
// 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
// 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"}}`
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 (

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
consumes: "application/json" // 对应 swagger 的 consumes,不填默认为 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
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 时生效
// securityDefinitionsFromJson 为自定义鉴权配置json 内容将直接放入 swagger 的 securityDefinitions 中,
// 格式参考 https://swagger.io/specification/v2/#security-definitions-object
// 在 api 的 @server 中可声明 authType 来指定其路由使用的鉴权类型
securityDefinitionsFromJson: `{"apiKey":{"description":"apiKey 类型鉴权自定义","type":"apiKey","name":"x-api-key","in":"header"}}`
useDefinitions: true// 开启声明将生成models 进行关联definitions 仅对响应体和 json 请求体生效
)
type (
@@ -52,7 +53,7 @@ type (
service Swagger {
@doc (
description: "query 接口"
bizCodeEnumDescription: " 1003-用不存在<br>1004-非法操作" // 接口级别业务错误码枚举描述会覆盖全局的业务错误码json 格式,key 为业务错误码value 为该错误码的描述,仅当 wrapCodeMsg 为 true 时生效
bizCodeEnumDescription: " 1003-用不存在<br>1004-非法操作" // 接口级别业务错误码枚举描述会覆盖全局的业务错误码json 格式,key 为业务错误码value 为该错误码的描述,仅当 wrapCodeMsg 为 true 且 useDefinitions 为 false 时生效
)
@handler query
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{}
}
func defValueFromOptions(options []string, apiType spec.Type) any {
tp := sampleTypeFromGoType(apiType)
return valueFromOptions(options, defFlag, tp)
func defValueFromOptions(ctx Context, options []string, apiType spec.Type) any {
tp := sampleTypeFromGoType(ctx, apiType)
return valueFromOptions(ctx, options, defFlag, tp)
}
func exampleValueFromOptions(options []string, apiType spec.Type) any {
tp := sampleTypeFromGoType(apiType)
val := valueFromOptions(options, exampleFlag, tp)
func exampleValueFromOptions(ctx Context, options []string, apiType spec.Type) any {
tp := sampleTypeFromGoType(ctx, apiType)
val := valueFromOptions(ctx, options, exampleFlag, tp)
if val != nil {
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 {
return nil
}
@@ -103,16 +103,18 @@ func valueFromOptions(options []string, key string, tp string) any {
if strings.HasPrefix(option, key) {
s := option[len(key):]
switch tp {
case "integer":
case swaggerTypeInteger:
val, _ := strconv.ParseInt(s, 10, 64)
return val
case "boolean":
case swaggerTypeBoolean:
val, _ := strconv.ParseBool(s)
return val
case "number":
case swaggerTypeNumber:
val, _ := strconv.ParseFloat(s, 64)
return val
case "string":
case swaggerTypeArray:
return s
case swaggerTypeString:
return s
default:
return nil

View File

@@ -161,7 +161,7 @@ func TestDefValueFromOptions(t *testing.T) {
for _, tt := range tests {
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)
})
}
@@ -202,7 +202,7 @@ func TestExampleValueFromOptions(t *testing.T) {
for _, tt := range tests {
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 {
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)
})
}

View File

@@ -8,7 +8,25 @@ import (
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 {
return []spec.Parameter{}
}
@@ -16,12 +34,30 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
if !ok {
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 (
resp []spec.Parameter
properties = map[string]spec.Schema{}
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)
hasHeader := headerTag != nil
@@ -44,10 +80,9 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Enum: enumsValueFromOptions(headerTag.Options),
},
SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(member.Type),
Default: defValueFromOptions(headerTag.Options, member.Type),
Example: exampleValueFromOptions(headerTag.Options, member.Type),
Items: sampleItemsFromGoType(member.Type),
Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, headerTag.Options, member.Type),
Items: sampleItemsFromGoType(ctx, member.Type),
},
ParamProps: spec.ParamProps{
In: paramsInHeader,
@@ -68,10 +103,9 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Enum: enumsValueFromOptions(pathParameterTag.Options),
},
SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(member.Type),
Default: defValueFromOptions(pathParameterTag.Options, member.Type),
Example: exampleValueFromOptions(pathParameterTag.Options, member.Type),
Items: sampleItemsFromGoType(member.Type),
Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, pathParameterTag.Options, member.Type),
Items: sampleItemsFromGoType(ctx, member.Type),
},
ParamProps: spec.ParamProps{
In: paramsInPath,
@@ -93,10 +127,9 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Enum: enumsValueFromOptions(formTag.Options),
},
SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(member.Type),
Default: defValueFromOptions(formTag.Options, member.Type),
Example: exampleValueFromOptions(formTag.Options, member.Type),
Items: sampleItemsFromGoType(member.Type),
Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, formTag.Options, member.Type),
Items: sampleItemsFromGoType(ctx, member.Type),
},
ParamProps: spec.ParamProps{
In: paramsInQuery,
@@ -116,10 +149,9 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Enum: enumsValueFromOptions(formTag.Options),
},
SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(member.Type),
Default: defValueFromOptions(formTag.Options, member.Type),
Example: exampleValueFromOptions(formTag.Options, member.Type),
Items: sampleItemsFromGoType(member.Type),
Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, formTag.Options, member.Type),
Items: sampleItemsFromGoType(ctx, member.Type),
},
ParamProps: spec.ParamProps{
In: paramsInForm,
@@ -139,25 +171,25 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
}
var schema = spec.Schema{
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: exampleValueFromOptions(jsonTag.Options, member.Type),
Example: exampleValueFromOptions(ctx, jsonTag.Options, member.Type),
},
SchemaProps: spec.SchemaProps{
Description: formatComment(member.Comment),
Type: typeFromGoType(member.Type),
Default: defValueFromOptions(jsonTag.Options, member.Type),
Type: typeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, jsonTag.Options, member.Type),
Maximum: maximum,
ExclusiveMaximum: exclusiveMaximum,
Minimum: minimum,
ExclusiveMinimum: exclusiveMinimum,
Enum: enumsValueFromOptions(jsonTag.Options),
AdditionalProperties: mapFromGoType(member.Type),
AdditionalProperties: mapFromGoType(ctx, member.Type),
},
}
switch sampleTypeFromGoType(member.Type) {
switch sampleTypeFromGoType(ctx, member.Type) {
case swaggerTypeArray:
schema.Items = itemsFromGoType(member.Type)
schema.Items = itemsFromGoType(ctx, member.Type)
case swaggerTypeObject:
p, r := propertiesFromType(member.Type)
p, r := propertiesFromType(ctx, member.Type)
schema.Properties = p
schema.Required = r
}
@@ -172,7 +204,7 @@ func parametersFromType(method string, tp apiSpec.Type) []spec.Parameter {
Required: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: typeFromGoType(structType),
Type: typeFromGoType(ctx, structType),
Properties: properties,
Required: requiredFields,
},

View File

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

View File

@@ -5,18 +5,18 @@ import (
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 (
properties = map[string]spec.Schema{}
requiredFields []string
)
switch val := tp.(type) {
case apiSpec.PointerType:
return propertiesFromType(val.Type)
return propertiesFromType(ctx, val.Type)
case apiSpec.ArrayType:
return propertiesFromType(val.Value)
return propertiesFromType(ctx, val.Value)
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 (
jsonTagString = member.Name
minimum, maximum *float64
@@ -28,38 +28,46 @@ func propertiesFromType(tp apiSpec.Type) (spec.SchemaProperties, []string) {
if jsonTag != nil {
jsonTagString = jsonTag.Name
minimum, maximum, exclusiveMinimum, exclusiveMaximum = rangeValueFromOptions(jsonTag.Options)
example = exampleValueFromOptions(jsonTag.Options, member.Type)
defaultValue = defValueFromOptions(jsonTag.Options, member.Type)
example = exampleValueFromOptions(ctx, jsonTag.Options, member.Type)
defaultValue = defValueFromOptions(ctx, jsonTag.Options, member.Type)
enum = enumsValueFromOptions(jsonTag.Options)
}
if required {
requiredFields = append(requiredFields, jsonTagString)
}
var schema = spec.Schema{
schema := spec.Schema{
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: example,
},
SchemaProps: spec.SchemaProps{
Description: formatComment(member.Comment),
Type: typeFromGoType(member.Type),
Type: typeFromGoType(ctx, member.Type),
Default: defaultValue,
Maximum: maximum,
ExclusiveMaximum: exclusiveMaximum,
Minimum: minimum,
ExclusiveMinimum: exclusiveMinimum,
Enum: enum,
AdditionalProperties: mapFromGoType(member.Type),
AdditionalProperties: mapFromGoType(ctx, member.Type),
},
}
switch sampleTypeFromGoType(member.Type) {
switch sampleTypeFromGoType(ctx, member.Type) {
case swaggerTypeArray:
schema.Items = itemsFromGoType(member.Type)
schema.Items = itemsFromGoType(ctx, member.Type)
case swaggerTypeObject:
p, r := propertiesFromType(member.Type)
p, r := propertiesFromType(ctx, member.Type)
schema.Properties = p
schema.Required = r
}
if ctx.UseDefinitions {
structName, containsStruct := containsStruct(member.Type)
if containsStruct {
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
}
}
properties[jsonTagString] = schema
})
@@ -67,3 +75,22 @@ func propertiesFromType(tp apiSpec.Type) (spec.SchemaProperties, []string) {
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
import (
"net/http"
"github.com/go-openapi/spec"
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
func jsonResponseFromType(info apiSpec.Info, atDoc apiSpec.AtDoc, tp apiSpec.Type) *spec.Responses {
p, _ := propertiesFromType(tp)
func jsonResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type) *spec.Responses {
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{
Type: typeFromGoType(tp),
Properties: p,
AdditionalProperties: mapFromGoType(tp),
Items: itemsFromGoType(tp),
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{
Default: &spec.Response{
ResponseProps: spec.ResponseProps{
Schema: &spec.Schema{
SchemaProps: wrapCodeMsgProps(props, info, atDoc),
StatusCodeResponses: map[int]spec.Response{
http.StatusOK: {
ResponseProps: spec.ResponseProps{
Schema: &spec.Schema{
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
},
},
},
},

View File

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

View File

@@ -19,7 +19,7 @@ func Test_pathVariable2SwaggerVariable(t *testing.T) {
}
for _, tc := range testCases {
result := pathVariable2SwaggerVariable(tc.input)
result := pathVariable2SwaggerVariable(testingContext(t), tc.input)
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}}",
"branch": "{{.global.branch}}",
"style": "{{.global.style}}",
"test": "Generate test files"
"test": "Generate test files",
"type-group": "Generate type group files"
},
"new": {
"short": "Fast create api service",

View File

@@ -6,7 +6,7 @@ import (
)
// 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}

View File

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

View File

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

View File

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

View File

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