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..59ac96c95ed2f909edc08e3140692be486bce2d6 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 |
+// value, this panics (so don't do it). If you want to handle the error |
+// normally, use SetValue instead. |
+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. |
+func MkDSPropertyNI(val interface{}) DSProperty { |
+ ret := DSProperty{} |
+ if err := ret.SetValue(val, true); err != nil { |
+ 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,39 @@ 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 more ErrDSFieldMismatch |
+ // objects. |
+ Load(DSPropertyMap) error |
+ Save(withMeta bool) (DSPropertyMap, error) |
-// 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>[,<value>]?"`. |
+ // |
+ // string, bool and int64 fields will return the <value> in the struct tag, |
+ // converted to the appropriate type, if the field has the zero value. |
// |
// Example: |
// type MyStruct struct { |
- // CoolField int `gae:"$id,cool"` |
+ // CoolField int64 `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 +356,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 |
// 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 |
+// 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") |
} |
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 |
+ } |
+ ret := make(DSPropertyMap, len(pm)) |
dnj
2015/07/14 19:34:57
Maybe include the allocation-avoiding optimization
iannucci
2015/07/14 22:45:48
done though it returns DSPropertyMap{} instead of
|
+ for k, v := range pm { |
+ if k[0] == '$' { // remove meta keys |
dnj
2015/07/14 19:34:57
This will have an issue with an empty key (""). Ei
iannucci
2015/07/14 22:45:48
Done.
|
+ continue |
dnj
2015/07/14 19:34:57
Since the action is a single line, avoid the "cont
iannucci
2015/07/14 22:45:48
Done.
|
+ } |
+ 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 { |
dnj
2015/07/14 19:34:57
Why do you differentiate between "nil" and empty?
iannucci
2015/07/14 22:45:48
I just don't check anymore... nil DSPropertyMap is
|
+ return errors.New("gae: nil DSPropertyMap") |
+ } |
+ return nil |
} |