bson 4.1.1 → 5.2.0

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