github.com/mjibson/goon@v1.1.0/entity.go (about)

     1  /*
     2   * Copyright (c) 2012 The Goon Authors
     3   *
     4   * Permission to use, copy, modify, and distribute this software for any
     5   * purpose with or without fee is hereby granted, provided that the above
     6   * copyright notice and this permission notice appear in all copies.
     7   *
     8   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     9   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    10   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    11   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    12   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    13   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    14   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    15   */
    16  
    17  package goon
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"fmt"
    23  	"math"
    24  	"reflect"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"google.golang.org/appengine"
    30  	"google.golang.org/appengine/datastore"
    31  )
    32  
    33  // KindNameResolver takes an Entity and returns what the Kind should be for
    34  // Datastore.
    35  type KindNameResolver func(src interface{}) string
    36  
    37  // ### Entity serialization ###
    38  
    39  const serializationFormatVersion = 5 // Increase this whenever the format changes
    40  
    41  // The entities are encoded to bytes with little endian ordering, as follows:
    42  //
    43  // [header][?prop1][..][?propN]
    44  //
    45  // header   | uint32 | Always present
    46  //   The first 30 bits are used for propCount. The last 2 bits for flags.
    47  //
    48  // propX    | []byte | X <= propCount
    49  //   Each property is serialized separately.
    50  //
    51  //
    52  // A property gets serialized into bytes with little endian ordering as follows:
    53  //
    54  // [nameLen][?name][header][?valueLen][?value]
    55  //
    56  // nameLen  | uint16 | Always present
    57  //   The length of the property name.
    58  //
    59  // name     | string | nameLen > 0
    60  //   The property name.
    61  //
    62  // header   | byte   | Always present
    63  //   The first 4 bits specify the property value type (propType..), the last 4 bits are used for flags.
    64  //
    65  // valueLen | uint24 | type == (string|BlobKey|ByteString|[]byte|*Key) && value != zeroValue
    66  //   The length of the value bytes.
    67  //
    68  // value    | []byte | type != (none|bool) && value != zeroValue
    69  //
    70  //    None         N/A
    71  //    Int64        8 bytes
    72  //    Bool         N/A (value == propHasValue header flag)
    73  //    String       valueLen bytes
    74  //    Float64      8 bytes
    75  //    ByteString   valueLen bytes
    76  //    KeyPtr       valueLen bytes
    77  //    Time         8 bytes
    78  //    BlobKey      valueLen bytes
    79  //    GeoPoint     16 bytes, Lat+Lng float64
    80  //    ByteSlice    valueLen bytes
    81  
    82  // Entity header flags
    83  const (
    84  	entityExists = 1 << 30
    85  	//entityRESERVED = 1 << 31 // Unused flag
    86  )
    87  
    88  const entityHeaderMaskPropCount = 1<<30 - 1              // All the bits used for propCount
    89  const entityHeaderMaskFlags = ^entityHeaderMaskPropCount // All the bits used for flags
    90  
    91  func serializeEntityHeader(propCount, flags int) uint32 {
    92  	return uint32((flags & entityHeaderMaskFlags) | (propCount & entityHeaderMaskPropCount))
    93  }
    94  
    95  func deserializeEntityHeader(header uint32) (propCount, flags int) {
    96  	return int(header) & entityHeaderMaskPropCount, int(header) & entityHeaderMaskFlags
    97  }
    98  
    99  // The valid datastore.Property.Value types are:
   100  //    - int64
   101  //    - bool
   102  //    - string
   103  //    - float64
   104  //    - datastore.ByteString
   105  //    - *datastore.Key
   106  //    - time.Time
   107  //    - appengine.BlobKey
   108  //    - appengine.GeoPoint
   109  //    - []byte (up to 1 megabyte in length)
   110  //    - *Entity (representing a nested struct)
   111  const (
   112  	propTypeNone       = 0
   113  	propTypeInt64      = 1
   114  	propTypeBool       = 2
   115  	propTypeString     = 3
   116  	propTypeFloat64    = 4
   117  	propTypeByteString = 5
   118  	propTypeKeyPtr     = 6
   119  	propTypeTime       = 7
   120  	propTypeBlobKey    = 8
   121  	propTypeGeoPoint   = 9
   122  	propTypeByteSlice  = 10
   123  	//propTypeEntityPtr  = 11 // TODO: Implement this?
   124  	// Space for 4 more types, as the propType value must fit in 4 bits (0-15)
   125  )
   126  
   127  // Property header flags
   128  const (
   129  	propHasValue = 1 << 4
   130  	propMultiple = 1 << 5
   131  	propNoIndex  = 1 << 6
   132  	//propRESERVED = 1 << 7 // Unused flag
   133  )
   134  
   135  // We limit the maximum length of datastore.Property.Name,
   136  // however this is our implementation specific and not datastore specific.
   137  const propMaxNameLength = 1<<16 - 1
   138  
   139  // Keep a pool of buffers around for more efficient allocation
   140  var bufferPool = sync.Pool{
   141  	New: func() interface{} {
   142  		return bytes.NewBuffer(make([]byte, 0, 16384)) // 16 KiB initial capacity
   143  	},
   144  }
   145  
   146  // getBuffer returns a reusable buffer from a pool.
   147  // Every buffer acquired with this function must be later freed via freeBuffer.
   148  func getBuffer() *bytes.Buffer {
   149  	return bufferPool.Get().(*bytes.Buffer)
   150  }
   151  
   152  // freeBuffer returns the buffer to the pool, allowing for reuse.
   153  func freeBuffer(buf *bytes.Buffer) {
   154  	buf.Reset()
   155  	bufferPool.Put(buf)
   156  }
   157  
   158  func writeInt16(buf *bytes.Buffer, i int) {
   159  	buf.WriteByte(byte(i))
   160  	buf.WriteByte(byte(i >> 8))
   161  }
   162  
   163  func writeInt24(buf *bytes.Buffer, i int) {
   164  	buf.WriteByte(byte(i))
   165  	buf.WriteByte(byte(i >> 8))
   166  	buf.WriteByte(byte(i >> 16))
   167  }
   168  
   169  func toUnixMicro(t time.Time) int64 {
   170  	// We cannot use t.UnixNano() / 1e3 because we want to handle times more than
   171  	// 2^63 nanoseconds (which is about 292 years) away from 1970, and those cannot
   172  	// be represented in the numerator of a single int64 divide.
   173  	return t.Unix()*1e6 + int64(t.Nanosecond()/1e3)
   174  }
   175  
   176  func fromUnixMicro(t int64) time.Time {
   177  	return time.Unix(t/1e6, (t%1e6)*1e3).UTC()
   178  }
   179  
   180  func isIndexValue(p *datastore.Property) bool {
   181  	v := reflect.ValueOf(p.Value)
   182  	return v.Type().String() == "datastore.indexValue"
   183  }
   184  
   185  func serializeProperty(buf *bytes.Buffer, p *datastore.Property) error {
   186  	nameLen := len(p.Name)
   187  	if nameLen > propMaxNameLength {
   188  		return fmt.Errorf("Maximum property name length is %d, but received %d", propMaxNameLength, nameLen)
   189  	}
   190  	writeInt16(buf, nameLen)
   191  	if nameLen > 0 {
   192  		buf.WriteString(p.Name)
   193  	}
   194  
   195  	var header byte
   196  	if p.Multiple {
   197  		header |= propMultiple
   198  	}
   199  	if p.NoIndex {
   200  		header |= propNoIndex
   201  	}
   202  
   203  	v := reflect.ValueOf(p.Value)
   204  	unsupported := false
   205  
   206  	switch v.Kind() {
   207  	case reflect.Invalid:
   208  		// Has no type and no value, but is legal
   209  		buf.WriteByte(header)
   210  	case reflect.Int64:
   211  		header |= propTypeInt64
   212  		val := uint64(v.Int())
   213  		if val == 0 {
   214  			buf.WriteByte(header)
   215  		} else {
   216  			header |= propHasValue
   217  			buf.WriteByte(header)
   218  			data := make([]byte, 8)
   219  			binary.LittleEndian.PutUint64(data, val)
   220  			buf.Write(data)
   221  		}
   222  	case reflect.Bool:
   223  		header |= propTypeBool
   224  		if v.Bool() {
   225  			header |= propHasValue
   226  		}
   227  		buf.WriteByte(header)
   228  	case reflect.String:
   229  		switch v.Interface().(type) {
   230  		case appengine.BlobKey:
   231  			header |= propTypeBlobKey
   232  		default:
   233  			header |= propTypeString
   234  		}
   235  		val := v.String()
   236  		if valLen := len(val); valLen == 0 {
   237  			buf.WriteByte(header)
   238  		} else {
   239  			header |= propHasValue
   240  			buf.WriteByte(header)
   241  			writeInt24(buf, valLen)
   242  			buf.WriteString(val)
   243  		}
   244  	case reflect.Float64:
   245  		header |= propTypeFloat64
   246  		val := v.Float()
   247  		if val == 0 {
   248  			buf.WriteByte(header)
   249  		} else {
   250  			header |= propHasValue
   251  			buf.WriteByte(header)
   252  			data := make([]byte, 8)
   253  			binary.LittleEndian.PutUint64(data, math.Float64bits(val))
   254  			buf.Write(data)
   255  		}
   256  	case reflect.Ptr:
   257  		if k, ok := v.Interface().(*datastore.Key); ok {
   258  			header |= propTypeKeyPtr
   259  			if k == nil {
   260  				buf.WriteByte(header)
   261  			} else {
   262  				header |= propHasValue
   263  				buf.WriteByte(header)
   264  				val := k.Encode()
   265  				writeInt24(buf, len(val))
   266  				buf.WriteString(val)
   267  			}
   268  		} else {
   269  			unsupported = true
   270  		}
   271  	case reflect.Struct:
   272  		switch s := v.Interface().(type) {
   273  		case time.Time:
   274  			header |= propTypeTime
   275  			if s.IsZero() {
   276  				buf.WriteByte(header)
   277  			} else {
   278  				header |= propHasValue
   279  				buf.WriteByte(header)
   280  				data := make([]byte, 8)
   281  				binary.LittleEndian.PutUint64(data, uint64(toUnixMicro(s)))
   282  				buf.Write(data)
   283  			}
   284  		case appengine.GeoPoint:
   285  			header |= propTypeGeoPoint
   286  			if s.Lat == 0 && s.Lng == 0 {
   287  				buf.WriteByte(header)
   288  			} else {
   289  				header |= propHasValue
   290  				buf.WriteByte(header)
   291  				data := make([]byte, 16)
   292  				binary.LittleEndian.PutUint64(data[:8], math.Float64bits(s.Lat))
   293  				binary.LittleEndian.PutUint64(data[8:], math.Float64bits(s.Lng))
   294  				buf.Write(data)
   295  			}
   296  		default:
   297  			unsupported = true
   298  		}
   299  	case reflect.Slice:
   300  		switch b := v.Interface().(type) {
   301  		case datastore.ByteString:
   302  			header |= propTypeByteString
   303  			if bLen := len(b); bLen == 0 {
   304  				buf.WriteByte(header)
   305  			} else {
   306  				header |= propHasValue
   307  				buf.WriteByte(header)
   308  				writeInt24(buf, bLen)
   309  				buf.Write(b)
   310  			}
   311  		case []byte:
   312  			header |= propTypeByteSlice
   313  			if bLen := len(b); bLen == 0 {
   314  				buf.WriteByte(header)
   315  			} else {
   316  				header |= propHasValue
   317  				buf.WriteByte(header)
   318  				writeInt24(buf, bLen)
   319  				buf.Write(b)
   320  			}
   321  		default:
   322  			unsupported = true
   323  		}
   324  	default:
   325  		unsupported = true
   326  	}
   327  	if unsupported {
   328  		return fmt.Errorf("unsupported datastore.Property value type: " + v.Type().String())
   329  	}
   330  	return nil
   331  }
   332  
   333  func deserializeProperty(buf *bytes.Buffer, prop *datastore.Property) error {
   334  	next := func(n int) ([]byte, error) {
   335  		b := buf.Next(n)
   336  		if bLen := len(b); bLen != n {
   337  			return b, fmt.Errorf("Buffer EOF, expected %d bytes but got %v", n, bLen)
   338  		}
   339  		return b, nil
   340  	}
   341  	getSize := func() (int, error) {
   342  		sizeBytes, err := next(3)
   343  		if err != nil {
   344  			return 0, err
   345  		}
   346  		return int(sizeBytes[0]) | int(sizeBytes[1])<<8 | int(sizeBytes[2])<<16, nil
   347  	}
   348  
   349  	// Read the name length
   350  	nameLenBytes, err := next(2)
   351  	if err != nil {
   352  		return err
   353  	}
   354  	nameLen := int(nameLenBytes[0]) | int(nameLenBytes[1])<<8
   355  	// If thehre's a name, read it
   356  	if nameLen > 0 {
   357  		nameBytes, err := next(nameLen)
   358  		if err != nil {
   359  			return err
   360  		}
   361  		prop.Name = string(nameBytes)
   362  	}
   363  
   364  	// Read the header
   365  	header, err := buf.ReadByte()
   366  	if err != nil {
   367  		return err
   368  	}
   369  
   370  	// Apply the flags
   371  	prop.Multiple = (header&propMultiple != 0)
   372  	prop.NoIndex = (header&propNoIndex != 0)
   373  
   374  	// Determine the value
   375  	valueType := header & 0xF
   376  	zeroValue := header&propHasValue == 0
   377  	switch valueType {
   378  	case propTypeNone:
   379  		// nil interface, so nothing to do
   380  	case propTypeInt64:
   381  		if zeroValue {
   382  			prop.Value = int64(0)
   383  		} else {
   384  			valBytes, err := next(8)
   385  			if err != nil {
   386  				return err
   387  			}
   388  			prop.Value = int64(binary.LittleEndian.Uint64(valBytes))
   389  		}
   390  	case propTypeBool:
   391  		prop.Value = !zeroValue
   392  	case propTypeString:
   393  		if zeroValue {
   394  			prop.Value = ""
   395  		} else {
   396  			size, err := getSize()
   397  			if err != nil {
   398  				return err
   399  			}
   400  			valBytes, err := next(size)
   401  			if err != nil {
   402  				return err
   403  			}
   404  			prop.Value = string(valBytes)
   405  		}
   406  	case propTypeBlobKey:
   407  		if zeroValue {
   408  			prop.Value = appengine.BlobKey("")
   409  		} else {
   410  			size, err := getSize()
   411  			if err != nil {
   412  				return err
   413  			}
   414  			valBytes, err := next(size)
   415  			if err != nil {
   416  				return err
   417  			}
   418  			prop.Value = appengine.BlobKey(valBytes)
   419  		}
   420  	case propTypeFloat64:
   421  		if zeroValue {
   422  			prop.Value = float64(0)
   423  		} else {
   424  			valBytes, err := next(8)
   425  			if err != nil {
   426  				return err
   427  			}
   428  			prop.Value = math.Float64frombits(binary.LittleEndian.Uint64(valBytes))
   429  		}
   430  	case propTypeKeyPtr:
   431  		if zeroValue {
   432  			var key *datastore.Key
   433  			prop.Value = key
   434  		} else {
   435  			size, err := getSize()
   436  			if err != nil {
   437  				return err
   438  			}
   439  			valBytes, err := next(size)
   440  			if err != nil {
   441  				return err
   442  			}
   443  			prop.Value, err = datastore.DecodeKey(string(valBytes))
   444  			if err != nil {
   445  				return err
   446  			}
   447  		}
   448  	case propTypeTime:
   449  		if zeroValue {
   450  			prop.Value = time.Time{}
   451  		} else {
   452  			valBytes, err := next(8)
   453  			if err != nil {
   454  				return err
   455  			}
   456  			prop.Value = fromUnixMicro(int64(binary.LittleEndian.Uint64(valBytes)))
   457  		}
   458  	case propTypeGeoPoint:
   459  		if zeroValue {
   460  			prop.Value = appengine.GeoPoint{}
   461  		} else {
   462  			valBytes, err := next(16)
   463  			if err != nil {
   464  				return err
   465  			}
   466  			prop.Value = appengine.GeoPoint{
   467  				Lat: math.Float64frombits(binary.LittleEndian.Uint64(valBytes[:8])),
   468  				Lng: math.Float64frombits(binary.LittleEndian.Uint64(valBytes[8:])),
   469  			}
   470  		}
   471  	case propTypeByteString:
   472  		if zeroValue {
   473  			prop.Value = datastore.ByteString{}
   474  		} else {
   475  			size, err := getSize()
   476  			if err != nil {
   477  				return err
   478  			}
   479  			prop.Value = make(datastore.ByteString, size)
   480  			if _, err := buf.Read(prop.Value.(datastore.ByteString)); err != nil {
   481  				return err
   482  			}
   483  		}
   484  	case propTypeByteSlice:
   485  		if zeroValue {
   486  			prop.Value = []byte{}
   487  		} else {
   488  			size, err := getSize()
   489  			if err != nil {
   490  				return err
   491  			}
   492  			prop.Value = make([]byte, size)
   493  			if _, err := buf.Read(prop.Value.([]byte)); err != nil {
   494  				return err
   495  			}
   496  		}
   497  	default:
   498  		return fmt.Errorf("Unrecognized value type %d", valueType)
   499  	}
   500  
   501  	return nil
   502  }
   503  
   504  // serializeStruct takes a struct and serializes it to portable bytes.
   505  func serializeStruct(src interface{}) ([]byte, error) {
   506  	if src == nil {
   507  		return serializeProperties(nil, false)
   508  	}
   509  	if k := reflect.Indirect(reflect.ValueOf(src)).Type().Kind(); k != reflect.Struct {
   510  		return nil, fmt.Errorf("goon: Expected struct, got instead: %v", k)
   511  	}
   512  
   513  	var err error
   514  	var props []datastore.Property
   515  	if pls, ok := src.(datastore.PropertyLoadSaver); ok {
   516  		props, err = pls.Save()
   517  	} else {
   518  		props, err = datastore.SaveStruct(src)
   519  	}
   520  	if err != nil {
   521  		return nil, err
   522  	}
   523  
   524  	return serializeProperties(props, true)
   525  }
   526  
   527  // serializeProperties takes a slice of properties and serializes it to portable bytes.
   528  func serializeProperties(props []datastore.Property, exists bool) ([]byte, error) {
   529  	// NOTE: We use a separate exists bool to support nil-props for existing structs
   530  	if !exists {
   531  		return []byte{0, 0, 0, 0}, nil
   532  	}
   533  
   534  	buf := getBuffer()
   535  	defer freeBuffer(buf)
   536  
   537  	// Serialize the entity header
   538  	header := serializeEntityHeader(len(props), entityExists)
   539  	headerBytes := make([]byte, 4)
   540  	binary.LittleEndian.PutUint32(headerBytes, header)
   541  	buf.Write(headerBytes)
   542  
   543  	// Serialize the properties
   544  	for i := range props {
   545  		if err := serializeProperty(buf, &props[i]); err != nil {
   546  			return nil, err
   547  		}
   548  	}
   549  
   550  	output := make([]byte, buf.Len())
   551  	copy(output, buf.Bytes())
   552  	return output, nil
   553  }
   554  
   555  // deserializeStruct takes portable bytes b, generated by serializeStruct, and assigns correct values to struct dst.
   556  func deserializeStruct(dst interface{}, b []byte) error {
   557  	if len(b) == 0 {
   558  		return fmt.Errorf("goon: Expected some data to deserialize, got none.")
   559  	}
   560  	if k := reflect.Indirect(reflect.ValueOf(dst)).Type().Kind(); k != reflect.Struct {
   561  		return fmt.Errorf("goon: Expected struct, got instead: %v", k)
   562  	}
   563  
   564  	// Deserialize the header
   565  	header := binary.LittleEndian.Uint32(b[:4])
   566  	propCount, flags := deserializeEntityHeader(header)
   567  	if flags&entityExists == 0 {
   568  		return datastore.ErrNoSuchEntity
   569  	}
   570  
   571  	// Deserialize the properties
   572  	buf := bytes.NewBuffer(b[4:])
   573  	props := make([]datastore.Property, propCount)
   574  	for i := 0; i < propCount; i++ {
   575  		if err := deserializeProperty(buf, &props[i]); err != nil {
   576  			return err
   577  		}
   578  	}
   579  
   580  	return deserializeProperties(dst, props)
   581  }
   582  
   583  // deserializeProperties takes a slice of properties and assigns correct values to struct dst.
   584  func deserializeProperties(dst interface{}, props []datastore.Property) error {
   585  	if k := reflect.Indirect(reflect.ValueOf(dst)).Type().Kind(); k != reflect.Struct {
   586  		return fmt.Errorf("goon: Expected struct, got instead: %v", k)
   587  	}
   588  	if pls, ok := dst.(datastore.PropertyLoadSaver); ok {
   589  		return pls.Load(props)
   590  	}
   591  	return datastore.LoadStruct(dst, props)
   592  }
   593  
   594  // getStructKey returns the key of the struct based in its reflected or
   595  // specified kind and id. The second return parameter is true if src has a
   596  // string id.
   597  func (g *Goon) getStructKey(src interface{}) (key *datastore.Key, hasStringId bool, err error) {
   598  	v := reflect.Indirect(reflect.ValueOf(src))
   599  	t := v.Type()
   600  	k := t.Kind()
   601  
   602  	if k != reflect.Struct {
   603  		err = fmt.Errorf("goon: Expected struct, got instead: %v", k)
   604  		return
   605  	}
   606  
   607  	var parent *datastore.Key
   608  	var stringID string
   609  	var intID int64
   610  	var kind string
   611  
   612  	for i := 0; i < v.NumField(); i++ {
   613  		tf := t.Field(i)
   614  		vf := v.Field(i)
   615  
   616  		tag := tf.Tag.Get("goon")
   617  		tagValues := strings.Split(tag, ",")
   618  		if len(tagValues) > 0 {
   619  			tagValue := tagValues[0]
   620  			if tagValue == "id" {
   621  				switch vf.Kind() {
   622  				case reflect.Int64:
   623  					if intID != 0 || stringID != "" {
   624  						err = fmt.Errorf("goon: Only one field may be marked id")
   625  						return
   626  					}
   627  					intID = vf.Int()
   628  				case reflect.String:
   629  					if intID != 0 || stringID != "" {
   630  						err = fmt.Errorf("goon: Only one field may be marked id")
   631  						return
   632  					}
   633  					stringID = vf.String()
   634  					hasStringId = true
   635  				default:
   636  					err = fmt.Errorf("goon: ID field must be int64 or string in %v", t.Name())
   637  					return
   638  				}
   639  			} else if tagValue == "kind" {
   640  				if vf.Kind() == reflect.String {
   641  					if kind != "" {
   642  						err = fmt.Errorf("goon: Only one field may be marked kind")
   643  						return
   644  					}
   645  					kind = vf.String()
   646  					if kind == "" && len(tagValues) > 1 && tagValues[1] != "" {
   647  						kind = tagValues[1]
   648  					}
   649  				}
   650  			} else if tagValue == "parent" {
   651  				dskeyType := reflect.TypeOf(&datastore.Key{})
   652  				if vf.Type().ConvertibleTo(dskeyType) {
   653  					if parent != nil {
   654  						err = fmt.Errorf("goon: Only one field may be marked parent")
   655  						return
   656  					}
   657  					parent = vf.Convert(dskeyType).Interface().(*datastore.Key)
   658  				}
   659  			}
   660  		}
   661  	}
   662  
   663  	// if kind has not been manually set, fetch it from src's type
   664  	if kind == "" {
   665  		kind = g.KindNameResolver(src)
   666  	}
   667  	key = datastore.NewKey(g.Context, kind, stringID, intID, parent)
   668  	return
   669  }
   670  
   671  // DefaultKindName is the default implementation to determine the Kind
   672  // an Entity has. Returns the basic Type of the src (no package name included).
   673  func DefaultKindName(src interface{}) string {
   674  	v := reflect.ValueOf(src)
   675  	v = reflect.Indirect(v)
   676  	t := v.Type()
   677  	return t.Name()
   678  }
   679  
   680  func (g *Goon) setStructKey(src interface{}, key *datastore.Key) error {
   681  	v := reflect.ValueOf(src)
   682  	t := v.Type()
   683  	k := t.Kind()
   684  
   685  	if k != reflect.Ptr {
   686  		return fmt.Errorf("goon: Expected pointer to struct, got instead: %v", k)
   687  	}
   688  
   689  	v = reflect.Indirect(v)
   690  	t = v.Type()
   691  	k = t.Kind()
   692  
   693  	if k != reflect.Struct {
   694  		return fmt.Errorf(fmt.Sprintf("goon: Expected struct, got instead: %v", k))
   695  	}
   696  
   697  	idSet := false
   698  	kindSet := false
   699  	parentSet := false
   700  	for i := 0; i < v.NumField(); i++ {
   701  		tf := t.Field(i)
   702  		vf := v.Field(i)
   703  
   704  		if !vf.CanSet() {
   705  			continue
   706  		}
   707  
   708  		tag := tf.Tag.Get("goon")
   709  		tagValues := strings.Split(tag, ",")
   710  		if len(tagValues) > 0 {
   711  			tagValue := tagValues[0]
   712  			if tagValue == "id" {
   713  				if idSet {
   714  					return fmt.Errorf("goon: Only one field may be marked id")
   715  				}
   716  
   717  				switch vf.Kind() {
   718  				case reflect.Int64:
   719  					vf.SetInt(key.IntID())
   720  					idSet = true
   721  				case reflect.String:
   722  					vf.SetString(key.StringID())
   723  					idSet = true
   724  				}
   725  			} else if tagValue == "kind" {
   726  				if kindSet {
   727  					return fmt.Errorf("goon: Only one field may be marked kind")
   728  				}
   729  				if vf.Kind() == reflect.String {
   730  					if (len(tagValues) <= 1 || key.Kind() != tagValues[1]) && g.KindNameResolver(src) != key.Kind() {
   731  						vf.Set(reflect.ValueOf(key.Kind()))
   732  					}
   733  					kindSet = true
   734  				}
   735  			} else if tagValue == "parent" {
   736  				if parentSet {
   737  					return fmt.Errorf("goon: Only one field may be marked parent")
   738  				}
   739  				dskeyType := reflect.TypeOf(&datastore.Key{})
   740  				vfType := vf.Type()
   741  				if vfType.ConvertibleTo(dskeyType) {
   742  					vf.Set(reflect.ValueOf(key.Parent()).Convert(vfType))
   743  					parentSet = true
   744  				}
   745  			}
   746  		}
   747  	}
   748  
   749  	if !idSet {
   750  		return fmt.Errorf("goon: Could not set id field")
   751  	}
   752  
   753  	return nil
   754  }