From 51a30b91ad1b4ee5ec2d4fea519d42761bf135c3 Mon Sep 17 00:00:00 2001 From: 0xE8551CCB Date: Wed, 15 Jan 2020 16:32:12 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20support=20default=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- USERGUIDE.md | 14 ++++++++++++++ _examples/todo/schema/schema.go | 20 +++++++++++--------- chell.go | 22 +++++++++++++++++----- field.go | 30 ++++++++++++++++++++++++++++++ field_test.go | 30 ++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/USERGUIDE.md b/USERGUIDE.md index dca0993..812de90 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -83,6 +83,20 @@ type UserSchema struct { ``` +### Set Default Value for Field: `default` + +Only works for types: pointer/slice/map. For basic types (integer, string, bool), default value will be converted and set to field directly. For complex types (eg. map/slice/pointer to custom struct), set default to `AUTO_INIT`, portal will initialize field to its zero value. + +```go +type ContentSchema struct { + BizID *string `json:"biz_id" portal:"default:100"` + SkuID *string `json:"sku_id"` // -> json null + Users []*UserSchema `json:"users" portal:"default:AUTO_INIT"` // -> json [] + Members map[string]int `json:"members" portal:"default:AUTO_INIT"` // -> json {} + User *UserSchema `json:"user" portal:"default:AUTO_INIT"` +} +``` + ## Embedding Schema ```go type PersonSchema struct { diff --git a/_examples/todo/schema/schema.go b/_examples/todo/schema/schema.go index 0530873..e155770 100644 --- a/_examples/todo/schema/schema.go +++ b/_examples/todo/schema/schema.go @@ -26,15 +26,17 @@ type UserSchema struct { } type TaskSchema struct { - ID string `json:"id,omitempty"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty" portal:"meth:GetDescription;async"` - Description1 string `json:"description1,omitempty" portal:"meth:GetDescription;async"` - Description2 string `json:"description2,omitempty" portal:"meth:GetDescription;async"` - Description3 string `json:"description3,omitempty" portal:"meth:GetDescription;async"` - User *UserSchema `json:"user,omitempty" portal:"nested"` - SimpleUser *UserSchema `json:"simple_user,omitempty" portal:"nested;only:Name;attr:User"` - Unknown string `json:"unknown"` + ID string `json:"id,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty" portal:"meth:GetDescription;async"` + Description1 string `json:"description1,omitempty" portal:"meth:GetDescription;async"` + Description2 string `json:"description2,omitempty" portal:"meth:GetDescription;async"` + Description3 string `json:"description3,omitempty" portal:"meth:GetDescription;async"` + User *UserSchema `json:"user,omitempty" portal:"nested"` + SimpleUser *UserSchema `json:"simple_user,omitempty" portal:"nested;only:Name;attr:User"` + Unknown string `json:"unknown"` + UsersWithDefault []*UserSchema `json:"users_with_default" portal:"nested;default:AUTO_INIT"` + UserWithDefault *UserSchema `json:"user_with_default" portal:"nested;default:AUTO_INIT"` } func (ts *TaskSchema) GetDescription(model *model.TaskModel) (string, error) { diff --git a/chell.go b/chell.go index 68104a3..0374300 100644 --- a/chell.go +++ b/chell.go @@ -73,7 +73,9 @@ func (c *Chell) DumpWithContext(ctx context.Context, dst, src interface{}) error return c.dumpMany( ctx, dst, src, extractFilterNodeNames(c.onlyFieldFilters[0], nil), - extractFilterNodeNames(c.excludeFieldFilters[0], &extractOption{ignoreNodeWithChildren: true})) + extractFilterNodeNames(c.excludeFieldFilters[0], &extractOption{ignoreNodeWithChildren: true}), + "", + ) } else { toSchema := newSchema(dst).withFieldAliasMapTagName(c.fieldAliasMapTagName) toSchema.setOnlyFields(extractFilterNodeNames(c.onlyFieldFilters[0], nil)...) @@ -210,8 +212,13 @@ func (c *Chell) dumpAsyncFields(ctx context.Context, dst *schema, src interface{ func (c *Chell) dumpField(ctx context.Context, field *schemaField, value interface{}) error { if isNil(value) { - logger.Warnf("[portal.chell] cannot get value for field %s, current input value is %v", field, value) - return nil + if field.hasDefaultValue() { + value = field.defaultValue() + logger.Infof("[portal.chell] use default value for field `%s`", field) + } else { + logger.Warnf("[portal.chell] cannot get value for field `%s`, current input value is %v", field, value) + return nil + } } if !field.isNested() { @@ -259,6 +266,7 @@ func (c *Chell) dumpFieldNestedMany(ctx context.Context, field *schemaField, src src, field.nestedOnlyNames(c.onlyFieldFilters[depth]), field.nestedExcludeNames(c.excludeFieldFilters[depth]), + field.String(), ) if err != nil { return err @@ -279,14 +287,18 @@ func (c *Chell) dumpFieldNestedMany(ctx context.Context, field *schemaField, src return nil } -func (c *Chell) dumpMany(ctx context.Context, dst, src interface{}, onlyFields, excludeFields []string) error { +func (c *Chell) dumpMany(ctx context.Context, dst, src interface{}, onlyFields, excludeFields []string, field string) error { rv := reflect.ValueOf(src) if rv.Kind() == reflect.Ptr { rv = reflect.Indirect(rv) } if rv.Kind() != reflect.Slice { - panic("input src must be a slice") + if field != "" { + panic(fmt.Sprintf("input src must be a slice, current processing field is `%s`", field)) + } else { + panic(fmt.Sprintf("input src must be a slice")) + } } schemaSlice := reflect.Indirect(reflect.ValueOf(dst)) diff --git a/field.go b/field.go index fbee75e..555e288 100644 --- a/field.go +++ b/field.go @@ -170,6 +170,36 @@ func (f *schemaField) hasConstValue() bool { return f.tagHasOption("CONST") } +func (f *schemaField) defaultValue() interface{} { + val, ok := f.settings["DEFAULT"] + if !ok { + return nil + } + + var defaultValue interface{} + if val == "AUTO_INIT" { + // just initialize this field, now support ptr/slice/map + typ := reflect.TypeOf(f.Value()) + switch typ.Kind() { + case reflect.Ptr: + defaultValue = reflect.New(typ.Elem()).Interface() + case reflect.Slice: + defaultValue = reflect.MakeSlice(typ, 0, 0).Interface() + case reflect.Map: + defaultValue = reflect.MakeMap(typ).Interface() + default: + defaultValue = reflect.New(typ).Elem().Interface() + } + return defaultValue + } + + return val +} + +func (f *schemaField) hasDefaultValue() bool { + return f.tagHasOption("DEFAULT") +} + func (f *schemaField) tagHasOption(opt string) bool { if _, ok := f.settings[opt]; ok { return true diff --git a/field_test.go b/field_test.go index ff17c0a..2b86a76 100644 --- a/field_test.go +++ b/field_test.go @@ -143,6 +143,36 @@ func TestField_ConstValue(t *testing.T) { assert.False(t, f.hasConstValue()) } +func TestField_DefaultValue(t *testing.T) { + type UserSchema struct { + ID string + } + + type FooSchema struct { + ID *string `json:"id" portal:"default:100"` + Settings map[string]string `json:"settings" portal:"default:AUTO_INIT"` + User *UserSchema `json:"user" portal:"default:AUTO_INIT"` + Users []*UserSchema `json:"users" portal:"default:AUTO_INIT"` + } + + schema := newSchema(&FooSchema{}) + f := newField(schema, schema.innerStruct().Field("ID")) + assert.Equal(t, "100", f.defaultValue()) + assert.True(t, f.hasDefaultValue()) + + f = newField(schema, schema.innerStruct().Field("Settings")) + assert.Equal(t, map[string]string{}, f.defaultValue()) + assert.True(t, f.hasDefaultValue()) + + f = newField(schema, schema.innerStruct().Field("User")) + assert.Equal(t, &UserSchema{}, f.defaultValue()) + assert.True(t, f.hasDefaultValue()) + + f = newField(schema, schema.innerStruct().Field("Users")) + assert.Equal(t, make([]*UserSchema, 0), f.defaultValue()) + assert.True(t, f.hasDefaultValue()) +} + func TestField_Async(t *testing.T) { type FooSchema struct { ID int