restic/vendor/cloud.google.com/go/firestore/from_value.go

424 lines
11 KiB
Go

// 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 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())
}