| Index: service/datastore/multiarg.go
 | 
| diff --git a/service/datastore/multiarg.go b/service/datastore/multiarg.go
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..494d3952c9f2ad4fc8b6eecc502053083ceeb3eb
 | 
| --- /dev/null
 | 
| +++ b/service/datastore/multiarg.go
 | 
| @@ -0,0 +1,304 @@
 | 
| +// Copyright 2015 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +package datastore
 | 
| +
 | 
| +import (
 | 
| +	"fmt"
 | 
| +	"reflect"
 | 
| +
 | 
| +	"github.com/luci/luci-go/common/errors"
 | 
| +)
 | 
| +
 | 
| +type multiArgType struct {
 | 
| +	valid bool
 | 
| +
 | 
| +	getKey  func(nk newKeyFunc, slot reflect.Value) (Key, error)
 | 
| +	getPM   func(slot reflect.Value) (PropertyMap, error)
 | 
| +	setPM   func(slot reflect.Value, pm PropertyMap) error
 | 
| +	setKey  func(slot reflect.Value, k Key)
 | 
| +	newElem func() reflect.Value
 | 
| +}
 | 
| +
 | 
| +func (mat *multiArgType) GetKeys(nk newKeyFunc, slice reflect.Value) ([]Key, error) {
 | 
| +	ret := make([]Key, slice.Len())
 | 
| +	lme := errors.LazyMultiError{Size: len(ret)}
 | 
| +	for i := range ret {
 | 
| +		key, err := mat.getKey(nk, slice.Index(i))
 | 
| +		lme.Assign(i, err)
 | 
| +		ret[i] = key
 | 
| +	}
 | 
| +	return ret, lme.Get()
 | 
| +}
 | 
| +
 | 
| +func (mat *multiArgType) GetPMs(slice reflect.Value) ([]PropertyMap, error) {
 | 
| +	ret := make([]PropertyMap, slice.Len())
 | 
| +	lme := errors.LazyMultiError{Size: len(ret)}
 | 
| +	for i := range ret {
 | 
| +		key, err := mat.getPM(slice.Index(i))
 | 
| +		lme.Assign(i, err)
 | 
| +		ret[i] = key
 | 
| +	}
 | 
| +	return ret, lme.Get()
 | 
| +}
 | 
| +
 | 
| +// parseMultiArg checks that v has type []S, []*S, []I, []P or []*P, for some
 | 
| +// struct type S, for some interface type I, or some non-interface non-pointer
 | 
| +// type P such that P or *P implements PropertyLoadSaver.
 | 
| +func parseMultiArg(e reflect.Type) multiArgType {
 | 
| +	if e.Kind() != reflect.Slice {
 | 
| +		return multiArgTypeInvalid()
 | 
| +	}
 | 
| +	return parseArg(e.Elem())
 | 
| +}
 | 
| +
 | 
| +// parseArg checks that et is of type S, *S, I, P or *P, for some
 | 
| +// struct type S, for some interface type I, or some non-interface non-pointer
 | 
| +// type P such that P or *P implements PropertyLoadSaver.
 | 
| +func parseArg(et reflect.Type) multiArgType {
 | 
| +	if reflect.PtrTo(et).Implements(typeOfPropertyLoadSaver) {
 | 
| +		return multiArgTypePLS(et)
 | 
| +	}
 | 
| +	if et.Implements(typeOfPropertyLoadSaver) && et.Kind() != reflect.Interface {
 | 
| +		return multiArgTypePLSPtr(et.Elem())
 | 
| +	}
 | 
| +	switch et.Kind() {
 | 
| +	case reflect.Struct:
 | 
| +		return multiArgTypeStruct(et)
 | 
| +	case reflect.Interface:
 | 
| +		return multiArgTypeInterface()
 | 
| +	case reflect.Ptr:
 | 
| +		et = et.Elem()
 | 
| +		if et.Kind() == reflect.Struct {
 | 
| +			return multiArgTypeStructPtr(et)
 | 
| +		}
 | 
| +	}
 | 
| +	return multiArgTypeInvalid()
 | 
| +}
 | 
| +
 | 
| +type newKeyFunc func(kind, sid string, iid int64, par Key) Key
 | 
| +
 | 
| +func multiArgTypeInvalid() multiArgType {
 | 
| +	return multiArgType{}
 | 
| +}
 | 
| +
 | 
| +// multiArgTypePLS == []P
 | 
| +// *P implements PropertyLoadSaver
 | 
| +func multiArgTypePLS(et reflect.Type) multiArgType {
 | 
| +	ret := multiArgType{
 | 
| +		valid: true,
 | 
| +
 | 
| +		getKey: func(nk newKeyFunc, slot reflect.Value) (Key, error) {
 | 
| +			return newKeyObjErr(nk, slot.Addr().Interface())
 | 
| +		},
 | 
| +		getPM: func(slot reflect.Value) (PropertyMap, error) {
 | 
| +			return slot.Addr().Interface().(PropertyLoadSaver).Save(true)
 | 
| +		},
 | 
| +		setPM: func(slot reflect.Value, pm PropertyMap) error {
 | 
| +			return slot.Addr().Interface().(PropertyLoadSaver).Load(pm)
 | 
| +		},
 | 
| +		setKey: func(slot reflect.Value, k Key) {
 | 
| +			setKey(slot.Addr().Interface(), k)
 | 
| +		},
 | 
| +	}
 | 
| +	if et.Kind() == reflect.Map {
 | 
| +		ret.newElem = func() reflect.Value {
 | 
| +			// Create a *map so that way slot.Addr() works above when this is
 | 
| +			// called from Run(). Otherwise the map is 'unaddressable' according
 | 
| +			// to reflect.  ¯\_(ツ)_/¯
 | 
| +			ptr := reflect.New(et)
 | 
| +			ptr.Elem().Set(reflect.MakeMap(et))
 | 
| +			return ptr.Elem()
 | 
| +		}
 | 
| +	} else {
 | 
| +		ret.newElem = func() reflect.Value {
 | 
| +			return reflect.New(et).Elem()
 | 
| +		}
 | 
| +	}
 | 
| +	return ret
 | 
| +}
 | 
| +
 | 
| +// multiArgTypePLSPtr == []*P
 | 
| +// *P implements PropertyLoadSaver
 | 
| +func multiArgTypePLSPtr(et reflect.Type) multiArgType {
 | 
| +	ret := multiArgType{
 | 
| +		valid: true,
 | 
| +
 | 
| +		getKey: func(nk newKeyFunc, slot reflect.Value) (Key, error) {
 | 
| +			return newKeyObjErr(nk, slot.Interface())
 | 
| +		},
 | 
| +		getPM: func(slot reflect.Value) (PropertyMap, error) {
 | 
| +			return slot.Interface().(PropertyLoadSaver).Save(true)
 | 
| +		},
 | 
| +		setPM: func(slot reflect.Value, pm PropertyMap) error {
 | 
| +			return slot.Interface().(PropertyLoadSaver).Load(pm)
 | 
| +		},
 | 
| +		setKey: func(slot reflect.Value, k Key) {
 | 
| +			setKey(slot.Interface(), k)
 | 
| +		},
 | 
| +	}
 | 
| +	if et.Kind() == reflect.Map {
 | 
| +		ret.newElem = func() reflect.Value {
 | 
| +			ptr := reflect.New(et)
 | 
| +			ptr.Elem().Set(reflect.MakeMap(et))
 | 
| +			return ptr
 | 
| +		}
 | 
| +	} else {
 | 
| +		ret.newElem = func() reflect.Value { return reflect.New(et) }
 | 
| +	}
 | 
| +	return ret
 | 
| +}
 | 
| +
 | 
| +// multiArgTypeStruct == []S
 | 
| +func multiArgTypeStruct(et reflect.Type) multiArgType {
 | 
| +	cdc := getCodec(et)
 | 
| +	if cdc.problem != nil {
 | 
| +		return multiArgTypeInvalid()
 | 
| +	}
 | 
| +	toPLS := func(slot reflect.Value) PropertyLoadSaver {
 | 
| +		return &structPLS{slot, cdc}
 | 
| +	}
 | 
| +	return multiArgType{
 | 
| +		valid: true,
 | 
| +
 | 
| +		getKey: func(nk newKeyFunc, slot reflect.Value) (Key, error) {
 | 
| +			return newKeyObjErr(nk, toPLS(slot))
 | 
| +		},
 | 
| +		getPM: func(slot reflect.Value) (PropertyMap, error) {
 | 
| +			return toPLS(slot).(PropertyLoadSaver).Save(true)
 | 
| +		},
 | 
| +		setPM: func(slot reflect.Value, pm PropertyMap) error {
 | 
| +			return toPLS(slot).(PropertyLoadSaver).Load(pm)
 | 
| +		},
 | 
| +		setKey: func(slot reflect.Value, k Key) {
 | 
| +			setKey(toPLS(slot), k)
 | 
| +		},
 | 
| +		newElem: func() reflect.Value {
 | 
| +			return reflect.New(et).Elem()
 | 
| +		},
 | 
| +	}
 | 
| +}
 | 
| +
 | 
| +// multiArgTypeStructPtr == []*S
 | 
| +func multiArgTypeStructPtr(et reflect.Type) multiArgType {
 | 
| +	cdc := getCodec(et)
 | 
| +	if cdc.problem != nil {
 | 
| +		return multiArgTypeInvalid()
 | 
| +	}
 | 
| +	toPLS := func(slot reflect.Value) PropertyLoadSaver {
 | 
| +		return &structPLS{slot.Elem(), cdc}
 | 
| +	}
 | 
| +	return multiArgType{
 | 
| +		valid: true,
 | 
| +
 | 
| +		getKey: func(nk newKeyFunc, slot reflect.Value) (Key, error) {
 | 
| +			return newKeyObjErr(nk, toPLS(slot))
 | 
| +		},
 | 
| +		getPM: func(slot reflect.Value) (PropertyMap, error) {
 | 
| +			return toPLS(slot).(PropertyLoadSaver).Save(true)
 | 
| +		},
 | 
| +		setPM: func(slot reflect.Value, pm PropertyMap) error {
 | 
| +			return toPLS(slot).(PropertyLoadSaver).Load(pm)
 | 
| +		},
 | 
| +		setKey: func(slot reflect.Value, k Key) {
 | 
| +			setKey(toPLS(slot), k)
 | 
| +		},
 | 
| +		newElem: func() reflect.Value {
 | 
| +			return reflect.New(et)
 | 
| +		},
 | 
| +	}
 | 
| +}
 | 
| +
 | 
| +// multiArgTypeInterface == []I
 | 
| +func multiArgTypeInterface() multiArgType {
 | 
| +	return multiArgType{
 | 
| +		valid: true,
 | 
| +
 | 
| +		getKey: func(nk newKeyFunc, slot reflect.Value) (Key, error) {
 | 
| +			return newKeyObjErr(nk, slot.Elem().Interface())
 | 
| +		},
 | 
| +		getPM: func(slot reflect.Value) (PropertyMap, error) {
 | 
| +			pls, _ := mkPLSName(slot.Elem().Interface())
 | 
| +			return pls.Save(true)
 | 
| +		},
 | 
| +		setPM: func(slot reflect.Value, pm PropertyMap) error {
 | 
| +			pls, _ := mkPLSName(slot.Elem().Interface())
 | 
| +			return pls.Load(pm)
 | 
| +		},
 | 
| +		setKey: func(slot reflect.Value, k Key) {
 | 
| +			setKey(slot.Elem().Interface(), k)
 | 
| +		},
 | 
| +	}
 | 
| +}
 | 
| +
 | 
| +func newKeyObjErr(nk newKeyFunc, src interface{}) (Key, error) {
 | 
| +	pls, name := mkPLSName(src)
 | 
| +	if key := getMetaKey(pls, "key"); key != nil {
 | 
| +		return key, nil
 | 
| +	}
 | 
| +
 | 
| +	// get kind
 | 
| +	kind := getMetaString(pls, "kind", name)
 | 
| +	if kind == "" {
 | 
| +		return nil, fmt.Errorf("unable to extract $kind from %v", src)
 | 
| +	}
 | 
| +
 | 
| +	// get id - allow both to be default for default keys
 | 
| +	sid := getMetaString(pls, "id", "")
 | 
| +	iid := getMetaInt64(pls, "id", 0)
 | 
| +
 | 
| +	// get parent
 | 
| +	par := getMetaKey(pls, "parent")
 | 
| +
 | 
| +	return nk(kind, sid, iid, par), nil
 | 
| +}
 | 
| +
 | 
| +func setKey(src interface{}, key Key) {
 | 
| +	pls, _ := mkPLSName(src)
 | 
| +	if pls.SetMeta("key", key) == ErrMetaFieldUnset {
 | 
| +		if key.StringID() != "" {
 | 
| +			pls.SetMeta("id", key.StringID())
 | 
| +		} else {
 | 
| +			pls.SetMeta("id", key.IntID())
 | 
| +		}
 | 
| +		pls.SetMeta("kind", key.Kind())
 | 
| +		pls.SetMeta("parent", key.Parent())
 | 
| +	}
 | 
| +}
 | 
| +
 | 
| +func mkPLSName(o interface{}) (PropertyLoadSaver, string) {
 | 
| +	if pls, ok := o.(*structPLS); ok {
 | 
| +		return pls, pls.o.Type().Name()
 | 
| +	}
 | 
| +	if pls, ok := o.(PropertyLoadSaver); ok {
 | 
| +		return pls, ""
 | 
| +	}
 | 
| +	pls := GetPLS(o)
 | 
| +	name := pls.(*structPLS).o.Type().Name()
 | 
| +	return pls, name
 | 
| +}
 | 
| +
 | 
| +func getMetaString(pls PropertyLoadSaver, key, dflt string) string {
 | 
| +	mstr, err := pls.GetMeta(key)
 | 
| +	ret, ok := mstr.(string)
 | 
| +	if err != nil || !ok {
 | 
| +		return dflt
 | 
| +	}
 | 
| +	return ret
 | 
| +}
 | 
| +
 | 
| +func getMetaInt64(pls PropertyLoadSaver, key string, dflt int64) int64 {
 | 
| +	mint, err := pls.GetMeta(key)
 | 
| +	ret, ok := mint.(int64)
 | 
| +	if err != nil || !ok {
 | 
| +		return dflt
 | 
| +	}
 | 
| +	return ret
 | 
| +}
 | 
| +
 | 
| +func getMetaKey(pls PropertyLoadSaver, key string) Key {
 | 
| +	mkey, _ := pls.GetMeta(key)
 | 
| +	ret, _ := mkey.(Key)
 | 
| +	return ret
 | 
| +}
 | 
| 
 |