Index: service/datastore/multiarg.go |
diff --git a/service/datastore/multiarg.go b/service/datastore/multiarg.go |
index 009c3f178b6431b56d7bd8b910933ff27c9e6a63..13bc3f02e74d2e294af0081402378dc5671bb69b 100644 |
--- a/service/datastore/multiarg.go |
+++ b/service/datastore/multiarg.go |
@@ -11,6 +11,30 @@ import ( |
"github.com/luci/luci-go/common/errors" |
) |
+type metaMultiArgConstraints int |
+ |
+const ( |
+ // mmaReadWrite allows a metaMultiArg to operate on any type that can be |
+ // both read and written to. |
+ mmaReadWrite metaMultiArgConstraints = iota |
+ // mmaKeysOnly implies mmaReadWrite, with the further statement that the only |
+ // operation that will be performed against the arguments will be key |
+ // extraction. |
+ mmaKeysOnly = iota |
+ // mmaWriteKeys indicates that the caller is only going to write key |
+ // values. This enables the same inputs as mmaReadWrite, but also allows |
+ // []*Key. |
+ mmaWriteKeys = iota |
+) |
+ |
+func (c metaMultiArgConstraints) allowSingleKey() bool { |
+ return c == mmaKeysOnly |
+} |
+ |
+func (c metaMultiArgConstraints) keyOperationsOnly() bool { |
+ return c >= mmaKeysOnly |
+} |
+ |
type multiArgType struct { |
getMGS func(slot reflect.Value) MetaGetterSetter |
getPLS func(slot reflect.Value) PropertyLoadSaver |
@@ -33,8 +57,8 @@ func (mat *multiArgType) setPM(slot reflect.Value, pm PropertyMap) error { |
return mat.getPLS(slot).Load(pm) |
} |
-func (mat *multiArgType) setKey(slot reflect.Value, k *Key) { |
- PopulateKey(mat.getMGS(slot), k) |
+func (mat *multiArgType) setKey(slot reflect.Value, k *Key) bool { |
+ return populateKeyMGS(mat.getMGS(slot), k) |
} |
// parseArg checks that et is of type S, *S, I, P or *P, for some |
@@ -44,13 +68,17 @@ func (mat *multiArgType) setKey(slot reflect.Value, k *Key) { |
// If et is a chan type that implements PropertyLoadSaver, new elements will be |
// allocated with a buffer of 0. |
// |
-// If keysOnly is true, a read-only key extraction multiArgType will be |
-// returned if et is a *Key. |
-func parseArg(et reflect.Type, keysOnly bool) *multiArgType { |
+// If allowKey is true, et may additional be type *Key. Only MetaGetterSetter |
+// fields will be populated in the result (see keyMGS). |
+func parseArg(et reflect.Type, allowKeys bool) *multiArgType { |
var mat multiArgType |
- if keysOnly && et == typeOfKey { |
- mat.getMGS = func(slot reflect.Value) MetaGetterSetter { return &keyExtractionMGS{key: slot.Interface().(*Key)} } |
+ if et == typeOfKey { |
+ if !allowKeys { |
+ return nil |
+ } |
+ |
+ mat.getMGS = func(slot reflect.Value) MetaGetterSetter { return &keyMGS{slot: slot} } |
return &mat |
} |
@@ -199,10 +227,10 @@ func mustParseMultiArg(et reflect.Type) *multiArgType { |
if et.Kind() != reflect.Slice { |
panic(fmt.Errorf("invalid argument type: expected slice, got %s", et)) |
} |
- return mustParseArg(et.Elem()) |
+ return mustParseArg(et.Elem(), true) |
} |
-func mustParseArg(et reflect.Type) *multiArgType { |
+func mustParseArg(et reflect.Type, sliceArg bool) *multiArgType { |
if mat := parseArg(et, false); mat != nil { |
return mat |
} |
@@ -230,13 +258,13 @@ func newKeyObjErr(aid, ns string, mgs MetaGetterSetter) (*Key, error) { |
return NewKey(aid, ns, kind, sid, iid, par), nil |
} |
-func isOKSingleType(t reflect.Type, keysOnly bool) error { |
+func isOKSingleType(t reflect.Type, allowKey bool) error { |
switch { |
case t == nil: |
return errors.New("no type information") |
case t.Implements(typeOfPropertyLoadSaver): |
return nil |
- case !keysOnly && t == typeOfKey: |
+ case !allowKey && t == typeOfKey: |
return errors.New("not user datatype") |
case t.Kind() != reflect.Ptr: |
@@ -249,19 +277,34 @@ func isOKSingleType(t reflect.Type, keysOnly bool) error { |
} |
} |
-// keyExtractionMGS is a MetaGetterSetter that wraps a key and only implements |
-// GetMeta, and even then only retrieves the wrapped key meta value, "key". |
-type keyExtractionMGS struct { |
- MetaGetterSetter |
+// keyMGS is a MetaGetterSetter that wraps a single key value/slot. It only |
+// implements operations on the "key" key. |
+// |
+// GetMeta will be implemented, returning the *Key for the "key" meta. |
+// |
+// If slot is addressable, SetMeta will allow it to be set to the supplied |
+// Value. |
+type keyMGS struct { |
+ slot reflect.Value |
+} |
+ |
+func (mgs *keyMGS) GetAllMeta() PropertyMap { |
+ return PropertyMap{"$key": []Property{MkPropertyNI(mgs.slot.Interface())}} |
+} |
- key *Key |
+func (mgs *keyMGS) GetMeta(key string) (interface{}, bool) { |
+ if key != "key" { |
+ return nil, false |
+ } |
+ return mgs.slot.Interface(), true |
} |
-func (mgs *keyExtractionMGS) GetMeta(key string) (interface{}, bool) { |
- if key == "key" { |
- return mgs.key, true |
+func (mgs *keyMGS) SetMeta(key string, value interface{}) bool { |
+ if !(key == "key" && mgs.slot.CanAddr()) { |
+ return false |
} |
- return nil, false |
+ mgs.slot.Set(reflect.ValueOf(value)) |
+ return true |
} |
type metaMultiArgElement struct { |
@@ -277,10 +320,18 @@ type metaMultiArg struct { |
count int // total number of elements, flattening slices |
} |
-func makeMetaMultiArg(args []interface{}, keysOnly bool) (*metaMultiArg, error) { |
+// makeMetaMultiArg returns a metaMultiArg for the supplied args. |
+// |
+// If an arg is a slice, a slice metaMultiArg will be returned, and errors for |
+// that slice will be written into a positional MultiError if they occur. |
+// |
+// If keysOnly is true, the caller is instructing metaMultiArg to only extract |
+// the datastore *Key from args. *Key entries will be permitted, but the caller |
+// may not write to them (since keys are read-only structs). |
+func makeMetaMultiArg(args []interface{}, c metaMultiArgConstraints) (*metaMultiArg, error) { |
mma := metaMultiArg{ |
elems: make([]metaMultiArgElement, len(args)), |
- keysOnly: keysOnly, |
+ keysOnly: c.keyOperationsOnly(), |
} |
lme := errors.NewLazyMultiError(len(args)) |
@@ -297,21 +348,29 @@ func makeMetaMultiArg(args []interface{}, keysOnly bool) (*metaMultiArg, error) |
// Try and treat the argument as a single-value first. This allows slices |
// that implement PropertyLoadSaver to be properly treated as a single |
// element. |
- var err error |
- isSlice := false |
- mat := parseArg(vt, keysOnly) |
- if mat == nil { |
+ var ( |
+ mat *multiArgType |
+ err error |
+ isSlice = false |
+ ) |
+ if mat = parseArg(vt, c.allowSingleKey()); mat == nil { |
// If this is a slice, treat it as a slice of arg candidates. |
+ // |
+ // If only keys are being read/written, we allow a []*Key to be accepted |
+ // here, since slices are addressable (write). |
if v.Kind() == reflect.Slice { |
isSlice = true |
- mat = parseArg(vt.Elem(), keysOnly) |
+ mat = parseArg(vt.Elem(), c.keyOperationsOnly()) |
} |
} else { |
// Single types need to be able to be assigned to. |
- err = isOKSingleType(vt, keysOnly) |
+ // |
+ // We only allow *Key here when the keys cannot be written to, since *Key |
+ // should not be modified in-place, as they are immutable. |
+ err = isOKSingleType(vt, c.allowSingleKey()) |
} |
- if mat == nil { |
- err = errors.New("not a PLS or pointer-to-struct") |
+ if mat == nil && err == nil { |
+ err = errors.New("not a PLS, pointer-to-struct, or slice thereof") |
} |
if err != nil { |
lme.Assign(i, fmt.Errorf("invalid input type (%T): %s", arg, err)) |