Merge pull request #516 from carsonkrueger/feat/json
add model json tag to args
This commit is contained in:
commit
afc4cee6d2
4 changed files with 279 additions and 7 deletions
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
|
@ -17,6 +18,7 @@ import (
|
||||||
postgresgen "github.com/go-jet/jet/v2/generator/postgres"
|
postgresgen "github.com/go-jet/jet/v2/generator/postgres"
|
||||||
sqlitegen "github.com/go-jet/jet/v2/generator/sqlite"
|
sqlitegen "github.com/go-jet/jet/v2/generator/sqlite"
|
||||||
"github.com/go-jet/jet/v2/generator/template"
|
"github.com/go-jet/jet/v2/generator/template"
|
||||||
|
"github.com/go-jet/jet/v2/internal/3rdparty/snaker"
|
||||||
"github.com/go-jet/jet/v2/internal/jet"
|
"github.com/go-jet/jet/v2/internal/jet"
|
||||||
"github.com/go-jet/jet/v2/internal/utils/errfmt"
|
"github.com/go-jet/jet/v2/internal/utils/errfmt"
|
||||||
"github.com/go-jet/jet/v2/internal/utils/strslice"
|
"github.com/go-jet/jet/v2/internal/utils/strslice"
|
||||||
|
|
@ -54,10 +56,12 @@ var (
|
||||||
tables string
|
tables string
|
||||||
views string
|
views string
|
||||||
enums string
|
enums string
|
||||||
|
|
||||||
|
modelJsonTag string
|
||||||
)
|
)
|
||||||
|
|
||||||
type templateFilter struct {
|
type templateFilter struct {
|
||||||
names []string
|
names []string
|
||||||
ignore bool
|
ignore bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +97,7 @@ func init() {
|
||||||
flag.StringVar(&tablePkg, "rel-table-path", "table", "Relative path for the Table files package from the destination directory.")
|
flag.StringVar(&tablePkg, "rel-table-path", "table", "Relative path for the Table files package from the destination directory.")
|
||||||
flag.StringVar(&viewPkg, "rel-view-path", "view", "Relative path for the View files package from the destination directory.")
|
flag.StringVar(&viewPkg, "rel-view-path", "view", "Relative path for the View files package from the destination directory.")
|
||||||
flag.StringVar(&enumPkg, "rel-enum-path", "enum", "Relative path for the Enum files package from the destination directory.")
|
flag.StringVar(&enumPkg, "rel-enum-path", "enum", "Relative path for the Enum files package from the destination directory.")
|
||||||
|
flag.StringVar(&modelJsonTag, "model-json-tag", "", "Json tag model to be included in Go structs. (optional)(default <empty>)(allowed values: <empty>, pascal-case, camel-case, snake-case")
|
||||||
|
|
||||||
flag.StringVar(&tables, "tables", "", `Comma-separated list of tables to generate.`)
|
flag.StringVar(&tables, "tables", "", `Comma-separated list of tables to generate.`)
|
||||||
flag.StringVar(&views, "views", "", `Comma-separated list of views to generate.`)
|
flag.StringVar(&views, "views", "", `Comma-separated list of views to generate.`)
|
||||||
|
|
@ -107,6 +112,10 @@ func main() {
|
||||||
printErrorAndExit("ERROR: required flag(s) missing")
|
printErrorAndExit("ERROR: required flag(s) missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !slices.Contains([]string{"", "snake-case", "pascal-case", "camel-case"}, modelJsonTag) {
|
||||||
|
printErrorAndExit("ERROR: json tag does not contain correct value")
|
||||||
|
}
|
||||||
|
|
||||||
source := getSource()
|
source := getSource()
|
||||||
tablesFilter := createTemplateFilter(ignoreTables, tables, "tables")
|
tablesFilter := createTemplateFilter(ignoreTables, tables, "tables")
|
||||||
viewsFilter := createTemplateFilter(ignoreViews, views, "views")
|
viewsFilter := createTemplateFilter(ignoreViews, views, "views")
|
||||||
|
|
@ -259,8 +268,6 @@ func parseList(list string) []string {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func genTemplate(dialect jet.Dialect, tablesFilter, viewsFilter, enumsFilter templateFilter) template.Template {
|
func genTemplate(dialect jet.Dialect, tablesFilter, viewsFilter, enumsFilter templateFilter) template.Template {
|
||||||
return template.Default(dialect).
|
return template.Default(dialect).
|
||||||
UseSchema(func(schemaMetaData metadata.Schema) template.Schema {
|
UseSchema(func(schemaMetaData metadata.Schema) template.Schema {
|
||||||
|
|
@ -270,13 +277,23 @@ func genTemplate(dialect jet.Dialect, tablesFilter, viewsFilter, enumsFilter tem
|
||||||
if shouldSkipTable(table, tablesFilter) {
|
if shouldSkipTable(table, tablesFilter) {
|
||||||
return template.TableModel{Skip: true}
|
return template.TableModel{Skip: true}
|
||||||
}
|
}
|
||||||
return template.DefaultTableModel(table)
|
return template.DefaultTableModel(table).
|
||||||
|
UseField(func(columnMetaData metadata.Column) template.TableModelField {
|
||||||
|
defaultTableModelField := template.DefaultTableModelField(columnMetaData)
|
||||||
|
tags := createModelTags(columnMetaData)
|
||||||
|
return defaultTableModelField.UseTags(tags...)
|
||||||
|
})
|
||||||
}).
|
}).
|
||||||
UseView(func(view metadata.Table) template.ViewModel {
|
UseView(func(view metadata.Table) template.ViewModel {
|
||||||
if shouldSkipTable(view, viewsFilter) {
|
if shouldSkipTable(view, viewsFilter) {
|
||||||
return template.ViewModel{Skip: true}
|
return template.ViewModel{Skip: true}
|
||||||
}
|
}
|
||||||
return template.DefaultViewModel(view)
|
return template.DefaultViewModel(view).
|
||||||
|
UseField(func(columnMetaData metadata.Column) template.TableModelField {
|
||||||
|
defaultTableModelField := template.DefaultTableModelField(columnMetaData)
|
||||||
|
tags := createModelTags(columnMetaData)
|
||||||
|
return defaultTableModelField.UseTags(tags...)
|
||||||
|
})
|
||||||
}).
|
}).
|
||||||
UseEnum(func(enum metadata.Enum) template.EnumModel {
|
UseEnum(func(enum metadata.Enum) template.EnumModel {
|
||||||
if shouldSkipEnum(enum, enumsFilter) {
|
if shouldSkipEnum(enum, enumsFilter) {
|
||||||
|
|
@ -318,13 +335,13 @@ func createTemplateFilter(ignoreList, allowList, filterType string) templateFilt
|
||||||
|
|
||||||
if allowList != "" {
|
if allowList != "" {
|
||||||
return templateFilter{
|
return templateFilter{
|
||||||
names: parseList(allowList),
|
names: parseList(allowList),
|
||||||
ignore: false,
|
ignore: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return templateFilter{
|
return templateFilter{
|
||||||
names: parseList(ignoreList),
|
names: parseList(ignoreList),
|
||||||
ignore: true,
|
ignore: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -344,3 +361,16 @@ func shouldSkipEnum(enum metadata.Enum, filter templateFilter) bool {
|
||||||
|
|
||||||
return !strslice.Contains(filter.names, strings.ToLower(enum.Name))
|
return !strslice.Contains(filter.names, strings.ToLower(enum.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createModelTags(columnMetaData metadata.Column) []string {
|
||||||
|
var tags []string
|
||||||
|
switch modelJsonTag {
|
||||||
|
case "snake-case":
|
||||||
|
tags = append(tags, fmt.Sprintf(`json:"%s"`, snaker.CamelToSnake(columnMetaData.Name)))
|
||||||
|
case "camel-case":
|
||||||
|
tags = append(tags, fmt.Sprintf(`json:"%s"`, snaker.SnakeToCamel(columnMetaData.Name, false)))
|
||||||
|
case "pascal-case":
|
||||||
|
tags = append(tags, fmt.Sprintf(`json:"%s"`, snaker.SnakeToCamel(columnMetaData.Name, true)))
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
|
||||||
50
internal/3rdparty/snaker/snaker.go
vendored
50
internal/3rdparty/snaker/snaker.go
vendored
|
|
@ -8,6 +8,44 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CamelToSnake converts a given string to snake case
|
||||||
|
func CamelToSnake(s string) string {
|
||||||
|
var result string
|
||||||
|
var words []string
|
||||||
|
var lastPos int
|
||||||
|
rs := []rune(s)
|
||||||
|
|
||||||
|
for i := 0; i < len(rs); i++ {
|
||||||
|
if i > 0 && unicode.IsUpper(rs[i]) {
|
||||||
|
if initialism := startsWithInitialism(s[lastPos:]); initialism != "" {
|
||||||
|
words = append(words, initialism)
|
||||||
|
|
||||||
|
i += len(initialism) - 1
|
||||||
|
lastPos = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
words = append(words, s[lastPos:i])
|
||||||
|
lastPos = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the last word
|
||||||
|
if s[lastPos:] != "" {
|
||||||
|
words = append(words, s[lastPos:])
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, word := range words {
|
||||||
|
if k > 0 {
|
||||||
|
result += "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
result += strings.ToLower(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// SnakeToCamel returns a string converted from snake case to uppercase
|
// SnakeToCamel returns a string converted from snake case to uppercase
|
||||||
func SnakeToCamel(s string, firstLetterUppercase ...bool) string {
|
func SnakeToCamel(s string, firstLetterUppercase ...bool) string {
|
||||||
upperCase := true
|
upperCase := true
|
||||||
|
|
@ -48,6 +86,18 @@ func snakeToCamel(s string, upperCase bool) string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// startsWithInitialism returns the initialism if the given string begins with it
|
||||||
|
func startsWithInitialism(s string) string {
|
||||||
|
var initialism string
|
||||||
|
// the longest initialism is 5 char, the shortest 2
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
if len(s) > i-1 && commonInitialisms[s[:i]] {
|
||||||
|
initialism = s[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return initialism
|
||||||
|
}
|
||||||
|
|
||||||
func toLowerFirstLetter(s string) string {
|
func toLowerFirstLetter(s string) string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return s
|
return s
|
||||||
|
|
|
||||||
10
internal/3rdparty/snaker/snaker_test.go
vendored
10
internal/3rdparty/snaker/snaker_test.go
vendored
|
|
@ -17,3 +17,13 @@ func TestSnakeToCamel(t *testing.T) {
|
||||||
require.Equal(t, SnakeToCamel("id"), "ID")
|
require.Equal(t, SnakeToCamel("id"), "ID")
|
||||||
require.Equal(t, SnakeToCamel("oauth_client"), "OAuthClient")
|
require.Equal(t, SnakeToCamel("oauth_client"), "OAuthClient")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCamelToSnake(t *testing.T) {
|
||||||
|
require.Equal(t, "", CamelToSnake(""))
|
||||||
|
require.Equal(t, "_", CamelToSnake("_"))
|
||||||
|
require.Equal(t, "snake_case", CamelToSnake("snake_case"))
|
||||||
|
require.Equal(t, "camel_case", CamelToSnake("camelCase"))
|
||||||
|
require.Equal(t, "jet_is_cool_as_hell", CamelToSnake("jetIsCoolAsHell"))
|
||||||
|
require.Equal(t, "jet_is_cool_as_hell", CamelToSnake("jet_is_cool_as_hell"))
|
||||||
|
require.Equal(t, "id", CamelToSnake("ID"))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model"
|
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model"
|
||||||
"github.com/go-jet/jet/v2/tests/dbconfig"
|
"github.com/go-jet/jet/v2/tests/dbconfig"
|
||||||
"github.com/go-jet/jet/v2/tests/internal/utils/file"
|
"github.com/go-jet/jet/v2/tests/internal/utils/file"
|
||||||
|
file2 "github.com/go-jet/jet/v2/tests/internal/utils/file"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dsn(host string, port int, dbName, user, password string) string {
|
func dsn(host string, port int, dbName, user, password string) string {
|
||||||
|
|
@ -1505,3 +1506,184 @@ func TestAllowAndIgnoreEnums(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJsonInvalidModelTags(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with invalid json tag",
|
||||||
|
args: []string{
|
||||||
|
"-dsn=" + defaultDSN(),
|
||||||
|
"-schema=dvds",
|
||||||
|
"-tables=actor,ADDRESS,country, Film , cITY,",
|
||||||
|
"-views=Actor_info, FILM_LIST ,staff_list",
|
||||||
|
"-enums=mpaa_rating",
|
||||||
|
"-path=" + genTestDir2,
|
||||||
|
"-model-json-tag=invalid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with invalid json tag",
|
||||||
|
args: []string{
|
||||||
|
"-dsn=" + defaultDSN(),
|
||||||
|
"-schema=dvds",
|
||||||
|
"-tables=actor,ADDRESS,country, Film , cITY,",
|
||||||
|
"-views=Actor_info, FILM_LIST ,staff_list",
|
||||||
|
"-enums=mpaa_rating",
|
||||||
|
"-path=" + genTestDir2,
|
||||||
|
"-model-json-tag= invalid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := exec.Command("jet", tt.args...)
|
||||||
|
|
||||||
|
var stdOut bytes.Buffer
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = &stdOut
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "exit status 1", err.Error())
|
||||||
|
|
||||||
|
stdOutput := stdOut.String()
|
||||||
|
require.Contains(t, stdOutput, "ERROR: json tag does not contain correct value")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnakeCaseModelJsonTag(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with snake-case",
|
||||||
|
args: []string{
|
||||||
|
"-dsn=" + defaultDSN(),
|
||||||
|
"-schema=dvds",
|
||||||
|
"-views=Actor_info",
|
||||||
|
"-tables=actor",
|
||||||
|
"-path=" + genTestDir2,
|
||||||
|
"-model-json-tag=snake-case",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := exec.Command("jet", tt.args...)
|
||||||
|
|
||||||
|
var stdOut bytes.Buffer
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = &stdOut
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
actor := file2.Exists(t, genTestDir2+"/jetdb/dvds/model/actor.go")
|
||||||
|
require.Contains(t, actor, `json:"actor_id"`)
|
||||||
|
require.Contains(t, actor, `json:"first_name"`)
|
||||||
|
require.Contains(t, actor, `json:"last_name"`)
|
||||||
|
require.Contains(t, actor, `json:"last_update"`)
|
||||||
|
|
||||||
|
actorInfo := file2.Exists(t, genTestDir2+"/jetdb/dvds/model/actor_info.go")
|
||||||
|
require.Contains(t, actorInfo, `json:"actor_id"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"first_name"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"last_name"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"film_info"`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPascalCaseModelJsonTag(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with pascal-case",
|
||||||
|
args: []string{
|
||||||
|
"-dsn=" + defaultDSN(),
|
||||||
|
"-schema=dvds",
|
||||||
|
"-views=Actor_info",
|
||||||
|
"-tables=actor",
|
||||||
|
"-path=" + genTestDir2,
|
||||||
|
"-model-json-tag=pascal-case",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := exec.Command("jet", tt.args...)
|
||||||
|
|
||||||
|
var stdOut bytes.Buffer
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = &stdOut
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
actor := file2.Exists(t, genTestDir2+"/jetdb/dvds/model/actor.go")
|
||||||
|
require.Contains(t, actor, `json:"ActorID"`)
|
||||||
|
require.Contains(t, actor, `json:"FirstName"`)
|
||||||
|
require.Contains(t, actor, `json:"LastName"`)
|
||||||
|
require.Contains(t, actor, `json:"LastUpdate"`)
|
||||||
|
|
||||||
|
actorInfo := file2.Exists(t, genTestDir2+"/jetdb/dvds/model/actor_info.go")
|
||||||
|
require.Contains(t, actorInfo, `json:"ActorID"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"FirstName"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"LastName"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"FilmInfo"`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCamelCaseModelJsonTag(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with camel-case",
|
||||||
|
args: []string{
|
||||||
|
"-dsn=" + defaultDSN(),
|
||||||
|
"-schema=dvds",
|
||||||
|
"-views=Actor_info",
|
||||||
|
"-tables=actor",
|
||||||
|
"-path=" + genTestDir2,
|
||||||
|
"-model-json-tag=camel-case",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := exec.Command("jet", tt.args...)
|
||||||
|
|
||||||
|
var stdOut bytes.Buffer
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = &stdOut
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
actor := file2.Exists(t, genTestDir2+"/jetdb/dvds/model/actor.go")
|
||||||
|
require.Contains(t, actor, `json:"actorID"`)
|
||||||
|
require.Contains(t, actor, `json:"firstName"`)
|
||||||
|
require.Contains(t, actor, `json:"lastName"`)
|
||||||
|
require.Contains(t, actor, `json:"lastUpdate"`)
|
||||||
|
|
||||||
|
actorInfo := file2.Exists(t, genTestDir2+"/jetdb/dvds/model/actor_info.go")
|
||||||
|
require.Contains(t, actorInfo, `json:"actorID"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"firstName"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"lastName"`)
|
||||||
|
require.Contains(t, actorInfo, `json:"filmInfo"`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue