feat(cmd/gf): add typeMapping and fieldMapping feature support for command gf gen genpbentity (#3970)
Some checks are pending
GoFrame Main CI / code-test (1.20, 386) (push) Waiting to run
GoFrame Main CI / code-test (1.20, amd64) (push) Waiting to run
GoFrame Main CI / code-test (1.21, 386) (push) Waiting to run
GoFrame Main CI / code-test (1.21, amd64) (push) Waiting to run
GoFrame Main CI / code-test (1.22, 386) (push) Waiting to run
GoFrame Main CI / code-test (1.22, amd64) (push) Waiting to run
GoFrame Main CI / code-test (1.23, 386) (push) Waiting to run
GoFrame Main CI / code-test (1.23, amd64) (push) Waiting to run
GoFrame Sub CI / code-test (1.20, 386) (push) Waiting to run
GoFrame Sub CI / code-test (1.20, amd64) (push) Waiting to run
GoFrame Sub CI / code-test (1.21, 386) (push) Waiting to run
GoFrame Sub CI / code-test (1.21, amd64) (push) Waiting to run
GoFrame Sub CI / code-test (1.22, 386) (push) Waiting to run
GoFrame Sub CI / code-test (1.22, amd64) (push) Waiting to run
GoFrame Sub CI / code-test (1.23, 386) (push) Waiting to run
GoFrame Sub CI / code-test (1.23, amd64) (push) Waiting to run
Sync to Gitee / Run (push) Waiting to run
GolangCI-Lint / golangci-lint (1.20) (push) Waiting to run
GolangCI-Lint / golangci-lint (1.21.4) (push) Waiting to run
GolangCI-Lint / golangci-lint (1.22) (push) Waiting to run
GolangCI-Lint / golangci-lint (1.23) (push) Waiting to run
Sonarcloud Scan / Scorecards analysis (push) Waiting to run

This commit is contained in:
Luoyy 2024-11-27 21:09:42 +08:00 committed by GitHub
parent 81aed06643
commit 5521d768ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 307 additions and 70 deletions

View File

@ -52,6 +52,8 @@ func Test_Gen_Pbentity_Default(t *testing.T) {
NameCase: "",
JsonCase: "",
Option: "",
TypeMapping: nil,
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
@ -115,6 +117,8 @@ func Test_Gen_Pbentity_NameCase_SnakeScreaming(t *testing.T) {
NameCase: "SnakeScreaming",
JsonCase: "",
Option: "",
TypeMapping: nil,
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
@ -179,6 +183,8 @@ func Test_Issue_3545(t *testing.T) {
NameCase: "",
JsonCase: "",
Option: "",
TypeMapping: nil,
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
@ -208,3 +214,74 @@ func Test_Issue_3545(t *testing.T) {
}
})
}
// https://github.com/gogf/gf/issues/3685
func Test_Issue_3685(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
db = testDB
table = "table_user"
sqlContent = fmt.Sprintf(
gtest.DataContent(`issue`, `3685`, `user.tpl.sql`),
table,
)
)
dropTableWithDb(db, table)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = db.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(db, table)
var (
path = gfile.Temp(guid.S())
in = genpbentity.CGenPbEntityInput{
Path: path,
Package: "",
Link: link,
Tables: "",
Prefix: "",
RemovePrefix: "",
RemoveFieldPrefix: "",
NameCase: "",
JsonCase: "",
Option: "",
TypeMapping: map[genpbentity.DBFieldTypeName]genpbentity.CustomAttributeType{
"json": {
Type: "google.protobuf.Value",
Import: "google/protobuf/struct.proto",
},
},
FieldMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
defer gfile.Remove(path)
_, err = genpbentity.CGenPbEntity{}.PbEntity(ctx, in)
t.AssertNil(err)
// files
files, err := gfile.ScanDir(path, "*.proto", false)
t.AssertNil(err)
t.Assert(files, []string{
path + filepath.FromSlash("/table_user.proto"),
})
// contents
testPath := gtest.DataPath("issue", "3685")
expectFiles := []string{
testPath + filepath.FromSlash("/table_user.proto"),
}
for i := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
}
})
}

View File

@ -11,6 +11,7 @@ import (
"context"
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
@ -18,6 +19,7 @@ import (
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
@ -43,6 +45,9 @@ type (
NameCase string `name:"nameCase" short:"n" brief:"{CGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenPbEntityBriefJsonCase}" d:"none"`
Option string `name:"option" short:"o" brief:"{CGenPbEntityBriefOption}"`
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenPbEntityBriefTypeMapping}" orphan:"true"`
FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenPbEntityBriefFieldMapping}" orphan:"true"`
}
CGenPbEntityOutput struct{}
@ -52,6 +57,13 @@ type (
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
DBTableFieldName = string
DBFieldTypeName = string
CustomAttributeType struct {
Type string `brief:"custom attribute type name"`
Import string `brief:"custom import for this type"`
}
)
const (
@ -88,6 +100,13 @@ CONFIGURATION SUPPORT
option go_package = "protobuf/demos";
option java_package = "protobuf/demos";
option php_namespace = "protobuf/demos";
typeMapping:
json:
type: google.protobuf.Value
import: google/protobuf/struct.proto
jsonb:
type: google.protobuf.Value
import: google/protobuf/struct.proto
`
CGenPbEntityBriefPath = `directory path for generated files storing`
CGenPbEntityBriefPackage = `package path for all entity proto files`
@ -119,8 +138,95 @@ case for message attribute names, default is "Camel":
case for message json tag, cases are the same as "nameCase", default "CamelLower".
set it to "none" to ignore json tag generating.
`
CGenPbEntityBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table`
CGenPbEntityBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table`
)
var defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{
// gdb.LocalTypeString
"string": {
Type: "string",
},
// gdb.LocalTypeTime
// "time": {
// Type: "google.protobuf.Duration",
// Import: "google/protobuf/duration.proto",
// },
// gdb.LocalTypeDate
"date": {
Type: "google.protobuf.Timestamp",
Import: "google/protobuf/timestamp.proto",
},
// gdb.LocalTypeDatetime
"datetime": {
Type: "google.protobuf.Timestamp",
Import: "google/protobuf/timestamp.proto",
},
// gdb.LocalTypeInt
"int": {
Type: "int32",
},
// gdb.LocalTypeUint
"uint": {
Type: "uint32",
},
// gdb.LocalTypeInt64
"int64": {
Type: "int64",
},
// gdb.LocalTypeUint64
"uint64": {
Type: "uint64",
},
// gdb.LocalTypeIntSlice
"[]int": {
Type: "repeated int32",
},
// gdb.LocalTypeInt64Slice
"[]int64": {
Type: "repeated int64",
},
// gdb.LocalTypeUint64Slice
"[]uint64": {
Type: "repeated uint64",
},
// gdb.LocalTypeInt64Bytes
"int64-bytes": {
Type: "repeated int64",
},
// gdb.LocalTypeUint64Bytes
"uint64-bytes": {
Type: "repeated uint64",
},
// gdb.LocalTypeFloat32
"float32": {
Type: "float",
},
// gdb.LocalTypeFloat64
"float64": {
Type: "double",
},
// gdb.LocalTypeBytes
"[]byte": {
Type: "bytes",
},
// gdb.LocalTypeBool
"bool": {
Type: "bool",
},
// gdb.LocalTypeJson
// "json": {
// Type: "google.protobuf.Value",
// Import: "google/protobuf/struct.proto",
// },
// gdb.LocalTypeJsonb
// "jsonb": {
// Type: "google.protobuf.Value",
// Import: "google/protobuf/struct.proto",
// },
}
func init() {
gtag.Sets(g.MapStrStr{
`CGenPbEntityConfig`: CGenPbEntityConfig,
@ -138,6 +244,8 @@ func init() {
`CGenPbEntityBriefNameCase`: CGenPbEntityBriefNameCase,
`CGenPbEntityBriefJsonCase`: CGenPbEntityBriefJsonCase,
`CGenPbEntityBriefOption`: CGenPbEntityBriefOption,
`CGenPbEntityBriefTypeMapping`: CGenPbEntityBriefTypeMapping,
`CGenPbEntityBriefFieldMapping`: CGenPbEntityBriefFieldMapping,
})
}
@ -210,6 +318,16 @@ func doGenPbEntityForArray(ctx context.Context, index int, in CGenPbEntityInput)
mlog.Fatalf("fetching tables failed: \n %v", err)
}
}
// merge default typeMapping to input typeMapping.
if in.TypeMapping == nil {
in.TypeMapping = defaultTypeMapping
} else {
for key, typeMapping := range defaultTypeMapping {
if _, ok := in.TypeMapping[key]; !ok {
in.TypeMapping[key] = typeMapping
}
}
}
for _, tableName := range tableNames {
newTableName := tableName
@ -234,18 +352,24 @@ func generatePbEntityContentFile(ctx context.Context, in CGenPbEntityInternalInp
// Change the `newTableName` if `Prefix` is given.
newTableName := in.Prefix + in.NewTableName
var (
imports string
tableNameCamelCase = gstr.CaseCamel(newTableName)
tableNameSnakeCase = gstr.CaseSnake(newTableName)
entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
path = filepath.FromSlash(gfile.Join(in.Path, fileName+".proto"))
tableNameCamelCase = gstr.CaseCamel(newTableName)
tableNameSnakeCase = gstr.CaseSnake(newTableName)
entityMessageDefine, appendImports = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
path = filepath.FromSlash(gfile.Join(in.Path, fileName+".proto"))
)
if gstr.Contains(entityMessageDefine, "google.protobuf.Timestamp") {
imports = `import "google/protobuf/timestamp.proto";`
packageImportStr := ""
var packageImportsArray = garray.NewStrArray()
if len(appendImports) > 0 {
for _, appendImport := range appendImports {
packageImportStr = fmt.Sprintf(`import "%s";`, appendImport)
if packageImportsArray.Search(packageImportStr) == -1 {
packageImportsArray.Append(packageImportStr)
}
}
}
entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
"{Imports}": imports,
"{Imports}": packageImportsArray.Join("\n"),
"{PackageName}": gfile.Basename(in.Package),
"{GoPackage}": in.Package,
"{OptionContent}": in.Option,
@ -259,14 +383,19 @@ func generatePbEntityContentFile(ctx context.Context, in CGenPbEntityInternalInp
}
// generateEntityMessageDefinition generates and returns the message definition for specified table.
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in CGenPbEntityInternalInput) string {
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in CGenPbEntityInternalInput) (string, []string) {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForPbEntity(fieldMap)
appendImports []string
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForPbEntity(fieldMap)
)
for index, name := range names {
array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
var imports string
array[index], imports = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
if imports != "" {
appendImports = append(appendImports, imports)
}
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
@ -277,48 +406,38 @@ func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb
tw.Render()
stContent := buffer.String()
// Let's do this hack of table writer for indent!
stContent = gstr.Replace(stContent, " #", "")
stContent = regexp.MustCompile(`\s+\n`).ReplaceAllString(gstr.Replace(stContent, " #", ""), "\n")
buffer.Reset()
buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String()
return buffer.String(), appendImports
}
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPbEntityInternalInput) []string {
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPbEntityInternalInput) (attrLines []string, appendImport string) {
var (
localTypeName gdb.LocalType
comment string
jsonTagStr string
err error
ctx = gctx.GetInitCtx()
localTypeNameStr string
localTypeName gdb.LocalType
comment string
jsonTagStr string
err error
ctx = gctx.GetInitCtx()
)
localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
}
if localTypeName != "" {
if typeMapping, ok := in.TypeMapping[strings.ToLower(string(localTypeName))]; ok {
localTypeNameStr = typeMapping.Type
appendImport = typeMapping.Import
}
}
}
var typeMapping = map[gdb.LocalType]string{
gdb.LocalTypeString: "string",
gdb.LocalTypeDate: "google.protobuf.Timestamp",
gdb.LocalTypeDatetime: "google.protobuf.Timestamp",
gdb.LocalTypeInt: "int32",
gdb.LocalTypeUint: "uint32",
gdb.LocalTypeInt64: "int64",
gdb.LocalTypeUint64: "uint64",
gdb.LocalTypeIntSlice: "repeated int32",
gdb.LocalTypeInt64Slice: "repeated int64",
gdb.LocalTypeUint64Slice: "repeated uint64",
gdb.LocalTypeInt64Bytes: "repeated int64",
gdb.LocalTypeUint64Bytes: "repeated uint64",
gdb.LocalTypeFloat32: "float",
gdb.LocalTypeFloat64: "double",
gdb.LocalTypeBytes: "bytes",
gdb.LocalTypeBool: "bool",
gdb.LocalTypeJson: "string",
gdb.LocalTypeJsonb: "string",
}
localTypeNameStr := typeMapping[localTypeName]
if localTypeNameStr == "" {
localTypeNameStr = "string"
}
@ -351,12 +470,19 @@ func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPb
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
}
if in.FieldMapping != nil && len(in.FieldMapping) > 0 {
if typeMapping, ok := in.FieldMapping[fmt.Sprintf("%s.%s", in.TableName, newFiledName)]; ok {
localTypeNameStr = typeMapping.Type
appendImport = typeMapping.Import
}
}
return []string{
" #" + localTypeNameStr,
" #" + formatCase(newFiledName, in.NameCase),
" #= " + gconv.String(index) + jsonTagStr + ";",
" #" + fmt.Sprintf(`// %s`, comment),
}
}, appendImport
}
func getTplPbEntityContent(tplEntityPath string) string {

View File

@ -0,0 +1,23 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
syntax = "proto3";
package pbentity;
option go_package = "github.com/gogf/gf/cmd/gf/v2/internal/cmd/api/pbentity";
import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";
message TableUser {
uint32 Id = 1; // User ID
string Passport = 2; // User Passport
string Password = 3; // User Password
string Nickname = 4; // User Nickname
string Score = 5; // Total score amount.
google.protobuf.Value Data = 6; // User Data
google.protobuf.Timestamp CreateAt = 7; // Created Time
google.protobuf.Timestamp UpdateAt = 8; // Updated Time
}

View File

@ -0,0 +1,11 @@
CREATE TABLE `%s` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
`passport` varchar(45) NOT NULL COMMENT 'User Passport',
`password` varchar(45) NOT NULL COMMENT 'User Password',
`nickname` varchar(45) NOT NULL COMMENT 'User Nickname',
`score` decimal(10,2) unsigned DEFAULT NULL COMMENT 'Total score amount.',
`data` json DEFAULT NULL COMMENT 'User Data',
`create_at` datetime DEFAULT NULL COMMENT 'Created Time',
`update_at` datetime DEFAULT NULL COMMENT 'Updated Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;