Chromium Code Reviews| Index: go/src/infra/gae/libs/gae/properties.go |
| diff --git a/go/src/infra/gae/libs/gae/properties.go b/go/src/infra/gae/libs/gae/properties.go |
| index c7aa9e063e9c992a7b0eeee48186ff171199ac3d..bd719b12e62dcb9b0a4acc21bf5d9a276a5ce917 100644 |
| --- a/go/src/infra/gae/libs/gae/properties.go |
| +++ b/go/src/infra/gae/libs/gae/properties.go |
| @@ -13,10 +13,10 @@ import ( |
| ) |
| var ( |
| - // ErrDSSpecialFieldUnset is returned from DSStructPLS.{Get,Set}Special |
| - // implementations when the specified special key isn't set on the struct at |
| + // ErrDSMetaFieldUnset is returned from DSPropertyLoadSaver.{Get,Set}Meta |
| + // implementations when the specified meta key isn't set on the struct at |
| // all. |
| - ErrDSSpecialFieldUnset = fmt.Errorf("gae: special field unset") |
| + ErrDSMetaFieldUnset = fmt.Errorf("gae: meta field unset") |
| ) |
| var ( |
| @@ -45,6 +45,28 @@ type DSProperty struct { |
| propType DSPropertyType |
| } |
| +// MkDSProperty makes a new DSProperty and returns it. If val is an invalid |
|
Vadim Sh.
2015/07/14 18:55:43
nit: a new indexed DSProperty
iannucci
2015/07/14 22:45:48
Done.
|
| +// value, this panics (so don't do it). If you want to handle the error |
| +// normally, use SetValue instead. |
|
Vadim Sh.
2015/07/14 18:55:44
nit: use SetValue(..., false) instead.
iannucci
2015/07/14 22:45:48
Done.
|
| +func MkDSProperty(val interface{}) DSProperty { |
| + ret := DSProperty{} |
| + if err := ret.SetValue(val, false); err != nil { |
| + panic(err) |
| + } |
| + return ret |
| +} |
| + |
| +// MkDSPropertyNI makes a new DSProperty (with noindex set to true), and returns |
| +// it. If val is an invalid value, this panics (so don't do it). If you want to |
| +// handle the error normally, use SetValue instead. |
|
Vadim Sh.
2015/07/14 18:55:43
nit: use SetValue(..., true) instead
iannucci
2015/07/14 22:45:48
Done.
|
| +func MkDSPropertyNI(val interface{}) DSProperty { |
| + ret := DSProperty{} |
| + if err := ret.SetValue(val, true); err != nil { |
|
Vadim Sh.
2015/07/14 18:55:44
nit: 'true' to indicate "noindex" is a bit confusi
iannucci
2015/07/14 22:45:48
Yeah, I think you're right (it was more copy pasta
|
| + panic(err) |
| + } |
| + return ret |
| +} |
| + |
| // DSPropertyConverter may be implemented by the pointer-to a struct field which |
| // is serialized by RawDatastore. Its ToDSProperty will be called on save, and |
| // it's FromDSProperty will be called on load (from datastore). The method may |
| @@ -292,34 +314,41 @@ func (p *DSProperty) SetValue(value interface{}, noIndex bool) (err error) { |
| // DSPropertyLoadSaver may be implemented by a user type, and RawDatastore will |
| // use this interface to serialize the type instead of trying to automatically |
| -// create a serialization codec for it with helper.GetStructPLS. |
| +// create a serialization codec for it with helper.GetPLS. |
| type DSPropertyLoadSaver interface { |
| - Load(DSPropertyMap) (convFailures []string, fatal error) |
| - Save() (DSPropertyMap, error) |
| -} |
| + // Load takes the values from the given map and attempts to save them into |
| + // the underlying object (usually a struct or a DSPropertyMap). If a fatal |
| + // error occurs, it's returned via error. If non-fatal conversion errors |
| + // occur, error will be a MultiError containing one or moer ErrDSFieldMismatch |
| + // objects. |
| + Load(DSPropertyMap) error |
| + Save(withMeta bool) (DSPropertyMap, error) |
|
Vadim Sh.
2015/07/14 18:55:44
document Save?
iannucci
2015/07/14 22:45:48
Done.
|
| -// DSStructPLS is a DSPropertyLoadSaver, but with some bonus features which only |
| -// apply to user structs (instead of raw DSPropertyMap's). |
| -type DSStructPLS interface { |
| - DSPropertyLoadSaver |
| - |
| - // GetSpecial will get information about the struct field which has the |
| - // struct tag in the form of `gae:"$<key>"`. |
| + // GetMeta will get information about the field which has the struct tag in |
| + // the form of `gae:"$<key>"`. |
| + // |
| + // string and {,u}int fields will return the value in the struct tag, |
| + // converted to the appropriate type, if the field has the zero value. If the |
| + // struct tag is improperly formatted, an error will be returned (e.g. having |
| + // an int field with a non-integer value in the tag). |
| // |
| // Example: |
| // type MyStruct struct { |
| - // CoolField int `gae:"$id,cool"` |
| + // CoolField int `gae:"$id,1"` |
| // } |
| - // val, current, err := helper.GetStructPLS(&MyStruct{10}).GetSpecial("id") |
| - // // val == "cool" |
| - // // current == 10 |
| + // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") |
| + // // val == 1 |
| // // err == nil |
| - GetSpecial(key string) (val string, current interface{}, err error) |
| + // |
| + // val, err := helper.GetPLS(&MyStruct{10}).GetMeta("id") |
| + // // val == 10 |
| + // // err == nil |
| + GetMeta(key string) (interface{}, error) |
| - // SetSpecial allows you to set the current value of the special-keyed field. |
| - SetSpecial(key string, val interface{}) error |
| + // SetMeta allows you to set the current value of the meta-keyed field. |
| + SetMeta(key string, val interface{}) error |
| - // Problem indicates that this StructPLS has a fatal problem. Usually this is |
| + // Problem indicates that this PLS has a fatal problem. Usually this is |
| // set when the underlying struct has recursion, invalid field types, nested |
| // slices, etc. |
| Problem() error |
| @@ -329,28 +358,87 @@ type DSStructPLS interface { |
| // It maps from property name to a list of property values which correspond to |
| // that property name. It is the spiritual successor to PropertyList from the |
|
Vadim Sh.
2015/07/14 18:55:43
spiritual successor, lol
|
| // original SDK. |
| +// |
| +// DSPropertyMap may contain "meta" values, which are keyed with a '$' prefix. |
| +// Technically the datastore allows property names, but all of the SDKs go out |
|
Vadim Sh.
2015/07/14 18:55:43
nit: "allows arbitrary property names"? I think yo
iannucci
2015/07/14 22:45:48
yes! good catch! Done.
|
| +// of their way to try to make all property names valid programming language |
| +// tokens. Special values must correspond to a single DSProperty... |
| +// corresponding to 0 is equivalent to unset, and corresponding to >1 is an |
| +// error. So: |
| +// |
| +// { |
| +// "$id": {MkDSProperty(1)}, // GetProperty("id") -> 1, nil |
| +// "$foo": {}, // GetProperty("foo") -> nil, ErrDSMetaFieldUnset |
| +// // GetProperty("bar") -> nil, ErrDSMetaFieldUnset |
| +// "$meep": { |
| +// MkDSProperty("hi"), |
| +// MkDSProperty("there")}, // GetProperty("meep") -> nil, error! |
| +// } |
| +// |
| +// Additionally, Save returns a copy of the map with the meta keys omitted (e.g. |
| +// these keys are not going to be serialized to the datastore). |
| type DSPropertyMap map[string][]DSProperty |
| -var _ DSPropertyLoadSaver = (*DSPropertyMap)(nil) |
| +var _ DSPropertyLoadSaver = DSPropertyMap(nil) |
| // Load implements DSPropertyLoadSaver.Load |
| -func (pm *DSPropertyMap) Load(props DSPropertyMap) (convErr []string, err error) { |
| +func (pm DSPropertyMap) Load(props DSPropertyMap) error { |
| if pm == nil { |
| - return nil, errors.New("gae: nil DSPropertyMap") |
| - } |
| - if *pm == nil { |
| - *pm = make(DSPropertyMap, len(props)) |
| + return errors.New("gae: nil DSPropertyMap") |
|
Vadim Sh.
2015/07/14 18:55:43
let it panic? It's caller responsibility to not pa
iannucci
2015/07/14 22:45:48
sgtm, done.
|
| } |
| for k, v := range props { |
| - (*pm)[k] = append((*pm)[k], v...) |
| + pm[k] = append(pm[k], v...) |
| } |
| - return nil, nil |
| + return nil |
| } |
| // Save implements DSPropertyLoadSaver.Save |
| -func (pm *DSPropertyMap) Save() (DSPropertyMap, error) { |
| +func (pm DSPropertyMap) Save(withMeta bool) (DSPropertyMap, error) { |
| + if pm == nil { |
| + return nil, errors.New("gae: nil DSPropertyMap") |
| + } |
| + if withMeta { |
| + return pm, nil |
|
Vadim Sh.
2015/07/14 18:55:44
copy pm before returning?
iannucci
2015/07/14 22:45:48
okay :/ I hate copying stuff... GO, Y U NO CONST :
|
| + } |
| + ret := make(DSPropertyMap, len(pm)) |
| + for k, v := range pm { |
| + if k[0] == '$' { // remove meta keys |
|
Vadim Sh.
2015/07/14 18:55:43
are you 100% sure k != ""?
iannucci
2015/07/14 22:45:48
good thought, done.
|
| + continue |
| + } |
| + ret[k] = append(ret[k], v...) |
| + } |
| + return ret, nil |
| +} |
| + |
| +func (pm DSPropertyMap) GetMeta(key string) (interface{}, error) { |
| if pm == nil { |
| return nil, errors.New("gae: nil DSPropertyMap") |
| } |
| - return *pm, nil |
| + v, ok := pm["$"+key] |
| + if !ok || len(v) == 0 { |
| + return nil, ErrDSMetaFieldUnset |
| + } |
| + if len(v) > 1 { |
| + return nil, errors.New("gae: too many values for Meta key") |
| + } |
| + return v[0].Value(), nil |
| +} |
| + |
| +func (pm DSPropertyMap) SetMeta(key string, val interface{}) error { |
| + if pm == nil { |
| + return errors.New("gae: nil DSPropertyMap") |
| + } |
| + prop := DSProperty{} |
| + if err := prop.SetValue(val, true); err != nil { |
| + return err |
| + } |
| + pm["$"+key] = []DSProperty{prop} |
| + return nil |
| +} |
| + |
| +func (pm DSPropertyMap) Problem() error { |
| + if pm == nil { |
| + return errors.New("gae: nil DSPropertyMap") |
| + } |
| + return nil |
| } |