diff --git a/api_internal_hook.go b/api_internal_hook.go index f192924..0fb97b5 100644 --- a/api_internal_hook.go +++ b/api_internal_hook.go @@ -1,6 +1,7 @@ package cosy import ( + "github.com/elliotchance/orderedmap/v3" "github.com/uozi-tech/cosy/model" ) @@ -28,15 +29,16 @@ func getListHook[T any]() func(core *Ctx[T]) { return func(core *Ctx[T]) { var ( - in []string - eq []string - fussy []string - orIn []string - orEq []string - orFussy []string - preload []string - search []string - between []string + in []string + eq []string + fussy []string + orIn []string + orEq []string + orFussy []string + preload []string + search []string + between []string + customFilters = orderedmap.NewOrderedMap[string, string]() // key=>filter ) for _, field := range resolved.OrderedFields { @@ -62,6 +64,8 @@ func getListHook[T any]() func(core *Ctx[T]) { search = append(search, field.JsonTag) case Between: between = append(between, field.JsonTag) + default: + customFilters.Set(field.JsonTag, dir) } } } @@ -93,5 +97,10 @@ func getListHook[T any]() func(core *Ctx[T]) { if len(between) > 0 { core.SetBetween(between...) } + if customFilters.Len() > 0 { + for key, filterName := range customFilters.AllFromFront() { + core.SetCustomFilter(key, filterName) + } + } } } diff --git a/cosy.go b/cosy.go index 57fc12b..33cb206 100644 --- a/cosy.go +++ b/cosy.go @@ -1,6 +1,7 @@ package cosy import ( + "github.com/elliotchance/orderedmap/v3" "github.com/gin-gonic/gin" "github.com/spf13/cast" "gorm.io/gorm" @@ -42,6 +43,7 @@ type Ctx[T any] struct { search []string between []string unique []string + customFilters *orderedmap.OrderedMap[string, string] } func Core[T any](c *gin.Context) *Ctx[T] { @@ -54,6 +56,7 @@ func Core[T any](c *gin.Context) *Ctx[T] { skipAssociationsOnCreate: true, columnWhiteList: make(map[string]bool), selectedFields: make(map[string]bool), + customFilters: orderedmap.NewOrderedMap[string, string](), } } diff --git a/filter.go b/filter.go index 1efd191..eaaf8d3 100644 --- a/filter.go +++ b/filter.go @@ -1,20 +1,17 @@ package cosy import ( - "fmt" - "github.com/gin-gonic/gin" + "github.com/uozi-tech/cosy/filter" "github.com/uozi-tech/cosy/logger" "github.com/uozi-tech/cosy/model" "gorm.io/gorm" - "gorm.io/gorm/clause" - "strings" ) func (c *Ctx[T]) SetFussy(keys ...string) *Ctx[T] { c.fussy = append(c.fussy, keys...) for _, key := range keys { c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueryToFussySearch(c.Context, tx, key) + return filter.QueryToFussySearch(c.Context, tx, key) }) } return c @@ -23,7 +20,7 @@ func (c *Ctx[T]) SetFussy(keys ...string) *Ctx[T] { func (c *Ctx[T]) SetSearchFussyKeys(keys ...string) *Ctx[T] { c.search = append(c.search, keys...) c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueryToFussyKeysSearch(c.Context, tx, keys...) + return filter.QueryToFussyKeysSearch(c.Context, tx, keys...) }) return c } @@ -31,7 +28,7 @@ func (c *Ctx[T]) SetSearchFussyKeys(keys ...string) *Ctx[T] { func (c *Ctx[T]) SetEqual(keys ...string) *Ctx[T] { c.eq = append(c.eq, keys...) c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueryToEqualSearch(c.Context, tx, keys...) + return filter.QueryToEqualSearch(c.Context, tx, keys...) }) return c } @@ -39,14 +36,14 @@ func (c *Ctx[T]) SetEqual(keys ...string) *Ctx[T] { func (c *Ctx[T]) SetIn(keys ...string) *Ctx[T] { c.in = append(c.in, keys...) c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueriesToInSearch(c.Context, tx, keys...) + return filter.QueriesToInSearch(c.Context, tx, keys...) }) return c } func (c *Ctx[T]) SetInWithKey(value string, key string) *Ctx[T] { c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueryToInSearch(c.Context, tx, value, key) + return filter.QueryToInSearch(c.Context, tx, value, key) }) return c } @@ -54,7 +51,7 @@ func (c *Ctx[T]) SetInWithKey(value string, key string) *Ctx[T] { func (c *Ctx[T]) SetOrFussy(keys ...string) *Ctx[T] { c.orFussy = append(c.orFussy, keys...) c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueryToOrFussySearch(c.Context, tx, keys...) + return filter.QueryToOrFussySearch(c.Context, tx, keys...) }) return c } @@ -62,7 +59,7 @@ func (c *Ctx[T]) SetOrFussy(keys ...string) *Ctx[T] { func (c *Ctx[T]) SetOrEqual(keys ...string) *Ctx[T] { c.orEq = append(c.orEq, keys...) c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueryToOrEqualSearch(c.Context, tx, keys...) + return filter.QueryToOrEqualSearch(c.Context, tx, keys...) }) return c } @@ -70,14 +67,14 @@ func (c *Ctx[T]) SetOrEqual(keys ...string) *Ctx[T] { func (c *Ctx[T]) SetBetween(keys ...string) *Ctx[T] { c.between = append(c.between, keys...) c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueriesToBetweenSearch(c.Context, tx, keys...) + return filter.QueriesToBetweenSearch(c.Context, tx, keys...) }) return c } func (c *Ctx[T]) SetBetweenWithKey(value string, key string) *Ctx[T] { c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueryToBetweenSearch(c.Context, tx, value, key) + return filter.QueryToBetweenSearch(c.Context, tx, value, key) }) return c } @@ -85,211 +82,21 @@ func (c *Ctx[T]) SetBetweenWithKey(value string, key string) *Ctx[T] { func (c *Ctx[T]) SetOrIn(keys ...string) *Ctx[T] { c.orIn = append(c.orIn, keys...) c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { - return QueryToOrInSearch(c.Context, tx, keys...) + return filter.QueryToOrInSearch(c.Context, tx, keys...) }) return c } -func QueriesToInSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { - for _, v := range keys { - QueryToInSearch(c, db, v) +func (c *Ctx[T]) SetCustomFilter(key string, filterName string) *Ctx[T] { + customFilter := filter.FilterMap[filterName] + if customFilter == nil { + logger.Errorf("Filter not found: %s", filterName) + return c } - return db -} - -func QueryToInSearch(c *gin.Context, db *gorm.DB, value string, key ...string) *gorm.DB { - queryArray := c.QueryArray(value + "[]") - if len(queryArray) == 0 { - queryArray = c.QueryArray(value) - } - if len(queryArray) == 1 && queryArray[0] == "" { - return db - } - - if len(queryArray) >= 1 { - var builder strings.Builder - stmt := db.Statement - - column := value - if len(key) != 0 { - column = key[0] - } - - stmt.QuoteTo(&builder, clause.Column{Table: stmt.Table, Name: column}) - builder.WriteString(" IN ?") - - return db.Where(builder.String(), queryArray) - } - return db -} - -func QueryToEqualSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { - for _, v := range keys { - if c.Query(v) != "" { - var sb strings.Builder - stmt := db.Statement - - stmt.QuoteTo(&sb, clause.Column{Table: stmt.Table, Name: v}) - sb.WriteString(" = ?") - - db = db.Where(sb.String(), c.Query(v)) - } - } - return db -} - -func QueryToFussySearch(c *gin.Context, db *gorm.DB, key string) *gorm.DB { - if qArr := c.QueryArray(key + "[]"); qArr != nil { - db = applyFuzzyCondition(db, key, qArr) - } else if q := c.Query(key); q != "" { - db = applyFuzzyCondition(db, key, []string{q}) - } - return db -} - -func applyFuzzyCondition(tx *gorm.DB, column string, values []string) *gorm.DB { - stmt := tx.Statement - - // build column name (column LIKE ?) - var colBuilder strings.Builder - stmt.QuoteTo(&colBuilder, clause.Column{Table: stmt.Table, Name: column}) - colBuilder.WriteString(" LIKE ?") - - db := model.UseDB() - var valueBuilder strings.Builder - - for _, value := range values { - // build value for query (%value%) - valueBuilder.Reset() - valueBuilder.WriteString("%") - valueBuilder.WriteString(value) - valueBuilder.WriteString("%") - - db = db.Or(colBuilder.String(), valueBuilder.String()) - } - - return tx.Where(db) -} - -func QueryToFussyKeysSearch(c *gin.Context, tx *gorm.DB, keys ...string) *gorm.DB { - value := c.Query("search") - if value == "" { - return tx - } - - // build value for query (%value%) - var valueBuilder strings.Builder - valueBuilder.WriteString("%") - valueBuilder.WriteString(value) - valueBuilder.WriteString("%") - likeValue := valueBuilder.String() - - db := model.UseDB() - var colBuilder strings.Builder - - for _, v := range keys { - // build column name (column LIKE ?) - colBuilder.Reset() - colBuilder.WriteString(v) - colBuilder.WriteString(" LIKE ?") - - db = db.Or(colBuilder.String(), likeValue) - } - - return tx.Where(db) -} - -func QueryToOrInSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { - for _, v := range keys { - queryArray := c.QueryArray(v + "[]") - if len(queryArray) == 0 { - queryArray = c.QueryArray(v) - } - if len(queryArray) == 1 && queryArray[0] == "" { - continue - } - if len(queryArray) >= 1 { - var sb strings.Builder - stmt := db.Statement - - stmt.QuoteTo(&sb, clause.Column{Table: stmt.Table, Name: v}) - sb.WriteString(" IN ?") - - db = db.Or(sb.String(), queryArray) - } - } - return db -} - -func QueryToOrEqualSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { - for _, v := range keys { - if c.Query(v) != "" { - var sb strings.Builder - stmt := db.Statement - - stmt.QuoteTo(&sb, clause.Column{Table: stmt.Table, Name: v}) - sb.WriteString(" = ?") - - db = db.Or(sb.String(), c.Query(v)) - } - } - return db -} - -func QueryToOrFussySearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { - for _, v := range keys { - if c.Query(v) != "" { - var sb strings.Builder - stmt := db.Statement - - stmt.QuoteTo(&sb, clause.Column{Table: stmt.Table, Name: v}) - - sb.WriteString(" LIKE ?") - - var sbValue strings.Builder - - _, err := fmt.Fprintf(&sbValue, "%%%s%%", c.Query(v)) - - if err != nil { - logger.Error(err) - continue - } - - db = db.Or(sb.String(), sbValue.String()) - } - } - return db -} - -func QueriesToBetweenSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { - for _, v := range keys { - db = QueryToBetweenSearch(c, db, v) - } - return db -} - -func QueryToBetweenSearch(c *gin.Context, db *gorm.DB, value string, key ...string) *gorm.DB { - queryArray := c.QueryArray(value + "[]") - if len(queryArray) == 0 { - queryArray = c.QueryArray(value) - } - if len(queryArray) <= 1 { - return db - } - - if len(queryArray) == 2 && queryArray[0] != "" && queryArray[1] != "" { - var builder strings.Builder - stmt := db.Statement - - column := value - if len(key) != 0 { - column = key[0] - } - - stmt.QuoteTo(&builder, clause.Column{Table: stmt.Table, Name: column}) - builder.WriteString(" BETWEEN ? AND ?") - - return db.Where(builder.String(), queryArray[0], queryArray[1]) - } - return db + c.customFilters.Set(key, filterName) + c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB { + resolvedModel := model.GetResolvedModel[T]() + return customFilter.Filter(c.Context, tx, key, resolvedModel.Fields[key], resolvedModel) + }) + return c } diff --git a/filter/between.go b/filter/between.go new file mode 100644 index 0000000..bb301e1 --- /dev/null +++ b/filter/between.go @@ -0,0 +1,42 @@ +package filter + +import ( + "strings" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func QueriesToBetweenSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { + for _, v := range keys { + db = QueryToBetweenSearch(c, db, v) + } + return db +} + +func QueryToBetweenSearch(c *gin.Context, db *gorm.DB, value string, key ...string) *gorm.DB { + queryArray := c.QueryArray(value + "[]") + if len(queryArray) == 0 { + queryArray = c.QueryArray(value) + } + if len(queryArray) <= 1 { + return db + } + + if len(queryArray) == 2 && queryArray[0] != "" && queryArray[1] != "" { + var builder strings.Builder + stmt := db.Statement + + column := value + if len(key) != 0 { + column = key[0] + } + + stmt.QuoteTo(&builder, clause.Column{Table: stmt.Table, Name: column}) + builder.WriteString(" BETWEEN ? AND ?") + + return db.Where(builder.String(), queryArray[0], queryArray[1]) + } + return db +} diff --git a/filter/equal.go b/filter/equal.go new file mode 100644 index 0000000..94cb9ec --- /dev/null +++ b/filter/equal.go @@ -0,0 +1,39 @@ +package filter + +import ( + "strings" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func QueryToEqualSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { + for _, v := range keys { + if c.Query(v) != "" { + var sb strings.Builder + stmt := db.Statement + + stmt.QuoteTo(&sb, clause.Column{Table: stmt.Table, Name: v}) + sb.WriteString(" = ?") + + db = db.Where(sb.String(), c.Query(v)) + } + } + return db +} + +func QueryToOrEqualSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { + for _, v := range keys { + if c.Query(v) != "" { + var sb strings.Builder + stmt := db.Statement + + stmt.QuoteTo(&sb, clause.Column{Table: stmt.Table, Name: v}) + sb.WriteString(" = ?") + + db = db.Or(sb.String(), c.Query(v)) + } + } + return db +} diff --git a/filter/filter.go b/filter/filter.go new file mode 100644 index 0000000..b5bf4b0 --- /dev/null +++ b/filter/filter.go @@ -0,0 +1,18 @@ +package filter + +import ( + "github.com/gin-gonic/gin" + "github.com/uozi-tech/cosy/model" + "gorm.io/gorm" +) + +type Filter interface { + Filter(c *gin.Context, db *gorm.DB, key string, field *model.ResolvedModelField, model *model.ResolvedModel) *gorm.DB +} + +// Customize filters +var FilterMap = make(map[string]Filter) + +func RegisterFilter(key string, filter Filter) { + FilterMap[key] = filter +} diff --git a/filter/fussy.go b/filter/fussy.go new file mode 100644 index 0000000..5a46250 --- /dev/null +++ b/filter/fussy.go @@ -0,0 +1,98 @@ +package filter + +import ( + "fmt" + "strings" + + "github.com/gin-gonic/gin" + "github.com/uozi-tech/cosy/logger" + "github.com/uozi-tech/cosy/model" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func QueryToFussySearch(c *gin.Context, db *gorm.DB, key string) *gorm.DB { + if qArr := c.QueryArray(key + "[]"); qArr != nil { + db = applyFuzzyCondition(db, key, qArr) + } else if q := c.Query(key); q != "" { + db = applyFuzzyCondition(db, key, []string{q}) + } + return db +} + +func applyFuzzyCondition(tx *gorm.DB, column string, values []string) *gorm.DB { + stmt := tx.Statement + + // build column name (column LIKE ?) + var colBuilder strings.Builder + stmt.QuoteTo(&colBuilder, clause.Column{Table: stmt.Table, Name: column}) + colBuilder.WriteString(" LIKE ?") + + db := model.UseDB() + var valueBuilder strings.Builder + + for _, value := range values { + // build value for query (%value%) + valueBuilder.Reset() + valueBuilder.WriteString("%") + valueBuilder.WriteString(value) + valueBuilder.WriteString("%") + + db = db.Or(colBuilder.String(), valueBuilder.String()) + } + + return tx.Where(db) +} + +func QueryToFussyKeysSearch(c *gin.Context, tx *gorm.DB, keys ...string) *gorm.DB { + value := c.Query("search") + if value == "" { + return tx + } + + // build value for query (%value%) + var valueBuilder strings.Builder + valueBuilder.WriteString("%") + valueBuilder.WriteString(value) + valueBuilder.WriteString("%") + likeValue := valueBuilder.String() + + db := model.UseDB() + var colBuilder strings.Builder + + for _, v := range keys { + // build column name (column LIKE ?) + colBuilder.Reset() + colBuilder.WriteString(v) + colBuilder.WriteString(" LIKE ?") + + db = db.Or(colBuilder.String(), likeValue) + } + + return tx.Where(db) +} + +func QueryToOrFussySearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { + for _, v := range keys { + if c.Query(v) != "" { + var sb strings.Builder + stmt := db.Statement + + stmt.QuoteTo(&sb, clause.Column{Table: stmt.Table, Name: v}) + + sb.WriteString(" LIKE ?") + + var sbValue strings.Builder + + _, err := fmt.Fprintf(&sbValue, "%%%s%%", c.Query(v)) + + if err != nil { + logger.Error(err) + continue + } + + db = db.Or(sb.String(), sbValue.String()) + } + } + return db +} diff --git a/filter/in.go b/filter/in.go new file mode 100644 index 0000000..a9773ce --- /dev/null +++ b/filter/in.go @@ -0,0 +1,64 @@ +package filter + +import ( + "strings" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func QueriesToInSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { + for _, v := range keys { + QueryToInSearch(c, db, v) + } + return db +} + +func QueryToInSearch(c *gin.Context, db *gorm.DB, value string, key ...string) *gorm.DB { + queryArray := c.QueryArray(value + "[]") + if len(queryArray) == 0 { + queryArray = c.QueryArray(value) + } + if len(queryArray) == 1 && queryArray[0] == "" { + return db + } + + if len(queryArray) >= 1 { + var builder strings.Builder + stmt := db.Statement + + column := value + if len(key) != 0 { + column = key[0] + } + + stmt.QuoteTo(&builder, clause.Column{Table: stmt.Table, Name: column}) + builder.WriteString(" IN ?") + + return db.Where(builder.String(), queryArray) + } + return db +} + +func QueryToOrInSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB { + for _, v := range keys { + queryArray := c.QueryArray(v + "[]") + if len(queryArray) == 0 { + queryArray = c.QueryArray(v) + } + if len(queryArray) == 1 && queryArray[0] == "" { + continue + } + if len(queryArray) >= 1 { + var sb strings.Builder + stmt := db.Statement + + stmt.QuoteTo(&sb, clause.Column{Table: stmt.Table, Name: v}) + sb.WriteString(" IN ?") + + db = db.Or(sb.String(), queryArray) + } + } + return db +} diff --git a/model/cosy_tag.go b/model/cosy_tag.go index b4f6039..493fb1c 100644 --- a/model/cosy_tag.go +++ b/model/cosy_tag.go @@ -1,18 +1,20 @@ package model import ( + "github.com/elliotchance/orderedmap/v3" "strings" ) type CosyTag struct { - all string - add string - update string - item string - list []string - json string - batch bool - unique bool + all string + add string + update string + item string + list []string + json string + batch bool + unique bool + customFilter *orderedmap.OrderedMap[string, string] } // NewCosyTag creates a new CosyTag from a tag string @@ -21,6 +23,8 @@ func NewCosyTag(tag string) (c CosyTag) { return } + c.customFilter = orderedmap.NewOrderedMap[string, string]() + // split tag by ; groups := strings.Split(tag, ";") for _, group := range groups { @@ -28,13 +32,14 @@ func NewCosyTag(tag string) (c CosyTag) { // we need to get the right side of : directives := strings.Split(group, ":") - // fix for cosy:"batch", cosy:"db_unique" + // fixed for cosy:"batch", cosy:"db_unique" if len(directives) == 1 { directives = append(directives, "") } if len(directives) < 2 { continue } + // now the directives are like // ["all", "omitempty"] // ["add", "required,max=100"], @@ -42,6 +47,7 @@ func NewCosyTag(tag string) (c CosyTag) { // ["update", "omitempty"] // ["item", "preload"] // ["json", "password"] + // ["list", "fussy[sakura]"] switch directives[0] { // for "add", "update", "item" directives, we only need the right side @@ -58,7 +64,7 @@ func NewCosyTag(tag string) (c CosyTag) { c.list = strings.Split(directives[1], ",") case "json": c.json = directives[1] - // for batch directives, we only need the left side + // for batch directives, we only need the left side case "batch": c.batch = true case "db_unique": diff --git a/model/models.go b/model/models.go index d1b3114..5cc7a2a 100644 --- a/model/models.go +++ b/model/models.go @@ -1,122 +1,123 @@ package model import ( - "reflect" - "strings" + "reflect" + "strings" ) var collection []any // GenerateAllModel generate all models func GenerateAllModel() []any { - return collection + return collection } // RegisterModels register models func RegisterModels(models ...any) { - collection = append(collection, models...) + collection = append(collection, models...) } // ClearCollection clear collection for testing purpose func ClearCollection() { - collection = make([]any, 0) + collection = make([]any, 0) } -type resolvedModelField struct { - Name string - Type string - JsonTag string - CosyTag CosyTag - Unique bool - DefaultValue string +type ResolvedModelField struct { + Name string + Type string + JsonTag string + CosyTag CosyTag + Unique bool + DefaultValue string } type ResolvedModel struct { - Name string - Fields map[string]*resolvedModelField - OrderedFields []*resolvedModelField + Name string + Fields map[string]*ResolvedModelField + OrderedFields []*ResolvedModelField } var resolvedModelMap = make(map[string]*ResolvedModel) func deepResolve(r *ResolvedModel, m reflect.Type) { - for i := 0; i < m.NumField(); i++ { - field := m.Field(i) - fieldType := field.Type - - // Check if the field is a pointer to a struct - if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct { - // If it is, we want to resolve the struct it points to - fieldType = fieldType.Elem() - } - - // Continue with the existing logic for anonymous structs - if fieldType.Kind() == reflect.Struct && field.Anonymous { - deepResolve(r, fieldType) - continue - } - - jsonTag := field.Tag.Get("json") - jsonTags := strings.Split(jsonTag, ",") - if len(jsonTags) > 0 { - jsonTag = jsonTags[0] - } else { - jsonTag = "" - } - - resolvedField := &resolvedModelField{ - Name: field.Name, - Type: field.Type.String(), - JsonTag: jsonTag, - CosyTag: NewCosyTag(field.Tag.Get("cosy")), - } - - gormTags := field.Tag.Get("gorm") - // gorm:"uniqueIndex;type:varchar(255);default:0" - if gormTags != "" { - tags := strings.Split(gormTags, ";") - for _, tag := range tags { - if strings.Contains(tag, "default") { - defaultValueTag := strings.Split(tag, ":") - if len(defaultValueTag) != 2 { - continue - } - resolvedField.DefaultValue = defaultValueTag[1] - } - if strings.Contains(tag, "unique") { - resolvedField.Unique = true - } - } - } - - // out-of-order, CamelCase as the key - r.Fields[field.Name] = resolvedField - // out-of-order, jsonTagName as the key - r.Fields[jsonTag] = resolvedField - // sorted - r.OrderedFields = append(r.OrderedFields, resolvedField) - } + for i := 0; i < m.NumField(); i++ { + field := m.Field(i) + fieldType := field.Type + + // Check if the field is a pointer to a struct + if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct { + // If it is, we want to resolve the struct it points to + fieldType = fieldType.Elem() + } + + // Continue with the existing logic for anonymous structs + if fieldType.Kind() == reflect.Struct && field.Anonymous { + deepResolve(r, fieldType) + continue + } + + jsonTag := field.Tag.Get("json") + jsonTags := strings.Split(jsonTag, ",") + if len(jsonTags) > 0 { + jsonTag = jsonTags[0] + } else { + jsonTag = "" + } + + resolvedField := &ResolvedModelField{ + Name: field.Name, + Type: field.Type.String(), + JsonTag: jsonTag, + CosyTag: NewCosyTag(field.Tag.Get("cosy")), + } + + gormTags := field.Tag.Get("gorm") + + if gormTags != "" { + tags := strings.Split(gormTags, ";") + for _, tag := range tags { + // gorm:"uniqueIndex;type:varchar(255);default:0" + if strings.Contains(tag, "default") { + defaultValueTag := strings.Split(tag, ":") + if len(defaultValueTag) != 2 { + continue + } + resolvedField.DefaultValue = defaultValueTag[1] + } + if strings.Contains(tag, "unique") { + resolvedField.Unique = true + } + } + } + + // out-of-order, CamelCase as the key + r.Fields[field.Name] = resolvedField + // out-of-order, jsonTagName as the key + r.Fields[jsonTag] = resolvedField + // sorted + r.OrderedFields = append(r.OrderedFields, resolvedField) + } } // ResolvedModels resolved meta of models func ResolvedModels() { - for _, model := range collection { - // resolve model meta - m := reflect.TypeOf(model) - r := &ResolvedModel{ - Name: m.Name(), - Fields: make(map[string]*resolvedModelField), - OrderedFields: make([]*resolvedModelField, 0), - } - - deepResolve(r, m) - - resolvedModelMap[r.Name] = r - } + for _, model := range collection { + // resolve model meta + m := reflect.TypeOf(model) + r := &ResolvedModel{ + Name: m.Name(), + Fields: make(map[string]*ResolvedModelField), + OrderedFields: make([]*ResolvedModelField, 0), + } + + deepResolve(r, m) + + resolvedModelMap[r.Name] = r + } } // GetResolvedModel get resolved model from resolvedModelMap func GetResolvedModel[T any]() *ResolvedModel { - name := reflect.TypeFor[T]().Name() - return resolvedModelMap[name] + name := reflect.TypeFor[T]().Name() + return resolvedModelMap[name] } diff --git a/model/models_test.go b/model/models_test.go index b0d54a0..59e586b 100644 --- a/model/models_test.go +++ b/model/models_test.go @@ -1,10 +1,11 @@ package model import ( - "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" "testing" "time" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" ) type TestEmbed struct { @@ -47,7 +48,7 @@ func TestResolvedModels(t *testing.T) { expectedModel := map[string]ResolvedModel{ "User": { Name: "User", - OrderedFields: []*resolvedModelField{ + OrderedFields: []*ResolvedModelField{ { Name: "ID", Type: "uint64", @@ -136,7 +137,7 @@ func TestResolvedModels(t *testing.T) { }, "Product": { Name: "Product", - OrderedFields: []*resolvedModelField{ + OrderedFields: []*ResolvedModelField{ { Name: "ID", Type: "uint64", diff --git a/sandbox/client.go b/sandbox/client.go index c99d1de..100e21c 100644 --- a/sandbox/client.go +++ b/sandbox/client.go @@ -19,7 +19,7 @@ func newClient() *Client { } } -// Request send a request and get response +// Request sends a request and get response func (c *Client) Request(method string, uri string, body any) (r *Response, err error) { buf, err := json.Marshal(body) if err != nil {