bson 4.2.2 → 4.12.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 (169) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +25 -7
  5. data/Rakefile +16 -9
  6. data/ext/bson/{native-endian.h → bson-endian.h} +5 -99
  7. data/ext/bson/bson-native.h +125 -0
  8. data/ext/bson/bytebuf.c +133 -0
  9. data/ext/bson/endian.c +117 -0
  10. data/ext/bson/init.c +355 -0
  11. data/ext/bson/libbson-utf8.c +230 -0
  12. data/ext/bson/read.c +411 -0
  13. data/ext/bson/util.c +95 -0
  14. data/ext/bson/write.c +680 -0
  15. data/lib/bson.rb +6 -3
  16. data/lib/bson/active_support.rb +17 -0
  17. data/lib/bson/array.rb +57 -17
  18. data/lib/bson/binary.rb +185 -13
  19. data/lib/bson/boolean.rb +12 -3
  20. data/lib/bson/code.rb +16 -2
  21. data/lib/bson/code_with_scope.rb +32 -5
  22. data/lib/bson/config.rb +1 -1
  23. data/lib/bson/date.rb +12 -2
  24. data/lib/bson/date_time.rb +2 -2
  25. data/lib/bson/db_pointer.rb +110 -0
  26. data/lib/bson/decimal128.rb +17 -3
  27. data/lib/bson/decimal128/builder.rb +1 -1
  28. data/lib/bson/document.rb +152 -5
  29. data/lib/bson/environment.rb +2 -1
  30. data/lib/bson/error.rb +27 -0
  31. data/lib/bson/ext_json.rb +383 -0
  32. data/lib/bson/false_class.rb +1 -1
  33. data/lib/bson/float.rb +48 -2
  34. data/lib/bson/hash.rb +68 -17
  35. data/lib/bson/int32.rb +52 -13
  36. data/lib/bson/int64.rb +59 -15
  37. data/lib/bson/integer.rb +36 -2
  38. data/lib/bson/json.rb +1 -1
  39. data/lib/bson/max_key.rb +13 -1
  40. data/lib/bson/min_key.rb +13 -1
  41. data/lib/bson/nil_class.rb +4 -2
  42. data/lib/bson/object.rb +28 -1
  43. data/lib/bson/object_id.rb +16 -2
  44. data/lib/bson/open_struct.rb +1 -1
  45. data/lib/bson/regexp.rb +27 -4
  46. data/lib/bson/registry.rb +3 -3
  47. data/lib/bson/specialized.rb +4 -2
  48. data/lib/bson/string.rb +5 -3
  49. data/lib/bson/symbol.rb +99 -7
  50. data/lib/bson/time.rb +63 -4
  51. data/lib/bson/time_with_zone.rb +54 -0
  52. data/lib/bson/timestamp.rb +44 -6
  53. data/lib/bson/true_class.rb +1 -1
  54. data/lib/bson/undefined.rb +12 -1
  55. data/lib/bson/version.rb +2 -2
  56. data/spec/bson/array_spec.rb +18 -1
  57. data/spec/bson/binary_spec.rb +100 -3
  58. data/spec/bson/binary_uuid_spec.rb +189 -0
  59. data/spec/bson/boolean_spec.rb +1 -1
  60. data/spec/bson/byte_buffer_read_spec.rb +197 -0
  61. data/spec/bson/byte_buffer_spec.rb +121 -381
  62. data/spec/bson/byte_buffer_write_spec.rb +854 -0
  63. data/spec/bson/code_spec.rb +1 -1
  64. data/spec/bson/code_with_scope_spec.rb +1 -1
  65. data/spec/bson/date_spec.rb +1 -1
  66. data/spec/bson/date_time_spec.rb +54 -1
  67. data/spec/bson/decimal128_spec.rb +35 -35
  68. data/spec/bson/document_as_spec.rb +46 -0
  69. data/spec/bson/document_spec.rb +197 -30
  70. data/spec/bson/ext_json_parse_spec.rb +308 -0
  71. data/spec/bson/false_class_spec.rb +1 -1
  72. data/spec/bson/float_spec.rb +37 -1
  73. data/spec/bson/hash_as_spec.rb +57 -0
  74. data/spec/bson/hash_spec.rb +209 -1
  75. data/spec/bson/int32_spec.rb +180 -6
  76. data/spec/bson/int64_spec.rb +199 -6
  77. data/spec/bson/integer_spec.rb +29 -3
  78. data/spec/bson/json_spec.rb +1 -1
  79. data/spec/bson/max_key_spec.rb +1 -1
  80. data/spec/bson/min_key_spec.rb +1 -1
  81. data/spec/bson/nil_class_spec.rb +1 -1
  82. data/spec/bson/object_id_spec.rb +1 -1
  83. data/spec/bson/object_spec.rb +1 -1
  84. data/spec/bson/open_struct_spec.rb +1 -1
  85. data/spec/bson/raw_spec.rb +34 -2
  86. data/spec/bson/regexp_spec.rb +1 -1
  87. data/spec/bson/registry_spec.rb +1 -1
  88. data/spec/bson/string_spec.rb +19 -1
  89. data/spec/bson/symbol_raw_spec.rb +45 -0
  90. data/spec/bson/symbol_spec.rb +63 -3
  91. data/spec/bson/time_spec.rb +205 -2
  92. data/spec/bson/time_with_zone_spec.rb +68 -0
  93. data/spec/bson/timestamp_spec.rb +56 -1
  94. data/spec/bson/true_class_spec.rb +1 -1
  95. data/spec/bson/undefined_spec.rb +1 -1
  96. data/spec/bson_spec.rb +1 -1
  97. data/spec/{support → runners}/common_driver.rb +1 -1
  98. data/spec/runners/corpus.rb +185 -0
  99. data/spec/{support/corpus.rb → runners/corpus_legacy.rb} +41 -59
  100. data/spec/spec_helper.rb +40 -3
  101. data/spec/{bson/driver_bson_spec.rb → spec_tests/common_driver_spec.rb} +1 -0
  102. data/spec/{bson/corpus_spec.rb → spec_tests/corpus_legacy_spec.rb} +10 -7
  103. data/spec/spec_tests/corpus_spec.rb +124 -0
  104. data/spec/spec_tests/data/corpus/README.md +15 -0
  105. data/spec/spec_tests/data/corpus/array.json +49 -0
  106. data/spec/spec_tests/data/corpus/binary.json +113 -0
  107. data/spec/spec_tests/data/corpus/boolean.json +27 -0
  108. data/spec/spec_tests/data/corpus/code.json +67 -0
  109. data/spec/spec_tests/data/corpus/code_w_scope.json +78 -0
  110. data/spec/spec_tests/data/corpus/datetime.json +42 -0
  111. data/spec/spec_tests/data/corpus/dbpointer.json +56 -0
  112. data/spec/spec_tests/data/corpus/dbref.json +31 -0
  113. data/spec/spec_tests/data/corpus/decimal128-1.json +317 -0
  114. data/spec/spec_tests/data/corpus/decimal128-2.json +793 -0
  115. data/spec/spec_tests/data/corpus/decimal128-3.json +1771 -0
  116. data/spec/spec_tests/data/corpus/decimal128-4.json +117 -0
  117. data/spec/spec_tests/data/corpus/decimal128-5.json +402 -0
  118. data/spec/spec_tests/data/corpus/decimal128-6.json +119 -0
  119. data/spec/spec_tests/data/corpus/decimal128-7.json +323 -0
  120. data/spec/spec_tests/data/corpus/document.json +36 -0
  121. data/spec/spec_tests/data/corpus/double.json +87 -0
  122. data/spec/spec_tests/data/corpus/int32.json +43 -0
  123. data/spec/spec_tests/data/corpus/int64.json +43 -0
  124. data/spec/spec_tests/data/corpus/maxkey.json +12 -0
  125. data/spec/spec_tests/data/corpus/minkey.json +12 -0
  126. data/spec/spec_tests/data/corpus/multi-type-deprecated.json +15 -0
  127. data/spec/spec_tests/data/corpus/multi-type.json +11 -0
  128. data/spec/spec_tests/data/corpus/null.json +12 -0
  129. data/spec/spec_tests/data/corpus/oid.json +28 -0
  130. data/spec/spec_tests/data/corpus/regex.json +65 -0
  131. data/spec/spec_tests/data/corpus/string.json +72 -0
  132. data/spec/spec_tests/data/corpus/symbol.json +80 -0
  133. data/spec/spec_tests/data/corpus/timestamp.json +34 -0
  134. data/spec/spec_tests/data/corpus/top.json +236 -0
  135. data/spec/spec_tests/data/corpus/undefined.json +15 -0
  136. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/array.json +8 -2
  137. data/spec/{support/corpus-tests/failures → spec_tests/data/corpus_legacy}/binary.json +0 -0
  138. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/boolean.json +0 -0
  139. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/code.json +1 -1
  140. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/code_w_scope.json +1 -1
  141. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/document.json +1 -1
  142. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/double.json +1 -1
  143. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/datetime.json +0 -0
  144. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/dbpointer.json +0 -0
  145. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/int64.json +0 -0
  146. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/symbol.json +0 -0
  147. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/int32.json +1 -1
  148. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/maxkey.json +1 -1
  149. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/minkey.json +1 -1
  150. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/null.json +1 -1
  151. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/oid.json +0 -0
  152. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/regex.json +1 -1
  153. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/string.json +0 -0
  154. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/timestamp.json +1 -1
  155. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/top.json +0 -0
  156. data/spec/{support/corpus-tests/failures → spec_tests/data/corpus_legacy}/undefined.json +0 -0
  157. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-1.json +0 -0
  158. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-2.json +0 -0
  159. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-3.json +0 -0
  160. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-4.json +0 -0
  161. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-5.json +0 -0
  162. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-6.json +0 -0
  163. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-7.json +0 -0
  164. data/spec/support/shared_examples.rb +3 -5
  165. data/spec/support/spec_config.rb +16 -0
  166. data/spec/support/utils.rb +10 -0
  167. metadata +227 -124
  168. metadata.gz.sig +0 -0
  169. data/ext/bson/bson_native.c +0 -762
@@ -0,0 +1,308 @@
1
+ # Copyright (C) 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 "spec_helper"
16
+
17
+ describe "BSON::ExtJSON.parse" do
18
+
19
+ let(:parsed) { BSON::ExtJSON.parse_obj(input) }
20
+
21
+ context 'when input is true' do
22
+ let(:input) { true }
23
+
24
+ it 'returns true' do
25
+ parsed.should == true
26
+ end
27
+ end
28
+
29
+ context 'when input is false' do
30
+ let(:input) { false }
31
+
32
+ it 'returns false' do
33
+ parsed.should == false
34
+ end
35
+ end
36
+
37
+ context 'when input is nil' do
38
+ let(:input) { nil }
39
+
40
+ it 'returns nil' do
41
+ parsed.should be nil
42
+ end
43
+ end
44
+
45
+ context 'when input is a string' do
46
+ let(:input) { 'hello' }
47
+
48
+ it 'returns the string' do
49
+ parsed.should == 'hello'
50
+ end
51
+ end
52
+
53
+ context 'when input is a BSON timestamp' do
54
+ let(:input) { {'$timestamp' => {'t' => 12345, 'i' => 42}} }
55
+
56
+ it 'returns a BSON::Timestamp instance' do
57
+ parsed.should == BSON::Timestamp.new(12345, 42)
58
+ end
59
+ end
60
+
61
+ context 'when input is an ISO time' do
62
+ let(:input) { {'$date' => '1970-01-01T00:00:04Z'} }
63
+
64
+ it 'returns a Time instance ' do
65
+ parsed.should be_a(Time)
66
+ end
67
+
68
+ it 'returns a Time instance with correct value' do
69
+ parsed.should == Time.at(4)
70
+ end
71
+
72
+ it 'returns a Time instance in UTC' do
73
+ parsed.zone.should == 'UTC'
74
+ end
75
+ end
76
+
77
+ context 'when input is a Unix timestamp' do
78
+ let(:input) { {'$date' => {'$numberLong' => '4000'}} }
79
+
80
+ it 'returns a Time instance ' do
81
+ parsed.should be_a(Time)
82
+ end
83
+
84
+ it 'returns a Time instance with correct value' do
85
+ parsed.should == Time.at(4)
86
+ end
87
+
88
+ it 'returns a Time instance in UTC' do
89
+ parsed.zone.should == 'UTC'
90
+ end
91
+ end
92
+
93
+ context 'when input is an int32' do
94
+ let(:input) do
95
+ {'$numberInt' => '42'}
96
+ end
97
+
98
+ let(:parsed) { BSON::ExtJSON.parse_obj(input, mode: mode) }
99
+
100
+ context 'when :mode is nil' do
101
+ let(:mode) { nil }
102
+
103
+ it 'returns Integer instance' do
104
+ parsed.should be_a(Integer)
105
+ parsed.should == 42
106
+ end
107
+ end
108
+
109
+ context 'when :mode is :bson' do
110
+ let(:mode) { :bson }
111
+
112
+ it 'returns Integer instance' do
113
+ parsed.should be_a(Integer)
114
+ parsed.should == 42
115
+ end
116
+ end
117
+ end
118
+
119
+ context 'when input is an int64' do
120
+ let(:input) do
121
+ {'$numberLong' => '42'}
122
+ end
123
+
124
+ let(:parsed) { BSON::ExtJSON.parse_obj(input, mode: mode) }
125
+
126
+ context 'when :mode is nil' do
127
+ let(:mode) { nil }
128
+
129
+ it 'returns Integer instance' do
130
+ parsed.should be_a(Integer)
131
+ parsed.should == 42
132
+ end
133
+ end
134
+
135
+ context 'when :mode is :bson' do
136
+ let(:mode) { :bson }
137
+
138
+ it 'returns Int64 instance' do
139
+ parsed.should be_a(BSON::Int64)
140
+ parsed.value.should == 42
141
+ end
142
+ end
143
+ end
144
+
145
+ context 'when input is a hash' do
146
+ let(:input) do
147
+ {}
148
+ end
149
+
150
+ let(:parsed) { BSON::ExtJSON.parse_obj(input, mode: mode) }
151
+
152
+ context 'when mode is invalid' do
153
+ let(:mode) { :foo }
154
+
155
+ it 'raises an exception' do
156
+ lambda do
157
+ parsed
158
+ end.should raise_error(ArgumentError, /Invalid value for :mode option/)
159
+ end
160
+ end
161
+ end
162
+
163
+ context 'when input is a binary' do
164
+ let(:data) do
165
+ Base64.decode64("//8=")
166
+ end
167
+
168
+ context 'in current format' do
169
+ let(:input) do
170
+ { "$binary" => { "base64"=>"//8=", "subType"=>"00" } }
171
+ end
172
+
173
+ context 'when :mode is nil' do
174
+ let(:mode) { nil }
175
+
176
+ it 'returns BSON::Binary instance' do
177
+ parsed.should be_a(BSON::Binary)
178
+ parsed.data.should == data
179
+ end
180
+ end
181
+
182
+ context 'when mode is :bson' do
183
+ let(:mode) { :bson }
184
+
185
+ it 'returns BSON::Binary instance' do
186
+ parsed.should be_a(BSON::Binary)
187
+ parsed.data.should == data
188
+ end
189
+ end
190
+ end
191
+
192
+ context 'in legacy format' do
193
+ let(:input) do
194
+ { "$binary"=>"//8=", "$type"=>"00" }
195
+ end
196
+
197
+ context 'when :mode is nil' do
198
+ let(:mode) { nil }
199
+
200
+ it 'returns BSON::Binary instance' do
201
+ parsed.should be_a(BSON::Binary)
202
+ parsed.data.should == data
203
+ end
204
+ end
205
+
206
+ context 'when mode is :bson' do
207
+ let(:mode) { :bson }
208
+
209
+ it 'returns BSON::Binary instance' do
210
+ parsed.should be_a(BSON::Binary)
211
+ parsed.data.should == data
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ context 'when input is a regex' do
218
+ let(:pattern) { 'abc' }
219
+ let(:options) { 'im' }
220
+
221
+ context 'in current format' do
222
+ let(:input) do
223
+ { "$regularExpression" => { "pattern" => pattern, "options" => options } }
224
+ end
225
+
226
+ context 'when :mode is nil' do
227
+ let(:mode) { nil }
228
+
229
+ it 'returns a BSON::Regexp::Raw instance' do
230
+ parsed.should be_a(BSON::Regexp::Raw)
231
+ parsed.pattern.should == pattern
232
+ parsed.options.should == options
233
+ end
234
+ end
235
+
236
+ context 'when :mode is :bson' do
237
+ let(:mode) { :bson }
238
+
239
+ it 'returns a BSON::Regexp::Raw instance' do
240
+ parsed.should be_a(BSON::Regexp::Raw)
241
+ parsed.pattern.should == pattern
242
+ parsed.options.should == options
243
+ end
244
+ end
245
+ end
246
+
247
+ context 'in legacy format' do
248
+ let(:input) do
249
+ { "$regex" => pattern, "$options" => options }
250
+ end
251
+
252
+ context 'when :mode is nil' do
253
+ let(:mode) { nil }
254
+
255
+ it 'returns a BSON::Regexp::Raw instance' do
256
+ parsed.should be_a(BSON::Regexp::Raw)
257
+ parsed.pattern.should == pattern
258
+ parsed.options.should == options
259
+ end
260
+ end
261
+
262
+ context 'when :mode is :bson' do
263
+ let(:mode) { :bson }
264
+
265
+ it 'returns a BSON::Regexp::Raw instance' do
266
+ parsed.should be_a(BSON::Regexp::Raw)
267
+ parsed.pattern.should == pattern
268
+ parsed.options.should == options
269
+ end
270
+ end
271
+ end
272
+
273
+ context 'when $regularExpression is nested in $regex' do
274
+ context 'with options' do
275
+ let(:input) do
276
+ {
277
+ "$regex" => {
278
+ "$regularExpression" => { "pattern" => "foo*", "options" => "" },
279
+ },
280
+ "$options" => "ix",
281
+ }
282
+ end
283
+
284
+ it 'parses' do
285
+ parsed.should == {
286
+ '$regex' => BSON::Regexp::Raw.new('foo*'), '$options' => 'ix'
287
+ }
288
+ end
289
+ end
290
+
291
+ context 'without options' do
292
+ let(:input) do
293
+ {
294
+ "$regex" => {
295
+ "$regularExpression" => { "pattern" => "foo*", "options" => "" },
296
+ },
297
+ }
298
+ end
299
+
300
+ it 'parses' do
301
+ parsed.should == {
302
+ '$regex' => BSON::Regexp::Raw.new('foo*'),
303
+ }
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009-2014 MongoDB Inc.
1
+ # Copyright (C) 2009-2020 MongoDB Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009-2014 MongoDB Inc.
1
+ # Copyright (C) 2009-2020 MongoDB Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -26,4 +26,40 @@ describe Float do
26
26
  it_behaves_like "a serializable bson element"
27
27
  it_behaves_like "a deserializable bson element"
28
28
  end
29
+
30
+ describe '#to_json' do
31
+ it 'returns float' do
32
+ 42.0.to_json.should == '42.0'
33
+ end
34
+ end
35
+
36
+ describe '#as_extended_json' do
37
+ context 'canonical mode' do
38
+ it 'returns $numberDouble' do
39
+ 42.0.as_extended_json.should == {'$numberDouble' => '42.0'}
40
+ end
41
+ end
42
+
43
+ context 'relaxed mode' do
44
+ let(:serialized) do
45
+ 42.0.as_extended_json(mode: :relaxed)
46
+ end
47
+
48
+ it 'returns float' do
49
+ serialized.should be_a(Float)
50
+ serialized.should be_within(0.00001).of(42)
51
+ end
52
+ end
53
+
54
+ context 'legacy mode' do
55
+ let(:serialized) do
56
+ 42.0.as_extended_json(mode: :legacy)
57
+ end
58
+
59
+ it 'returns float' do
60
+ serialized.should be_a(Float)
61
+ serialized.should be_within(0.00001).of(42)
62
+ end
63
+ end
64
+ end
29
65
  end
@@ -0,0 +1,57 @@
1
+ # Copyright (C) 2021 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 "spec_helper"
16
+
17
+ describe 'Hash ActiveSupport extensions' do
18
+ require_active_support
19
+
20
+ describe '#symbolize_keys' do
21
+ let(:symbolized) { hash.symbolize_keys }
22
+
23
+ shared_examples 'works correctly' do
24
+ it 'returns a hash' do
25
+ symbolized.class.should be Hash
26
+ end
27
+
28
+ it 'works correctly' do
29
+ hash.symbolize_keys.should == {foo: 'bar'}
30
+ end
31
+ end
32
+
33
+ context 'string keys' do
34
+ let(:hash) do
35
+ {'foo' => 'bar'}
36
+ end
37
+
38
+ include_examples 'works correctly'
39
+ end
40
+
41
+ context 'symbol keys' do
42
+ let(:hash) do
43
+ {foo: 'bar'}
44
+ end
45
+
46
+ include_examples 'works correctly'
47
+ end
48
+
49
+ context 'both string and symbol keys' do
50
+ let(:hash) do
51
+ {'foo' => 42, foo: 'bar'}
52
+ end
53
+
54
+ include_examples 'works correctly'
55
+ end
56
+ end
57
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009-2014 MongoDB Inc.
1
+ # Copyright (C) 2009-2020 MongoDB Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -157,5 +157,213 @@ describe Hash do
157
157
  it_behaves_like "a serializable bson element"
158
158
  it_behaves_like "a deserializable bson element"
159
159
  end
160
+
161
+ context 'with symbol values' do
162
+ let(:value) { :foo }
163
+
164
+ let(:serialized) do
165
+ {foo: value}.to_bson.to_s
166
+ end
167
+
168
+ def perform_test(bson_type_to_use)
169
+ Symbol.class_eval do
170
+ alias_method :bson_type_orig, :bson_type
171
+ define_method(:bson_type) do
172
+ bson_type_to_use
173
+ end
174
+ end
175
+
176
+ begin
177
+ yield
178
+ ensure
179
+ Symbol.class_eval do
180
+ alias_method :bson_type, :bson_type_orig
181
+ remove_method :bson_type_orig
182
+ end
183
+ end
184
+ end
185
+
186
+ let(:bson_with_symbol) do
187
+ "\x12\x00\x00\x00\x0Efoo\x00\x04\x00\x00\x00bar\x00\x00".force_encoding('binary')
188
+ end
189
+
190
+ let(:deserialized) do
191
+ Hash.from_bson(BSON::ByteBuffer.new(bson_with_symbol))
192
+ end
193
+
194
+ context 'when Symbol#bson_type is set to symbol' do
195
+ let(:bson_type_to_use) { BSON::Symbol::BSON_TYPE }
196
+
197
+ let(:expected) do
198
+ "\x12\x00\x00\x00\x0Efoo\x00\x04\x00\x00\x00foo\x00\x00".force_encoding('binary')
199
+ end
200
+
201
+ it 'serializes to BSON symbol' do
202
+ perform_test(bson_type_to_use) do
203
+ serialized
204
+ end.should == expected
205
+ end
206
+
207
+ it 'deserializes to Symbol' do
208
+ deserialized.should == {'foo' => :bar}
209
+ end
210
+ end
211
+
212
+ context 'when Symbol#bson_type is set to string' do
213
+ let(:bson_type_to_use) { BSON::String::BSON_TYPE }
214
+
215
+ let(:expected) do
216
+ "\x12\x00\x00\x00\x02foo\x00\x04\x00\x00\x00foo\x00\x00".force_encoding('binary')
217
+ end
218
+
219
+ it 'serializes to BSON string' do
220
+ perform_test(bson_type_to_use) do
221
+ serialized
222
+ end.should == expected
223
+ end
224
+
225
+ it 'deserializes to Symbol' do
226
+ deserialized.should == {'foo' => :bar}
227
+ end
228
+ end
229
+ end
230
+
231
+ context 'when hash contains value of an unserializable class' do
232
+ class HashSpecUnserializableClass
233
+ end
234
+
235
+ let(:obj) do
236
+ {foo: HashSpecUnserializableClass.new}
237
+ end
238
+
239
+ it 'raises UnserializableClass' do
240
+ lambda do
241
+ obj.to_bson
242
+ end.should raise_error(BSON::Error::UnserializableClass,
243
+ # C extension does not provide hash key in the exception message.
244
+ /(Hash value for key 'foo'|Value) does not define its BSON serialized type:.*HashSpecUnserializableClass/)
245
+ end
246
+ end
247
+
248
+ context 'when reading from a byte buffer that was previously written to' do
249
+ let(:buffer) do
250
+ {foo: 42}.to_bson
251
+ end
252
+
253
+ it 'returns the original hash' do
254
+ expect(Hash.from_bson(buffer)).to eq('foo' => 42)
255
+ end
256
+ end
257
+ end
258
+
259
+ describe '#to_bson' do
260
+ context 'when a key is not valid utf-8' do
261
+ let(:key) { Utils.make_byte_string([254, 253, 255]) }
262
+ let(:hash) do
263
+ {key => 'foo'}
264
+ end
265
+
266
+ let(:expected_message) do
267
+ if BSON::Environment.jruby?
268
+ # Uses JRE conversion to another encoding
269
+ /Error serializing key.*Encoding::UndefinedConversionError/
270
+ else
271
+ # Uses our validator
272
+ /Key.*is not valid UTF-8/
273
+ end
274
+ end
275
+
276
+ it 'raises EncodingError' do
277
+ expect do
278
+ hash.to_bson
279
+ end.to raise_error(EncodingError, expected_message)
280
+ end
281
+ end
282
+
283
+ context 'when a key contains null bytes' do
284
+ let(:hash) do
285
+ {"\x00".force_encoding('BINARY') => 'foo'}
286
+ end
287
+
288
+ it 'raises ArgumentError' do
289
+ expect do
290
+ hash.to_bson
291
+ end.to raise_error(ArgumentError, /[Kk]ey.*contains null bytes/)
292
+ end
293
+ end
294
+
295
+ context 'when a value is not valid utf-8' do
296
+ let(:hash) do
297
+ {'foo' => [254, 253, 255].map(&:chr).join.force_encoding('BINARY')}
298
+ end
299
+
300
+ let(:expected_message) do
301
+ /from ASCII-8BIT to UTF-8/
302
+ end
303
+
304
+ it 'raises EncodingError' do
305
+ expect do
306
+ hash.to_bson
307
+ end.to raise_error(EncodingError, expected_message)
308
+ end
309
+ end
310
+
311
+ context 'when a value contains null bytes' do
312
+ let(:hash) do
313
+ {'foo' => "\x00".force_encoding('BINARY')}
314
+ end
315
+
316
+ it 'works' do
317
+ expect do
318
+ hash.to_bson
319
+ end.not_to raise_error
320
+ end
321
+ end
322
+ end
323
+
324
+ describe '#from_bson' do
325
+ context 'when bson document has duplicate keys' do
326
+ let(:buf) do
327
+ buf = BSON::ByteBuffer.new
328
+ buf.put_int32(37)
329
+ buf.put_byte("\x02")
330
+ buf.put_cstring('foo')
331
+ buf.put_string('bar')
332
+ buf.put_byte("\x02")
333
+ buf.put_cstring('foo')
334
+ buf.put_string('overwrite')
335
+ buf.put_byte("\x00")
336
+
337
+ BSON::ByteBuffer.new(buf.to_s)
338
+ end
339
+
340
+ let(:doc) { Hash.from_bson(buf) }
341
+
342
+ it 'overwrites first value with second value' do
343
+ doc.should == {'foo' => 'overwrite'}
344
+ end
345
+ end
346
+
347
+ context 'when bson document has string and symbol keys of the same name' do
348
+ let(:buf) do
349
+ buf = BSON::ByteBuffer.new
350
+ buf.put_int32(31)
351
+ buf.put_byte("\x02")
352
+ buf.put_cstring('foo')
353
+ buf.put_string('bar')
354
+ buf.put_byte("\x0e")
355
+ buf.put_cstring('foo')
356
+ buf.put_string('bar')
357
+ buf.put_byte("\x00")
358
+
359
+ BSON::ByteBuffer.new(buf.to_s)
360
+ end
361
+
362
+ let(:doc) { Hash.from_bson(buf) }
363
+
364
+ it 'overwrites first value with second value' do
365
+ doc.should == {'foo' => :bar}
366
+ end
367
+ end
160
368
  end
161
369
  end