Skip to content

Commit

Permalink
✨ feat: support custom field tag setting
Browse files Browse the repository at this point in the history
  • Loading branch information
0xE8551CCB committed Oct 31, 2019
1 parent 0367bd9 commit 815a808
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 15 deletions.
5 changes: 4 additions & 1 deletion _examples/todo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ func printFullFields(task *model.TaskModel) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()

err := portal.DumpWithContext(ctx, &taskSchema, task)
err := portal.DumpWithContext(ctx, &taskSchema, task, portal.CustomFieldTagMap(map[string]string{
"TaskSchema.Title": "const:hello",
"UserSchema.Name": "const:xiaoming",
}))
if err != nil {
fmt.Printf("%++v", err)
return
Expand Down
12 changes: 12 additions & 0 deletions chell.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package portal

import (
"context"
"fmt"
"reflect"

"github.com/pkg/errors"
Expand All @@ -14,6 +15,9 @@ type Chell struct {
disableConcurrency bool
onlyFieldFilters map[int][]*filterNode
excludeFieldFilters map[int][]*filterNode

// custom field tags
customFieldTagMap map[string]string
}

// New creates a new Chell instance with a worker pool waiting to be feed.
Expand Down Expand Up @@ -113,6 +117,14 @@ func (c *Chell) SetExcludeFields(fields ...string) error {
}

func (c *Chell) dump(ctx context.Context, dst *schema, src interface{}) error {
// read custom field tags
for _, field := range dst.fields {
key := fmt.Sprintf("%s.%s", field.schema.name(), field.Name())
if v, ok := c.customFieldTagMap[key]; ok {
field.settings = parseTagSettings(v)
}
}

err := c.dumpSyncFields(ctx, dst, src)
if err != nil {
return errors.WithStack(err)
Expand Down
22 changes: 9 additions & 13 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,10 @@ type schemaField struct {

func newField(schema *schema, field *structs.Field) *schemaField {
tagStr := field.Tag(defaultTagName)

var settings map[string]string
cachedSettings, ok := cachedFieldTagSettings.Load(tagStr)
if ok {
result, _ := cachedSettings.(map[string]string)
settings = result
} else {
result := parseTagSettings(tagStr)
cachedFieldTagSettings.Store(tagStr, result)
settings = result
}

return &schemaField{
Field: field,
schema: schema,
settings: settings,
settings: parseTagSettings(tagStr),
alias: parseAlias(field.Tag(schema.fieldAliasMapTagName)),
}
}
Expand Down Expand Up @@ -234,6 +222,12 @@ func (f *schemaField) async() bool {
}

func parseTagSettings(s string) map[string]string {
cachedSettings, ok := cachedFieldTagSettings.Load(s)
if ok {
result, _ := cachedSettings.(map[string]string)
return result
}

settings := make(map[string]string)
for _, item := range strings.Split(s, ";") {
parts := strings.Split(item, ":")
Expand All @@ -243,6 +237,8 @@ func parseTagSettings(s string) map[string]string {
settings[strings.ToUpper(strings.TrimSpace(parts[0]))] = ""
}
}

cachedFieldTagSettings.Store(s, settings)
return settings
}

Expand Down
10 changes: 10 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,13 @@ func DisableConcurrency() option {
return nil
}
}

// CustomFieldTagMap sets custom tag for each field.
// It will override the default tag settings defined in your struct.
// The key should be: `<StructName>.<FieldName>`
func CustomFieldTagMap(in map[string]string) option {
return func(c *Chell) error {
c.customFieldTagMap = in
return nil
}
}
29 changes: 28 additions & 1 deletion schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"reflect"
"strings"

"github.com/fatih/structs"
)
Expand All @@ -14,9 +15,11 @@ type schema struct {
schemaStruct *structs.Struct
availableFieldNames map[string]bool
fields []*schemaField

parent *schema
}

func newSchema(v interface{}) *schema {
func newSchema(v interface{}, parent ...*schema) *schema {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
panic("expect a pointer to struct")
Expand Down Expand Up @@ -52,6 +55,10 @@ func newSchema(v interface{}) *schema {
fieldAliasMapTagName: "json",
}

if len(parent) > 0 {
sch.parent = parent[0]
}

for _, name := range getAvailableFieldNames(sch.schemaStruct.Fields()) {
sch.availableFieldNames[name] = true
sch.fields = append(sch.fields, newField(sch, sch.schemaStruct.Field(name)))
Expand Down Expand Up @@ -183,6 +190,26 @@ func (s *schema) name() string {
return structName(s.rawValue)
}

func (s *schema) nameWithParents() string {
var names []string

p := s
for p != nil {
names = append(names, p.name())
p = p.parent
}

// reverse names (two cursors)
i, j := 0, len(names)-1
for i < j {
names[i], names[j] = names[j], names[i]
i++
j--
}

return strings.Join(names, ".")
}

func (s *schema) fieldByNameOrAlias(name string) *schemaField {
for _, f := range s.fields {
if f.alias == name || f.Name() == name {
Expand Down
9 changes: 9 additions & 0 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,12 @@ func filedNames(fields []*schemaField) (names []string) {
}
return
}

func TestSchema_NameWithParent(t *testing.T) {
s1 := newSchema(&SchoolSchema{})
s2 := newSchema(&PersonSchema{}, s1)
s3 := newSchema(&UserSchema2{}, s2)
assert.Equal(t, s1.nameWithParents(), "SchoolSchema")
assert.Equal(t, s2.nameWithParents(), "SchoolSchema.PersonSchema")
assert.Equal(t, s3.nameWithParents(), "SchoolSchema.PersonSchema.UserSchema2")
}

0 comments on commit 815a808

Please sign in to comment.