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.
 
 
 

300 lines
9.5 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/v1"
  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. //
  44. // toProtoValue also reports whether it recursively encountered a transform.
  45. func toProtoValue(v reflect.Value) (pbv *pb.Value, sawTransform bool, err error) {
  46. if !v.IsValid() {
  47. return nullValue, false, nil
  48. }
  49. vi := v.Interface()
  50. if vi == Delete {
  51. return nil, false, errors.New("firestore: cannot use Delete in value")
  52. }
  53. if vi == ServerTimestamp {
  54. return nil, false, errors.New("firestore: must use ServerTimestamp as a map value")
  55. }
  56. switch x := vi.(type) {
  57. case []byte:
  58. return &pb.Value{ValueType: &pb.Value_BytesValue{x}}, false, nil
  59. case time.Time:
  60. ts, err := ptypes.TimestampProto(x)
  61. if err != nil {
  62. return nil, false, err
  63. }
  64. return &pb.Value{ValueType: &pb.Value_TimestampValue{ts}}, false, nil
  65. case *ts.Timestamp:
  66. if x == nil {
  67. // gRPC doesn't like nil oneofs. Use NullValue.
  68. return nullValue, false, nil
  69. }
  70. return &pb.Value{ValueType: &pb.Value_TimestampValue{x}}, false, nil
  71. case *latlng.LatLng:
  72. if x == nil {
  73. // gRPC doesn't like nil oneofs. Use NullValue.
  74. return nullValue, false, nil
  75. }
  76. return &pb.Value{ValueType: &pb.Value_GeoPointValue{x}}, false, nil
  77. case *DocumentRef:
  78. if x == nil {
  79. // gRPC doesn't like nil oneofs. Use NullValue.
  80. return nullValue, false, nil
  81. }
  82. return &pb.Value{ValueType: &pb.Value_ReferenceValue{x.Path}}, false, nil
  83. // Do not add bool, string, int, etc. to this switch; leave them in the
  84. // reflect-based switch below. Moving them here would drop support for
  85. // types whose underlying types are those primitives.
  86. // E.g. Given "type mybool bool", an ordinary type switch on bool will
  87. // not catch a mybool, but the reflect.Kind of a mybool is reflect.Bool.
  88. }
  89. switch v.Kind() {
  90. case reflect.Bool:
  91. return &pb.Value{ValueType: &pb.Value_BooleanValue{v.Bool()}}, false, nil
  92. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  93. return &pb.Value{ValueType: &pb.Value_IntegerValue{v.Int()}}, false, nil
  94. case reflect.Uint8, reflect.Uint16, reflect.Uint32:
  95. return &pb.Value{ValueType: &pb.Value_IntegerValue{int64(v.Uint())}}, false, nil
  96. case reflect.Float32, reflect.Float64:
  97. return &pb.Value{ValueType: &pb.Value_DoubleValue{v.Float()}}, false, nil
  98. case reflect.String:
  99. return &pb.Value{ValueType: &pb.Value_StringValue{v.String()}}, false, nil
  100. case reflect.Slice:
  101. return sliceToProtoValue(v)
  102. case reflect.Map:
  103. return mapToProtoValue(v)
  104. case reflect.Struct:
  105. return structToProtoValue(v)
  106. case reflect.Ptr:
  107. if v.IsNil() {
  108. return nullValue, false, nil
  109. }
  110. return toProtoValue(v.Elem())
  111. case reflect.Interface:
  112. if v.NumMethod() == 0 { // empty interface: recurse on its contents
  113. return toProtoValue(v.Elem())
  114. }
  115. fallthrough // any other interface value is an error
  116. default:
  117. return nil, false, fmt.Errorf("firestore: cannot convert type %s to value", v.Type())
  118. }
  119. }
  120. // sliceToProtoValue converts a slice to a Firestore Value protobuf and reports
  121. // whether a transform was encountered.
  122. func sliceToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
  123. // A nil slice is converted to a null value.
  124. if v.IsNil() {
  125. return nullValue, false, nil
  126. }
  127. vals := make([]*pb.Value, v.Len())
  128. for i := 0; i < v.Len(); i++ {
  129. val, sawTransform, err := toProtoValue(v.Index(i))
  130. if err != nil {
  131. return nil, false, err
  132. }
  133. if sawTransform {
  134. return nil, false, fmt.Errorf("firestore: transforms cannot occur in an array, but saw some in %v", v.Index(i))
  135. }
  136. vals[i] = val
  137. }
  138. return &pb.Value{ValueType: &pb.Value_ArrayValue{&pb.ArrayValue{Values: vals}}}, false, nil
  139. }
  140. // mapToProtoValue converts a map to a Firestore Value protobuf and reports whether
  141. // a transform was encountered.
  142. func mapToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
  143. if v.Type().Key().Kind() != reflect.String {
  144. return nil, false, errors.New("firestore: map key type must be string")
  145. }
  146. // A nil map is converted to a null value.
  147. if v.IsNil() {
  148. return nullValue, false, nil
  149. }
  150. m := map[string]*pb.Value{}
  151. sawTransform := false
  152. for _, k := range v.MapKeys() {
  153. mi := v.MapIndex(k)
  154. if mi.Interface() == ServerTimestamp {
  155. sawTransform = true
  156. continue
  157. } else if _, ok := mi.Interface().(arrayUnion); ok {
  158. sawTransform = true
  159. continue
  160. } else if _, ok := mi.Interface().(arrayRemove); ok {
  161. sawTransform = true
  162. continue
  163. }
  164. val, sst, err := toProtoValue(mi)
  165. if err != nil {
  166. return nil, false, err
  167. }
  168. if sst {
  169. sawTransform = true
  170. }
  171. if val == nil { // value was a map with all ServerTimestamp values
  172. continue
  173. }
  174. m[k.String()] = val
  175. }
  176. var pv *pb.Value
  177. if len(m) == 0 && sawTransform {
  178. // The entire map consisted of transform values.
  179. pv = nil
  180. } else {
  181. pv = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: m}}}
  182. }
  183. return pv, sawTransform, nil
  184. }
  185. // structToProtoValue converts a struct to a Firestore Value protobuf and reports
  186. // whether a transform was encountered.
  187. func structToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
  188. m := map[string]*pb.Value{}
  189. fields, err := fieldCache.Fields(v.Type())
  190. if err != nil {
  191. return nil, false, err
  192. }
  193. sawTransform := false
  194. if _, ok := v.Interface().(arrayUnion); ok {
  195. return nil, false, errors.New("firestore: ArrayUnion may not be used in structs")
  196. }
  197. if _, ok := v.Interface().(arrayRemove); ok {
  198. return nil, false, errors.New("firestore: ArrayRemove may not be used in structs")
  199. }
  200. for _, f := range fields {
  201. fv := v.FieldByIndex(f.Index)
  202. opts := f.ParsedTag.(tagOptions)
  203. if opts.serverTimestamp {
  204. // TODO(jba): should we return a non-zero time?
  205. sawTransform = true
  206. continue
  207. }
  208. if opts.omitEmpty && isEmptyValue(fv) {
  209. continue
  210. }
  211. val, sst, err := toProtoValue(fv)
  212. if err != nil {
  213. return nil, false, err
  214. }
  215. if sst {
  216. sawTransform = true
  217. }
  218. if val == nil { // value was a map with all ServerTimestamp values
  219. continue
  220. }
  221. m[f.Name] = val
  222. }
  223. var pv *pb.Value
  224. if len(m) == 0 && sawTransform {
  225. // The entire struct consisted of ServerTimestamp or omitempty values.
  226. pv = nil
  227. } else {
  228. pv = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: m}}}
  229. }
  230. return pv, sawTransform, nil
  231. }
  232. type tagOptions struct {
  233. omitEmpty bool // do not marshal value if empty
  234. serverTimestamp bool // set time.Time to server timestamp on write
  235. }
  236. // parseTag interprets firestore struct field tags.
  237. func parseTag(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
  238. name, keep, opts, err := fields.ParseStandardTag("firestore", t)
  239. if err != nil {
  240. return "", false, nil, fmt.Errorf("firestore: %v", err)
  241. }
  242. tagOpts := tagOptions{}
  243. for _, opt := range opts {
  244. switch opt {
  245. case "omitempty":
  246. tagOpts.omitEmpty = true
  247. case "serverTimestamp":
  248. tagOpts.serverTimestamp = true
  249. default:
  250. return "", false, nil, fmt.Errorf("firestore: unknown tag option: %q", opt)
  251. }
  252. }
  253. return name, keep, tagOpts, nil
  254. }
  255. // isLeafType determines whether or not a type is a 'leaf type'
  256. // and should not be recursed into, but considered one field.
  257. func isLeafType(t reflect.Type) bool {
  258. return t == typeOfGoTime || t == typeOfLatLng || t == typeOfProtoTimestamp
  259. }
  260. var fieldCache = fields.NewCache(parseTag, nil, isLeafType)
  261. // isEmptyValue is taken from the encoding/json package in the
  262. // standard library.
  263. // TODO(jba): move to the fields package
  264. func isEmptyValue(v reflect.Value) bool {
  265. switch v.Kind() {
  266. case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
  267. return v.Len() == 0
  268. case reflect.Bool:
  269. return !v.Bool()
  270. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  271. return v.Int() == 0
  272. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  273. return v.Uint() == 0
  274. case reflect.Float32, reflect.Float64:
  275. return v.Float() == 0
  276. case reflect.Interface, reflect.Ptr:
  277. return v.IsNil()
  278. }
  279. if v.Type() == typeOfGoTime {
  280. return v.Interface().(time.Time).IsZero()
  281. }
  282. return false
  283. }