// Copyright 2017 Google Inc. All Rights Reserved. // // 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" pb "google.golang.org/genproto/googleapis/firestore/v1beta1" "github.com/golang/protobuf/ptypes" ) 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 "" } } func overflowErr(v reflect.Value, x interface{}) error { return fmt.Errorf("firestore: value %v overflows type %s", x, v.Type()) }