bson 4.7.1 → 4.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -1
  4. data/README.md +6 -2
  5. data/ext/bson/bson-endian.h +1 -1
  6. data/ext/bson/bson-native.h +15 -5
  7. data/ext/bson/bytebuf.c +1 -1
  8. data/ext/bson/endian.c +1 -1
  9. data/ext/bson/init.c +26 -3
  10. data/ext/bson/read.c +127 -33
  11. data/ext/bson/util.c +41 -1
  12. data/ext/bson/write.c +18 -14
  13. data/lib/bson.rb +4 -1
  14. data/lib/bson/active_support.rb +1 -1
  15. data/lib/bson/array.rb +34 -5
  16. data/lib/bson/binary.rb +42 -4
  17. data/lib/bson/boolean.rb +12 -3
  18. data/lib/bson/code.rb +16 -2
  19. data/lib/bson/code_with_scope.rb +32 -5
  20. data/lib/bson/config.rb +1 -1
  21. data/lib/bson/date.rb +1 -1
  22. data/lib/bson/date_time.rb +1 -1
  23. data/lib/bson/db_pointer.rb +110 -0
  24. data/lib/bson/decimal128.rb +16 -2
  25. data/lib/bson/decimal128/builder.rb +1 -1
  26. data/lib/bson/document.rb +1 -1
  27. data/lib/bson/environment.rb +2 -1
  28. data/lib/bson/error.rb +21 -0
  29. data/lib/bson/ext_json.rb +375 -0
  30. data/lib/bson/false_class.rb +1 -1
  31. data/lib/bson/float.rb +48 -2
  32. data/lib/bson/hash.rb +36 -5
  33. data/lib/bson/int32.rb +22 -2
  34. data/lib/bson/int64.rb +29 -4
  35. data/lib/bson/integer.rb +35 -1
  36. data/lib/bson/json.rb +1 -1
  37. data/lib/bson/max_key.rb +13 -1
  38. data/lib/bson/min_key.rb +13 -1
  39. data/lib/bson/nil_class.rb +4 -2
  40. data/lib/bson/object.rb +28 -1
  41. data/lib/bson/object_id.rb +16 -2
  42. data/lib/bson/open_struct.rb +1 -1
  43. data/lib/bson/regexp.rb +20 -3
  44. data/lib/bson/registry.rb +1 -1
  45. data/lib/bson/specialized.rb +4 -2
  46. data/lib/bson/string.rb +4 -2
  47. data/lib/bson/symbol.rb +93 -4
  48. data/lib/bson/time.rb +63 -4
  49. data/lib/bson/time_with_zone.rb +1 -1
  50. data/lib/bson/timestamp.rb +16 -2
  51. data/lib/bson/true_class.rb +1 -1
  52. data/lib/bson/undefined.rb +12 -1
  53. data/lib/bson/version.rb +2 -2
  54. data/spec/bson/array_spec.rb +1 -1
  55. data/spec/bson/binary_spec.rb +34 -4
  56. data/spec/bson/binary_uuid_spec.rb +1 -1
  57. data/spec/bson/boolean_spec.rb +1 -1
  58. data/spec/bson/code_spec.rb +1 -1
  59. data/spec/bson/code_with_scope_spec.rb +1 -1
  60. data/spec/bson/date_spec.rb +1 -1
  61. data/spec/bson/date_time_spec.rb +1 -1
  62. data/spec/bson/decimal128_spec.rb +1 -1
  63. data/spec/bson/document_spec.rb +1 -1
  64. data/spec/bson/ext_json_parse_spec.rb +308 -0
  65. data/spec/bson/false_class_spec.rb +1 -1
  66. data/spec/bson/float_spec.rb +37 -1
  67. data/spec/bson/hash_spec.rb +71 -1
  68. data/spec/bson/int32_spec.rb +21 -1
  69. data/spec/bson/int64_spec.rb +39 -1
  70. data/spec/bson/integer_spec.rb +27 -1
  71. data/spec/bson/json_spec.rb +1 -1
  72. data/spec/bson/max_key_spec.rb +1 -1
  73. data/spec/bson/min_key_spec.rb +1 -1
  74. data/spec/bson/nil_class_spec.rb +1 -1
  75. data/spec/bson/object_id_spec.rb +1 -1
  76. data/spec/bson/object_spec.rb +1 -1
  77. data/spec/bson/open_struct_spec.rb +1 -1
  78. data/spec/bson/raw_spec.rb +22 -1
  79. data/spec/bson/regexp_spec.rb +1 -1
  80. data/spec/bson/registry_spec.rb +1 -1
  81. data/spec/bson/string_spec.rb +1 -1
  82. data/spec/bson/symbol_raw_spec.rb +45 -0
  83. data/spec/bson/symbol_spec.rb +61 -1
  84. data/spec/bson/time_spec.rb +205 -2
  85. data/spec/bson/time_with_zone_spec.rb +1 -1
  86. data/spec/bson/timestamp_spec.rb +1 -1
  87. data/spec/bson/true_class_spec.rb +1 -1
  88. data/spec/bson/undefined_spec.rb +1 -1
  89. data/spec/bson_spec.rb +1 -1
  90. data/spec/{support → runners}/common_driver.rb +1 -1
  91. data/spec/runners/corpus.rb +185 -0
  92. data/spec/{support/corpus.rb → runners/corpus_legacy.rb} +41 -59
  93. data/spec/spec_helper.rb +10 -3
  94. data/spec/{bson/driver_bson_spec.rb → spec_tests/common_driver_spec.rb} +1 -0
  95. data/spec/{bson/corpus_spec.rb → spec_tests/corpus_legacy_spec.rb} +4 -4
  96. data/spec/spec_tests/corpus_spec.rb +124 -0
  97. data/spec/spec_tests/data/corpus/README.md +15 -0
  98. data/spec/spec_tests/data/corpus/array.json +49 -0
  99. data/spec/spec_tests/data/corpus/binary.json +85 -0
  100. data/spec/spec_tests/data/corpus/boolean.json +27 -0
  101. data/spec/spec_tests/data/corpus/code.json +67 -0
  102. data/spec/spec_tests/data/corpus/code_w_scope.json +78 -0
  103. data/spec/spec_tests/data/corpus/datetime.json +42 -0
  104. data/spec/spec_tests/data/corpus/dbpointer.json +56 -0
  105. data/spec/spec_tests/data/corpus/dbref.json +31 -0
  106. data/spec/spec_tests/data/corpus/decimal128-1.json +317 -0
  107. data/spec/spec_tests/data/corpus/decimal128-2.json +793 -0
  108. data/spec/spec_tests/data/corpus/decimal128-3.json +1771 -0
  109. data/spec/spec_tests/data/corpus/decimal128-4.json +117 -0
  110. data/spec/spec_tests/data/corpus/decimal128-5.json +402 -0
  111. data/spec/spec_tests/data/corpus/decimal128-6.json +119 -0
  112. data/spec/spec_tests/data/corpus/decimal128-7.json +323 -0
  113. data/spec/spec_tests/data/corpus/document.json +36 -0
  114. data/spec/spec_tests/data/corpus/double.json +87 -0
  115. data/spec/spec_tests/data/corpus/int32.json +43 -0
  116. data/spec/spec_tests/data/corpus/int64.json +43 -0
  117. data/spec/spec_tests/data/corpus/maxkey.json +12 -0
  118. data/spec/spec_tests/data/corpus/minkey.json +12 -0
  119. data/spec/spec_tests/data/corpus/multi-type-deprecated.json +15 -0
  120. data/spec/spec_tests/data/corpus/multi-type.json +11 -0
  121. data/spec/spec_tests/data/corpus/null.json +12 -0
  122. data/spec/spec_tests/data/corpus/oid.json +28 -0
  123. data/spec/spec_tests/data/corpus/regex.json +65 -0
  124. data/spec/spec_tests/data/corpus/string.json +72 -0
  125. data/spec/spec_tests/data/corpus/symbol.json +80 -0
  126. data/spec/spec_tests/data/corpus/timestamp.json +24 -0
  127. data/spec/spec_tests/data/corpus/top.json +236 -0
  128. data/spec/spec_tests/data/corpus/undefined.json +15 -0
  129. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/array.json +0 -0
  130. data/spec/{support/corpus-tests/failures → spec_tests/data/corpus_legacy}/binary.json +0 -0
  131. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/boolean.json +0 -0
  132. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/code.json +1 -1
  133. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/code_w_scope.json +1 -1
  134. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/document.json +1 -1
  135. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/double.json +1 -1
  136. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/datetime.json +0 -0
  137. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/dbpointer.json +0 -0
  138. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/int64.json +0 -0
  139. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/symbol.json +0 -0
  140. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/int32.json +1 -1
  141. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/maxkey.json +1 -1
  142. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/minkey.json +1 -1
  143. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/null.json +1 -1
  144. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/oid.json +0 -0
  145. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/regex.json +1 -1
  146. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/string.json +0 -0
  147. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/timestamp.json +1 -1
  148. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/top.json +0 -0
  149. data/spec/{support/corpus-tests/failures → spec_tests/data/corpus_legacy}/undefined.json +0 -0
  150. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-1.json +0 -0
  151. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-2.json +0 -0
  152. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-3.json +0 -0
  153. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-4.json +0 -0
  154. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-5.json +0 -0
  155. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-6.json +0 -0
  156. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-7.json +0 -0
  157. data/spec/support/shared_examples.rb +1 -1
  158. metadata +176 -102
  159. metadata.gz.sig +0 -0
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2016-2019 MongoDB Inc.
1
+ # Copyright (C) 2016-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.
@@ -63,7 +63,19 @@ module BSON
63
63
  # @return [ Hash ] The number as a JSON hash.
64
64
  #
65
65
  # @since 4.2.0
66
+ # @deprecated Use as_extended_json instead.
66
67
  def as_json(*args)
68
+ as_extended_json
69
+ end
70
+
71
+ # Converts this object to a representation directly serializable to
72
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json.rst).
73
+ #
74
+ # @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
75
+ # (default is canonical extended JSON)
76
+ #
77
+ # @return [ Hash ] The extended json representation.
78
+ def as_extended_json(**options)
67
79
  { EXTENDED_JSON_KEY => to_s }
68
80
  end
69
81
 
@@ -197,10 +209,12 @@ module BSON
197
209
  #
198
210
  # @param [ ByteBuffer ] buffer The byte buffer.
199
211
  #
212
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
213
+ #
200
214
  # @return [ BSON::Decimal128 ] The decimal object.
201
215
  #
202
216
  # @since 4.2.0
203
- def from_bson(buffer)
217
+ def from_bson(buffer, **options)
204
218
  from_bits(*buffer.get_decimal128_bytes.unpack('Q<*'))
205
219
  end
206
220
 
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2016-2019 MongoDB Inc.
1
+ # Copyright (C) 2016-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.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009-2019 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.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009-2019 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
@@ -0,0 +1,21 @@
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
+ end
21
+ end
@@ -0,0 +1,375 @@
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
+ when '$code'
223
+ unless value.is_a?(String)
224
+ raise Error::ExtJSONParseError, "Invalid $code value: #{value}"
225
+ end
226
+ Code.new(value)
227
+ when '$timestamp'
228
+ unless value.keys.sort == %w(i t)
229
+ raise Error::ExtJSONParseError, "Invalid $timestamp value: #{value}"
230
+ end
231
+ t = value['t']
232
+ unless t.is_a?(Integer)
233
+ raise Error::ExtJSONParseError, "Invalid t value: #{value}"
234
+ end
235
+ i = value['i']
236
+ unless i.is_a?(Integer)
237
+ raise Error::ExtJSONParseError, "Invalid i value: #{value}"
238
+ end
239
+ Timestamp.new(t, i)
240
+ when '$regularExpression'
241
+ unless value.keys.sort == %w(options pattern)
242
+ raise Error::ExtJSONParseError, "Invalid $regularExpression value: #{value}"
243
+ end
244
+ # TODO consider returning Ruby regular expression object here
245
+ create_regexp(value['pattern'], value['options'])
246
+ when '$dbPointer'
247
+ unless value.keys.sort == %w($id $ref)
248
+ raise Error::ExtJSONParseError, "Invalid $dbPointer value: #{value}"
249
+ end
250
+ DbPointer.new(value['$ref'], parse_hash(value['$id']))
251
+ when '$date'
252
+ case value
253
+ when String
254
+ ::Time.parse(value).utc
255
+ when Hash
256
+ unless value.keys.sort == %w($numberLong)
257
+ raise Error::ExtJSONParseError, "Invalid value for $date: #{value}"
258
+ end
259
+ sec, msec = value.values.first.to_i.divmod(1000)
260
+ ::Time.at(sec, msec*1000).utc
261
+ else
262
+ raise Error::ExtJSONParseError, "Invalid value for $date: #{value}"
263
+ end
264
+ when '$minKey'
265
+ unless value == 1
266
+ raise Error::ExtJSONParseError, "Invalid $minKey value: #{value}"
267
+ end
268
+ MinKey.new
269
+ when '$maxKey'
270
+ unless value == 1
271
+ raise Error::ExtJSONParseError, "Invalid $maxKey value: #{value}"
272
+ end
273
+ MaxKey.new
274
+ when '$undefined'
275
+ unless value == true
276
+ raise Error::ExtJSONParseError, "Invalid $undefined value: #{value}"
277
+ end
278
+ Undefined.new
279
+ else
280
+ map_hash(hash, **options)
281
+ end
282
+ end
283
+
284
+ if hash.length == 2
285
+ sorted_keys = hash.keys.sort
286
+ first_key = sorted_keys.first
287
+ last_key = sorted_keys.last
288
+
289
+ if first_key == '$code'
290
+ unless sorted_keys == %w($code $scope)
291
+ raise Error::ExtJSONParseError, "Invalid $code value: #{hash}"
292
+ end
293
+ unless hash['$code'].is_a?(String)
294
+ raise Error::ExtJSONParseError, "Invalid $code value: #{value}"
295
+ end
296
+
297
+ return CodeWithScope.new(hash['$code'], map_hash(hash['$scope']))
298
+ end
299
+
300
+ if first_key == '$binary'
301
+ unless sorted_keys == %w($binary $type)
302
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{hash}"
303
+ end
304
+ unless hash['$binary'].is_a?(String)
305
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{value}"
306
+ end
307
+ unless hash['$type'].is_a?(String)
308
+ raise Error::ExtJSONParseError, "Invalid $binary subtype: #{hash['$type']}"
309
+ end
310
+
311
+ return create_binary(hash['$binary'], hash['$type'])
312
+ end
313
+
314
+ if last_key == '$regex'
315
+ unless sorted_keys == %w($options $regex)
316
+ raise Error::ExtJSONParseError, "Invalid $regex value: #{hash}"
317
+ end
318
+
319
+ if hash['$regex'].is_a?(Hash)
320
+ return {
321
+ '$regex' => parse_hash(hash['$regex']),
322
+ '$options' => hash['$options']
323
+ }
324
+ end
325
+
326
+ unless hash['$regex'].is_a?(String)
327
+ raise Error::ExtJSONParseError, "Invalid $regex pattern: #{hash['$regex']}"
328
+ end
329
+ unless hash['$options'].is_a?(String)
330
+ raise Error::ExtJSONParseError, "Invalid $regex options: #{hash['$options']}"
331
+ end
332
+
333
+ return create_regexp(hash['$regex'], hash['$options'])
334
+ end
335
+
336
+ verify_no_reserved_keys(hash, **options)
337
+ end
338
+
339
+ verify_no_reserved_keys(hash, **options)
340
+ end
341
+
342
+ module_function def verify_no_reserved_keys(hash, **options)
343
+ if hash.length > RESERVED_KEYS.length
344
+ if RESERVED_KEYS.any? { |key| hash.key?(key) }
345
+ raise Error::ExtJSONParseError, "Hash uses reserved keys but does not match a known type: #{hash}"
346
+ end
347
+ else
348
+ if hash.keys.any? { |key| RESERVED_KEYS_HASH.key?(key) }
349
+ raise Error::ExtJSONParseError, "Hash uses reserved keys but does not match a known type: #{hash}"
350
+ end
351
+ end
352
+ map_hash(hash, **options)
353
+ end
354
+
355
+ module_function def map_hash(hash, **options)
356
+ ::Hash[hash.map do |key, value|
357
+ [key, parse_obj(value, **options)]
358
+ end]
359
+ end
360
+
361
+ module_function def create_binary(encoded_value, encoded_subtype)
362
+ subtype = encoded_subtype.hex
363
+ type = Binary::TYPES[subtype.chr]
364
+ unless type
365
+ # Requires https://jira.mongodb.org/browse/RUBY-2056
366
+ raise NotImplementedError, "Binary subtype #{encoded_subtype} is not currently supported"
367
+ end
368
+ Binary.new(Base64.decode64(encoded_value), type)
369
+ end
370
+
371
+ module_function def create_regexp(pattern, options)
372
+ Regexp::Raw.new(pattern, options)
373
+ end
374
+ end
375
+ end