| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |  | 
| 2 // Use of this source code is governed by a BSD-style license that can be |  | 
| 3 // found in the LICENSE file. |  | 
| 4 |  | 
| 5 // adapted from github.com/golang/appengine/datastore |  | 
| 6 |  | 
| 7 package helper |  | 
| 8 |  | 
| 9 import ( |  | 
| 10         "bytes" |  | 
| 11         "encoding/json" |  | 
| 12         "fmt" |  | 
| 13         "math" |  | 
| 14         "reflect" |  | 
| 15         "strconv" |  | 
| 16         "strings" |  | 
| 17         "testing" |  | 
| 18         "time" |  | 
| 19 |  | 
| 20         "github.com/luci/gae" |  | 
| 21         . "github.com/smartystreets/goconvey/convey" |  | 
| 22 ) |  | 
| 23 |  | 
| 24 var ( |  | 
| 25         mp   = gae.MkDSProperty |  | 
| 26         mpNI = gae.MkDSPropertyNI |  | 
| 27 ) |  | 
| 28 |  | 
| 29 const testAppID = "testApp" |  | 
| 30 |  | 
| 31 type ( |  | 
| 32         myBlob   []byte |  | 
| 33         myByte   byte |  | 
| 34         myString string |  | 
| 35 ) |  | 
| 36 |  | 
| 37 func makeMyByteSlice(n int) []myByte { |  | 
| 38         b := make([]myByte, n) |  | 
| 39         for i := range b { |  | 
| 40                 b[i] = myByte(i) |  | 
| 41         } |  | 
| 42         return b |  | 
| 43 } |  | 
| 44 |  | 
| 45 func makeInt8Slice(n int) []int8 { |  | 
| 46         b := make([]int8, n) |  | 
| 47         for i := range b { |  | 
| 48                 b[i] = int8(i) |  | 
| 49         } |  | 
| 50         return b |  | 
| 51 } |  | 
| 52 |  | 
| 53 func makeUint8Slice(n int) []uint8 { |  | 
| 54         b := make([]uint8, n) |  | 
| 55         for i := range b { |  | 
| 56                 b[i] = uint8(i) |  | 
| 57         } |  | 
| 58         return b |  | 
| 59 } |  | 
| 60 |  | 
| 61 var ( |  | 
| 62         testKey0     = mkKey("aid", "", "kind", "name0") |  | 
| 63         testKey1a    = mkKey("aid", "", "kind", "name1") |  | 
| 64         testKey1b    = mkKey("aid", "", "kind", "name1") |  | 
| 65         testKey2a    = mkKey("aid", "", "kind", "name0", "kind", "name2") |  | 
| 66         testKey2b    = mkKey("aid", "", "kind", "name0", "kind", "name2") |  | 
| 67         testGeoPt0   = gae.DSGeoPoint{Lat: 1.2, Lng: 3.4} |  | 
| 68         testGeoPt1   = gae.DSGeoPoint{Lat: 5, Lng: 10} |  | 
| 69         testBadGeoPt = gae.DSGeoPoint{Lat: 1000, Lng: 34} |  | 
| 70 ) |  | 
| 71 |  | 
| 72 type B0 struct { |  | 
| 73         B []byte |  | 
| 74 } |  | 
| 75 |  | 
| 76 type B1 struct { |  | 
| 77         B []int8 |  | 
| 78 } |  | 
| 79 |  | 
| 80 type B2 struct { |  | 
| 81         B myBlob |  | 
| 82 } |  | 
| 83 |  | 
| 84 type B3 struct { |  | 
| 85         B []myByte |  | 
| 86 } |  | 
| 87 |  | 
| 88 type B4 struct { |  | 
| 89         B [][]byte |  | 
| 90 } |  | 
| 91 |  | 
| 92 type B5 struct { |  | 
| 93         B gae.DSByteString |  | 
| 94 } |  | 
| 95 |  | 
| 96 type C0 struct { |  | 
| 97         I int |  | 
| 98         C chan int |  | 
| 99 } |  | 
| 100 |  | 
| 101 type C1 struct { |  | 
| 102         I int |  | 
| 103         C *chan int |  | 
| 104 } |  | 
| 105 |  | 
| 106 type C2 struct { |  | 
| 107         I int |  | 
| 108         C []chan int |  | 
| 109 } |  | 
| 110 |  | 
| 111 type C3 struct { |  | 
| 112         C string |  | 
| 113 } |  | 
| 114 |  | 
| 115 type E struct{} |  | 
| 116 |  | 
| 117 type G0 struct { |  | 
| 118         G gae.DSGeoPoint |  | 
| 119 } |  | 
| 120 |  | 
| 121 type G1 struct { |  | 
| 122         G []gae.DSGeoPoint |  | 
| 123 } |  | 
| 124 |  | 
| 125 type K0 struct { |  | 
| 126         K gae.DSKey |  | 
| 127 } |  | 
| 128 |  | 
| 129 type K1 struct { |  | 
| 130         K []gae.DSKey |  | 
| 131 } |  | 
| 132 |  | 
| 133 type N0 struct { |  | 
| 134         X0 |  | 
| 135         ID       int64  `gae:"$id"` |  | 
| 136         _kind    string `gae:"$kind,whatnow"` |  | 
| 137         Nonymous X0 |  | 
| 138         Ignore   string `gae:"-"` |  | 
| 139         Other    string |  | 
| 140 } |  | 
| 141 |  | 
| 142 type N1 struct { |  | 
| 143         X0 |  | 
| 144         Nonymous []X0 |  | 
| 145         Ignore   string `gae:"-"` |  | 
| 146         Other    string |  | 
| 147 } |  | 
| 148 |  | 
| 149 type N2 struct { |  | 
| 150         N1    `gae:"red"` |  | 
| 151         Green N1 `gae:"green"` |  | 
| 152         Blue  N1 |  | 
| 153         White N1 `gae:"-"` |  | 
| 154 } |  | 
| 155 |  | 
| 156 type O0 struct { |  | 
| 157         I int64 |  | 
| 158 } |  | 
| 159 |  | 
| 160 type O1 struct { |  | 
| 161         I int32 |  | 
| 162 } |  | 
| 163 |  | 
| 164 type U0 struct { |  | 
| 165         U uint |  | 
| 166 } |  | 
| 167 |  | 
| 168 type U1 struct { |  | 
| 169         U string |  | 
| 170 } |  | 
| 171 |  | 
| 172 type T struct { |  | 
| 173         T time.Time |  | 
| 174 } |  | 
| 175 |  | 
| 176 type X0 struct { |  | 
| 177         S string |  | 
| 178         I int |  | 
| 179         i int |  | 
| 180 } |  | 
| 181 |  | 
| 182 type X1 struct { |  | 
| 183         S myString |  | 
| 184         I int32 |  | 
| 185         J int64 |  | 
| 186 } |  | 
| 187 |  | 
| 188 type X2 struct { |  | 
| 189         Z string |  | 
| 190         i int |  | 
| 191 } |  | 
| 192 |  | 
| 193 type X3 struct { |  | 
| 194         S bool |  | 
| 195         I int |  | 
| 196 } |  | 
| 197 |  | 
| 198 type Y0 struct { |  | 
| 199         B bool |  | 
| 200         F []float64 |  | 
| 201         G []float64 |  | 
| 202 } |  | 
| 203 |  | 
| 204 type Y1 struct { |  | 
| 205         B bool |  | 
| 206         F float64 |  | 
| 207 } |  | 
| 208 |  | 
| 209 type Y2 struct { |  | 
| 210         B bool |  | 
| 211         F []int64 |  | 
| 212 } |  | 
| 213 |  | 
| 214 type Y3 struct { |  | 
| 215         B bool |  | 
| 216         F int64 |  | 
| 217 } |  | 
| 218 |  | 
| 219 type Tagged struct { |  | 
| 220         A int   `gae:"a,noindex"` |  | 
| 221         B []int `gae:"b1"` |  | 
| 222         C int   `gae:",noindex"` |  | 
| 223         D int   `gae:""` |  | 
| 224         E int |  | 
| 225         I int `gae:"-"` |  | 
| 226         J int `gae:",noindex" json:"j"` |  | 
| 227 |  | 
| 228         Y0 `gae:"-"` |  | 
| 229         Z  chan int `gae:"-,"` |  | 
| 230 } |  | 
| 231 |  | 
| 232 type InvalidTagged1 struct { |  | 
| 233         I int `gae:"\t"` |  | 
| 234 } |  | 
| 235 |  | 
| 236 type InvalidTagged2 struct { |  | 
| 237         I int |  | 
| 238         J int `gae:"I"` |  | 
| 239 } |  | 
| 240 |  | 
| 241 type InvalidTagged3 struct { |  | 
| 242         I int `gae:"a\t"` |  | 
| 243 } |  | 
| 244 |  | 
| 245 type InvalidTagged4 struct { |  | 
| 246         I int `gae:"a."` |  | 
| 247 } |  | 
| 248 |  | 
| 249 type InvalidTaggedSub struct { |  | 
| 250         I int |  | 
| 251 } |  | 
| 252 |  | 
| 253 type InvalidTagged5 struct { |  | 
| 254         I int `gae:"V.I"` |  | 
| 255         V []InvalidTaggedSub |  | 
| 256 } |  | 
| 257 |  | 
| 258 type Inner1 struct { |  | 
| 259         W int32 |  | 
| 260         X string |  | 
| 261 } |  | 
| 262 |  | 
| 263 type Inner2 struct { |  | 
| 264         Y float64 |  | 
| 265 } |  | 
| 266 |  | 
| 267 type Inner3 struct { |  | 
| 268         Z bool |  | 
| 269 } |  | 
| 270 |  | 
| 271 type Outer struct { |  | 
| 272         A int16 |  | 
| 273         I []Inner1 |  | 
| 274         J Inner2 |  | 
| 275         Inner3 |  | 
| 276 } |  | 
| 277 |  | 
| 278 type OuterEquivalent struct { |  | 
| 279         A     int16 |  | 
| 280         IDotW []int32  `gae:"I.W"` |  | 
| 281         IDotX []string `gae:"I.X"` |  | 
| 282         JDotY float64  `gae:"J.Y"` |  | 
| 283         Z     bool |  | 
| 284 } |  | 
| 285 |  | 
| 286 type Dotted struct { |  | 
| 287         A DottedA `gae:"A0.A1.A2"` |  | 
| 288 } |  | 
| 289 |  | 
| 290 type DottedA struct { |  | 
| 291         B DottedB `gae:"B3"` |  | 
| 292 } |  | 
| 293 |  | 
| 294 type DottedB struct { |  | 
| 295         C int `gae:"C4.C5"` |  | 
| 296 } |  | 
| 297 |  | 
| 298 type SliceOfSlices struct { |  | 
| 299         I int |  | 
| 300         S []struct { |  | 
| 301                 J int |  | 
| 302                 F []float64 |  | 
| 303         } |  | 
| 304 } |  | 
| 305 |  | 
| 306 type Recursive struct { |  | 
| 307         I int |  | 
| 308         R []Recursive |  | 
| 309 } |  | 
| 310 |  | 
| 311 type MutuallyRecursive0 struct { |  | 
| 312         I int |  | 
| 313         R []MutuallyRecursive1 |  | 
| 314 } |  | 
| 315 |  | 
| 316 type MutuallyRecursive1 struct { |  | 
| 317         I int |  | 
| 318         R []MutuallyRecursive0 |  | 
| 319 } |  | 
| 320 |  | 
| 321 type ExoticTypes struct { |  | 
| 322         BS   gae.BSKey |  | 
| 323         DSBS gae.DSByteString |  | 
| 324 } |  | 
| 325 |  | 
| 326 type Underspecified struct { |  | 
| 327         Iface gae.DSPropertyConverter |  | 
| 328 } |  | 
| 329 |  | 
| 330 type MismatchTypes struct { |  | 
| 331         S  string |  | 
| 332         B  bool |  | 
| 333         F  float32 |  | 
| 334         K  gae.DSKey |  | 
| 335         T  time.Time |  | 
| 336         G  gae.DSGeoPoint |  | 
| 337         IS []int |  | 
| 338 } |  | 
| 339 |  | 
| 340 type BadSpecial struct { |  | 
| 341         ID int64  `gae:"$id"` |  | 
| 342         id string `gae:"$id"` |  | 
| 343 } |  | 
| 344 |  | 
| 345 type Doubler struct { |  | 
| 346         S string |  | 
| 347         I int64 |  | 
| 348         B bool |  | 
| 349 } |  | 
| 350 |  | 
| 351 func (d *Doubler) Load(props gae.DSPropertyMap) error { |  | 
| 352         return GetPLS(d).Load(props) |  | 
| 353 } |  | 
| 354 |  | 
| 355 func (d *Doubler) Save(withMeta bool) (gae.DSPropertyMap, error) { |  | 
| 356         pls := GetPLS(d) |  | 
| 357         propMap, err := pls.Save(withMeta) |  | 
| 358         if err != nil { |  | 
| 359                 return nil, err |  | 
| 360         } |  | 
| 361 |  | 
| 362         // Edit that map and send it on. |  | 
| 363         for _, props := range propMap { |  | 
| 364                 for i := range props { |  | 
| 365                         switch v := props[i].Value().(type) { |  | 
| 366                         case string: |  | 
| 367                                 // + means string concatenation. |  | 
| 368                                 props[i].SetValue(v+v, props[i].IndexSetting()) |  | 
| 369                         case int64: |  | 
| 370                                 // + means integer addition. |  | 
| 371                                 props[i].SetValue(v+v, props[i].IndexSetting()) |  | 
| 372                         } |  | 
| 373                 } |  | 
| 374         } |  | 
| 375         return propMap, nil |  | 
| 376 } |  | 
| 377 |  | 
| 378 func (d *Doubler) GetMeta(string) (interface{}, error) { return nil, gae.ErrDSMe
      taFieldUnset } |  | 
| 379 func (d *Doubler) SetMeta(string, interface{}) error   { return gae.ErrDSMetaFie
      ldUnset } |  | 
| 380 func (d *Doubler) Problem() error                      { return nil } |  | 
| 381 |  | 
| 382 var _ gae.DSPropertyLoadSaver = (*Doubler)(nil) |  | 
| 383 |  | 
| 384 type Deriver struct { |  | 
| 385         S, Derived, Ignored string |  | 
| 386 } |  | 
| 387 |  | 
| 388 func (d *Deriver) Load(props gae.DSPropertyMap) error { |  | 
| 389         for name, p := range props { |  | 
| 390                 if name != "S" { |  | 
| 391                         continue |  | 
| 392                 } |  | 
| 393                 d.S = p[0].Value().(string) |  | 
| 394                 d.Derived = "derived+" + d.S |  | 
| 395         } |  | 
| 396         return nil |  | 
| 397 } |  | 
| 398 |  | 
| 399 func (d *Deriver) Save(withMeta bool) (gae.DSPropertyMap, error) { |  | 
| 400         return map[string][]gae.DSProperty{ |  | 
| 401                 "S": {mp(d.S)}, |  | 
| 402         }, nil |  | 
| 403 } |  | 
| 404 |  | 
| 405 func (d *Deriver) GetMeta(string) (interface{}, error) { return nil, gae.ErrDSMe
      taFieldUnset } |  | 
| 406 func (d *Deriver) SetMeta(string, interface{}) error   { return gae.ErrDSMetaFie
      ldUnset } |  | 
| 407 func (d *Deriver) Problem() error                      { return nil } |  | 
| 408 |  | 
| 409 var _ gae.DSPropertyLoadSaver = (*Deriver)(nil) |  | 
| 410 |  | 
| 411 type BK struct { |  | 
| 412         Key gae.BSKey |  | 
| 413 } |  | 
| 414 |  | 
| 415 type Convertable []int64 |  | 
| 416 |  | 
| 417 var _ gae.DSPropertyConverter = (*Convertable)(nil) |  | 
| 418 |  | 
| 419 func (c *Convertable) ToDSProperty() (ret gae.DSProperty, err error) { |  | 
| 420         buf := make([]string, len(*c)) |  | 
| 421         for i, v := range *c { |  | 
| 422                 buf[i] = strconv.FormatInt(v, 10) |  | 
| 423         } |  | 
| 424         err = ret.SetValue(strings.Join(buf, ","), gae.NoIndex) |  | 
| 425         return |  | 
| 426 } |  | 
| 427 |  | 
| 428 func (c *Convertable) FromDSProperty(pv gae.DSProperty) error { |  | 
| 429         if sval, ok := pv.Value().(string); ok { |  | 
| 430                 for _, t := range strings.Split(sval, ",") { |  | 
| 431                         ival, err := strconv.ParseInt(t, 10, 64) |  | 
| 432                         if err != nil { |  | 
| 433                                 return err |  | 
| 434                         } |  | 
| 435                         *c = append(*c, ival) |  | 
| 436                 } |  | 
| 437                 return nil |  | 
| 438         } |  | 
| 439         return fmt.Errorf("nope") |  | 
| 440 } |  | 
| 441 |  | 
| 442 type Impossible struct { |  | 
| 443         Nested []ImpossibleInner |  | 
| 444 } |  | 
| 445 |  | 
| 446 type ImpossibleInner struct { |  | 
| 447         Ints Convertable `gae:"wot"` |  | 
| 448 } |  | 
| 449 |  | 
| 450 type Convertable2 struct { |  | 
| 451         Data     string |  | 
| 452         Exploded []string |  | 
| 453 } |  | 
| 454 |  | 
| 455 func (c *Convertable2) ToDSProperty() (ret gae.DSProperty, err error) { |  | 
| 456         err = ret.SetValue(c.Data, gae.ShouldIndex) |  | 
| 457         return |  | 
| 458 } |  | 
| 459 |  | 
| 460 func (c *Convertable2) FromDSProperty(pv gae.DSProperty) error { |  | 
| 461         if sval, ok := pv.Value().(string); ok { |  | 
| 462                 c.Data = sval |  | 
| 463                 c.Exploded = []string{"turn", "down", "for", "what"} |  | 
| 464                 return nil |  | 
| 465         } |  | 
| 466         return fmt.Errorf("nope") |  | 
| 467 } |  | 
| 468 |  | 
| 469 type Impossible2 struct { |  | 
| 470         Nested []ImpossibleInner2 |  | 
| 471 } |  | 
| 472 |  | 
| 473 type ImpossibleInner2 struct { |  | 
| 474         Thingy Convertable2 `gae:"nerb"` |  | 
| 475 } |  | 
| 476 |  | 
| 477 type JSONKVProp map[string]interface{} |  | 
| 478 |  | 
| 479 var _ gae.DSPropertyConverter = (*JSONKVProp)(nil) |  | 
| 480 |  | 
| 481 func (j *JSONKVProp) ToDSProperty() (ret gae.DSProperty, err error) { |  | 
| 482         data, err := json.Marshal(map[string]interface{}(*j)) |  | 
| 483         if err != nil { |  | 
| 484                 return |  | 
| 485         } |  | 
| 486         err = ret.SetValue(data, gae.NoIndex) |  | 
| 487         return |  | 
| 488 } |  | 
| 489 |  | 
| 490 func (j *JSONKVProp) FromDSProperty(pv gae.DSProperty) error { |  | 
| 491         if bval, ok := pv.Value().([]byte); ok { |  | 
| 492                 dec := json.NewDecoder(bytes.NewBuffer(bval)) |  | 
| 493                 dec.UseNumber() |  | 
| 494                 return dec.Decode((*map[string]interface{})(j)) |  | 
| 495         } |  | 
| 496         return fmt.Errorf("nope") |  | 
| 497 } |  | 
| 498 |  | 
| 499 type Impossible3 struct { |  | 
| 500         KMap JSONKVProp `gae:"kewelmap"` |  | 
| 501 } |  | 
| 502 |  | 
| 503 type Complex complex128 |  | 
| 504 |  | 
| 505 var _ gae.DSPropertyConverter = (*Complex)(nil) |  | 
| 506 |  | 
| 507 func (c *Complex) ToDSProperty() (ret gae.DSProperty, err error) { |  | 
| 508         // cheat hardkore and usurp GeoPoint so datastore will index these sucke
      rs |  | 
| 509         // (note that this won't REALLY work, since GeoPoints are limited to a v
      ery |  | 
| 510         // limited range of values, but it's nice to pretend ;)). You'd probably |  | 
| 511         // really end up with a packed binary representation. |  | 
| 512         err = ret.SetValue(gae.DSGeoPoint{Lat: real(*c), Lng: imag(*c)}, gae.Sho
      uldIndex) |  | 
| 513         return |  | 
| 514 } |  | 
| 515 |  | 
| 516 func (c *Complex) FromDSProperty(p gae.DSProperty) error { |  | 
| 517         if gval, ok := p.Value().(gae.DSGeoPoint); ok { |  | 
| 518                 *c = Complex(complex(gval.Lat, gval.Lng)) |  | 
| 519                 return nil |  | 
| 520         } |  | 
| 521         return fmt.Errorf("nope") |  | 
| 522 } |  | 
| 523 |  | 
| 524 type Impossible4 struct { |  | 
| 525         Values []Complex |  | 
| 526 } |  | 
| 527 |  | 
| 528 type DerivedKey struct { |  | 
| 529         K *GenericDSKey |  | 
| 530 } |  | 
| 531 |  | 
| 532 type IfaceKey struct { |  | 
| 533         K gae.DSKey |  | 
| 534 } |  | 
| 535 |  | 
| 536 type testCase struct { |  | 
| 537         desc          string |  | 
| 538         src           interface{} |  | 
| 539         want          interface{} |  | 
| 540         plsErr        string |  | 
| 541         saveErr       string |  | 
| 542         actualNoIndex bool |  | 
| 543         plsLoadErr    string |  | 
| 544         loadErr       string |  | 
| 545 } |  | 
| 546 |  | 
| 547 var testCases = []testCase{ |  | 
| 548         { |  | 
| 549                 desc:   "chan save fails", |  | 
| 550                 src:    &C0{I: -1}, |  | 
| 551                 plsErr: `field "C" has invalid type: chan int`, |  | 
| 552         }, |  | 
| 553         { |  | 
| 554                 desc:   "*chan save fails", |  | 
| 555                 src:    &C1{I: -1}, |  | 
| 556                 plsErr: `field "C" has invalid type: *chan int`, |  | 
| 557         }, |  | 
| 558         { |  | 
| 559                 desc:   "[]chan save fails", |  | 
| 560                 src:    &C2{I: -1, C: make([]chan int, 8)}, |  | 
| 561                 plsErr: `field "C" has invalid type: []chan int`, |  | 
| 562         }, |  | 
| 563         { |  | 
| 564                 desc:       "chan load fails", |  | 
| 565                 src:        &C3{C: "not a chan"}, |  | 
| 566                 want:       &C0{}, |  | 
| 567                 plsLoadErr: `field "C" has invalid type: chan int`, |  | 
| 568         }, |  | 
| 569         { |  | 
| 570                 desc:       "*chan load fails", |  | 
| 571                 src:        &C3{C: "not a *chan"}, |  | 
| 572                 want:       &C1{}, |  | 
| 573                 plsLoadErr: `field "C" has invalid type: *chan int`, |  | 
| 574         }, |  | 
| 575         { |  | 
| 576                 desc:       "[]chan load fails", |  | 
| 577                 src:        &C3{C: "not a []chan"}, |  | 
| 578                 want:       &C2{}, |  | 
| 579                 plsLoadErr: `field "C" has invalid type: []chan int`, |  | 
| 580         }, |  | 
| 581         { |  | 
| 582                 desc: "empty struct", |  | 
| 583                 src:  &E{}, |  | 
| 584                 want: &E{}, |  | 
| 585         }, |  | 
| 586         { |  | 
| 587                 desc: "geopoint", |  | 
| 588                 src:  &G0{G: testGeoPt0}, |  | 
| 589                 want: &G0{G: testGeoPt0}, |  | 
| 590         }, |  | 
| 591         { |  | 
| 592                 desc:    "geopoint invalid", |  | 
| 593                 src:     &G0{G: testBadGeoPt}, |  | 
| 594                 saveErr: "invalid GeoPoint value", |  | 
| 595         }, |  | 
| 596         { |  | 
| 597                 desc: "geopoint as props", |  | 
| 598                 src:  &G0{G: testGeoPt0}, |  | 
| 599                 want: gae.DSPropertyMap{ |  | 
| 600                         "G": {mp(testGeoPt0)}, |  | 
| 601                 }, |  | 
| 602         }, |  | 
| 603         { |  | 
| 604                 desc: "geopoint slice", |  | 
| 605                 src:  &G1{G: []gae.DSGeoPoint{testGeoPt0, testGeoPt1}}, |  | 
| 606                 want: &G1{G: []gae.DSGeoPoint{testGeoPt0, testGeoPt1}}, |  | 
| 607         }, |  | 
| 608         { |  | 
| 609                 desc: "key", |  | 
| 610                 src:  &K0{K: testKey1a}, |  | 
| 611                 want: &K0{K: testKey1b}, |  | 
| 612         }, |  | 
| 613         { |  | 
| 614                 desc: "key with parent", |  | 
| 615                 src:  &K0{K: testKey2a}, |  | 
| 616                 want: &K0{K: testKey2b}, |  | 
| 617         }, |  | 
| 618         { |  | 
| 619                 desc: "nil key", |  | 
| 620                 src:  &K0{}, |  | 
| 621                 want: &K0{}, |  | 
| 622         }, |  | 
| 623         { |  | 
| 624                 desc: "all nil keys in slice", |  | 
| 625                 src:  &K1{[]gae.DSKey{nil, nil}}, |  | 
| 626                 want: &K1{[]gae.DSKey{nil, nil}}, |  | 
| 627         }, |  | 
| 628         { |  | 
| 629                 desc: "some nil keys in slice", |  | 
| 630                 src:  &K1{[]gae.DSKey{testKey1a, nil, testKey2a}}, |  | 
| 631                 want: &K1{[]gae.DSKey{testKey1b, nil, testKey2b}}, |  | 
| 632         }, |  | 
| 633         { |  | 
| 634                 desc:    "overflow", |  | 
| 635                 src:     &O0{I: 1 << 48}, |  | 
| 636                 want:    &O1{}, |  | 
| 637                 loadErr: "overflow", |  | 
| 638         }, |  | 
| 639         { |  | 
| 640                 desc: "time", |  | 
| 641                 src:  &T{T: time.Unix(1e9, 0).UTC()}, |  | 
| 642                 want: &T{T: time.Unix(1e9, 0).UTC()}, |  | 
| 643         }, |  | 
| 644         { |  | 
| 645                 desc: "time as props", |  | 
| 646                 src:  &T{T: time.Unix(1e9, 0).UTC()}, |  | 
| 647                 want: gae.DSPropertyMap{ |  | 
| 648                         "T": {mp(time.Unix(1e9, 0).UTC())}, |  | 
| 649                 }, |  | 
| 650         }, |  | 
| 651         { |  | 
| 652                 desc:   "uint save", |  | 
| 653                 src:    &U0{U: 1}, |  | 
| 654                 plsErr: `field "U" has invalid type: uint`, |  | 
| 655         }, |  | 
| 656         { |  | 
| 657                 desc:       "uint load", |  | 
| 658                 src:        &U1{U: "not a uint"}, |  | 
| 659                 want:       &U0{}, |  | 
| 660                 plsLoadErr: `field "U" has invalid type: uint`, |  | 
| 661         }, |  | 
| 662         { |  | 
| 663                 desc: "zero", |  | 
| 664                 src:  &X0{}, |  | 
| 665                 want: &X0{}, |  | 
| 666         }, |  | 
| 667         { |  | 
| 668                 desc: "basic", |  | 
| 669                 src:  &X0{S: "one", I: 2, i: 3}, |  | 
| 670                 want: &X0{S: "one", I: 2}, |  | 
| 671         }, |  | 
| 672         { |  | 
| 673                 desc: "save string/int load myString/int32", |  | 
| 674                 src:  &X0{S: "one", I: 2, i: 3}, |  | 
| 675                 want: &X1{S: "one", I: 2}, |  | 
| 676         }, |  | 
| 677         { |  | 
| 678                 desc:    "missing fields", |  | 
| 679                 src:     &X0{S: "one", I: 2, i: 3}, |  | 
| 680                 want:    &X2{}, |  | 
| 681                 loadErr: "no such struct field", |  | 
| 682         }, |  | 
| 683         { |  | 
| 684                 desc:    "save string load bool", |  | 
| 685                 src:     &X0{S: "one", I: 2, i: 3}, |  | 
| 686                 want:    &X3{I: 2}, |  | 
| 687                 loadErr: "type mismatch", |  | 
| 688         }, |  | 
| 689         { |  | 
| 690                 desc: "basic slice", |  | 
| 691                 src:  &Y0{B: true, F: []float64{7, 8, 9}}, |  | 
| 692                 want: &Y0{B: true, F: []float64{7, 8, 9}}, |  | 
| 693         }, |  | 
| 694         { |  | 
| 695                 desc:    "save []float64 load float64", |  | 
| 696                 src:     &Y0{B: true, F: []float64{7, 8, 9}}, |  | 
| 697                 want:    &Y1{B: true}, |  | 
| 698                 loadErr: "requires a slice", |  | 
| 699         }, |  | 
| 700         { |  | 
| 701                 desc: "save single []int64 load int64", |  | 
| 702                 src:  &Y2{B: true, F: []int64{7}}, |  | 
| 703                 want: &Y3{B: true, F: 7}, |  | 
| 704         }, |  | 
| 705         { |  | 
| 706                 desc: "save int64 load single []int64", |  | 
| 707                 src:  &Y3{B: true, F: 7}, |  | 
| 708                 want: &Y2{B: true, F: []int64{7}}, |  | 
| 709         }, |  | 
| 710         { |  | 
| 711                 desc: "use convertable slice", |  | 
| 712                 src:  &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
      vertable{2, 4, 6}}}}, |  | 
| 713                 want: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
      vertable{2, 4, 6}}}}, |  | 
| 714         }, |  | 
| 715         { |  | 
| 716                 desc: "use convertable slice (to map)", |  | 
| 717                 src:  &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
      vertable{2, 4, 6}}}}, |  | 
| 718                 want: gae.DSPropertyMap{ |  | 
| 719                         "Nested.wot": {mpNI("1,5,9"), mpNI("2,4,6")}, |  | 
| 720                 }, |  | 
| 721         }, |  | 
| 722         { |  | 
| 723                 desc:    "convertable slice (bad load)", |  | 
| 724                 src:     gae.DSPropertyMap{"Nested.wot": {mpNI([]byte("ohai"))}}
      , |  | 
| 725                 want:    &Impossible{[]ImpossibleInner{{}}}, |  | 
| 726                 loadErr: "nope", |  | 
| 727         }, |  | 
| 728         { |  | 
| 729                 desc: "use convertable struct", |  | 
| 730                 src: &Impossible2{ |  | 
| 731                         []ImpossibleInner2{ |  | 
| 732                                 {Convertable2{"nerb", nil}}, |  | 
| 733                         }, |  | 
| 734                 }, |  | 
| 735                 want: &Impossible2{ |  | 
| 736                         []ImpossibleInner2{ |  | 
| 737                                 {Convertable2{"nerb", []string{"turn", "down", "
      for", "what"}}}, |  | 
| 738                         }, |  | 
| 739                 }, |  | 
| 740         }, |  | 
| 741         { |  | 
| 742                 desc: "convertable json KVMap", |  | 
| 743                 src: &Impossible3{ |  | 
| 744                         JSONKVProp{ |  | 
| 745                                 "epic":    "success", |  | 
| 746                                 "no_way!": []interface{}{true, "story"}, |  | 
| 747                                 "what":    []interface{}{"is", "really", 100}, |  | 
| 748                         }, |  | 
| 749                 }, |  | 
| 750                 want: &Impossible3{ |  | 
| 751                         JSONKVProp{ |  | 
| 752                                 "epic":    "success", |  | 
| 753                                 "no_way!": []interface{}{true, "story"}, |  | 
| 754                                 "what":    []interface{}{"is", "really", json.Nu
      mber("100")}, |  | 
| 755                         }, |  | 
| 756                 }, |  | 
| 757         }, |  | 
| 758         { |  | 
| 759                 desc: "convertable json KVMap (to map)", |  | 
| 760                 src: &Impossible3{ |  | 
| 761                         JSONKVProp{ |  | 
| 762                                 "epic":    "success", |  | 
| 763                                 "no_way!": []interface{}{true, "story"}, |  | 
| 764                                 "what":    []interface{}{"is", "really", 100}, |  | 
| 765                         }, |  | 
| 766                 }, |  | 
| 767                 want: gae.DSPropertyMap{ |  | 
| 768                         "kewelmap": { |  | 
| 769                                 mpNI([]byte( |  | 
| 770                                         `{"epic":"success","no_way!":[true,"stor
      y"],"what":["is","really",100]}`))}, |  | 
| 771                 }, |  | 
| 772         }, |  | 
| 773         { |  | 
| 774                 desc: "convertable complex slice", |  | 
| 775                 src: &Impossible4{ |  | 
| 776                         []Complex{complex(1, 2), complex(3, 4)}, |  | 
| 777                 }, |  | 
| 778                 want: &Impossible4{ |  | 
| 779                         []Complex{complex(1, 2), complex(3, 4)}, |  | 
| 780                 }, |  | 
| 781         }, |  | 
| 782         { |  | 
| 783                 desc: "convertable complex slice (to map)", |  | 
| 784                 src: &Impossible4{ |  | 
| 785                         []Complex{complex(1, 2), complex(3, 4)}, |  | 
| 786                 }, |  | 
| 787                 want: gae.DSPropertyMap{ |  | 
| 788                         "Values": { |  | 
| 789                                 mp(gae.DSGeoPoint{Lat: 1, Lng: 2}), mp(gae.DSGeo
      Point{Lat: 3, Lng: 4})}, |  | 
| 790                 }, |  | 
| 791         }, |  | 
| 792         { |  | 
| 793                 desc:    "convertable complex slice (bad load)", |  | 
| 794                 src:     gae.DSPropertyMap{"Values": {mp("hello")}}, |  | 
| 795                 want:    &Impossible4{[]Complex(nil)}, |  | 
| 796                 loadErr: "nope", |  | 
| 797         }, |  | 
| 798         { |  | 
| 799                 desc: "allow concrete gae.DSKey implementors (save)", |  | 
| 800                 src:  &DerivedKey{testKey2a.(*GenericDSKey)}, |  | 
| 801                 want: &IfaceKey{testKey2b}, |  | 
| 802         }, |  | 
| 803         { |  | 
| 804                 desc: "allow concrete gae.DSKey implementors (load)", |  | 
| 805                 src:  &IfaceKey{testKey2b}, |  | 
| 806                 want: &DerivedKey{testKey2a.(*GenericDSKey)}, |  | 
| 807         }, |  | 
| 808         { |  | 
| 809                 desc:    "save []float64 load []int64", |  | 
| 810                 src:     &Y0{B: true, F: []float64{7, 8, 9}}, |  | 
| 811                 want:    &Y2{B: true}, |  | 
| 812                 loadErr: "type mismatch", |  | 
| 813         }, |  | 
| 814         { |  | 
| 815                 desc:    "single slice is too long", |  | 
| 816                 src:     &Y0{F: make([]float64, maxIndexedProperties+1)}, |  | 
| 817                 want:    &Y0{}, |  | 
| 818                 saveErr: "gae: too many indexed properties", |  | 
| 819         }, |  | 
| 820         { |  | 
| 821                 desc:    "two slices are too long", |  | 
| 822                 src:     &Y0{F: make([]float64, maxIndexedProperties), G: make([
      ]float64, maxIndexedProperties)}, |  | 
| 823                 want:    &Y0{}, |  | 
| 824                 saveErr: "gae: too many indexed properties", |  | 
| 825         }, |  | 
| 826         { |  | 
| 827                 desc:    "one slice and one scalar are too long", |  | 
| 828                 src:     &Y0{F: make([]float64, maxIndexedProperties), B: true}, |  | 
| 829                 want:    &Y0{}, |  | 
| 830                 saveErr: "gae: too many indexed properties", |  | 
| 831         }, |  | 
| 832         { |  | 
| 833                 desc: "long blob", |  | 
| 834                 src:  &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, |  | 
| 835                 want: &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, |  | 
| 836         }, |  | 
| 837         { |  | 
| 838                 desc:    "long []int8 is too long", |  | 
| 839                 src:     &B1{B: makeInt8Slice(maxIndexedProperties + 1)}, |  | 
| 840                 want:    &B1{}, |  | 
| 841                 saveErr: "gae: too many indexed properties", |  | 
| 842         }, |  | 
| 843         { |  | 
| 844                 desc: "short []int8", |  | 
| 845                 src:  &B1{B: makeInt8Slice(3)}, |  | 
| 846                 want: &B1{B: makeInt8Slice(3)}, |  | 
| 847         }, |  | 
| 848         { |  | 
| 849                 desc: "long myBlob", |  | 
| 850                 src:  &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, |  | 
| 851                 want: &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, |  | 
| 852         }, |  | 
| 853         { |  | 
| 854                 desc: "short myBlob", |  | 
| 855                 src:  &B2{B: makeUint8Slice(3)}, |  | 
| 856                 want: &B2{B: makeUint8Slice(3)}, |  | 
| 857         }, |  | 
| 858         { |  | 
| 859                 desc: "long []myByte", |  | 
| 860                 src:  &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, |  | 
| 861                 want: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, |  | 
| 862         }, |  | 
| 863         { |  | 
| 864                 desc: "short []myByte", |  | 
| 865                 src:  &B3{B: makeMyByteSlice(3)}, |  | 
| 866                 want: &B3{B: makeMyByteSlice(3)}, |  | 
| 867         }, |  | 
| 868         { |  | 
| 869                 desc: "slice of blobs", |  | 
| 870                 src: &B4{B: [][]byte{ |  | 
| 871                         makeUint8Slice(3), |  | 
| 872                         makeUint8Slice(4), |  | 
| 873                         makeUint8Slice(5), |  | 
| 874                 }}, |  | 
| 875                 want: &B4{B: [][]byte{ |  | 
| 876                         makeUint8Slice(3), |  | 
| 877                         makeUint8Slice(4), |  | 
| 878                         makeUint8Slice(5), |  | 
| 879                 }}, |  | 
| 880         }, |  | 
| 881         { |  | 
| 882                 desc: "short gae.DSByteString", |  | 
| 883                 src:  &B5{B: gae.DSByteString(makeUint8Slice(3))}, |  | 
| 884                 want: &B5{B: gae.DSByteString(makeUint8Slice(3))}, |  | 
| 885         }, |  | 
| 886         { |  | 
| 887                 desc: "short gae.DSByteString as props", |  | 
| 888                 src:  &B5{B: gae.DSByteString(makeUint8Slice(3))}, |  | 
| 889                 want: gae.DSPropertyMap{ |  | 
| 890                         "B": {mp(gae.DSByteString(makeUint8Slice(3)))}, |  | 
| 891                 }, |  | 
| 892         }, |  | 
| 893         { |  | 
| 894                 desc: "[]byte must be noindex", |  | 
| 895                 src: gae.DSPropertyMap{ |  | 
| 896                         "B": {mp(makeUint8Slice(3))}, |  | 
| 897                 }, |  | 
| 898                 actualNoIndex: true, |  | 
| 899         }, |  | 
| 900         { |  | 
| 901                 desc: "save tagged load props", |  | 
| 902                 src:  &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6
      , J: 7}, |  | 
| 903                 want: gae.DSPropertyMap{ |  | 
| 904                         // A and B are renamed to a and b; A and C are noindex, 
      I is ignored. |  | 
| 905                         // Indexed properties are loaded before raw properties. 
      Thus, the |  | 
| 906                         // result is: b, b, b, D, E, a, c. |  | 
| 907                         "b1": { |  | 
| 908                                 mp(21), |  | 
| 909                                 mp(22), |  | 
| 910                                 mp(23), |  | 
| 911                         }, |  | 
| 912                         "D": {mp(4)}, |  | 
| 913                         "E": {mp(5)}, |  | 
| 914                         "a": {mpNI(1)}, |  | 
| 915                         "C": {mpNI(3)}, |  | 
| 916                         "J": {mpNI(7)}, |  | 
| 917                 }, |  | 
| 918         }, |  | 
| 919         { |  | 
| 920                 desc: "save tagged load tagged", |  | 
| 921                 src:  &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6
      , J: 7}, |  | 
| 922                 want: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7
      }, |  | 
| 923         }, |  | 
| 924         { |  | 
| 925                 desc: "save props load tagged", |  | 
| 926                 src: gae.DSPropertyMap{ |  | 
| 927                         "A": {mpNI(11)}, |  | 
| 928                         "a": {mpNI(12)}, |  | 
| 929                 }, |  | 
| 930                 want:    &Tagged{A: 12}, |  | 
| 931                 loadErr: `cannot load field "A"`, |  | 
| 932         }, |  | 
| 933         { |  | 
| 934                 desc:   "invalid tagged1", |  | 
| 935                 src:    &InvalidTagged1{I: 1}, |  | 
| 936                 plsErr: `struct tag has invalid property name: "\t"`, |  | 
| 937         }, |  | 
| 938         { |  | 
| 939                 desc:   "invalid tagged2", |  | 
| 940                 src:    &InvalidTagged2{I: 1, J: 2}, |  | 
| 941                 want:   &InvalidTagged2{}, |  | 
| 942                 plsErr: `struct tag has repeated property name: "I"`, |  | 
| 943         }, |  | 
| 944         { |  | 
| 945                 desc:   "invalid tagged3", |  | 
| 946                 src:    &InvalidTagged3{I: 1}, |  | 
| 947                 plsErr: `struct tag has invalid property name: "a\t"`, |  | 
| 948         }, |  | 
| 949         { |  | 
| 950                 desc:   "invalid tagged4", |  | 
| 951                 src:    &InvalidTagged4{I: 1}, |  | 
| 952                 plsErr: `struct tag has invalid property name: "a."`, |  | 
| 953         }, |  | 
| 954         { |  | 
| 955                 desc:   "invalid tagged5", |  | 
| 956                 src:    &InvalidTagged5{I: 19, V: []InvalidTaggedSub{{1}}}, |  | 
| 957                 plsErr: `struct tag has repeated property name: "V.I"`, |  | 
| 958         }, |  | 
| 959         { |  | 
| 960                 desc: "doubler", |  | 
| 961                 src:  &Doubler{S: "s", I: 1, B: true}, |  | 
| 962                 want: &Doubler{S: "ss", I: 2, B: true}, |  | 
| 963         }, |  | 
| 964         { |  | 
| 965                 desc: "save struct load props", |  | 
| 966                 src:  &X0{S: "s", I: 1}, |  | 
| 967                 want: gae.DSPropertyMap{ |  | 
| 968                         "S": {mp("s")}, |  | 
| 969                         "I": {mp(1)}, |  | 
| 970                 }, |  | 
| 971         }, |  | 
| 972         { |  | 
| 973                 desc: "save props load struct", |  | 
| 974                 src: gae.DSPropertyMap{ |  | 
| 975                         "S": {mp("s")}, |  | 
| 976                         "I": {mp(1)}, |  | 
| 977                 }, |  | 
| 978                 want: &X0{S: "s", I: 1}, |  | 
| 979         }, |  | 
| 980         { |  | 
| 981                 desc: "nil-value props", |  | 
| 982                 src: gae.DSPropertyMap{ |  | 
| 983                         "I": {mp(nil)}, |  | 
| 984                         "B": {mp(nil)}, |  | 
| 985                         "S": {mp(nil)}, |  | 
| 986                         "F": {mp(nil)}, |  | 
| 987                         "K": {mp(nil)}, |  | 
| 988                         "T": {mp(nil)}, |  | 
| 989                         "J": { |  | 
| 990                                 mp(nil), |  | 
| 991                                 mp(7), |  | 
| 992                                 mp(nil), |  | 
| 993                         }, |  | 
| 994                 }, |  | 
| 995                 want: &struct { |  | 
| 996                         I int64 |  | 
| 997                         B bool |  | 
| 998                         S string |  | 
| 999                         F float64 |  | 
| 1000                         K gae.DSKey |  | 
| 1001                         T time.Time |  | 
| 1002                         J []int64 |  | 
| 1003                 }{ |  | 
| 1004                         J: []int64{0, 7, 0}, |  | 
| 1005                 }, |  | 
| 1006         }, |  | 
| 1007         { |  | 
| 1008                 desc: "save outer load props", |  | 
| 1009                 src: &Outer{ |  | 
| 1010                         A: 1, |  | 
| 1011                         I: []Inner1{ |  | 
| 1012                                 {10, "ten"}, |  | 
| 1013                                 {20, "twenty"}, |  | 
| 1014                                 {30, "thirty"}, |  | 
| 1015                         }, |  | 
| 1016                         J: Inner2{ |  | 
| 1017                                 Y: 3.14, |  | 
| 1018                         }, |  | 
| 1019                         Inner3: Inner3{ |  | 
| 1020                                 Z: true, |  | 
| 1021                         }, |  | 
| 1022                 }, |  | 
| 1023                 want: gae.DSPropertyMap{ |  | 
| 1024                         "A": {mp(1)}, |  | 
| 1025                         "I.W": { |  | 
| 1026                                 mp(10), |  | 
| 1027                                 mp(20), |  | 
| 1028                                 mp(30), |  | 
| 1029                         }, |  | 
| 1030                         "I.X": { |  | 
| 1031                                 mp("ten"), |  | 
| 1032                                 mp("twenty"), |  | 
| 1033                                 mp("thirty"), |  | 
| 1034                         }, |  | 
| 1035                         "J.Y": {mp(3.14)}, |  | 
| 1036                         "Z":   {mp(true)}, |  | 
| 1037                 }, |  | 
| 1038         }, |  | 
| 1039         { |  | 
| 1040                 desc: "save props load outer-equivalent", |  | 
| 1041                 src: gae.DSPropertyMap{ |  | 
| 1042                         "A": {mp(1)}, |  | 
| 1043                         "I.W": { |  | 
| 1044                                 mp(10), |  | 
| 1045                                 mp(20), |  | 
| 1046                                 mp(30), |  | 
| 1047                         }, |  | 
| 1048                         "I.X": { |  | 
| 1049                                 mp("ten"), |  | 
| 1050                                 mp("twenty"), |  | 
| 1051                                 mp("thirty"), |  | 
| 1052                         }, |  | 
| 1053                         "J.Y": {mp(3.14)}, |  | 
| 1054                         "Z":   {mp(true)}, |  | 
| 1055                 }, |  | 
| 1056                 want: &OuterEquivalent{ |  | 
| 1057                         A:     1, |  | 
| 1058                         IDotW: []int32{10, 20, 30}, |  | 
| 1059                         IDotX: []string{"ten", "twenty", "thirty"}, |  | 
| 1060                         JDotY: 3.14, |  | 
| 1061                         Z:     true, |  | 
| 1062                 }, |  | 
| 1063         }, |  | 
| 1064         { |  | 
| 1065                 desc: "save outer-equivalent load outer", |  | 
| 1066                 src: &OuterEquivalent{ |  | 
| 1067                         A:     1, |  | 
| 1068                         IDotW: []int32{10, 20, 30}, |  | 
| 1069                         IDotX: []string{"ten", "twenty", "thirty"}, |  | 
| 1070                         JDotY: 3.14, |  | 
| 1071                         Z:     true, |  | 
| 1072                 }, |  | 
| 1073                 want: &Outer{ |  | 
| 1074                         A: 1, |  | 
| 1075                         I: []Inner1{ |  | 
| 1076                                 {10, "ten"}, |  | 
| 1077                                 {20, "twenty"}, |  | 
| 1078                                 {30, "thirty"}, |  | 
| 1079                         }, |  | 
| 1080                         J: Inner2{ |  | 
| 1081                                 Y: 3.14, |  | 
| 1082                         }, |  | 
| 1083                         Inner3: Inner3{ |  | 
| 1084                                 Z: true, |  | 
| 1085                         }, |  | 
| 1086                 }, |  | 
| 1087         }, |  | 
| 1088         { |  | 
| 1089                 desc: "dotted names save", |  | 
| 1090                 src:  &Dotted{A: DottedA{B: DottedB{C: 88}}}, |  | 
| 1091                 want: gae.DSPropertyMap{ |  | 
| 1092                         "A0.A1.A2.B3.C4.C5": {mp(88)}, |  | 
| 1093                 }, |  | 
| 1094         }, |  | 
| 1095         { |  | 
| 1096                 desc: "dotted names load", |  | 
| 1097                 src: gae.DSPropertyMap{ |  | 
| 1098                         "A0.A1.A2.B3.C4.C5": {mp(99)}, |  | 
| 1099                 }, |  | 
| 1100                 want: &Dotted{A: DottedA{B: DottedB{C: 99}}}, |  | 
| 1101         }, |  | 
| 1102         { |  | 
| 1103                 desc: "save struct load deriver", |  | 
| 1104                 src:  &X0{S: "s", I: 1}, |  | 
| 1105                 want: &Deriver{S: "s", Derived: "derived+s"}, |  | 
| 1106         }, |  | 
| 1107         { |  | 
| 1108                 desc: "save deriver load struct", |  | 
| 1109                 src:  &Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"}
      , |  | 
| 1110                 want: &X0{S: "s"}, |  | 
| 1111         }, |  | 
| 1112         // Regression: CL 25062824 broke handling of appengine.BlobKey fields. |  | 
| 1113         { |  | 
| 1114                 desc: "appengine.BlobKey", |  | 
| 1115                 src:  &BK{Key: "blah"}, |  | 
| 1116                 want: &BK{Key: "blah"}, |  | 
| 1117         }, |  | 
| 1118         { |  | 
| 1119                 desc: "zero time.Time", |  | 
| 1120                 src:  &T{T: time.Time{}}, |  | 
| 1121                 want: &T{T: time.Time{}}, |  | 
| 1122         }, |  | 
| 1123         { |  | 
| 1124                 desc: "time.Time near Unix zero time", |  | 
| 1125                 src:  &T{T: time.Unix(0, 4e3).UTC()}, |  | 
| 1126                 want: &T{T: time.Unix(0, 4e3).UTC()}, |  | 
| 1127         }, |  | 
| 1128         { |  | 
| 1129                 desc: "time.Time, far in the future", |  | 
| 1130                 src:  &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, |  | 
| 1131                 want: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, |  | 
| 1132         }, |  | 
| 1133         { |  | 
| 1134                 desc:    "time.Time, very far in the past", |  | 
| 1135                 src:     &T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)}, |  | 
| 1136                 want:    &T{}, |  | 
| 1137                 saveErr: "time value out of range", |  | 
| 1138         }, |  | 
| 1139         { |  | 
| 1140                 desc:    "time.Time, very far in the future", |  | 
| 1141                 src:     &T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)}, |  | 
| 1142                 want:    &T{}, |  | 
| 1143                 saveErr: "time value out of range", |  | 
| 1144         }, |  | 
| 1145         { |  | 
| 1146                 desc: "structs", |  | 
| 1147                 src: &N0{ |  | 
| 1148                         X0:       X0{S: "one", I: 2, i: 3}, |  | 
| 1149                         Nonymous: X0{S: "four", I: 5, i: 6}, |  | 
| 1150                         Ignore:   "ignore", |  | 
| 1151                         Other:    "other", |  | 
| 1152                 }, |  | 
| 1153                 want: &N0{ |  | 
| 1154                         X0:       X0{S: "one", I: 2}, |  | 
| 1155                         Nonymous: X0{S: "four", I: 5}, |  | 
| 1156                         Other:    "other", |  | 
| 1157                 }, |  | 
| 1158         }, |  | 
| 1159         { |  | 
| 1160                 desc: "exotic types", |  | 
| 1161                 src: &ExoticTypes{ |  | 
| 1162                         BS:   "sup", |  | 
| 1163                         DSBS: gae.DSByteString("nerds"), |  | 
| 1164                 }, |  | 
| 1165                 want: &ExoticTypes{ |  | 
| 1166                         BS:   "sup", |  | 
| 1167                         DSBS: gae.DSByteString("nerds"), |  | 
| 1168                 }, |  | 
| 1169         }, |  | 
| 1170         { |  | 
| 1171                 desc:   "underspecified types", |  | 
| 1172                 src:    &Underspecified{}, |  | 
| 1173                 plsErr: "non-concrete interface", |  | 
| 1174         }, |  | 
| 1175         { |  | 
| 1176                 desc: "mismatch (string)", |  | 
| 1177                 src: gae.DSPropertyMap{ |  | 
| 1178                         "K": {mp(199)}, |  | 
| 1179                         "S": {mp([]byte("cats"))}, |  | 
| 1180                         "F": {mp(gae.DSByteString("nurbs"))}, |  | 
| 1181                 }, |  | 
| 1182                 want:    &MismatchTypes{}, |  | 
| 1183                 loadErr: "type mismatch", |  | 
| 1184         }, |  | 
| 1185         { |  | 
| 1186                 desc:    "mismatch (float)", |  | 
| 1187                 src:     gae.DSPropertyMap{"F": {mp(gae.BSKey("wot"))}}, |  | 
| 1188                 want:    &MismatchTypes{}, |  | 
| 1189                 loadErr: "type mismatch", |  | 
| 1190         }, |  | 
| 1191         { |  | 
| 1192                 desc:    "mismatch (float/overflow)", |  | 
| 1193                 src:     gae.DSPropertyMap{"F": {mp(math.MaxFloat64)}}, |  | 
| 1194                 want:    &MismatchTypes{}, |  | 
| 1195                 loadErr: "overflows", |  | 
| 1196         }, |  | 
| 1197         { |  | 
| 1198                 desc:    "mismatch (key)", |  | 
| 1199                 src:     gae.DSPropertyMap{"K": {mp(false)}}, |  | 
| 1200                 want:    &MismatchTypes{}, |  | 
| 1201                 loadErr: "type mismatch", |  | 
| 1202         }, |  | 
| 1203         { |  | 
| 1204                 desc:    "mismatch (bool)", |  | 
| 1205                 src:     gae.DSPropertyMap{"B": {mp(testKey0)}}, |  | 
| 1206                 want:    &MismatchTypes{}, |  | 
| 1207                 loadErr: "type mismatch", |  | 
| 1208         }, |  | 
| 1209         { |  | 
| 1210                 desc:    "mismatch (time)", |  | 
| 1211                 src:     gae.DSPropertyMap{"T": {mp(gae.DSGeoPoint{})}}, |  | 
| 1212                 want:    &MismatchTypes{}, |  | 
| 1213                 loadErr: "type mismatch", |  | 
| 1214         }, |  | 
| 1215         { |  | 
| 1216                 desc:    "mismatch (geopoint)", |  | 
| 1217                 src:     gae.DSPropertyMap{"G": {mp(time.Now().UTC())}}, |  | 
| 1218                 want:    &MismatchTypes{}, |  | 
| 1219                 loadErr: "type mismatch", |  | 
| 1220         }, |  | 
| 1221         { |  | 
| 1222                 desc: "slice of structs", |  | 
| 1223                 src: &N1{ |  | 
| 1224                         X0: X0{S: "one", I: 2, i: 3}, |  | 
| 1225                         Nonymous: []X0{ |  | 
| 1226                                 {S: "four", I: 5, i: 6}, |  | 
| 1227                                 {S: "seven", I: 8, i: 9}, |  | 
| 1228                                 {S: "ten", I: 11, i: 12}, |  | 
| 1229                                 {S: "thirteen", I: 14, i: 15}, |  | 
| 1230                         }, |  | 
| 1231                         Ignore: "ignore", |  | 
| 1232                         Other:  "other", |  | 
| 1233                 }, |  | 
| 1234                 want: &N1{ |  | 
| 1235                         X0: X0{S: "one", I: 2}, |  | 
| 1236                         Nonymous: []X0{ |  | 
| 1237                                 {S: "four", I: 5}, |  | 
| 1238                                 {S: "seven", I: 8}, |  | 
| 1239                                 {S: "ten", I: 11}, |  | 
| 1240                                 {S: "thirteen", I: 14}, |  | 
| 1241                         }, |  | 
| 1242                         Other: "other", |  | 
| 1243                 }, |  | 
| 1244         }, |  | 
| 1245         { |  | 
| 1246                 desc: "structs with slices of structs", |  | 
| 1247                 src: &N2{ |  | 
| 1248                         N1: N1{ |  | 
| 1249                                 X0: X0{S: "rouge"}, |  | 
| 1250                                 Nonymous: []X0{ |  | 
| 1251                                         {S: "rosso0"}, |  | 
| 1252                                         {S: "rosso1"}, |  | 
| 1253                                 }, |  | 
| 1254                         }, |  | 
| 1255                         Green: N1{ |  | 
| 1256                                 X0: X0{S: "vert"}, |  | 
| 1257                                 Nonymous: []X0{ |  | 
| 1258                                         {S: "verde0"}, |  | 
| 1259                                         {S: "verde1"}, |  | 
| 1260                                         {S: "verde2"}, |  | 
| 1261                                 }, |  | 
| 1262                         }, |  | 
| 1263                         Blue: N1{ |  | 
| 1264                                 X0: X0{S: "bleu"}, |  | 
| 1265                                 Nonymous: []X0{ |  | 
| 1266                                         {S: "blu0"}, |  | 
| 1267                                         {S: "blu1"}, |  | 
| 1268                                         {S: "blu2"}, |  | 
| 1269                                         {S: "blu3"}, |  | 
| 1270                                 }, |  | 
| 1271                         }, |  | 
| 1272                 }, |  | 
| 1273                 want: &N2{ |  | 
| 1274                         N1: N1{ |  | 
| 1275                                 X0: X0{S: "rouge"}, |  | 
| 1276                                 Nonymous: []X0{ |  | 
| 1277                                         {S: "rosso0"}, |  | 
| 1278                                         {S: "rosso1"}, |  | 
| 1279                                 }, |  | 
| 1280                         }, |  | 
| 1281                         Green: N1{ |  | 
| 1282                                 X0: X0{S: "vert"}, |  | 
| 1283                                 Nonymous: []X0{ |  | 
| 1284                                         {S: "verde0"}, |  | 
| 1285                                         {S: "verde1"}, |  | 
| 1286                                         {S: "verde2"}, |  | 
| 1287                                 }, |  | 
| 1288                         }, |  | 
| 1289                         Blue: N1{ |  | 
| 1290                                 X0: X0{S: "bleu"}, |  | 
| 1291                                 Nonymous: []X0{ |  | 
| 1292                                         {S: "blu0"}, |  | 
| 1293                                         {S: "blu1"}, |  | 
| 1294                                         {S: "blu2"}, |  | 
| 1295                                         {S: "blu3"}, |  | 
| 1296                                 }, |  | 
| 1297                         }, |  | 
| 1298                 }, |  | 
| 1299         }, |  | 
| 1300         { |  | 
| 1301                 desc: "save structs load props", |  | 
| 1302                 src: &N2{ |  | 
| 1303                         N1: N1{ |  | 
| 1304                                 X0: X0{S: "rouge"}, |  | 
| 1305                                 Nonymous: []X0{ |  | 
| 1306                                         {S: "rosso0"}, |  | 
| 1307                                         {S: "rosso1"}, |  | 
| 1308                                 }, |  | 
| 1309                         }, |  | 
| 1310                         Green: N1{ |  | 
| 1311                                 X0: X0{S: "vert"}, |  | 
| 1312                                 Nonymous: []X0{ |  | 
| 1313                                         {S: "verde0"}, |  | 
| 1314                                         {S: "verde1"}, |  | 
| 1315                                         {S: "verde2"}, |  | 
| 1316                                 }, |  | 
| 1317                         }, |  | 
| 1318                         Blue: N1{ |  | 
| 1319                                 X0: X0{S: "bleu"}, |  | 
| 1320                                 Nonymous: []X0{ |  | 
| 1321                                         {S: "blu0"}, |  | 
| 1322                                         {S: "blu1"}, |  | 
| 1323                                         {S: "blu2"}, |  | 
| 1324                                         {S: "blu3"}, |  | 
| 1325                                 }, |  | 
| 1326                         }, |  | 
| 1327                 }, |  | 
| 1328                 want: gae.DSPropertyMap{ |  | 
| 1329                         "red.S":            {mp("rouge")}, |  | 
| 1330                         "red.I":            {mp(0)}, |  | 
| 1331                         "red.Nonymous.S":   {mp("rosso0"), mp("rosso1")}, |  | 
| 1332                         "red.Nonymous.I":   {mp(0), mp(0)}, |  | 
| 1333                         "red.Other":        {mp("")}, |  | 
| 1334                         "green.S":          {mp("vert")}, |  | 
| 1335                         "green.I":          {mp(0)}, |  | 
| 1336                         "green.Nonymous.S": {mp("verde0"), mp("verde1"), mp("ver
      de2")}, |  | 
| 1337                         "green.Nonymous.I": {mp(0), mp(0), mp(0)}, |  | 
| 1338                         "green.Other":      {mp("")}, |  | 
| 1339                         "Blue.S":           {mp("bleu")}, |  | 
| 1340                         "Blue.I":           {mp(0)}, |  | 
| 1341                         "Blue.Nonymous.S":  {mp("blu0"), mp("blu1"), mp("blu2"),
       mp("blu3")}, |  | 
| 1342                         "Blue.Nonymous.I":  {mp(0), mp(0), mp(0), mp(0)}, |  | 
| 1343                         "Blue.Other":       {mp("")}, |  | 
| 1344                 }, |  | 
| 1345         }, |  | 
| 1346         { |  | 
| 1347                 desc: "save props load structs with ragged fields", |  | 
| 1348                 src: gae.DSPropertyMap{ |  | 
| 1349                         "red.S":            {mp("rot")}, |  | 
| 1350                         "green.Nonymous.I": {mp(10), mp(11), mp(12), mp(13)}, |  | 
| 1351                         "Blue.Nonymous.S":  {mp("blau0"), mp("blau1"), mp("blau2
      ")}, |  | 
| 1352                         "Blue.Nonymous.I":  {mp(20), mp(21)}, |  | 
| 1353                 }, |  | 
| 1354                 want: &N2{ |  | 
| 1355                         N1: N1{ |  | 
| 1356                                 X0: X0{S: "rot"}, |  | 
| 1357                         }, |  | 
| 1358                         Green: N1{ |  | 
| 1359                                 Nonymous: []X0{ |  | 
| 1360                                         {I: 10}, |  | 
| 1361                                         {I: 11}, |  | 
| 1362                                         {I: 12}, |  | 
| 1363                                         {I: 13}, |  | 
| 1364                                 }, |  | 
| 1365                         }, |  | 
| 1366                         Blue: N1{ |  | 
| 1367                                 Nonymous: []X0{ |  | 
| 1368                                         {S: "blau0", I: 20}, |  | 
| 1369                                         {S: "blau1", I: 21}, |  | 
| 1370                                         {S: "blau2"}, |  | 
| 1371                                 }, |  | 
| 1372                         }, |  | 
| 1373                 }, |  | 
| 1374         }, |  | 
| 1375         { |  | 
| 1376                 desc: "save structs with noindex tags", |  | 
| 1377                 src: &struct { |  | 
| 1378                         A struct { |  | 
| 1379                                 X string `gae:",noindex"` |  | 
| 1380                                 Y string |  | 
| 1381                         } `gae:",noindex"` |  | 
| 1382                         B struct { |  | 
| 1383                                 X string `gae:",noindex"` |  | 
| 1384                                 Y string |  | 
| 1385                         } |  | 
| 1386                 }{}, |  | 
| 1387                 want: gae.DSPropertyMap{ |  | 
| 1388                         "B.Y": {mp("")}, |  | 
| 1389                         "A.X": {mpNI("")}, |  | 
| 1390                         "A.Y": {mpNI("")}, |  | 
| 1391                         "B.X": {mpNI("")}, |  | 
| 1392                 }, |  | 
| 1393         }, |  | 
| 1394         { |  | 
| 1395                 desc: "embedded struct with name override", |  | 
| 1396                 src: &struct { |  | 
| 1397                         Inner1 `gae:"foo"` |  | 
| 1398                 }{}, |  | 
| 1399                 want: gae.DSPropertyMap{ |  | 
| 1400                         "foo.W": {mp(0)}, |  | 
| 1401                         "foo.X": {mp("")}, |  | 
| 1402                 }, |  | 
| 1403         }, |  | 
| 1404         { |  | 
| 1405                 desc:   "slice of slices", |  | 
| 1406                 src:    &SliceOfSlices{}, |  | 
| 1407                 plsErr: `flattening nested structs leads to a slice of slices: f
      ield "S"`, |  | 
| 1408         }, |  | 
| 1409         { |  | 
| 1410                 desc:   "recursive struct", |  | 
| 1411                 src:    &Recursive{}, |  | 
| 1412                 plsErr: `field "R" is recursively defined`, |  | 
| 1413         }, |  | 
| 1414         { |  | 
| 1415                 desc:   "mutually recursive struct", |  | 
| 1416                 src:    &MutuallyRecursive0{}, |  | 
| 1417                 plsErr: `field "R" has problem: field "R" is recursively defined
      `, |  | 
| 1418         }, |  | 
| 1419         { |  | 
| 1420                 desc: "non-exported struct fields", |  | 
| 1421                 src: &struct { |  | 
| 1422                         i, J int64 |  | 
| 1423                 }{i: 1, J: 2}, |  | 
| 1424                 want: gae.DSPropertyMap{ |  | 
| 1425                         "J": {mp(2)}, |  | 
| 1426                 }, |  | 
| 1427         }, |  | 
| 1428         { |  | 
| 1429                 desc: "json.RawMessage", |  | 
| 1430                 src: &struct { |  | 
| 1431                         J json.RawMessage |  | 
| 1432                 }{ |  | 
| 1433                         J: json.RawMessage("rawr"), |  | 
| 1434                 }, |  | 
| 1435                 want: gae.DSPropertyMap{ |  | 
| 1436                         "J": {mp([]byte("rawr"))}, |  | 
| 1437                 }, |  | 
| 1438         }, |  | 
| 1439         { |  | 
| 1440                 desc: "json.RawMessage to myBlob", |  | 
| 1441                 src: &struct { |  | 
| 1442                         B json.RawMessage |  | 
| 1443                 }{ |  | 
| 1444                         B: json.RawMessage("rawr"), |  | 
| 1445                 }, |  | 
| 1446                 want: &B2{B: myBlob("rawr")}, |  | 
| 1447         }, |  | 
| 1448 } |  | 
| 1449 |  | 
| 1450 // checkErr returns the empty string if either both want and err are zero, |  | 
| 1451 // or if want is a non-empty substring of err's string representation. |  | 
| 1452 func checkErr(want string, err error) string { |  | 
| 1453         if err != nil { |  | 
| 1454                 got := err.Error() |  | 
| 1455                 if want == "" || strings.Index(got, want) == -1 { |  | 
| 1456                         return got |  | 
| 1457                 } |  | 
| 1458         } else if want != "" { |  | 
| 1459                 return fmt.Sprintf("want error %q", want) |  | 
| 1460         } |  | 
| 1461         return "" |  | 
| 1462 } |  | 
| 1463 |  | 
| 1464 func ShouldErrLike(actual interface{}, expected ...interface{}) string { |  | 
| 1465         e2s := func(o interface{}) (string, bool) { |  | 
| 1466                 switch x := o.(type) { |  | 
| 1467                 case nil: |  | 
| 1468                         return "", true |  | 
| 1469                 case string: |  | 
| 1470                         return x, true |  | 
| 1471                 case error: |  | 
| 1472                         if x != nil { |  | 
| 1473                                 return x.Error(), true |  | 
| 1474                         } |  | 
| 1475                         return "", true |  | 
| 1476                 } |  | 
| 1477                 return fmt.Sprintf("unknown argument type %T, expected string, e
      rror or nil", actual), false |  | 
| 1478         } |  | 
| 1479 |  | 
| 1480         as, ok := e2s(actual) |  | 
| 1481         if !ok { |  | 
| 1482                 return as |  | 
| 1483         } |  | 
| 1484 |  | 
| 1485         if len(expected) != 1 { |  | 
| 1486                 return fmt.Sprintf("Assertion requires 1 expected value, got %d"
      , len(expected)) |  | 
| 1487         } |  | 
| 1488 |  | 
| 1489         err, ok := e2s(expected[0]) |  | 
| 1490         if !ok { |  | 
| 1491                 return err |  | 
| 1492         } |  | 
| 1493         return ShouldContainSubstring(as, err) |  | 
| 1494 } |  | 
| 1495 |  | 
| 1496 func TestRoundTrip(t *testing.T) { |  | 
| 1497         t.Parallel() |  | 
| 1498 |  | 
| 1499         checkErr := func(actual interface{}, expected string) bool { |  | 
| 1500                 So(actual, ShouldErrLike, expected) |  | 
| 1501                 return expected != "" |  | 
| 1502         } |  | 
| 1503 |  | 
| 1504         Convey("Test round-trip", t, func() { |  | 
| 1505                 for _, tc := range testCases { |  | 
| 1506                         tc := tc |  | 
| 1507                         Convey(tc.desc, func() { |  | 
| 1508                                 pls, ok := tc.src.(gae.DSPropertyLoadSaver) |  | 
| 1509                                 if !ok { |  | 
| 1510                                         pls = GetPLS(tc.src) |  | 
| 1511                                 } |  | 
| 1512                                 if checkErr(pls.Problem(), tc.plsErr) { |  | 
| 1513                                         return |  | 
| 1514                                 } |  | 
| 1515                                 So(pls, ShouldNotBeNil) |  | 
| 1516 |  | 
| 1517                                 savedProps, err := pls.Save(false) |  | 
| 1518                                 if checkErr(err, tc.saveErr) { |  | 
| 1519                                         return |  | 
| 1520                                 } |  | 
| 1521                                 So(savedProps, ShouldNotBeNil) |  | 
| 1522 |  | 
| 1523                                 if tc.actualNoIndex { |  | 
| 1524                                         for _, props := range savedProps { |  | 
| 1525                                                 So(props[0].IndexSetting(), Shou
      ldEqual, gae.NoIndex) |  | 
| 1526                                                 return |  | 
| 1527                                         } |  | 
| 1528                                         So(true, ShouldBeFalse) // shouldn't get
       here |  | 
| 1529                                 } |  | 
| 1530 |  | 
| 1531                                 var got interface{} |  | 
| 1532                                 if _, ok := tc.want.(gae.DSPropertyMap); ok { |  | 
| 1533                                         pls = gae.DSPropertyMap{} |  | 
| 1534                                         got = pls |  | 
| 1535                                 } else { |  | 
| 1536                                         got = reflect.New(reflect.TypeOf(tc.want
      ).Elem()).Interface() |  | 
| 1537                                         if pls, ok = got.(gae.DSPropertyLoadSave
      r); !ok { |  | 
| 1538                                                 pls = GetPLS(got) |  | 
| 1539                                         } |  | 
| 1540                                 } |  | 
| 1541 |  | 
| 1542                                 if checkErr(pls.Problem(), tc.plsLoadErr) { |  | 
| 1543                                         return |  | 
| 1544                                 } |  | 
| 1545                                 So(pls, ShouldNotBeNil) |  | 
| 1546 |  | 
| 1547                                 err = pls.Load(savedProps) |  | 
| 1548                                 if checkErr(err, tc.loadErr) { |  | 
| 1549                                         return |  | 
| 1550                                 } |  | 
| 1551                                 if tc.want == nil { |  | 
| 1552                                         return |  | 
| 1553                                 } |  | 
| 1554 |  | 
| 1555                                 if gotT, ok := got.(*T); ok { |  | 
| 1556                                         // Round tripping a time.Time can result
       in a different time.Location: Local instead of UTC. |  | 
| 1557                                         // We therefore test equality explicitly
      , instead of relying on reflect.DeepEqual. |  | 
| 1558                                         So(gotT.T.Equal(tc.want.(*T).T), ShouldB
      eTrue) |  | 
| 1559                                 } else { |  | 
| 1560                                         So(got, ShouldResemble, tc.want) |  | 
| 1561                                 } |  | 
| 1562                         }) |  | 
| 1563                 } |  | 
| 1564         }) |  | 
| 1565 } |  | 
| 1566 |  | 
| 1567 func TestSpecial(t *testing.T) { |  | 
| 1568         t.Parallel() |  | 
| 1569 |  | 
| 1570         Convey("Test special fields", t, func() { |  | 
| 1571                 Convey("Can retrieve from struct", func() { |  | 
| 1572                         o := &N0{ID: 100} |  | 
| 1573                         pls := GetPLS(o) |  | 
| 1574                         val, err := pls.GetMeta("id") |  | 
| 1575                         So(err, ShouldBeNil) |  | 
| 1576                         So(val, ShouldEqual, 100) |  | 
| 1577 |  | 
| 1578                         val, err = pls.GetMeta("kind") |  | 
| 1579                         So(err, ShouldBeNil) |  | 
| 1580                         So(val, ShouldEqual, "whatnow") |  | 
| 1581                 }) |  | 
| 1582 |  | 
| 1583                 Convey("Getting something not there is an error", func() { |  | 
| 1584                         o := &N0{ID: 100} |  | 
| 1585                         pls := GetPLS(o) |  | 
| 1586                         _, err := pls.GetMeta("wat") |  | 
| 1587                         So(err, ShouldEqual, gae.ErrDSMetaFieldUnset) |  | 
| 1588                 }) |  | 
| 1589 |  | 
| 1590                 Convey("getting/setting from a bad struct is an error", func() { |  | 
| 1591                         o := &Recursive{} |  | 
| 1592                         pls := GetPLS(o) |  | 
| 1593                         _, err := pls.GetMeta("wat") |  | 
| 1594                         So(err, ShouldNotBeNil) |  | 
| 1595 |  | 
| 1596                         err = pls.SetMeta("wat", 100) |  | 
| 1597                         So(err, ShouldNotBeNil) |  | 
| 1598                 }) |  | 
| 1599 |  | 
| 1600                 Convey("can assign values to exported special fields", func() { |  | 
| 1601                         o := &N0{ID: 100} |  | 
| 1602                         pls := GetPLS(o) |  | 
| 1603                         err := pls.SetMeta("id", int64(200)) |  | 
| 1604                         So(err, ShouldBeNil) |  | 
| 1605                         So(o.ID, ShouldEqual, 200) |  | 
| 1606 |  | 
| 1607                 }) |  | 
| 1608 |  | 
| 1609                 Convey("assigning to unsassiagnable fields is a simple error", f
      unc() { |  | 
| 1610                         o := &N0{ID: 100} |  | 
| 1611                         pls := GetPLS(o) |  | 
| 1612                         err := pls.SetMeta("kind", "hi") |  | 
| 1613                         So(err.Error(), ShouldContainSubstring, "unexported fiel
      d") |  | 
| 1614 |  | 
| 1615                         err = pls.SetMeta("noob", "hi") |  | 
| 1616                         So(err, ShouldEqual, gae.ErrDSMetaFieldUnset) |  | 
| 1617                 }) |  | 
| 1618         }) |  | 
| 1619 |  | 
| 1620         Convey("StructPLS Miscellaneous", t, func() { |  | 
| 1621                 Convey("multiple overlapping fields is an error", func() { |  | 
| 1622                         o := &BadSpecial{} |  | 
| 1623                         pls := GetPLS(o) |  | 
| 1624                         err := pls.Load(nil) |  | 
| 1625                         So(err, ShouldErrLike, "multiple times") |  | 
| 1626                         e := pls.Problem() |  | 
| 1627                         _, err = pls.Save(true) |  | 
| 1628                         So(err, ShouldEqual, e) |  | 
| 1629                         err = pls.Load(nil) |  | 
| 1630                         So(err, ShouldEqual, e) |  | 
| 1631                 }) |  | 
| 1632 |  | 
| 1633                 Convey("empty property names are invalid", func() { |  | 
| 1634                         So(validPropertyName(""), ShouldBeFalse) |  | 
| 1635                 }) |  | 
| 1636 |  | 
| 1637                 Convey("attempting to get a PLS for a non *struct is an error", 
      func() { |  | 
| 1638                         pls := GetPLS((*[]string)(nil)) |  | 
| 1639                         So(pls.Problem(), ShouldEqual, gae.ErrDSInvalidEntityTyp
      e) |  | 
| 1640                 }) |  | 
| 1641 |  | 
| 1642                 Convey("convertible meta default types", func() { |  | 
| 1643                         type OKDefaults struct { |  | 
| 1644                                 When   string `gae:"$when,tomorrow"` |  | 
| 1645                                 Amount int64  `gae:"$amt,100"` |  | 
| 1646                         } |  | 
| 1647                         pls := GetPLS(&OKDefaults{}) |  | 
| 1648                         So(pls.Problem(), ShouldBeNil) |  | 
| 1649 |  | 
| 1650                         v, err := pls.GetMeta("when") |  | 
| 1651                         So(err, ShouldBeNil) |  | 
| 1652                         So(v, ShouldEqual, "tomorrow") |  | 
| 1653 |  | 
| 1654                         v, err = pls.GetMeta("amt") |  | 
| 1655                         So(err, ShouldBeNil) |  | 
| 1656                         So(v, ShouldEqual, int64(100)) |  | 
| 1657                 }) |  | 
| 1658 |  | 
| 1659                 Convey("meta fields can be saved", func() { |  | 
| 1660                         type OKDefaults struct { |  | 
| 1661                                 When   string `gae:"$when,tomorrow"` |  | 
| 1662                                 Amount int64  `gae:"$amt,100"` |  | 
| 1663                         } |  | 
| 1664                         pls := GetPLS(&OKDefaults{}) |  | 
| 1665                         pm, err := pls.Save(true) |  | 
| 1666                         So(err, ShouldBeNil) |  | 
| 1667                         So(pm, ShouldResemble, gae.DSPropertyMap{ |  | 
| 1668                                 "$when": {gae.MkDSPropertyNI("tomorrow")}, |  | 
| 1669                                 "$amt":  {gae.MkDSPropertyNI(100)}, |  | 
| 1670                         }) |  | 
| 1671 |  | 
| 1672                         v, err := pm.GetMeta("when") |  | 
| 1673                         So(err, ShouldBeNil) |  | 
| 1674                         So(v, ShouldEqual, "tomorrow") |  | 
| 1675 |  | 
| 1676                         v, err = pm.GetMeta("amt") |  | 
| 1677                         So(err, ShouldBeNil) |  | 
| 1678                         So(v, ShouldEqual, int64(100)) |  | 
| 1679                 }) |  | 
| 1680 |  | 
| 1681                 Convey("default are optional", func() { |  | 
| 1682                         type OverrideDefault struct { |  | 
| 1683                                 Val int64 `gae:"$val"` |  | 
| 1684                         } |  | 
| 1685                         o := &OverrideDefault{} |  | 
| 1686                         pls := GetPLS(o) |  | 
| 1687 |  | 
| 1688                         v, err := pls.GetMeta("val") |  | 
| 1689                         So(err, ShouldBeNil) |  | 
| 1690                         So(v, ShouldEqual, int64(0)) |  | 
| 1691                 }) |  | 
| 1692 |  | 
| 1693                 Convey("overridable defaults", func() { |  | 
| 1694                         type OverrideDefault struct { |  | 
| 1695                                 Val int64 `gae:"$val,100"` |  | 
| 1696                         } |  | 
| 1697                         o := &OverrideDefault{} |  | 
| 1698                         pls := GetPLS(o) |  | 
| 1699 |  | 
| 1700                         v, err := pls.GetMeta("val") |  | 
| 1701                         So(err, ShouldBeNil) |  | 
| 1702                         So(v, ShouldEqual, int64(100)) |  | 
| 1703 |  | 
| 1704                         o.Val = 10 |  | 
| 1705                         v, err = pls.GetMeta("val") |  | 
| 1706                         So(err, ShouldBeNil) |  | 
| 1707                         So(v, ShouldEqual, int64(10)) |  | 
| 1708                 }) |  | 
| 1709 |  | 
| 1710                 Convey("Bad default meta type", func() { |  | 
| 1711                         type BadDefault struct { |  | 
| 1712                                 Val time.Time `gae:"$meta,tomorrow"` |  | 
| 1713                         } |  | 
| 1714                         pls := GetPLS(&BadDefault{}) |  | 
| 1715                         So(pls.Problem().Error(), ShouldContainSubstring, "bad t
      ype") |  | 
| 1716                 }) |  | 
| 1717         }) |  | 
| 1718 } |  | 
| OLD | NEW | 
|---|