diff --git a/_examples/todo/main.go b/_examples/todo/main.go index df09925..ee95c6c 100644 --- a/_examples/todo/main.go +++ b/_examples/todo/main.go @@ -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 diff --git a/chell.go b/chell.go index 52f32c5..68104a3 100644 --- a/chell.go +++ b/chell.go @@ -2,6 +2,7 @@ package portal import ( "context" + "fmt" "reflect" "github.com/pkg/errors" @@ -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. @@ -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) diff --git a/field.go b/field.go index 4094009..fbee75e 100644 --- a/field.go +++ b/field.go @@ -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)), } } @@ -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, ":") @@ -243,6 +237,8 @@ func parseTagSettings(s string) map[string]string { settings[strings.ToUpper(strings.TrimSpace(parts[0]))] = "" } } + + cachedFieldTagSettings.Store(s, settings) return settings } diff --git a/option.go b/option.go index 4362b54..ea21c43 100644 --- a/option.go +++ b/option.go @@ -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: `.` +func CustomFieldTagMap(in map[string]string) option { + return func(c *Chell) error { + c.customFieldTagMap = in + return nil + } +} diff --git a/schema.go b/schema.go index 28f60f9..71e572b 100644 --- a/schema.go +++ b/schema.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "reflect" + "strings" "github.com/fatih/structs" ) @@ -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") @@ -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))) @@ -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 { diff --git a/schema_test.go b/schema_test.go index 8631360..1cbebf3 100644 --- a/schema_test.go +++ b/schema_test.go @@ -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") +}