datastore: allow field names ending in '.' (#52)
restructures Load logic to map fields ending in '.' to the anonymous
struct field specified in the codec
Fixes #41
diff --git a/datastore/datastore_test.go b/datastore/datastore_test.go
index 8a297df..b3888e9 100644
--- a/datastore/datastore_test.go
+++ b/datastore/datastore_test.go
@@ -71,6 +71,8 @@
testGeoPt0 = appengine.GeoPoint{Lat: 1.2, Lng: 3.4}
testGeoPt1 = appengine.GeoPoint{Lat: 5, Lng: 10}
testBadGeoPt = appengine.GeoPoint{Lat: 1000, Lng: 34}
+
+ now = time.Unix(1e9, 0).UTC()
)
type B0 struct {
@@ -351,6 +353,14 @@
return LoadStruct(d, props)
}
+type EmbeddedTime struct {
+ time.Time
+}
+
+type SpecialTime struct {
+ MyTime EmbeddedTime
+}
+
func (d *Doubler) Save() ([]Property, error) {
// Save the default Property slice to an in-memory buffer (a PropertyList).
props, err := SaveStruct(d)
@@ -1413,6 +1423,22 @@
"",
"",
},
+ {
+ "embedded time field",
+ &SpecialTime{MyTime: EmbeddedTime{now}},
+ &SpecialTime{MyTime: EmbeddedTime{now}},
+ "",
+ "",
+ },
+ {
+ "embedded time load",
+ &PropertyList{
+ Property{Name: "MyTime.", Value: now, NoIndex: false, Multiple: false},
+ },
+ &SpecialTime{MyTime: EmbeddedTime{now}},
+ "",
+ "",
+ },
}
// checkErr returns the empty string if either both want and err are zero,
diff --git a/datastore/load.go b/datastore/load.go
index 7878cbf..38a6365 100644
--- a/datastore/load.go
+++ b/datastore/load.go
@@ -65,36 +65,35 @@
var sliceIndex int
name := p.Name
- for name != "" {
- // First we try to find a field with name matching
- // the value of 'name' exactly.
- decoder, ok := codec.fields[name]
- if ok {
- name = ""
- } else {
- // Now try for legacy flattened nested field (named eg. "A.B.C.D").
- parent := name
- child := ""
+ // If name ends with a '.', the last field is anonymous.
+ // In this case, strings.Split will give us "" as the
+ // last element of our fields slice, which will match the ""
+ // field name in the substruct codec.
+ fields := strings.Split(name, ".")
- // Cut off the last field (delimited by ".") and find its parent
- // in the codec.
- // eg. for name "A.B.C.D", split off "A.B.C" and try to
- // find a field in the codec with this name.
- // Loop again with "A.B", etc.
- for !ok {
- i := strings.LastIndex(parent, ".")
- if i < 0 {
- return "no such struct field"
- }
- if i == len(name)-1 {
- return "field name cannot end with '.'"
- }
- parent, child = name[:i], name[i+1:]
- decoder, ok = codec.fields[parent]
+ for len(fields) > 0 {
+ var decoder fieldCodec
+ var ok bool
+
+ // Cut off the last field (delimited by ".") and find its parent
+ // in the codec.
+ // eg. for name "A.B.C.D", split off "A.B.C" and try to
+ // find a field in the codec with this name.
+ // Loop again with "A.B", etc.
+ for i := len(fields); i > 0; i-- {
+ parent := strings.Join(fields[:i], ".")
+ decoder, ok = codec.fields[parent]
+ if ok {
+ fields = fields[i:]
+ break
}
+ }
- name = child
+ // If we never found a matching field in the codec, return
+ // error message.
+ if !ok {
+ return "no such struct field"
}
v = initField(structValue, decoder.path)