bson 4.2.2 → 4.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +25 -7
  5. data/Rakefile +16 -9
  6. data/ext/bson/{native-endian.h → bson-endian.h} +5 -99
  7. data/ext/bson/bson-native.h +125 -0
  8. data/ext/bson/bytebuf.c +133 -0
  9. data/ext/bson/endian.c +117 -0
  10. data/ext/bson/init.c +355 -0
  11. data/ext/bson/libbson-utf8.c +230 -0
  12. data/ext/bson/read.c +411 -0
  13. data/ext/bson/util.c +95 -0
  14. data/ext/bson/write.c +680 -0
  15. data/lib/bson.rb +6 -3
  16. data/lib/bson/active_support.rb +17 -0
  17. data/lib/bson/array.rb +57 -17
  18. data/lib/bson/binary.rb +185 -13
  19. data/lib/bson/boolean.rb +12 -3
  20. data/lib/bson/code.rb +16 -2
  21. data/lib/bson/code_with_scope.rb +32 -5
  22. data/lib/bson/config.rb +1 -1
  23. data/lib/bson/date.rb +12 -2
  24. data/lib/bson/date_time.rb +2 -2
  25. data/lib/bson/db_pointer.rb +110 -0
  26. data/lib/bson/decimal128.rb +17 -3
  27. data/lib/bson/decimal128/builder.rb +1 -1
  28. data/lib/bson/document.rb +152 -5
  29. data/lib/bson/environment.rb +2 -1
  30. data/lib/bson/error.rb +27 -0
  31. data/lib/bson/ext_json.rb +383 -0
  32. data/lib/bson/false_class.rb +1 -1
  33. data/lib/bson/float.rb +48 -2
  34. data/lib/bson/hash.rb +68 -17
  35. data/lib/bson/int32.rb +52 -13
  36. data/lib/bson/int64.rb +59 -15
  37. data/lib/bson/integer.rb +36 -2
  38. data/lib/bson/json.rb +1 -1
  39. data/lib/bson/max_key.rb +13 -1
  40. data/lib/bson/min_key.rb +13 -1
  41. data/lib/bson/nil_class.rb +4 -2
  42. data/lib/bson/object.rb +28 -1
  43. data/lib/bson/object_id.rb +16 -2
  44. data/lib/bson/open_struct.rb +1 -1
  45. data/lib/bson/regexp.rb +27 -4
  46. data/lib/bson/registry.rb +3 -3
  47. data/lib/bson/specialized.rb +4 -2
  48. data/lib/bson/string.rb +5 -3
  49. data/lib/bson/symbol.rb +99 -7
  50. data/lib/bson/time.rb +63 -4
  51. data/lib/bson/time_with_zone.rb +54 -0
  52. data/lib/bson/timestamp.rb +44 -6
  53. data/lib/bson/true_class.rb +1 -1
  54. data/lib/bson/undefined.rb +12 -1
  55. data/lib/bson/version.rb +2 -2
  56. data/spec/bson/array_spec.rb +18 -1
  57. data/spec/bson/binary_spec.rb +100 -3
  58. data/spec/bson/binary_uuid_spec.rb +189 -0
  59. data/spec/bson/boolean_spec.rb +1 -1
  60. data/spec/bson/byte_buffer_read_spec.rb +197 -0
  61. data/spec/bson/byte_buffer_spec.rb +121 -381
  62. data/spec/bson/byte_buffer_write_spec.rb +854 -0
  63. data/spec/bson/code_spec.rb +1 -1
  64. data/spec/bson/code_with_scope_spec.rb +1 -1
  65. data/spec/bson/date_spec.rb +1 -1
  66. data/spec/bson/date_time_spec.rb +54 -1
  67. data/spec/bson/decimal128_spec.rb +35 -35
  68. data/spec/bson/document_as_spec.rb +46 -0
  69. data/spec/bson/document_spec.rb +197 -30
  70. data/spec/bson/ext_json_parse_spec.rb +308 -0
  71. data/spec/bson/false_class_spec.rb +1 -1
  72. data/spec/bson/float_spec.rb +37 -1
  73. data/spec/bson/hash_as_spec.rb +57 -0
  74. data/spec/bson/hash_spec.rb +209 -1
  75. data/spec/bson/int32_spec.rb +180 -6
  76. data/spec/bson/int64_spec.rb +199 -6
  77. data/spec/bson/integer_spec.rb +29 -3
  78. data/spec/bson/json_spec.rb +1 -1
  79. data/spec/bson/max_key_spec.rb +1 -1
  80. data/spec/bson/min_key_spec.rb +1 -1
  81. data/spec/bson/nil_class_spec.rb +1 -1
  82. data/spec/bson/object_id_spec.rb +1 -1
  83. data/spec/bson/object_spec.rb +1 -1
  84. data/spec/bson/open_struct_spec.rb +1 -1
  85. data/spec/bson/raw_spec.rb +34 -2
  86. data/spec/bson/regexp_spec.rb +1 -1
  87. data/spec/bson/registry_spec.rb +1 -1
  88. data/spec/bson/string_spec.rb +19 -1
  89. data/spec/bson/symbol_raw_spec.rb +45 -0
  90. data/spec/bson/symbol_spec.rb +63 -3
  91. data/spec/bson/time_spec.rb +205 -2
  92. data/spec/bson/time_with_zone_spec.rb +68 -0
  93. data/spec/bson/timestamp_spec.rb +56 -1
  94. data/spec/bson/true_class_spec.rb +1 -1
  95. data/spec/bson/undefined_spec.rb +1 -1
  96. data/spec/bson_spec.rb +1 -1
  97. data/spec/{support → runners}/common_driver.rb +1 -1
  98. data/spec/runners/corpus.rb +185 -0
  99. data/spec/{support/corpus.rb → runners/corpus_legacy.rb} +41 -59
  100. data/spec/spec_helper.rb +40 -3
  101. data/spec/{bson/driver_bson_spec.rb → spec_tests/common_driver_spec.rb} +1 -0
  102. data/spec/{bson/corpus_spec.rb → spec_tests/corpus_legacy_spec.rb} +10 -7
  103. data/spec/spec_tests/corpus_spec.rb +124 -0
  104. data/spec/spec_tests/data/corpus/README.md +15 -0
  105. data/spec/spec_tests/data/corpus/array.json +49 -0
  106. data/spec/spec_tests/data/corpus/binary.json +113 -0
  107. data/spec/spec_tests/data/corpus/boolean.json +27 -0
  108. data/spec/spec_tests/data/corpus/code.json +67 -0
  109. data/spec/spec_tests/data/corpus/code_w_scope.json +78 -0
  110. data/spec/spec_tests/data/corpus/datetime.json +42 -0
  111. data/spec/spec_tests/data/corpus/dbpointer.json +56 -0
  112. data/spec/spec_tests/data/corpus/dbref.json +31 -0
  113. data/spec/spec_tests/data/corpus/decimal128-1.json +317 -0
  114. data/spec/spec_tests/data/corpus/decimal128-2.json +793 -0
  115. data/spec/spec_tests/data/corpus/decimal128-3.json +1771 -0
  116. data/spec/spec_tests/data/corpus/decimal128-4.json +117 -0
  117. data/spec/spec_tests/data/corpus/decimal128-5.json +402 -0
  118. data/spec/spec_tests/data/corpus/decimal128-6.json +119 -0
  119. data/spec/spec_tests/data/corpus/decimal128-7.json +323 -0
  120. data/spec/spec_tests/data/corpus/document.json +36 -0
  121. data/spec/spec_tests/data/corpus/double.json +87 -0
  122. data/spec/spec_tests/data/corpus/int32.json +43 -0
  123. data/spec/spec_tests/data/corpus/int64.json +43 -0
  124. data/spec/spec_tests/data/corpus/maxkey.json +12 -0
  125. data/spec/spec_tests/data/corpus/minkey.json +12 -0
  126. data/spec/spec_tests/data/corpus/multi-type-deprecated.json +15 -0
  127. data/spec/spec_tests/data/corpus/multi-type.json +11 -0
  128. data/spec/spec_tests/data/corpus/null.json +12 -0
  129. data/spec/spec_tests/data/corpus/oid.json +28 -0
  130. data/spec/spec_tests/data/corpus/regex.json +65 -0
  131. data/spec/spec_tests/data/corpus/string.json +72 -0
  132. data/spec/spec_tests/data/corpus/symbol.json +80 -0
  133. data/spec/spec_tests/data/corpus/timestamp.json +34 -0
  134. data/spec/spec_tests/data/corpus/top.json +236 -0
  135. data/spec/spec_tests/data/corpus/undefined.json +15 -0
  136. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/array.json +8 -2
  137. data/spec/{support/corpus-tests/failures → spec_tests/data/corpus_legacy}/binary.json +0 -0
  138. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/boolean.json +0 -0
  139. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/code.json +1 -1
  140. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/code_w_scope.json +1 -1
  141. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/document.json +1 -1
  142. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/double.json +1 -1
  143. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/datetime.json +0 -0
  144. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/dbpointer.json +0 -0
  145. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/int64.json +0 -0
  146. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/symbol.json +0 -0
  147. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/int32.json +1 -1
  148. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/maxkey.json +1 -1
  149. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/minkey.json +1 -1
  150. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/null.json +1 -1
  151. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/oid.json +0 -0
  152. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/regex.json +1 -1
  153. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/string.json +0 -0
  154. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/timestamp.json +1 -1
  155. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/top.json +0 -0
  156. data/spec/{support/corpus-tests/failures → spec_tests/data/corpus_legacy}/undefined.json +0 -0
  157. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-1.json +0 -0
  158. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-2.json +0 -0
  159. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-3.json +0 -0
  160. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-4.json +0 -0
  161. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-5.json +0 -0
  162. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-6.json +0 -0
  163. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-7.json +0 -0
  164. data/spec/support/shared_examples.rb +3 -5
  165. data/spec/support/spec_config.rb +16 -0
  166. data/spec/support/utils.rb +10 -0
  167. metadata +227 -124
  168. metadata.gz.sig +0 -0
  169. data/ext/bson/bson_native.c +0 -762
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009-2014 MongoDB Inc.
1
+ # Copyright (C) 2009-2020 MongoDB Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@ module BSON
41
41
  # @return [ true, false ] If the Ruby version is 1.9.
42
42
  #
43
43
  # @since 4.2.0
44
+ # @deprecated
44
45
  def ruby_1_9?
45
46
  @ruby_1_9 ||= RUBY_VERSION < '2.0.0'
46
47
  end
data/lib/bson/error.rb ADDED
@@ -0,0 +1,27 @@
1
+ module BSON
2
+ # Base exception class for all BSON-related errors.
3
+ #
4
+ # @note Many existing exceptions raised by bson-ruby do not derive from
5
+ # this base class. This will change in the next major version (5.0).
6
+ class Error < StandardError
7
+
8
+ # Exception raised when Extended JSON parsing fails.
9
+ class ExtJSONParseError < Error
10
+ end
11
+
12
+ # Exception raised when decoding BSON and the data contains an
13
+ # unsupported binary subtype.
14
+ class UnsupportedBinarySubtype < Error
15
+ end
16
+
17
+ # Exception raised when BSON decoding fails.
18
+ class BSONDecodeError < Error
19
+ end
20
+
21
+ # Exception raised when serializing an Array or Hash to BSON and an
22
+ # array or hash element is of a class that does not define how to serialize
23
+ # itself to BSON.
24
+ class UnserializableClass < Error
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,383 @@
1
+ # Copyright (C) 2019-2020 MongoDB Inc.
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
+
15
+ require 'json'
16
+
17
+ module BSON
18
+
19
+ # This module contains methods for parsing Extended JSON 2.0.
20
+ # https://github.com/mongodb/specifications/blob/master/source/extended-json.rst
21
+ module ExtJSON
22
+
23
+ # Parses JSON in a string into a Ruby object tree.
24
+ #
25
+ # There are two strategies that this method can follow. If the canonical
26
+ # strategy is used which is the default, this method returns BSON types
27
+ # as much as possible. This allows the resulting object tree to be
28
+ # serialized back to extended JSON or to BSON while preserving the types.
29
+ # The relaxed strategy, enabled by passing {emit_relaxed: true} option,
30
+ # returns native Ruby types as much as possible which makes the resulting
31
+ # object tree easier to work with but may lose type information.
32
+ #
33
+ # Please note the following aspects of this method when emitting relaxed
34
+ # object trees:
35
+ #
36
+ # 1. $numberInt and $numberLong inputs produce Integer instances.
37
+ # 2. $regularExpression inputs produce BSON Regexp instances. This may
38
+ # change in a future version of bson-ruby to produce Ruby Regexp
39
+ # instances, potentially depending on regular expression options.
40
+ # 3. $numberDecimal inputs produce BSON Decimal128 instances. This may
41
+ # change in a future version of bson-ruby to produce Ruby BigDecimal
42
+ # instances instead.
43
+ #
44
+ # This method accepts canonical extended JSON, relaxed extended JSON and
45
+ # JSON without type information as well as a mix of the above.
46
+ #
47
+ # @note This method uses Ruby standard library's JSON.parse method to
48
+ # perform JSON parsing. As the JSON.parse method accepts inputs other
49
+ # than hashes, so does this method and therefore this method can return
50
+ # objects of any type.
51
+ #
52
+ # @param [ String ] str The string to parse.
53
+ #
54
+ # @option options [ nil | :bson ] :mode Which types to emit
55
+ #
56
+ # @return [ Object ] Parsed object tree.
57
+ module_function def parse(str, **options)
58
+ parse_obj(::JSON.parse(str), **options)
59
+ end
60
+
61
+ # Transforms a Ruby object tree containing extended JSON type hashes
62
+ # into a Ruby object tree with said hashes replaced by BSON or Ruby native
63
+ # types.
64
+ #
65
+ # @example Convert extended JSON type hashes:
66
+ # BSON::ExtJSON.parse_obj('foo' => {'$numberLong' => '42'})
67
+ # => {"foo"=>#<BSON::Int64:0x000055e55f4d40f0 @value=42>}
68
+ #
69
+ # @example Convert a non-hash value:
70
+ # BSON::ExtJSON.parse_obj('$numberLong' => '42')
71
+ # => #<BSON::Int64:0x000055e55f4e6ed0 @value=42>
72
+ #
73
+ # There are two strategies that this method can follow. If the canonical
74
+ # strategy is used which is the default, this method returns BSON types
75
+ # as much as possible. This allows the resulting object tree to be
76
+ # serialized back to extended JSON or to BSON while preserving the types.
77
+ # The relaxed strategy, enabled by passing {emit_relaxed: true} option,
78
+ # returns native Ruby types as much as possible which makes the resulting
79
+ # object tree easier to work with but may lose type information.
80
+ #
81
+ # Please note the following aspects of this method when emitting relaxed
82
+ # object trees:
83
+ #
84
+ # 1. $numberInt and $numberLong inputs produce Integer instances.
85
+ # 2. $regularExpression inputs produce BSON Regexp instances. This may
86
+ # change in a future version of bson-ruby to produce Ruby Regexp
87
+ # instances, potentially depending on regular expression options.
88
+ # 3. $numberDecimal inputs produce BSON Decimal128 instances. This may
89
+ # change in a future version of bson-ruby to produce Ruby BigDecimal
90
+ # instances instead.
91
+ #
92
+ # This method accepts object trees resulting from parsing canonical
93
+ # extended JSON, relaxed extended JSON and JSON without type information
94
+ # as well as a mix of the above.
95
+ #
96
+ # @note This method accepts any types as input, not just Hash instances.
97
+ # Consequently, it can return values of any type.
98
+ #
99
+ # @param [ Object ] value The object tree to convert.
100
+ #
101
+ # @option options [ nil | :bson ] :mode Which types to emit
102
+ #
103
+ # @return [ Object ] Converted object tree.
104
+ module_function def parse_obj(value, **options)
105
+ # TODO implement :ruby and :ruby! modes
106
+ unless [nil, :bson].include?(options[:mode])
107
+ raise ArgumentError, "Invalid value for :mode option: #{options[:mode].inspect}"
108
+ end
109
+
110
+ case value
111
+ when String, TrueClass, FalseClass, NilClass, Numeric
112
+ value
113
+ when Hash
114
+ parse_hash(value, **options)
115
+ when Array
116
+ value.map do |item|
117
+ parse_obj(item, **options)
118
+ end
119
+ else
120
+ raise Error::ExtJSONParseError, "Unknown value type: #{value}"
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ RESERVED_KEYS = %w(
127
+ $oid $symbol $numberInt $numberLong $numberDouble $numberDecimal
128
+ $binary $code $scope $timestamp $regularExpression $dbPointer
129
+ $date $minKey $maxKey $undefined
130
+ ).freeze
131
+
132
+ RESERVED_KEYS_HASH = Hash[RESERVED_KEYS.map do |key|
133
+ [key, true]
134
+ end].freeze
135
+
136
+ module_function def parse_hash(hash, **options)
137
+ if hash.empty?
138
+ return {}
139
+ end
140
+
141
+ if hash.key?('$ref')
142
+ # Legacy dbref handling.
143
+ # Note that according to extended json spec, only hash values (but
144
+ # not the top-level BSON document itself) may be of type "dbref".
145
+ # This code applies to both hash values and the hash overall; however,
146
+ # since we do not have DBRef as a distinct type, applying the below
147
+ # logic to top level hashes doesn't cause harm.
148
+ hash = hash.dup
149
+ ref = hash.delete('$ref')
150
+ # $ref can be a string value or an ObjectId
151
+ unless ref.is_a?(String)
152
+ raise Error::ExtJSONParseError, "Invalid $ref value: #{ref}"
153
+ end
154
+ # $id, if present, can be anything
155
+ id = hash.delete('$id')
156
+ if id.is_a?(Hash)
157
+ id = parse_hash(id)
158
+ end
159
+ # Preserve $id value as it was, do not convert either to ObjectId
160
+ # or to a string. But if the value was in {'$oid' => ...} format,
161
+ # the value is converted to an ObjectId instance so that
162
+ # serialization to BSON later on works correctly.
163
+ out = {'$ref' => ref, '$id' => id}
164
+ if hash.key?('$db')
165
+ # $db must always be a string, if provided
166
+ db = hash.delete('$db')
167
+ unless db.is_a?(String)
168
+ raise Error::ExtJSONParseError, "Invalid $db value: #{db}"
169
+ end
170
+ out['$db'] = db
171
+ end
172
+ return out.update(hash)
173
+ end
174
+
175
+ if hash.length == 1
176
+ key, value = hash.first
177
+ return case key
178
+ when '$oid'
179
+ ObjectId.from_string(value)
180
+ when '$symbol'
181
+ Symbol::Raw.new(value)
182
+ when '$numberInt'
183
+ unless value.is_a?(String)
184
+ raise Error::ExtJSONParseError, "$numberInt value is of an incorrect type: #{value}"
185
+ end
186
+ value.to_i
187
+ when '$numberLong'
188
+ unless value.is_a?(String)
189
+ raise Error::ExtJSONParseError, "$numberLong value is of an incorrect type: #{value}"
190
+ end
191
+ value = value.to_i
192
+ if options[:mode] != :bson
193
+ value
194
+ else
195
+ Int64.new(value)
196
+ end
197
+ when '$numberDouble'
198
+ # This handles string to double conversion as well as inf/-inf/nan
199
+ unless value.is_a?(String)
200
+ raise Error::ExtJSONParseError, "Invalid $numberDouble value: #{value}"
201
+ end
202
+ BigDecimal(value).to_f
203
+ when '$numberDecimal'
204
+ # TODO consider returning BigDecimal here instead of Decimal128
205
+ Decimal128.new(value)
206
+ when '$binary'
207
+ unless value.is_a?(Hash)
208
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{value}"
209
+ end
210
+ unless value.keys.sort == %w(base64 subType)
211
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{value}"
212
+ end
213
+ encoded_value = value['base64']
214
+ unless encoded_value.is_a?(String)
215
+ raise Error::ExtJSONParseError, "Invalid base64 value in $binary: #{value}"
216
+ end
217
+ subtype = value['subType']
218
+ unless subtype.is_a?(String)
219
+ raise Error::ExtJSONParseError, "Invalid subType value in $binary: #{value}"
220
+ end
221
+ create_binary(encoded_value, subtype)
222
+
223
+ when '$uuid'
224
+ unless /\A[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\z/.match(value)
225
+ raise Error::ExtJSONParseError, "Invalid $uuid value: #{value}"
226
+ end
227
+
228
+ return Binary.from_uuid(value)
229
+
230
+ when '$code'
231
+ unless value.is_a?(String)
232
+ raise Error::ExtJSONParseError, "Invalid $code value: #{value}"
233
+ end
234
+ Code.new(value)
235
+ when '$timestamp'
236
+ unless value.keys.sort == %w(i t)
237
+ raise Error::ExtJSONParseError, "Invalid $timestamp value: #{value}"
238
+ end
239
+ t = value['t']
240
+ unless t.is_a?(Integer)
241
+ raise Error::ExtJSONParseError, "Invalid t value: #{value}"
242
+ end
243
+ i = value['i']
244
+ unless i.is_a?(Integer)
245
+ raise Error::ExtJSONParseError, "Invalid i value: #{value}"
246
+ end
247
+ Timestamp.new(t, i)
248
+ when '$regularExpression'
249
+ unless value.keys.sort == %w(options pattern)
250
+ raise Error::ExtJSONParseError, "Invalid $regularExpression value: #{value}"
251
+ end
252
+ # TODO consider returning Ruby regular expression object here
253
+ create_regexp(value['pattern'], value['options'])
254
+ when '$dbPointer'
255
+ unless value.keys.sort == %w($id $ref)
256
+ raise Error::ExtJSONParseError, "Invalid $dbPointer value: #{value}"
257
+ end
258
+ DbPointer.new(value['$ref'], parse_hash(value['$id']))
259
+ when '$date'
260
+ case value
261
+ when String
262
+ ::Time.parse(value).utc
263
+ when Hash
264
+ unless value.keys.sort == %w($numberLong)
265
+ raise Error::ExtJSONParseError, "Invalid value for $date: #{value}"
266
+ end
267
+ sec, msec = value.values.first.to_i.divmod(1000)
268
+ ::Time.at(sec, msec*1000).utc
269
+ else
270
+ raise Error::ExtJSONParseError, "Invalid value for $date: #{value}"
271
+ end
272
+ when '$minKey'
273
+ unless value == 1
274
+ raise Error::ExtJSONParseError, "Invalid $minKey value: #{value}"
275
+ end
276
+ MinKey.new
277
+ when '$maxKey'
278
+ unless value == 1
279
+ raise Error::ExtJSONParseError, "Invalid $maxKey value: #{value}"
280
+ end
281
+ MaxKey.new
282
+ when '$undefined'
283
+ unless value == true
284
+ raise Error::ExtJSONParseError, "Invalid $undefined value: #{value}"
285
+ end
286
+ Undefined.new
287
+ else
288
+ map_hash(hash, **options)
289
+ end
290
+ end
291
+
292
+ if hash.length == 2
293
+ sorted_keys = hash.keys.sort
294
+ first_key = sorted_keys.first
295
+ last_key = sorted_keys.last
296
+
297
+ if first_key == '$code'
298
+ unless sorted_keys == %w($code $scope)
299
+ raise Error::ExtJSONParseError, "Invalid $code value: #{hash}"
300
+ end
301
+ unless hash['$code'].is_a?(String)
302
+ raise Error::ExtJSONParseError, "Invalid $code value: #{value}"
303
+ end
304
+
305
+ return CodeWithScope.new(hash['$code'], map_hash(hash['$scope']))
306
+ end
307
+
308
+ if first_key == '$binary'
309
+ unless sorted_keys == %w($binary $type)
310
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{hash}"
311
+ end
312
+ unless hash['$binary'].is_a?(String)
313
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{value}"
314
+ end
315
+ unless hash['$type'].is_a?(String)
316
+ raise Error::ExtJSONParseError, "Invalid $binary subtype: #{hash['$type']}"
317
+ end
318
+
319
+ return create_binary(hash['$binary'], hash['$type'])
320
+ end
321
+
322
+ if last_key == '$regex'
323
+ unless sorted_keys == %w($options $regex)
324
+ raise Error::ExtJSONParseError, "Invalid $regex value: #{hash}"
325
+ end
326
+
327
+ if hash['$regex'].is_a?(Hash)
328
+ return {
329
+ '$regex' => parse_hash(hash['$regex']),
330
+ '$options' => hash['$options']
331
+ }
332
+ end
333
+
334
+ unless hash['$regex'].is_a?(String)
335
+ raise Error::ExtJSONParseError, "Invalid $regex pattern: #{hash['$regex']}"
336
+ end
337
+ unless hash['$options'].is_a?(String)
338
+ raise Error::ExtJSONParseError, "Invalid $regex options: #{hash['$options']}"
339
+ end
340
+
341
+ return create_regexp(hash['$regex'], hash['$options'])
342
+ end
343
+
344
+ verify_no_reserved_keys(hash, **options)
345
+ end
346
+
347
+ verify_no_reserved_keys(hash, **options)
348
+ end
349
+
350
+ module_function def verify_no_reserved_keys(hash, **options)
351
+ if hash.length > RESERVED_KEYS.length
352
+ if RESERVED_KEYS.any? { |key| hash.key?(key) }
353
+ raise Error::ExtJSONParseError, "Hash uses reserved keys but does not match a known type: #{hash}"
354
+ end
355
+ else
356
+ if hash.keys.any? { |key| RESERVED_KEYS_HASH.key?(key) }
357
+ raise Error::ExtJSONParseError, "Hash uses reserved keys but does not match a known type: #{hash}"
358
+ end
359
+ end
360
+ map_hash(hash, **options)
361
+ end
362
+
363
+ module_function def map_hash(hash, **options)
364
+ ::Hash[hash.map do |key, value|
365
+ [key, parse_obj(value, **options)]
366
+ end]
367
+ end
368
+
369
+ module_function def create_binary(encoded_value, encoded_subtype)
370
+ subtype = encoded_subtype.hex
371
+ type = Binary::TYPES[subtype.chr]
372
+ unless type
373
+ # Requires https://jira.mongodb.org/browse/RUBY-2056
374
+ raise NotImplementedError, "Binary subtype #{encoded_subtype} is not currently supported"
375
+ end
376
+ Binary.new(Base64.decode64(encoded_value), type)
377
+ end
378
+
379
+ module_function def create_regexp(pattern, options)
380
+ Regexp::Raw.new(pattern, options)
381
+ end
382
+ end
383
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009-2014 MongoDB Inc.
1
+ # Copyright (C) 2009-2020 MongoDB Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/bson/float.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009-2014 MongoDB Inc.
1
+ # Copyright (C) 2009-2020 MongoDB Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -46,18 +46,64 @@ module BSON
46
46
  buffer.put_double(self)
47
47
  end
48
48
 
49
+ # Converts this object to a representation directly serializable to
50
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json.rst).
51
+ #
52
+ # This method returns the float itself if relaxed representation is
53
+ # requested and the value is finite, otherwise a $numberDouble hash.
54
+ #
55
+ # @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
56
+ # (default is canonical extended JSON)
57
+ #
58
+ # @return [ Hash | Float ] The extended json representation.
59
+ def as_extended_json(**options)
60
+ case infinite?
61
+ when 1
62
+ {'$numberDouble' => 'Infinity'}
63
+ when -1
64
+ {'$numberDouble' => '-Infinity'}
65
+ else
66
+ if nan?
67
+ {'$numberDouble' => 'NaN'}
68
+ else
69
+ if options[:mode] == :relaxed || options[:mode] == :legacy
70
+ self
71
+ else
72
+ value = if BSON::Environment.jruby?
73
+ # Hack to make bson corpus spec tests pass.
74
+ # JRuby serializes -1.2345678901234568e+18 as
75
+ # -1234567890123456770.0, which is valid but differs from MRI
76
+ # serialization. Extended JSON spec does not define precise
77
+ # stringification of floats.
78
+ # https://jira.mongodb.org/browse/SPEC-1536
79
+ if abs > 1e15
80
+ '%.17g' % to_s
81
+ else
82
+ to_s
83
+ end
84
+ else
85
+ to_s
86
+ end
87
+ {'$numberDouble' => value.upcase}
88
+ end
89
+ end
90
+ end
91
+ end
92
+
49
93
  module ClassMethods
50
94
 
51
95
  # Deserialize an instance of a Float from a BSON double.
52
96
  #
53
97
  # @param [ ByteBuffer ] buffer The byte buffer.
54
98
  #
99
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
100
+ #
55
101
  # @return [ Float ] The decoded Float.
56
102
  #
57
103
  # @see http://bsonspec.org/#/specification
58
104
  #
59
105
  # @since 2.0.0
60
- def from_bson(buffer)
106
+ def from_bson(buffer, **options)
61
107
  buffer.get_double
62
108
  end
63
109
  end