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
data/lib/bson/binary.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.
@@ -12,8 +14,9 @@
12
14
  # See the License for the specific language governing permissions and
13
15
  # limitations under the License.
14
16
 
15
- module BSON
17
+ require 'base64'
16
18
 
19
+ module BSON
17
20
  # Represents binary data.
18
21
  #
19
22
  # @see http://bsonspec.org/#/specification
@@ -21,37 +24,66 @@ module BSON
21
24
  # @since 2.0.0
22
25
  class Binary
23
26
  include JSON
27
+ include Comparable
24
28
 
25
29
  # A binary is type 0x05 in the BSON spec.
26
30
  #
27
31
  # @since 2.0.0
28
- BSON_TYPE = 5.chr.force_encoding(BINARY).freeze
32
+ BSON_TYPE = ::String.new(5.chr, encoding: BINARY).freeze
29
33
 
30
34
  # The mappings of subtypes to their single byte identifiers.
31
35
  #
36
+ # @note subtype 6 (ciphertext) is used for the Client-Side Encryption
37
+ # feature. Data represented by this subtype is often encrypted, but
38
+ # may also be plaintext. All instances of this subtype necessary for
39
+ # Client-Side Encryption will be created internally by the Ruby driver.
40
+ # An application should not create new BSON::Binary objects of this subtype.
41
+ #
32
42
  # @since 2.0.0
33
43
  SUBTYPES = {
34
- :generic => 0.chr,
35
- :function => 1.chr,
36
- :old => 2.chr,
37
- :uuid_old => 3.chr,
38
- :uuid => 4.chr,
39
- :md5 => 5.chr,
40
- :user => 128.chr
44
+ generic: 0.chr,
45
+ function: 1.chr,
46
+ old: 2.chr,
47
+ uuid_old: 3.chr,
48
+ uuid: 4.chr,
49
+ md5: 5.chr,
50
+ ciphertext: 6.chr,
51
+ column: 7.chr,
52
+ sensitive: 8.chr,
53
+ vector: 9.chr,
54
+ user: 128.chr,
41
55
  }.freeze
42
56
 
57
+ # The starting point of the user-defined subtype range.
58
+ USER_SUBTYPE = 0x80
59
+
43
60
  # The mappings of single byte subtypes to their symbol counterparts.
44
61
  #
45
62
  # @since 2.0.0
46
63
  TYPES = SUBTYPES.invert.freeze
47
64
 
48
- # @!attribute data
49
- # @return [ Object ] The raw binary data.
50
- # @since 2.0.0
51
- # @!attribute type
52
- # @return [ Symbol ] The binary type.
53
- # @since 2.0.0
54
- attr_reader :data, :type
65
+ # Types of vector data.
66
+ VECTOR_DATA_TYPES = {
67
+ int8: '0x03'.hex,
68
+ float32: '0x27'.hex,
69
+ packed_bit: '0x10'.hex
70
+ }.freeze
71
+
72
+ # @api private
73
+ VECTOR_DATA_TYPES_INVERSE = VECTOR_DATA_TYPES.invert.freeze
74
+
75
+ # @return [ String ] The raw binary data.
76
+ #
77
+ # The string is always stored in BINARY encoding.
78
+ #
79
+ # @since 2.0.0
80
+ attr_reader :data
81
+
82
+ # @return [ Symbol ] The binary type.
83
+ attr_reader :type
84
+
85
+ # @return [ String ] The raw type value, as an encoded integer.
86
+ attr_reader :raw_type
55
87
 
56
88
  # Determine if this binary object is equal to another object.
57
89
  #
@@ -65,10 +97,26 @@ module BSON
65
97
  # @since 2.0.0
66
98
  def ==(other)
67
99
  return false unless other.is_a?(Binary)
100
+
68
101
  type == other.type && data == other.data
69
102
  end
103
+
70
104
  alias eql? ==
71
105
 
106
+ # Compare this binary object to another object. The two objects must have
107
+ # the same type for any meaningful comparison.
108
+ #
109
+ # @param [ Object ] other The object to compare against.
110
+ #
111
+ # @return [ Integer | nil ] If the objects have the same type, the result
112
+ # is -1 if self < other, 0 if self == other, and 1 if self > other. If
113
+ # other is not a Binary, or is a Binary of a different type, returns nil.
114
+ def <=>(other)
115
+ return nil unless other.is_a?(Binary) && type == other.type
116
+
117
+ data <=> other.data
118
+ end
119
+
72
120
  # Generates a Fixnum hash value for this object.
73
121
  #
74
122
  # Allows using Binary as hash keys.
@@ -77,34 +125,86 @@ module BSON
77
125
  #
78
126
  # @since 2.3.1
79
127
  def hash
80
- data.hash + type.hash
128
+ [data, type].hash
81
129
  end
82
130
 
83
- # Get the binary as JSON hash data.
131
+ # Return a representation of the object for use in
132
+ # application-level JSON serialization. Since BSON::Binary
133
+ # is used exclusively in BSON-related contexts, this
134
+ # method returns the canonical Extended JSON representation.
135
+ #
136
+ # @return [ Hash ] The extended json representation.
137
+ def as_json(*_args)
138
+ as_extended_json
139
+ end
140
+
141
+ # Converts this object to a representation directly serializable to
142
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json/extended-json.md).
84
143
  #
85
- # @example Get the binary as a JSON hash.
86
- # binary.as_json
144
+ # @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
145
+ # (default is canonical extended JSON)
87
146
  #
88
- # @return [ Hash ] The binary as a JSON hash.
147
+ # @return [ Hash ] The extended json representation.
148
+ def as_extended_json(**options)
149
+ subtype = @raw_type.each_byte.map { |c| c.to_s(16) }.join
150
+ subtype = "0#{subtype}" if subtype.length == 1
151
+
152
+ value = Base64.encode64(data).strip
153
+
154
+ if options[:mode] == :legacy
155
+ { '$binary' => value, '$type' => subtype }
156
+ else
157
+ { '$binary' => { 'base64' => value, 'subType' => subtype } }
158
+ end
159
+ end
160
+
161
+ # Decode the binary data as a vector data type.
89
162
  #
90
- # @since 2.0.0
91
- def as_json(*args)
92
- { "$binary" => data, "$type" => type }
163
+ # @return [ BSON::Vector ] The decoded vector data.
164
+ def as_vector
165
+ raise BSON::Error, "Cannot decode subtype #{type} as vector" unless type == :vector
166
+
167
+ dtype_value, padding, = data[0..1].unpack('CC')
168
+ dtype = VECTOR_DATA_TYPES_INVERSE[dtype_value]
169
+ raise ArgumentError, "Unsupported vector type: #{dtype_value}" unless dtype
170
+
171
+ format = case dtype
172
+ when :int8 then 'c*'
173
+ when :float32 then 'f*'
174
+ when :packed_bit then 'C*'
175
+ else
176
+ raise ArgumentError, "Unsupported type: #{dtype}"
177
+ end
178
+ BSON::Vector.new(data[2..-1].unpack(format), dtype, padding)
93
179
  end
94
180
 
95
181
  # Instantiate the new binary object.
96
182
  #
183
+ # This method accepts a string in any encoding; however, if a string is
184
+ # of a non-BINARY encoding, the encoding is set to BINARY. This does not
185
+ # change the bytes of the string but it means that applications referencing
186
+ # the data of a Binary instance cannot assume it is in a non-binary
187
+ # encoding, even if the string given to the constructor was in such an
188
+ # encoding.
189
+ #
97
190
  # @example Instantiate a binary.
98
191
  # BSON::Binary.new(data, :md5)
99
192
  #
100
- # @param [ Object ] data The raw binary data.
193
+ # @param [ String ] data The raw binary data.
101
194
  # @param [ Symbol ] type The binary type.
102
195
  #
103
196
  # @since 2.0.0
104
- def initialize(data = "", type = :generic)
105
- validate_type!(type)
106
- @data = data
107
- @type = type
197
+ def initialize(data = '', type = :generic)
198
+ initialize_instance(data, type)
199
+ end
200
+
201
+ # For legacy deserialization support where BSON::Binary objects are
202
+ # expected to have a specific internal representation (with only
203
+ # @type and @data instance variables).
204
+ #
205
+ # @api private
206
+ def init_with(coder)
207
+ initialize_instance(coder['data'], coder['type'])
108
208
  end
109
209
 
110
210
  # Get a nice string for use with object inspection.
@@ -116,7 +216,49 @@ module BSON
116
216
  #
117
217
  # @since 2.3.0
118
218
  def inspect
119
- "<BSON::Binary:0x#{object_id} type=#{type} data=0x#{data[0, 8].unpack('H*').first}...>"
219
+ "<BSON::Binary:0x#{object_id} type=#{type} data=0x#{data[0, 8].unpack1('H*')}...>"
220
+ end
221
+
222
+ # Returns a string representation of the UUID stored in this Binary.
223
+ #
224
+ # If the Binary is of subtype 4 (:uuid), this method returns the UUID
225
+ # in RFC 4122 format. If the representation parameter is provided, it
226
+ # must be the value :standard as a symbol or a string.
227
+ #
228
+ # If the Binary is of subtype 3 (:uuid_old), this method requires that
229
+ # the representation parameter is provided and is one of :csharp_legacy,
230
+ # :java_legacy or :python_legacy or the equivalent strings. In this case
231
+ # the method assumes the Binary stores the UUID in the specified format,
232
+ # transforms the stored bytes to the standard RFC 4122 representation
233
+ # and returns the UUID in RFC 4122 format.
234
+ #
235
+ # If the Binary is of another subtype, this method raises TypeError.
236
+ #
237
+ # @param [ Symbol ] representation How to interpret the UUID.
238
+ #
239
+ # @return [ String ] The string representation of the UUID.
240
+ #
241
+ # @raise [ TypeError ] If the subtype of Binary is not :uuid nor :uuid_old.
242
+ # @raise [ ArgumentError ] If the representation other than :standard
243
+ # is requested for Binary subtype 4 (:uuid), if :standard representation
244
+ # is requested for Binary subtype 3 (:uuid_old), or if an invalid
245
+ # representation is requested.
246
+ #
247
+ # @api experimental
248
+ def to_uuid(representation = nil)
249
+ if representation.is_a?(String)
250
+ raise ArgumentError,
251
+ "Representation must be given as a symbol: #{representation.inspect}"
252
+ end
253
+
254
+ case type
255
+ when :uuid
256
+ from_uuid_to_uuid(representation || :standard)
257
+ when :uuid_old
258
+ from_uuid_old_to_uuid(representation)
259
+ else
260
+ raise TypeError, "The type of Binary must be :uuid or :uuid_old, this object is: #{type.inspect}"
261
+ end
120
262
  end
121
263
 
122
264
  # Encode the binary type
@@ -124,17 +266,17 @@ module BSON
124
266
  # @example Encode the binary.
125
267
  # binary.to_bson
126
268
  #
127
- # @return [ String ] The encoded binary.
269
+ # @return [ BSON::ByteBuffer ] The buffer with the encoded object.
128
270
  #
129
271
  # @see http://bsonspec.org/#/specification
130
272
  #
131
273
  # @since 2.0.0
132
- def to_bson(buffer = ByteBuffer.new, validating_keys = Config.validating_keys?)
274
+ def to_bson(buffer = ByteBuffer.new)
133
275
  position = buffer.length
134
276
  buffer.put_int32(0)
135
- buffer.put_byte(SUBTYPES[type])
277
+ buffer.put_byte(@raw_type)
136
278
  buffer.put_int32(data.bytesize) if type == :old
137
- buffer.put_bytes(data.force_encoding(BINARY))
279
+ buffer.put_bytes(data)
138
280
  buffer.replace_int32(position, buffer.length - position - 5)
139
281
  end
140
282
 
@@ -142,57 +284,309 @@ module BSON
142
284
  #
143
285
  # @param [ ByteBuffer ] buffer The byte buffer.
144
286
  #
287
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
288
+ #
145
289
  # @return [ Binary ] The decoded binary data.
146
290
  #
147
291
  # @see http://bsonspec.org/#/specification
148
292
  #
149
293
  # @since 2.0.0
150
- def self.from_bson(buffer)
294
+ def self.from_bson(buffer, **_options)
151
295
  length = buffer.get_int32
152
- type = TYPES[buffer.get_byte]
296
+ type_byte = buffer.get_byte
297
+
298
+ if type_byte.bytes.first < USER_SUBTYPE
299
+ type = TYPES[type_byte]
300
+
301
+ if type.nil?
302
+ raise Error::UnsupportedBinarySubtype,
303
+ "BSON data contains unsupported binary subtype #{'0x%02x' % type_byte.ord}"
304
+ end
305
+ else
306
+ type = type_byte
307
+ end
308
+
153
309
  length = buffer.get_int32 if type == :old
154
310
  data = buffer.get_bytes(length)
155
311
  new(data, type)
156
312
  end
157
313
 
158
- # Raised when providing an invalid type to the Binary.
314
+ # Creates a BSON::Binary from a string representation of a UUID.
159
315
  #
160
- # @since 2.0.0
161
- class InvalidType < RuntimeError
162
-
163
- # @!attribute type
164
- # @return [ Object ] The invalid type.
165
- # @since 2.0.0
166
- attr_reader :type
167
-
168
- # Instantiate the new error.
169
- #
170
- # @example Instantiate the error.
171
- # InvalidType.new(:error)
172
- #
173
- # @param [ Object ] type The invalid type.
174
- #
175
- # @since 2.0.0
176
- def initialize(type)
177
- @type = type
316
+ # The UUID may be given in either 00112233-4455-6677-8899-aabbccddeeff or
317
+ # 00112233445566778899AABBCCDDEEFF format - specifically, any dashes in
318
+ # the UUID are removed and both upper and lower case letters are acceptable.
319
+ #
320
+ # The input UUID string is always interpreted to be in the RFC 4122 format.
321
+ #
322
+ # If representation is not provided, this method creates a BSON::Binary
323
+ # of subtype 4 (:uuid). If representation is provided, it must be one of
324
+ # :standard, :csharp_legacy, :java_legacy or :python_legacy. If
325
+ # representation is :standard, this method creates a subtype 4 (:uuid)
326
+ # binary which is the same behavior as if representation was not provided.
327
+ # For other representations, this method creates a Binary of subtype 3
328
+ # (:uuid_old) with the UUID converted to the appropriate legacy MongoDB
329
+ # UUID storage format.
330
+ #
331
+ # @param [ String ] uuid The string representation of the UUID.
332
+ # @param [ Symbol ] representation How to interpret the UUID.
333
+ #
334
+ # @return [ Binary ] The binary.
335
+ #
336
+ # @raise [ ArgumentError ] If invalid representation is requested.
337
+ #
338
+ # @api experimental
339
+ def self.from_uuid(uuid, representation = nil)
340
+ raise ArgumentError, "Representation must be given as a symbol: #{representation}" if representation.is_a?(String)
341
+
342
+ uuid_binary = uuid.delete('-').scan(/../).map(&:hex).map(&:chr).join
343
+ representation ||= :standard
344
+
345
+ handler = :"from_#{representation}_uuid"
346
+ raise ArgumentError, "Invalid representation: #{representation}" unless respond_to?(handler)
347
+
348
+ send(handler, uuid_binary)
349
+ end
350
+
351
+ # Constructs a new binary object from a standard-format binary UUID
352
+ # representation.
353
+ #
354
+ # @param [ String ] uuid_binary the UUID data
355
+ #
356
+ # @return [ BSON::Binary ] the Binary object
357
+ #
358
+ # @api private
359
+ def self.from_standard_uuid(uuid_binary)
360
+ new(uuid_binary, :uuid)
361
+ end
362
+
363
+ # Constructs a new binary object from a csharp legacy-format binary UUID
364
+ # representation.
365
+ #
366
+ # @param [ String ] uuid_binary the UUID data
367
+ #
368
+ # @return [ BSON::Binary ] the Binary object
369
+ #
370
+ # @api private
371
+ def self.from_csharp_legacy_uuid(uuid_binary)
372
+ uuid_binary.sub!(/\A(.)(.)(.)(.)(.)(.)(.)(.)(.{8})\z/, '\4\3\2\1\6\5\8\7\9')
373
+ new(uuid_binary, :uuid_old)
374
+ end
375
+
376
+ # Constructs a new binary object from a java legacy-format binary UUID
377
+ # representation.
378
+ #
379
+ # @param [ String ] uuid_binary the UUID data
380
+ #
381
+ # @return [ BSON::Binary ] the Binary object
382
+ #
383
+ # @api private
384
+ def self.from_java_legacy_uuid(uuid_binary)
385
+ uuid_binary.sub!(/\A(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\z/) do
386
+ (::Regexp.last_match[1..8].reverse + ::Regexp.last_match[9..16].reverse).join
178
387
  end
388
+ new(uuid_binary, :uuid_old)
389
+ end
179
390
 
180
- # Get the custom error message for the exception.
181
- #
182
- # @example Get the message.
183
- # error.message
184
- #
185
- # @return [ String ] The error message.
186
- #
187
- # @since 2.0.0
188
- def message
189
- "#{type.inspect} is not a valid binary type. " +
190
- "Please use one of #{SUBTYPES.keys.map(&:inspect).join(", ")}."
391
+ # Constructs a new binary object from a python legacy-format binary UUID
392
+ # representation.
393
+ #
394
+ # @param [ String ] uuid_binary the UUID data
395
+ #
396
+ # @return [ BSON::Binary ] the Binary object
397
+ #
398
+ # @api private
399
+ def self.from_python_legacy_uuid(uuid_binary)
400
+ new(uuid_binary, :uuid_old)
401
+ end
402
+
403
+ # Constructs a new binary object from a binary vector.
404
+
405
+ # @param [ BSON::Vector | Array ] vector The vector data.
406
+ # @param [ Symbol | nil ] dtype The vector data type, must be nil if vector is a BSON::Vector.
407
+ # @param [ Integer ] padding The number of bits in the final byte that are to
408
+ # be ignored when a vector element's size is less than a byte. Must be 0 if vector is a BSON::Vector.
409
+ # @param [ Boolean ] validate_vector_data Whether to validate the vector data.
410
+ #
411
+ # @return [ BSON::Binary ] The binary object.
412
+ def self.from_vector(vector, dtype = nil, padding = 0, validate_vector_data: false)
413
+ data, dtype, padding = extract_args_for_vector(vector, dtype, padding)
414
+ validate_args_for_vector!(data, dtype, padding)
415
+
416
+ format = case dtype
417
+ when :int8 then 'c*'
418
+ when :float32 then 'f*'
419
+ when :packed_bit then 'C*'
420
+ else raise ArgumentError, "Unsupported type: #{dtype}"
421
+ end
422
+ if validate_vector_data
423
+ validate_vector_data!(data, dtype)
191
424
  end
425
+ metadata = [ VECTOR_DATA_TYPES[dtype], padding ].pack('CC')
426
+ data = data.pack(format)
427
+ new(metadata.concat(data), :vector)
192
428
  end
193
429
 
194
430
  private
195
431
 
432
+ # Extracts the arguments for a binary vector.
433
+ #
434
+ # @param [ BSON::Vector | Array ] vector The vector data.
435
+ # @param [ ::Symbol | nil ] dtype The vector data type, must be nil if vector is a BSON::Vector.
436
+ # @param [ Integer ] padding The padding. Must be 0 if vector is a BSON::Vector.
437
+ #
438
+ # @return [ Array ] The extracted data, dtype, and padding.
439
+ def self.extract_args_for_vector(vector, dtype, padding)
440
+ if vector.is_a?(BSON::Vector)
441
+ if dtype || padding != 0
442
+ raise ArgumentError, 'Do not specify dtype and padding if the first argument is BSON::Vector'
443
+ end
444
+
445
+ data = vector.data
446
+ dtype = vector.dtype
447
+ padding = vector.padding
448
+ else
449
+ data = vector
450
+ end
451
+ [ data, dtype, padding ]
452
+ end
453
+ private_class_method :extract_args_for_vector
454
+
455
+ # Validate the arguments for a binary vector.
456
+ # @param [ Array ] data The vector data.
457
+ # @param [ ::Symbol ] dtype The vector data type.
458
+ # @param [ Integer ] padding The padding. Must be 0 if vector is a BSON::Vector.
459
+ # @raise [ ArgumentError ] If the arguments are invalid.
460
+ def self.validate_args_for_vector!(data, dtype, padding)
461
+ raise ArgumentError, "Unknown dtype #{dtype}" unless VECTOR_DATA_TYPES.key?(dtype)
462
+
463
+ if %i[int8 float32].include?(dtype)
464
+ raise ArgumentError, 'Padding applies only to packed_bit' if padding != 0
465
+ elsif padding.positive? && data.empty?
466
+ raise ArgumentError, 'Padding must be zero when the vector is empty for PACKED_BIT'
467
+ elsif padding.negative? || padding > 7
468
+ raise ArgumentError, "Padding must be between 0 and 7, got #{padding}"
469
+ end
470
+ end
471
+ private_class_method :validate_args_for_vector!
472
+
473
+ # Validate that all the values in the vector data are valid for the given dtype.
474
+ #
475
+ # @param [ Array ] data The vector data.
476
+ # @param [ ::Symbol ] dtype The vector data type.
477
+ def self.validate_vector_data!(data, dtype)
478
+ validator = case dtype
479
+ when :int8
480
+ ->(v) { v.is_a?(Integer) && v.between?(-128, 127) }
481
+ when :float32
482
+ ->(v) { v.is_a?(Float) }
483
+ when :packed_bit
484
+ ->(v) { v.is_a?(Integer) && v.between?(0, 255) }
485
+ else
486
+ raise ArgumentError, "Unsupported type: #{dtype}"
487
+ end
488
+ data.each do |v|
489
+ raise ArgumentError, "Invalid value #{v} for type #{dtype}" unless validator.call(v)
490
+ end
491
+ end
492
+ private_class_method :validate_vector_data!
493
+
494
+ # initializes an instance of BSON::Binary.
495
+ #
496
+ # @param [ String ] data the data to initialize the object with
497
+ # @param [ Symbol ] type the type to assign the binary object
498
+ def initialize_instance(data, type)
499
+ @type = validate_type!(type)
500
+
501
+ # The Binary class used to force encoding to BINARY when serializing to
502
+ # BSON. Instead of doing that during serialization, perform this
503
+ # operation during Binary construction to make it clear that once
504
+ # the string is given to the Binary, the data is treated as a binary
505
+ # string and not a text string in any encoding.
506
+ data = data.dup.force_encoding('BINARY') unless data.encoding == Encoding.find('BINARY')
507
+
508
+ @data = data
509
+ end
510
+
511
+ # Converts the Binary UUID object to a UUID of the given representation.
512
+ # Currently, only :standard representation is supported.
513
+ #
514
+ # @param [ Symbol ] representation The representation to target (must be
515
+ # :standard)
516
+ #
517
+ # @return [ String ] the UUID as a string
518
+ def from_uuid_to_uuid(representation)
519
+ if representation != :standard
520
+ raise ArgumentError,
521
+ 'Binary of type :uuid can only be stringified to :standard representation, ' \
522
+ "requested: #{representation.inspect}"
523
+ end
524
+
525
+ data
526
+ .chars
527
+ .map { |n| '%02x' % n.ord }
528
+ .join
529
+ .sub(/\A(.{8})(.{4})(.{4})(.{4})(.{12})\z/, '\1-\2-\3-\4-\5')
530
+ end
531
+
532
+ # Converts the UUID-old object to a UUID of the given representation.
533
+ #
534
+ # @param [ Symbol ] representation The representation to target
535
+ #
536
+ # @return [ String ] the UUID as a string
537
+ def from_uuid_old_to_uuid(representation)
538
+ if representation.nil?
539
+ raise ArgumentError, 'Representation must be specified for BSON::Binary objects of type :uuid_old'
540
+ end
541
+
542
+ hex = data.chars.map { |n| '%02x' % n.ord }.join
543
+ handler = :"from_uuid_old_to_#{representation}_uuid"
544
+
545
+ raise ArgumentError, "Invalid representation: #{representation}" unless respond_to?(handler, true)
546
+
547
+ send(handler, hex)
548
+ .sub(/\A(.{8})(.{4})(.{4})(.{4})(.{12})\z/, '\1-\2-\3-\4-\5')
549
+ end
550
+
551
+ # Tries to convert a UUID-old object to a standard representation, which is
552
+ # not supported.
553
+ #
554
+ # @param [ String ] hex The hexadecimal string to convert
555
+ #
556
+ # @raise [ ArgumentError ] because standard representation is not supported
557
+ def from_uuid_old_to_standard_uuid(_hex)
558
+ raise ArgumentError, 'BSON::Binary objects of type :uuid_old cannot be stringified to :standard representation'
559
+ end
560
+
561
+ # Converts a UUID-old object to a csharp-legacy representation.
562
+ #
563
+ # @param [ String ] hex The hexadecimal string to convert
564
+ #
565
+ # @return [ String ] the csharp-legacy-formatted UUID
566
+ def from_uuid_old_to_csharp_legacy_uuid(hex)
567
+ hex.sub(/\A(..)(..)(..)(..)(..)(..)(..)(..)(.{16})\z/, '\4\3\2\1\6\5\8\7\9')
568
+ end
569
+
570
+ # Converts a UUID-old object to a java-legacy representation.
571
+ #
572
+ # @param [ String ] hex The hexadecimal string to convert
573
+ #
574
+ # @return [ String ] the java-legacy-formatted UUID
575
+ def from_uuid_old_to_java_legacy_uuid(hex)
576
+ hex.sub(/\A(..)(..)(..)(..)(..)(..)(..)(..)(..)(..)(..)(..)(..)(..)(..)(..)\z/) do
577
+ (::Regexp.last_match[1..8].reverse + ::Regexp.last_match[9..16].reverse).join
578
+ end
579
+ end
580
+
581
+ # Converts a UUID-old object to a python-legacy representation.
582
+ #
583
+ # @param [ String ] hex The hexadecimal string to convert
584
+ #
585
+ # @return [ String ] the python-legacy-formatted UUID
586
+ def from_uuid_old_to_python_legacy_uuid(hex)
587
+ hex
588
+ end
589
+
196
590
  # Validate the provided type is a valid type.
197
591
  #
198
592
  # @api private
@@ -200,13 +594,59 @@ module BSON
200
594
  # @example Validate the type.
201
595
  # binary.validate_type!(:user)
202
596
  #
203
- # @param [ Object ] type The provided type.
597
+ # @param [ Symbol | String | Integer ] type The provided type.
598
+ #
599
+ # @return [ Symbol ] the symbolic type corresponding to the argument.
204
600
  #
205
- # @raise [ InvalidType ] The the type is invalid.
601
+ # @raise [ BSON::Error::InvalidBinaryType ] The the type is invalid.
206
602
  #
207
603
  # @since 2.0.0
208
604
  def validate_type!(type)
209
- raise InvalidType.new(type) unless SUBTYPES.has_key?(type)
605
+ case type
606
+ when Integer then validate_integer_type!(type)
607
+ when String
608
+ if type.length > 1
609
+ validate_symbol_type!(type.to_sym)
610
+ else
611
+ validate_integer_type!(type.bytes.first)
612
+ end
613
+ when Symbol then validate_symbol_type!(type)
614
+ else
615
+ raise BSON::Error::InvalidBinaryType, type
616
+ end
617
+ end
618
+
619
+ # Test that the given integer type is valid.
620
+ #
621
+ # @param [ Integer ] type the provided type
622
+ #
623
+ # @return [ Symbol ] the symbolic type corresponding to the argument.
624
+ #
625
+ # @raise [ BSON::Error::InvalidBinaryType] if the type is invalid.
626
+ def validate_integer_type!(type)
627
+ @raw_type = type.chr.force_encoding('BINARY').freeze
628
+
629
+ if type < USER_SUBTYPE
630
+ raise BSON::Error::InvalidBinaryType, type unless TYPES.key?(@raw_type)
631
+
632
+ return TYPES[@raw_type]
633
+ end
634
+
635
+ :user
636
+ end
637
+
638
+ # Test that the given symbol type is valid.
639
+ #
640
+ # @param [ Symbol ] type the provided type
641
+ #
642
+ # @return [ Symbol ] the symbolic type corresponding to the argument.
643
+ #
644
+ # @raise [ BSON::Error::InvalidBinaryType] if the type is invalid.
645
+ def validate_symbol_type!(type)
646
+ raise BSON::Error::InvalidBinaryType, type unless SUBTYPES.key?(type)
647
+
648
+ @raw_type = SUBTYPES[type]
649
+ type
210
650
  end
211
651
 
212
652
  # Register this type when the module is loaded.