blob: d1ecdeb1a208796c1e217a7e8af7920a45462fd3 [file] [log] [blame]
/*
* Copyright (c) 2012 Matt Jibson <matt.jibson@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package goon
import (
"bytes"
"container/list"
"encoding/gob"
"fmt"
"reflect"
"strings"
"sync"
"time"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
)
type fieldInfo struct {
sliceIndex []int
fieldIndex []int
}
type fieldMetadata struct {
name string
index int
}
type structMetaData struct {
metaDatas []string
totalLength int
}
// KindNameResolver takes an Entity and returns what the Kind should be for
// Datastore.
type KindNameResolver func(src interface{}) string
// A special bootstrapping struct that contains all the datastore-supported types
// that need to be registered with gob. Using this to initialize every encoder/decoder,
// we get reusable encoders/decoders. Additionally, this cuts down on the serialized bytes length.
// https://developers.google.com/appengine/docs/go/datastore/reference
type seBootstrap struct {
v01 *datastore.Key
v02 time.Time
v03 appengine.BlobKey
v04 []*datastore.Key
v05 []time.Time
v06 []int
v07 []int8
v08 []int16
v09 []int32
v10 []int64
v11 []float32
v12 []float64
v13 []bool
v14 []string
v15 [][]byte
}
type serializationEncoder struct {
buf *bytes.Buffer
enc *gob.Encoder
}
type serializationDecoder struct {
sr *serializationReader
dec *gob.Decoder
}
type serializationReader struct {
r *bytes.Reader
}
func (sr *serializationReader) Read(p []byte) (n int, err error) {
return sr.r.Read(p)
}
func (sr *serializationReader) ReadByte() (c byte, err error) {
return sr.r.ReadByte()
}
const (
serializationStateEmpty = 0x00
serializationStateNormal = 0x01
)
var (
timeType = reflect.TypeOf(time.Time{})
fieldInfos = make(map[reflect.Type]map[string]*fieldInfo)
fieldMetadatas = make(map[reflect.Type][]fieldMetadata)
fieldIMLock sync.RWMutex
serializationEncoders = list.New()
serializationEncodersLock sync.Mutex
serializationDecoders = list.New()
serializationDecodersLock sync.Mutex
seBoot = seBootstrap{v01: &datastore.Key{}}
seBootBytes []byte
seBootBytesLock sync.RWMutex
)
func init() {
// The serialization type cache bootstrapping optimization suffers from a race condition.
// Because encoding/gob uses a partial global type cache of its own, it is possible that
// some other piece of code uses encoding/gob before we do our bootstrapping. To be exact,
// inconsistent use of encoding/gob with custom types before us is what causes problems.
// Using encoding/gob after us isn't a problem. Neither is using it before us, if the usage
// always happens before us. The order of usage before us only matters if the custom types
// match a type that we have in our bootstrapping. To mostly solve this race condition,
// we bootstrap a single decoder in this init() function. However a rare edge case remains
// where the race condition can still cause trouble for us. The requirements for it are:
// 1) Some other init() function has to use encoding/gob
// 2) Be in a package that does not import goon, nor import anything that imports goon
// 3) Executes before goon's init()
// 4) That init()'s encoding/gob usage has to either:
// a) Happen inconsistently, i.e. not always happen, e.g.:
// * The usage is under a conditional, e.g. only executes when the clock is 09:00
// * The order in which the package is presented to the compiler gets moved after goon
// * The usage is removed during a code update
// b) Register a same type as us, but in an inconsistent order between multiple executions
freeSerializationDecoder(getSerializationDecoder([]byte{}))
}
// getFieldInfoAndMetadata returns metadata about a struct. Its main purpose is to cut down
// reflection usage on types, which is slow. Instead, metadata is generated once per type, and cached.
func getFieldInfoAndMetadata(t reflect.Type) (map[string]*fieldInfo, []fieldMetadata) {
fieldIMLock.RLock()
// Attempt to get the data for this type under an efficient read lock
fieldMap, ok := fieldInfos[t]
fms := fieldMetadatas[t]
fieldIMLock.RUnlock()
if !ok {
fieldIMLock.Lock()
// Check again, the data could have appeared when we didn't have a lock
fieldMap, ok = fieldInfos[t]
if !ok {
// We are going to generate the data for this type
fieldMap = make(map[string]*fieldInfo, 16)
generateFieldInfoAndMetadata(t, "", make([]int, 0, 16), []int{}, fieldMap)
fieldInfos[t] = fieldMap
}
fms = fieldMetadatas[t]
fieldIMLock.Unlock()
}
return fieldMap, fms
}
// getFieldMetadata returns already generated, cached, metadata about a struct.
func getFieldMetadata(t reflect.Type) []fieldMetadata {
fieldIMLock.RLock()
defer fieldIMLock.RUnlock()
return fieldMetadatas[t]
}
// generateFieldInfoAndMetadata generates metadata about the fields of a struct.
//
// NB! generateFieldInfoAndMetadata should only be called under a fieldIMLock write-lock
func generateFieldInfoAndMetadata(t reflect.Type, namePrefix string, indexPrefix, sliceIndex []int, fieldMap map[string]*fieldInfo) {
var fieldName string
fms, havefms := fieldMetadatas[t]
numFields := t.NumField()
for i := 0; i < numFields; i++ {
tf := t.Field(i)
if tf.PkgPath != "" && !tf.Anonymous {
continue
}
tag := tf.Tag.Get("datastore")
if len(tag) > 0 {
if commaPos := strings.Index(tag, ","); commaPos == -1 {
fieldName = tag
} else if commaPos == 0 {
fieldName = tf.Name
} else {
fieldName = tag[:commaPos]
}
if fieldName == "-" {
continue
}
} else {
fieldName = tf.Name
}
if !havefms {
fms = append(fms, fieldMetadata{name: fieldName, index: i})
}
if namePrefix != "" {
fieldName = namePrefix + fieldName
}
if tf.Type.Kind() == reflect.Slice {
elemType := tf.Type.Elem()
if elemType.Kind() == reflect.Struct && elemType != timeType {
generateFieldInfoAndMetadata(elemType, fieldName+".", make([]int, 0, 8), append(indexPrefix, i), fieldMap)
continue
}
} else if tf.Type.Kind() == reflect.Struct && tf.Type != timeType {
generateFieldInfoAndMetadata(tf.Type, fieldName+".", append(indexPrefix, i), sliceIndex, fieldMap)
continue
}
finalIndex := append(indexPrefix, i)
fi := &fieldInfo{sliceIndex: make([]int, len(sliceIndex)), fieldIndex: make([]int, len(finalIndex))}
copy(fi.sliceIndex, sliceIndex)
copy(fi.fieldIndex, finalIndex)
fieldMap[fieldName] = fi
}
if !havefms {
fieldMetadatas[t] = fms
}
}
// serializeStructMetaData is a fast encoder of struct metadata, which doesn't depend on gob.
// Struct metadata is just a slice of strings, which this function converts into a series of bytes.
func serializeStructMetaData(buf []byte, smd *structMetaData) {
pos := 0
for _, metaData := range smd.metaDatas {
copy(buf[pos:], metaData)
pos += len(metaData)
buf[pos] = '+'
pos++
}
if pos > 0 {
buf[pos-1] = '|'
}
}
// deserializeStructMetaData is a fast decoder of struct metadata, which doesn't depend on gob.
// It takes a slice of bytes buf, generated by serializeStructMetaData, and converts it into a slice of strings.
func deserializeStructMetaData(buf []byte) *structMetaData {
smd := &structMetaData{metaDatas: make([]string, 0, 16)}
pos, bufLen := 0, len(buf)
for i := 0; i < bufLen; i++ {
if buf[i] == '+' || buf[i] == '|' {
smd.metaDatas = append(smd.metaDatas, string(buf[pos:i]))
smd.totalLength += i - pos
pos = i + 1
if buf[i] == '|' {
break
}
}
}
return smd
}
// bootstrapSerializationEncoder runs the encoder through a bootstrapping process,
// which registers all datastore-supported types. This is an optimization that must
// be done only once per a reusable encoder.
//
// NB! updateSEBootBytes should only be set true if called under seBootBytesLock write-lock
func bootstrapSerializationEncoder(se *serializationEncoder, updateSEBootBytes bool) {
se.enc.Encode(seBoot.v01)
se.enc.Encode(seBoot.v02)
se.enc.Encode(seBoot.v03)
se.enc.Encode(seBoot.v04)
se.enc.Encode(seBoot.v05)
se.enc.Encode(seBoot.v06)
se.enc.Encode(seBoot.v07)
se.enc.Encode(seBoot.v08)
se.enc.Encode(seBoot.v09)
se.enc.Encode(seBoot.v10)
se.enc.Encode(seBoot.v11)
se.enc.Encode(seBoot.v12)
se.enc.Encode(seBoot.v13)
se.enc.Encode(seBoot.v14)
se.enc.Encode(seBoot.v15)
if updateSEBootBytes {
seBootBytes = make([]byte, se.buf.Len())
copy(seBootBytes, se.buf.Bytes())
}
se.buf.Reset()
}
// bootstrapSerializationDecoder runs the decoder through a bootstrapping process,
// which registers all datastore-supported types. This is an optimization that must
// be done only once per a reusable decoder.
func bootstrapSerializationDecoder(sd *serializationDecoder) {
seBootBytesLock.RLock()
ok := len(seBootBytes) > 0
seBootBytesLock.RUnlock()
if !ok {
seBootBytesLock.Lock()
ok = len(seBootBytes) > 0
if !ok {
buf := bytes.NewBuffer(make([]byte, 0, 512))
enc := gob.NewEncoder(buf)
se := &serializationEncoder{buf: buf, enc: enc}
bootstrapSerializationEncoder(se, true)
}
seBootBytesLock.Unlock()
}
sd.sr.r = bytes.NewReader(seBootBytes)
for i := 0; i < 15; i++ {
sd.dec.Decode(nil)
}
}
// getSerializationEncoder returns an efficient reusable encoder from a pool.
// Every encoder acquired with this function must be later freed via freeSerializationEncoder.
func getSerializationEncoder() *serializationEncoder {
serializationEncodersLock.Lock()
// Use an existing one if possible
if serializationEncoders.Len() > 0 {
defer serializationEncodersLock.Unlock()
return serializationEncoders.Remove(serializationEncoders.Front()).(*serializationEncoder)
}
serializationEncodersLock.Unlock()
// Otherwise allocate a new one
buf := bytes.NewBuffer(make([]byte, 0, 16384)) // 16 KiB initial capacity
enc := gob.NewEncoder(buf)
se := &serializationEncoder{buf: buf, enc: enc}
bootstrapSerializationEncoder(se, false)
return se
}
// freeSerializationEncoder returns the encoder to the pool, allowing for reuse.
func freeSerializationEncoder(se *serializationEncoder) {
se.buf.Reset()
serializationEncodersLock.Lock()
serializationEncoders.PushBack(se)
// TODO: Perhaps some occasional clean-up is in order?
// Running 50 goroutines concurrently can allocate a bunch of these
// but they may not be needed again for quite some time
serializationEncodersLock.Unlock()
}
// getSerializationDecoder returns an efficient reusable decoder from a pool.
// Every decoder acquired with this function must be later freed via freeSerializationDecoder.
func getSerializationDecoder(data []byte) *serializationDecoder {
serializationDecodersLock.Lock()
// Use an existing one if possible
if serializationDecoders.Len() > 0 {
sd := serializationDecoders.Remove(serializationDecoders.Front()).(*serializationDecoder)
serializationDecodersLock.Unlock()
sd.sr.r = bytes.NewReader(data)
return sd
}
serializationDecodersLock.Unlock()
// Otherwise allocate a new one
sr := &serializationReader{}
dec := gob.NewDecoder(sr)
sd := &serializationDecoder{sr: sr, dec: dec}
bootstrapSerializationDecoder(sd)
sd.sr.r = bytes.NewReader(data)
return sd
}
// freeSerializationDecoder returns the decoder to the pool, allowing for reuse.
func freeSerializationDecoder(sd *serializationDecoder) {
sd.sr.r = nil // Avoid memory leaks
serializationDecodersLock.Lock()
serializationDecoders.PushBack(sd)
// TODO: Perhaps some occasional clean-up is in order?
// Running 50 goroutines concurrently can allocate a bunch of these
// but they may not be needed again for quite some time
serializationDecodersLock.Unlock()
}
// serializeStruct takes a struct and serializes it to portable bytes.
func serializeStruct(src interface{}) ([]byte, error) {
if src == nil {
return []byte{serializationStateEmpty}, nil
}
v := reflect.Indirect(reflect.ValueOf(src))
t := v.Type()
k := t.Kind()
if k != reflect.Struct {
return nil, fmt.Errorf("goon: Expected struct, got instead: %v", k)
}
se := getSerializationEncoder()
defer freeSerializationEncoder(se)
smd := &structMetaData{metaDatas: make([]string, 0, 16)}
_, fms := getFieldInfoAndMetadata(t) // Use this function to force generation if needed
if err := serializeStructInternal(se.enc, smd, fms, "", v); err != nil {
return nil, err
}
bufSize := se.buf.Len()
// final size = header + all metadatas + separators for metadata + data
finalBufSize := 1 + smd.totalLength + len(smd.metaDatas) + bufSize
finalBuf := make([]byte, finalBufSize)
finalBuf[0] = byte(serializationStateNormal) // Set the header
serializeStructMetaData(finalBuf[1:], smd) // Serialize the metadata
copy(finalBuf[finalBufSize-bufSize:], se.buf.Bytes()) // Copy the actual data
return finalBuf, nil
}
// serializeStructInternal is a helper function for serializeStruct, mainly for easier recursion.
// It takes a bunch of metadata that describes a struct, and encodes field values with gob.
func serializeStructInternal(enc *gob.Encoder, smd *structMetaData, fms []fieldMetadata, namePrefix string, v reflect.Value) error {
var fieldName string
var metaData string
var elemType reflect.Type
for _, fm := range fms {
vf := v.Field(fm.index)
if namePrefix != "" {
fieldName = namePrefix + fm.name
} else {
fieldName = fm.name
}
if vf.Kind() == reflect.Slice {
elemType = vf.Type().Elem()
if elemType != timeType {
// Unroll slices of structs
if elemType.Kind() == reflect.Struct {
if vfLen := vf.Len(); vfLen > 0 {
subFms := getFieldMetadata(elemType)
subPrefix := fieldName + "."
for j := 0; j < vfLen; j++ {
vi := vf.Index(j)
if err := serializeStructInternal(enc, smd, subFms, subPrefix, vi); err != nil {
return err
}
}
}
continue
} else if elemType.Kind() == reflect.Ptr {
// For a slice of pointers we need to check if any index is nil,
// because Gob unfortunately fails at encoding nil values
anyNil := false
vfLen := vf.Len()
for j := 0; j < vfLen; j++ {
vi := vf.Index(j)
if vi.IsNil() {
anyNil = true
break
}
}
if anyNil {
for j := 0; j < vfLen; j++ {
vi := vf.Index(j)
encodeValue := true
if vi.IsNil() {
// Gob unfortunately fails at encoding nil values
metaData = "!" + fieldName
encodeValue = false
} else {
metaData = fieldName
}
if err := serializeStructInternalEncode(enc, smd, fieldName, metaData, encodeValue, vi); err != nil {
return err
}
}
continue
}
} else if elemType.Kind().String() != elemType.Name() {
// For slices of custom non-struct types, encode them as slices of the underlying type.
// This is required to be able to re-use a global gob encoding machine,
// as custom types require the type info to be declared by gob for every encoded struct!
// NB! We don't currently update the elemType variable as an optimization!
vfLen := vf.Len()
switch elemType.Kind() {
case reflect.String:
copy := make([]string, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = vf.Index(i).String()
}
vf = reflect.ValueOf(copy)
case reflect.Bool:
copy := make([]bool, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = vf.Index(i).Bool()
}
vf = reflect.ValueOf(copy)
case reflect.Int:
copy := make([]int, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = int(vf.Index(i).Int())
}
vf = reflect.ValueOf(copy)
case reflect.Int8:
copy := make([]int8, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = int8(vf.Index(i).Int())
}
vf = reflect.ValueOf(copy)
case reflect.Int16:
copy := make([]int16, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = int16(vf.Index(i).Int())
}
vf = reflect.ValueOf(copy)
case reflect.Int32:
copy := make([]int32, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = int32(vf.Index(i).Int())
}
vf = reflect.ValueOf(copy)
case reflect.Int64:
copy := make([]int64, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = vf.Index(i).Int()
}
vf = reflect.ValueOf(copy)
case reflect.Float32:
copy := make([]float32, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = float32(vf.Index(i).Float())
}
vf = reflect.ValueOf(copy)
case reflect.Float64:
copy := make([]float64, vfLen, vfLen)
for i := 0; i < vfLen; i++ {
copy[i] = vf.Index(i).Float()
}
vf = reflect.ValueOf(copy)
}
}
}
} else if vf.Kind() == reflect.Struct {
if vfType := vf.Type(); vfType != timeType {
if err := serializeStructInternal(enc, smd, getFieldMetadata(vfType), fieldName+".", vf); err != nil {
return err
}
continue
}
}
encodeValue := true
if vf.Kind() == reflect.Slice && elemType.Kind() != reflect.Uint8 {
// NB! []byte is a special case and not a slice
// When decoding, if the target is a slice but metadata doesn't have the $ sign,
// then we can properly create a single value slice instead of panicing
metaData = "$" + fieldName
} else if vf.Kind() == reflect.Ptr && vf.IsNil() {
// Gob unfortunately fails at encoding nil values
metaData = "!" + fieldName
encodeValue = false
} else {
metaData = fieldName
}
if err := serializeStructInternalEncode(enc, smd, fieldName, metaData, encodeValue, vf); err != nil {
return err
}
}
return nil
}
// serializeStructInternalEncode takes struct field metadata and encodes the value using gob.
func serializeStructInternalEncode(enc *gob.Encoder, smd *structMetaData, fieldName, metaData string, encodeValue bool, v reflect.Value) error {
smd.metaDatas = append(smd.metaDatas, metaData)
smd.totalLength += len(metaData)
if encodeValue {
if err := enc.EncodeValue(v); err != nil {
return fmt.Errorf("goon: Failed to encode field %v value %v - %v", fieldName, v.Interface(), err)
}
}
return nil
}
// deserializeStruct takes portable bytes b, generated by serializeStruct, and assigns correct values to struct dst.
func deserializeStruct(dst interface{}, b []byte) error {
if len(b) == 0 {
return fmt.Errorf("goon: Expected some data to deserialize, got none.")
}
v := reflect.Indirect(reflect.ValueOf(dst))
t := v.Type()
k := t.Kind()
if k != reflect.Struct {
return fmt.Errorf("goon: Expected struct, got instead: %v", k)
}
if header := b[0]; header == serializationStateEmpty {
return datastore.ErrNoSuchEntity
} else if header != serializationStateNormal {
return fmt.Errorf("goon: Unrecognized cache header: %v", header)
}
smd := deserializeStructMetaData(b[1:])
dataPos := 1 + smd.totalLength + len(smd.metaDatas)
sd := getSerializationDecoder(b[dataPos:])
defer freeSerializationDecoder(sd)
structHistory := make(map[string]map[string]bool, 8)
fieldMap, _ := getFieldInfoAndMetadata(t)
for _, metaData := range smd.metaDatas {
fieldName, slice, zeroValue := metaData, false, false
if metaData[0] == '$' {
fieldName, slice = metaData[1:], true
} else if metaData[0] == '!' {
fieldName, zeroValue = metaData[1:], true
}
nameParts := strings.Split(fieldName, ".")
fi, ok := fieldMap[fieldName]
if !ok {
return fmt.Errorf("goon: Could not find field %v", fieldName)
}
if err := deserializeStructInternal(sd.dec, fi, fieldName, nameParts, slice, zeroValue, structHistory, v, t); err != nil {
return err
}
}
return nil
}
// deserializeStructInternal is a helper function for deserializeStruct.
// It takes a gob decoder and struct field metadata, and then assigns the correct value to the specified struct field.
func deserializeStructInternal(dec *gob.Decoder, fi *fieldInfo, fieldName string, nameParts []string, slice, zeroValue bool, structHistory map[string]map[string]bool, v reflect.Value, t reflect.Type) error {
if len(fi.sliceIndex) > 0 {
v = v.FieldByIndex(fi.sliceIndex)
t = v.Type()
var sv reflect.Value
createNew := false
nameIdx := len(fi.sliceIndex)
absName, childName := strings.Join(nameParts[:nameIdx], "."), strings.Join(nameParts[nameIdx:], ".")
sh, ok := structHistory[absName]
if !ok || sh[childName] {
sh = make(map[string]bool, 8)
structHistory[absName] = sh
createNew = true
} else if len(sh) == 0 {
createNew = true
}
if createNew {
structType := t.Elem()
sv = reflect.New(structType).Elem()
v.Set(reflect.Append(v, sv))
}
sv = v.Index(v.Len() - 1)
sh[childName] = true
v = sv
t = v.Type()
}
vf := v.FieldByIndex(fi.fieldIndex)
if vf.Kind() == reflect.Slice && !slice {
elemType := vf.Type().Elem()
if elemType.Kind() == reflect.Uint8 {
if !zeroValue {
if err := dec.DecodeValue(vf); err != nil {
return fmt.Errorf("goon: Failed to decode field %v - %v", fieldName, err)
}
}
} else {
ev := reflect.New(elemType).Elem()
if !zeroValue {
if err := dec.DecodeValue(ev); err != nil {
return fmt.Errorf("goon: Failed to decode field %v - %v", fieldName, err)
}
}
vf.Set(reflect.Append(vf, ev))
}
} else if !zeroValue {
if err := dec.DecodeValue(vf); err != nil {
return fmt.Errorf("goon: Failed to decode field %v - %v", fieldName, err)
}
}
return nil
}
// getStructKey returns the key of the struct based in its reflected or
// specified kind and id. The second return parameter is true if src has a
// string id.
func (g *Goon) getStructKey(src interface{}) (key *datastore.Key, hasStringId bool, err error) {
v := reflect.Indirect(reflect.ValueOf(src))
t := v.Type()
k := t.Kind()
if k != reflect.Struct {
err = fmt.Errorf("goon: Expected struct, got instead: %v", k)
return
}
var parent *datastore.Key
var stringID string
var intID int64
var kind string
for i := 0; i < v.NumField(); i++ {
tf := t.Field(i)
vf := v.Field(i)
tag := tf.Tag.Get("goon")
tagValues := strings.Split(tag, ",")
if len(tagValues) > 0 {
tagValue := tagValues[0]
if tagValue == "id" {
switch vf.Kind() {
case reflect.Int64:
if intID != 0 || stringID != "" {
err = fmt.Errorf("goon: Only one field may be marked id")
return
}
intID = vf.Int()
case reflect.String:
if intID != 0 || stringID != "" {
err = fmt.Errorf("goon: Only one field may be marked id")
return
}
stringID = vf.String()
hasStringId = true
default:
err = fmt.Errorf("goon: ID field must be int64 or string in %v", t.Name())
return
}
} else if tagValue == "kind" {
if vf.Kind() == reflect.String {
if kind != "" {
err = fmt.Errorf("goon: Only one field may be marked kind")
return
}
kind = vf.String()
if kind == "" && len(tagValues) > 1 && tagValues[1] != "" {
kind = tagValues[1]
}
}
} else if tagValue == "parent" {
dskeyType := reflect.TypeOf(&datastore.Key{})
if vf.Type().ConvertibleTo(dskeyType) {
if parent != nil {
err = fmt.Errorf("goon: Only one field may be marked parent")
return
}
parent = vf.Convert(dskeyType).Interface().(*datastore.Key)
}
}
}
}
// if kind has not been manually set, fetch it from src's type
if kind == "" {
kind = g.KindNameResolver(src)
}
key = datastore.NewKey(g.Context, kind, stringID, intID, parent)
return
}
// DefaultKindName is the default implementation to determine the Kind
// an Entity has. Returns the basic Type of the src (no package name included).
func DefaultKindName(src interface{}) string {
v := reflect.ValueOf(src)
v = reflect.Indirect(v)
t := v.Type()
return t.Name()
}
func (g *Goon) setStructKey(src interface{}, key *datastore.Key) error {
v := reflect.ValueOf(src)
t := v.Type()
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("goon: Expected pointer to struct, got instead: %v", k)
}
v = reflect.Indirect(v)
t = v.Type()
k = t.Kind()
if k != reflect.Struct {
return fmt.Errorf(fmt.Sprintf("goon: Expected struct, got instead: %v", k))
}
idSet := false
kindSet := false
parentSet := false
for i := 0; i < v.NumField(); i++ {
tf := t.Field(i)
vf := v.Field(i)
if !vf.CanSet() {
continue
}
tag := tf.Tag.Get("goon")
tagValues := strings.Split(tag, ",")
if len(tagValues) > 0 {
tagValue := tagValues[0]
if tagValue == "id" {
if idSet {
return fmt.Errorf("goon: Only one field may be marked id")
}
switch vf.Kind() {
case reflect.Int64:
vf.SetInt(key.IntID())
idSet = true
case reflect.String:
vf.SetString(key.StringID())
idSet = true
}
} else if tagValue == "kind" {
if kindSet {
return fmt.Errorf("goon: Only one field may be marked kind")
}
if vf.Kind() == reflect.String {
if (len(tagValues) <= 1 || key.Kind() != tagValues[1]) && g.KindNameResolver(src) != key.Kind() {
vf.Set(reflect.ValueOf(key.Kind()))
}
kindSet = true
}
} else if tagValue == "parent" {
if parentSet {
return fmt.Errorf("goon: Only one field may be marked parent")
}
dskeyType := reflect.TypeOf(&datastore.Key{})
vfType := vf.Type()
if vfType.ConvertibleTo(dskeyType) {
vf.Set(reflect.ValueOf(key.Parent()).Convert(vfType))
parentSet = true
}
}
}
}
if !idSet {
return fmt.Errorf("goon: Could not set id field")
}
return nil
}