mirror of
https://github.com/octoleo/restic.git
synced 2024-12-01 17:23:57 +00:00
401 lines
10 KiB
Go
401 lines
10 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:
|
|
x, ok := val.(*pb.Value_IntegerValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
i := x.IntegerValue
|
|
if v.OverflowInt(i) {
|
|
return overflowErr(v, i)
|
|
}
|
|
v.SetInt(i)
|
|
|
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
|
x, ok := val.(*pb.Value_IntegerValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
u := uint64(x.IntegerValue)
|
|
if v.OverflowUint(u) {
|
|
return overflowErr(v, u)
|
|
}
|
|
v.SetUint(u)
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
x, ok := val.(*pb.Value_DoubleValue)
|
|
if !ok {
|
|
return typeErr()
|
|
}
|
|
f := x.DoubleValue
|
|
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())
|
|
}
|