| // Copyright 2017 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package firestore |
| |
| import ( |
| "errors" |
| "fmt" |
| "reflect" |
| "strings" |
| |
| "cloud.google.com/go/internal/fields" |
| "github.com/golang/protobuf/ptypes" |
| pb "google.golang.org/genproto/googleapis/firestore/v1" |
| ) |
| |
| func setFromProtoValue(x interface{}, vproto *pb.Value, c *Client) error { |
| v := reflect.ValueOf(x) |
| if v.Kind() != reflect.Ptr || v.IsNil() { |
| return errors.New("firestore: nil or not a pointer") |
| } |
| return setReflectFromProtoValue(v.Elem(), vproto, c) |
| } |
| |
| // setReflectFromProtoValue sets v from a Firestore Value. |
| // v must be a settable value. |
| func setReflectFromProtoValue(v reflect.Value, vproto *pb.Value, c *Client) error { |
| typeErr := func() error { |
| return fmt.Errorf("firestore: cannot set type %s to %s", v.Type(), typeString(vproto)) |
| } |
| |
| val := vproto.ValueType |
| // A Null value sets anything nullable to nil, and has no effect |
| // on anything else. |
| if _, ok := val.(*pb.Value_NullValue); ok { |
| switch v.Kind() { |
| case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: |
| v.Set(reflect.Zero(v.Type())) |
| } |
| return nil |
| } |
| |
| // Handle special types first. |
| switch v.Type() { |
| case typeOfByteSlice: |
| x, ok := val.(*pb.Value_BytesValue) |
| if !ok { |
| return typeErr() |
| } |
| v.SetBytes(x.BytesValue) |
| return nil |
| |
| case typeOfGoTime: |
| x, ok := val.(*pb.Value_TimestampValue) |
| if !ok { |
| return typeErr() |
| } |
| t, err := ptypes.Timestamp(x.TimestampValue) |
| if err != nil { |
| return err |
| } |
| v.Set(reflect.ValueOf(t)) |
| return nil |
| |
| case typeOfProtoTimestamp: |
| x, ok := val.(*pb.Value_TimestampValue) |
| if !ok { |
| return typeErr() |
| } |
| v.Set(reflect.ValueOf(x.TimestampValue)) |
| return nil |
| |
| case typeOfLatLng: |
| x, ok := val.(*pb.Value_GeoPointValue) |
| if !ok { |
| return typeErr() |
| } |
| v.Set(reflect.ValueOf(x.GeoPointValue)) |
| return nil |
| |
| case typeOfDocumentRef: |
| x, ok := val.(*pb.Value_ReferenceValue) |
| if !ok { |
| return typeErr() |
| } |
| dr, err := pathToDoc(x.ReferenceValue, c) |
| if err != nil { |
| return err |
| } |
| v.Set(reflect.ValueOf(dr)) |
| return nil |
| } |
| |
| switch v.Kind() { |
| case reflect.Bool: |
| x, ok := val.(*pb.Value_BooleanValue) |
| if !ok { |
| return typeErr() |
| } |
| v.SetBool(x.BooleanValue) |
| |
| case reflect.String: |
| x, ok := val.(*pb.Value_StringValue) |
| if !ok { |
| return typeErr() |
| } |
| v.SetString(x.StringValue) |
| |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| var i int64 |
| switch x := val.(type) { |
| case *pb.Value_IntegerValue: |
| i = x.IntegerValue |
| case *pb.Value_DoubleValue: |
| f := x.DoubleValue |
| i = int64(f) |
| if float64(i) != f { |
| return fmt.Errorf("firestore: float %f does not fit into %s", f, v.Type()) |
| } |
| default: |
| return typeErr() |
| } |
| if v.OverflowInt(i) { |
| return overflowErr(v, i) |
| } |
| v.SetInt(i) |
| |
| case reflect.Uint8, reflect.Uint16, reflect.Uint32: |
| var u uint64 |
| switch x := val.(type) { |
| case *pb.Value_IntegerValue: |
| u = uint64(x.IntegerValue) |
| case *pb.Value_DoubleValue: |
| f := x.DoubleValue |
| u = uint64(f) |
| if float64(u) != f { |
| return fmt.Errorf("firestore: float %f does not fit into %s", f, v.Type()) |
| } |
| default: |
| return typeErr() |
| } |
| if v.OverflowUint(u) { |
| return overflowErr(v, u) |
| } |
| v.SetUint(u) |
| |
| case reflect.Float32, reflect.Float64: |
| var f float64 |
| switch x := val.(type) { |
| case *pb.Value_DoubleValue: |
| f = x.DoubleValue |
| case *pb.Value_IntegerValue: |
| f = float64(x.IntegerValue) |
| if int64(f) != x.IntegerValue { |
| return overflowErr(v, x.IntegerValue) |
| } |
| default: |
| return typeErr() |
| } |
| if v.OverflowFloat(f) { |
| return overflowErr(v, f) |
| } |
| v.SetFloat(f) |
| |
| case reflect.Slice: |
| x, ok := val.(*pb.Value_ArrayValue) |
| if !ok { |
| return typeErr() |
| } |
| vals := x.ArrayValue.Values |
| vlen := v.Len() |
| xlen := len(vals) |
| // Make a slice of the right size, avoiding allocation if possible. |
| switch { |
| case vlen < xlen: |
| v.Set(reflect.MakeSlice(v.Type(), xlen, xlen)) |
| case vlen > xlen: |
| v.SetLen(xlen) |
| } |
| return populateRepeated(v, vals, xlen, c) |
| |
| case reflect.Array: |
| x, ok := val.(*pb.Value_ArrayValue) |
| if !ok { |
| return typeErr() |
| } |
| vals := x.ArrayValue.Values |
| xlen := len(vals) |
| vlen := v.Len() |
| minlen := vlen |
| // Set extra elements to their zero value. |
| if vlen > xlen { |
| z := reflect.Zero(v.Type().Elem()) |
| for i := xlen; i < vlen; i++ { |
| v.Index(i).Set(z) |
| } |
| minlen = xlen |
| } |
| return populateRepeated(v, vals, minlen, c) |
| |
| case reflect.Map: |
| x, ok := val.(*pb.Value_MapValue) |
| if !ok { |
| return typeErr() |
| } |
| return populateMap(v, x.MapValue.Fields, c) |
| |
| case reflect.Ptr: |
| // If the pointer is nil, set it to a zero value. |
| if v.IsNil() { |
| v.Set(reflect.New(v.Type().Elem())) |
| } |
| return setReflectFromProtoValue(v.Elem(), vproto, c) |
| |
| case reflect.Struct: |
| x, ok := val.(*pb.Value_MapValue) |
| if !ok { |
| return typeErr() |
| } |
| return populateStruct(v, x.MapValue.Fields, c) |
| |
| case reflect.Interface: |
| if v.NumMethod() == 0 { // empty interface |
| // If v holds a pointer, set the pointer. |
| if !v.IsNil() && v.Elem().Kind() == reflect.Ptr { |
| return setReflectFromProtoValue(v.Elem(), vproto, c) |
| } |
| // Otherwise, create a fresh value. |
| x, err := createFromProtoValue(vproto, c) |
| if err != nil { |
| return err |
| } |
| v.Set(reflect.ValueOf(x)) |
| return nil |
| } |
| // Any other kind of interface is an error. |
| fallthrough |
| |
| default: |
| return fmt.Errorf("firestore: cannot set type %s", v.Type()) |
| } |
| return nil |
| } |
| |
| // populateRepeated sets the first n elements of vr, which must be a slice or |
| // array, to the corresponding elements of vals. |
| func populateRepeated(vr reflect.Value, vals []*pb.Value, n int, c *Client) error { |
| for i := 0; i < n; i++ { |
| if err := setReflectFromProtoValue(vr.Index(i), vals[i], c); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // populateMap sets the elements of vm, which must be a map, from the |
| // corresponding elements of pm. |
| // |
| // Since a map value is not settable, this function always creates a new |
| // element for each corresponding map key. Existing values of vm are |
| // overwritten. This happens even if the map value is something like a pointer |
| // to a struct, where we could in theory populate the existing struct value |
| // instead of discarding it. This behavior matches encoding/json. |
| func populateMap(vm reflect.Value, pm map[string]*pb.Value, c *Client) error { |
| t := vm.Type() |
| if t.Key().Kind() != reflect.String { |
| return errors.New("firestore: map key type is not string") |
| } |
| if vm.IsNil() { |
| vm.Set(reflect.MakeMap(t)) |
| } |
| et := t.Elem() |
| for k, vproto := range pm { |
| el := reflect.New(et).Elem() |
| if err := setReflectFromProtoValue(el, vproto, c); err != nil { |
| return err |
| } |
| vm.SetMapIndex(reflect.ValueOf(k), el) |
| } |
| return nil |
| } |
| |
| // createMapFromValueMap creates a fresh map and populates it with pm. |
| func createMapFromValueMap(pm map[string]*pb.Value, c *Client) (map[string]interface{}, error) { |
| m := map[string]interface{}{} |
| for k, pv := range pm { |
| v, err := createFromProtoValue(pv, c) |
| if err != nil { |
| return nil, err |
| } |
| m[k] = v |
| } |
| return m, nil |
| } |
| |
| // populateStruct sets the fields of vs, which must be a struct, from |
| // the matching elements of pm. |
| func populateStruct(vs reflect.Value, pm map[string]*pb.Value, c *Client) error { |
| fs, err := fieldCache.Fields(vs.Type()) |
| if err != nil { |
| return err |
| } |
| |
| type match struct { |
| vproto *pb.Value |
| f *fields.Field |
| } |
| // Find best field matches |
| matched := make(map[string]match) |
| for k, vproto := range pm { |
| f := fs.Match(k) |
| if f == nil { |
| continue |
| } |
| if _, ok := matched[f.Name]; ok { |
| // If multiple case insensitive fields match, the exact match |
| // should win. |
| if f.Name == k { |
| matched[k] = match{vproto: vproto, f: f} |
| } |
| } else { |
| matched[f.Name] = match{vproto: vproto, f: f} |
| } |
| } |
| |
| // Reflect values |
| for _, v := range matched { |
| f := v.f |
| vproto := v.vproto |
| |
| if err := setReflectFromProtoValue(vs.FieldByIndex(f.Index), vproto, c); err != nil { |
| return fmt.Errorf("%s.%s: %v", vs.Type(), f.Name, err) |
| } |
| } |
| return nil |
| } |
| |
| func createFromProtoValue(vproto *pb.Value, c *Client) (interface{}, error) { |
| switch v := vproto.ValueType.(type) { |
| case *pb.Value_NullValue: |
| return nil, nil |
| case *pb.Value_BooleanValue: |
| return v.BooleanValue, nil |
| case *pb.Value_IntegerValue: |
| return v.IntegerValue, nil |
| case *pb.Value_DoubleValue: |
| return v.DoubleValue, nil |
| case *pb.Value_TimestampValue: |
| return ptypes.Timestamp(v.TimestampValue) |
| case *pb.Value_StringValue: |
| return v.StringValue, nil |
| case *pb.Value_BytesValue: |
| return v.BytesValue, nil |
| case *pb.Value_ReferenceValue: |
| return pathToDoc(v.ReferenceValue, c) |
| case *pb.Value_GeoPointValue: |
| return v.GeoPointValue, nil |
| case *pb.Value_ArrayValue: |
| vals := v.ArrayValue.Values |
| ret := make([]interface{}, len(vals)) |
| for i, v := range vals { |
| r, err := createFromProtoValue(v, c) |
| if err != nil { |
| return nil, err |
| } |
| ret[i] = r |
| } |
| return ret, nil |
| |
| case *pb.Value_MapValue: |
| fields := v.MapValue.Fields |
| ret := make(map[string]interface{}, len(fields)) |
| for k, v := range fields { |
| r, err := createFromProtoValue(v, c) |
| if err != nil { |
| return nil, err |
| } |
| ret[k] = r |
| } |
| return ret, nil |
| |
| default: |
| return nil, fmt.Errorf("firestore: unknown value type %T", v) |
| } |
| } |
| |
| // Convert a document path to a DocumentRef. |
| func pathToDoc(docPath string, c *Client) (*DocumentRef, error) { |
| projID, dbID, docIDs, err := parseDocumentPath(docPath) |
| if err != nil { |
| return nil, err |
| } |
| parentResourceName := fmt.Sprintf("projects/%s/databases/%s", projID, dbID) |
| _, doc := c.idsToRef(docIDs, parentResourceName) |
| return doc, nil |
| } |
| |
| // A document path should be of the form "projects/P/databases/D/documents/coll1/doc1/coll2/doc2/...". |
| func parseDocumentPath(path string) (projectID, databaseID string, docPath []string, err error) { |
| parts := strings.Split(path, "/") |
| if len(parts) < 6 || parts[0] != "projects" || parts[2] != "databases" || parts[4] != "documents" { |
| return "", "", nil, fmt.Errorf("firestore: malformed document path %q", path) |
| } |
| docp := parts[5:] |
| if len(docp)%2 != 0 { |
| return "", "", nil, fmt.Errorf("firestore: path %q refers to collection, not document", path) |
| } |
| return parts[1], parts[3], docp, nil |
| } |
| |
| func typeString(vproto *pb.Value) string { |
| switch vproto.ValueType.(type) { |
| case *pb.Value_NullValue: |
| return "null" |
| case *pb.Value_BooleanValue: |
| return "bool" |
| case *pb.Value_IntegerValue: |
| return "int" |
| case *pb.Value_DoubleValue: |
| return "float" |
| case *pb.Value_TimestampValue: |
| return "timestamp" |
| case *pb.Value_StringValue: |
| return "string" |
| case *pb.Value_BytesValue: |
| return "bytes" |
| case *pb.Value_ReferenceValue: |
| return "reference" |
| case *pb.Value_GeoPointValue: |
| return "GeoPoint" |
| case *pb.Value_MapValue: |
| return "map" |
| case *pb.Value_ArrayValue: |
| return "array" |
| default: |
| return "<unknown Value type>" |
| } |
| } |
| |
| func overflowErr(v reflect.Value, x interface{}) error { |
| return fmt.Errorf("firestore: value %v overflows type %s", x, v.Type()) |
| } |