You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

279 lines
8.7 KiB

  1. // Copyright 2017 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package firestore
  15. import (
  16. "errors"
  17. "fmt"
  18. "reflect"
  19. "time"
  20. "cloud.google.com/go/internal/fields"
  21. "github.com/golang/protobuf/ptypes"
  22. ts "github.com/golang/protobuf/ptypes/timestamp"
  23. pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
  24. "google.golang.org/genproto/googleapis/type/latlng"
  25. )
  26. var nullValue = &pb.Value{ValueType: &pb.Value_NullValue{}}
  27. var (
  28. typeOfByteSlice = reflect.TypeOf([]byte{})
  29. typeOfGoTime = reflect.TypeOf(time.Time{})
  30. typeOfLatLng = reflect.TypeOf((*latlng.LatLng)(nil))
  31. typeOfDocumentRef = reflect.TypeOf((*DocumentRef)(nil))
  32. typeOfProtoTimestamp = reflect.TypeOf((*ts.Timestamp)(nil))
  33. )
  34. // toProtoValue converts a Go value to a Firestore Value protobuf.
  35. // Some corner cases:
  36. // - All nils (nil interface, nil slice, nil map, nil pointer) are converted to
  37. // a NullValue (not a nil *pb.Value). toProtoValue never returns (nil, false, nil).
  38. // It returns (nil, true, nil) if everything in the value is ServerTimestamp.
  39. // - An error is returned for uintptr, uint and uint64, because Firestore uses
  40. // an int64 to represent integral values, and those types can't be properly
  41. // represented in an int64.
  42. // - An error is returned for the special Delete value.
  43. func toProtoValue(v reflect.Value) (pbv *pb.Value, sawServerTimestamp bool, err error) {
  44. if !v.IsValid() {
  45. return nullValue, false, nil
  46. }
  47. vi := v.Interface()
  48. if vi == Delete {
  49. return nil, false, errors.New("firestore: cannot use Delete in value")
  50. }
  51. if vi == ServerTimestamp {
  52. return nil, false, errors.New("firestore: must use ServerTimestamp as a map value")
  53. }
  54. switch x := vi.(type) {
  55. case []byte:
  56. return &pb.Value{ValueType: &pb.Value_BytesValue{x}}, false, nil
  57. case time.Time:
  58. ts, err := ptypes.TimestampProto(x)
  59. if err != nil {
  60. return nil, false, err
  61. }
  62. return &pb.Value{ValueType: &pb.Value_TimestampValue{ts}}, false, nil
  63. case *ts.Timestamp:
  64. if x == nil {
  65. // gRPC doesn't like nil oneofs. Use NullValue.
  66. return nullValue, false, nil
  67. }
  68. return &pb.Value{ValueType: &pb.Value_TimestampValue{x}}, false, nil
  69. case *latlng.LatLng:
  70. if x == nil {
  71. // gRPC doesn't like nil oneofs. Use NullValue.
  72. return nullValue, false, nil
  73. }
  74. return &pb.Value{ValueType: &pb.Value_GeoPointValue{x}}, false, nil
  75. case *DocumentRef:
  76. if x == nil {
  77. // gRPC doesn't like nil oneofs. Use NullValue.
  78. return nullValue, false, nil
  79. }
  80. return &pb.Value{ValueType: &pb.Value_ReferenceValue{x.Path}}, false, nil
  81. // Do not add bool, string, int, etc. to this switch; leave them in the
  82. // reflect-based switch below. Moving them here would drop support for
  83. // types whose underlying types are those primitives.
  84. // E.g. Given "type mybool bool", an ordinary type switch on bool will
  85. // not catch a mybool, but the reflect.Kind of a mybool is reflect.Bool.
  86. }
  87. switch v.Kind() {
  88. case reflect.Bool:
  89. return &pb.Value{ValueType: &pb.Value_BooleanValue{v.Bool()}}, false, nil
  90. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  91. return &pb.Value{ValueType: &pb.Value_IntegerValue{v.Int()}}, false, nil
  92. case reflect.Uint8, reflect.Uint16, reflect.Uint32:
  93. return &pb.Value{ValueType: &pb.Value_IntegerValue{int64(v.Uint())}}, false, nil
  94. case reflect.Float32, reflect.Float64:
  95. return &pb.Value{ValueType: &pb.Value_DoubleValue{v.Float()}}, false, nil
  96. case reflect.String:
  97. return &pb.Value{ValueType: &pb.Value_StringValue{v.String()}}, false, nil
  98. case reflect.Slice:
  99. return sliceToProtoValue(v)
  100. case reflect.Map:
  101. return mapToProtoValue(v)
  102. case reflect.Struct:
  103. return structToProtoValue(v)
  104. case reflect.Ptr:
  105. if v.IsNil() {
  106. return nullValue, false, nil
  107. }
  108. return toProtoValue(v.Elem())
  109. case reflect.Interface:
  110. if v.NumMethod() == 0 { // empty interface: recurse on its contents
  111. return toProtoValue(v.Elem())
  112. }
  113. fallthrough // any other interface value is an error
  114. default:
  115. return nil, false, fmt.Errorf("firestore: cannot convert type %s to value", v.Type())
  116. }
  117. }
  118. func sliceToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
  119. // A nil slice is converted to a null value.
  120. if v.IsNil() {
  121. return nullValue, false, nil
  122. }
  123. vals := make([]*pb.Value, v.Len())
  124. for i := 0; i < v.Len(); i++ {
  125. val, sawServerTimestamp, err := toProtoValue(v.Index(i))
  126. if err != nil {
  127. return nil, false, err
  128. }
  129. if sawServerTimestamp {
  130. return nil, false, errors.New("firestore: ServerTimestamp cannot occur in an array")
  131. }
  132. vals[i] = val
  133. }
  134. return &pb.Value{ValueType: &pb.Value_ArrayValue{&pb.ArrayValue{Values: vals}}}, false, nil
  135. }
  136. func mapToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
  137. if v.Type().Key().Kind() != reflect.String {
  138. return nil, false, errors.New("firestore: map key type must be string")
  139. }
  140. // A nil map is converted to a null value.
  141. if v.IsNil() {
  142. return nullValue, false, nil
  143. }
  144. m := map[string]*pb.Value{}
  145. sawServerTimestamp := false
  146. for _, k := range v.MapKeys() {
  147. mi := v.MapIndex(k)
  148. if mi.Interface() == ServerTimestamp {
  149. sawServerTimestamp = true
  150. continue
  151. }
  152. val, sst, err := toProtoValue(mi)
  153. if err != nil {
  154. return nil, false, err
  155. }
  156. if sst {
  157. sawServerTimestamp = true
  158. }
  159. if val == nil { // value was a map with all ServerTimestamp values
  160. continue
  161. }
  162. m[k.String()] = val
  163. }
  164. var pv *pb.Value
  165. if len(m) == 0 && sawServerTimestamp {
  166. // The entire map consisted of ServerTimestamp values.
  167. pv = nil
  168. } else {
  169. pv = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: m}}}
  170. }
  171. return pv, sawServerTimestamp, nil
  172. }
  173. func structToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
  174. m := map[string]*pb.Value{}
  175. fields, err := fieldCache.Fields(v.Type())
  176. if err != nil {
  177. return nil, false, err
  178. }
  179. sawServerTimestamp := false
  180. for _, f := range fields {
  181. fv := v.FieldByIndex(f.Index)
  182. opts := f.ParsedTag.(tagOptions)
  183. if opts.serverTimestamp {
  184. // TODO(jba): should we return a non-zero time?
  185. sawServerTimestamp = true
  186. continue
  187. }
  188. if opts.omitEmpty && isEmptyValue(fv) {
  189. continue
  190. }
  191. val, sst, err := toProtoValue(fv)
  192. if err != nil {
  193. return nil, false, err
  194. }
  195. if sst {
  196. sawServerTimestamp = true
  197. }
  198. if val == nil { // value was a map with all ServerTimestamp values
  199. continue
  200. }
  201. m[f.Name] = val
  202. }
  203. var pv *pb.Value
  204. if len(m) == 0 && sawServerTimestamp {
  205. // The entire struct consisted of ServerTimestamp or omitempty values.
  206. pv = nil
  207. } else {
  208. pv = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: m}}}
  209. }
  210. return pv, sawServerTimestamp, nil
  211. }
  212. type tagOptions struct {
  213. omitEmpty bool // do not marshal value if empty
  214. serverTimestamp bool // set time.Time to server timestamp on write
  215. }
  216. // parseTag interprets firestore struct field tags.
  217. func parseTag(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
  218. name, keep, opts, err := fields.ParseStandardTag("firestore", t)
  219. if err != nil {
  220. return "", false, nil, fmt.Errorf("firestore: %v", err)
  221. }
  222. tagOpts := tagOptions{}
  223. for _, opt := range opts {
  224. switch opt {
  225. case "omitempty":
  226. tagOpts.omitEmpty = true
  227. case "serverTimestamp":
  228. tagOpts.serverTimestamp = true
  229. default:
  230. return "", false, nil, fmt.Errorf("firestore: unknown tag option: %q", opt)
  231. }
  232. }
  233. return name, keep, tagOpts, nil
  234. }
  235. // isLeafType determines whether or not a type is a 'leaf type'
  236. // and should not be recursed into, but considered one field.
  237. func isLeafType(t reflect.Type) bool {
  238. return t == typeOfGoTime || t == typeOfLatLng || t == typeOfProtoTimestamp
  239. }
  240. var fieldCache = fields.NewCache(parseTag, nil, isLeafType)
  241. // isEmptyValue is taken from the encoding/json package in the
  242. // standard library.
  243. // TODO(jba): move to the fields package
  244. func isEmptyValue(v reflect.Value) bool {
  245. switch v.Kind() {
  246. case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
  247. return v.Len() == 0
  248. case reflect.Bool:
  249. return !v.Bool()
  250. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  251. return v.Int() == 0
  252. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  253. return v.Uint() == 0
  254. case reflect.Float32, reflect.Float64:
  255. return v.Float() == 0
  256. case reflect.Interface, reflect.Ptr:
  257. return v.IsNil()
  258. }
  259. if v.Type() == typeOfGoTime {
  260. return v.Interface().(time.Time).IsZero()
  261. }
  262. return false
  263. }