|
- // 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"
-
- "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 {
- fields, err := fieldCache.Fields(vs.Type())
- if err != nil {
- return err
- }
- for k, vproto := range pm {
- f := fields.Match(k)
- if f == nil {
- continue
- }
- 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())
- }
|