bson 4.7.1-java → 4.8.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -2
  3. data.tar.gz.sig +0 -0
  4. data/README.md +5 -1
  5. data/lib/bson-ruby.jar +0 -0
  6. data/lib/bson.rb +3 -0
  7. data/lib/bson/array.rb +21 -3
  8. data/lib/bson/binary.rb +41 -3
  9. data/lib/bson/boolean.rb +3 -1
  10. data/lib/bson/code.rb +15 -1
  11. data/lib/bson/code_with_scope.rb +17 -3
  12. data/lib/bson/db_pointer.rb +104 -0
  13. data/lib/bson/decimal128.rb +15 -1
  14. data/lib/bson/error.rb +17 -0
  15. data/lib/bson/ext_json.rb +374 -0
  16. data/lib/bson/float.rb +47 -1
  17. data/lib/bson/hash.rb +23 -3
  18. data/lib/bson/int32.rb +21 -1
  19. data/lib/bson/int64.rb +28 -3
  20. data/lib/bson/integer.rb +34 -0
  21. data/lib/bson/max_key.rb +12 -0
  22. data/lib/bson/min_key.rb +12 -0
  23. data/lib/bson/nil_class.rb +3 -1
  24. data/lib/bson/object.rb +27 -0
  25. data/lib/bson/object_id.rb +15 -1
  26. data/lib/bson/regexp.rb +19 -2
  27. data/lib/bson/specialized.rb +3 -1
  28. data/lib/bson/string.rb +3 -1
  29. data/lib/bson/symbol.rb +92 -3
  30. data/lib/bson/time.rb +28 -3
  31. data/lib/bson/timestamp.rb +15 -1
  32. data/lib/bson/undefined.rb +11 -0
  33. data/lib/bson/version.rb +1 -1
  34. data/spec/bson/binary_spec.rb +33 -3
  35. data/spec/bson/ext_json_parse_spec.rb +276 -0
  36. data/spec/bson/float_spec.rb +36 -0
  37. data/spec/bson/hash_spec.rb +70 -0
  38. data/spec/bson/int32_spec.rb +20 -0
  39. data/spec/bson/int64_spec.rb +38 -0
  40. data/spec/bson/integer_spec.rb +26 -0
  41. data/spec/bson/raw_spec.rb +22 -1
  42. data/spec/bson/symbol_raw_spec.rb +45 -0
  43. data/spec/bson/symbol_spec.rb +60 -0
  44. data/spec/{support → runners}/common_driver.rb +0 -0
  45. data/spec/runners/corpus.rb +182 -0
  46. data/spec/{support/corpus.rb → runners/corpus_legacy.rb} +40 -58
  47. data/spec/spec_helper.rb +9 -2
  48. data/spec/{bson/driver_bson_spec.rb → spec_tests/common_driver_spec.rb} +1 -0
  49. data/spec/{bson/corpus_spec.rb → spec_tests/corpus_legacy_spec.rb} +4 -4
  50. data/spec/spec_tests/corpus_spec.rb +124 -0
  51. data/spec/spec_tests/data/corpus/README.md +15 -0
  52. data/spec/spec_tests/data/corpus/array.json +49 -0
  53. data/spec/spec_tests/data/corpus/binary.json +85 -0
  54. data/spec/spec_tests/data/corpus/boolean.json +27 -0
  55. data/spec/spec_tests/data/corpus/code.json +67 -0
  56. data/spec/spec_tests/data/corpus/code_w_scope.json +78 -0
  57. data/spec/spec_tests/data/corpus/datetime.json +42 -0
  58. data/spec/spec_tests/data/corpus/dbpointer.json +56 -0
  59. data/spec/spec_tests/data/corpus/dbref.json +31 -0
  60. data/spec/spec_tests/data/corpus/decimal128-1.json +317 -0
  61. data/spec/spec_tests/data/corpus/decimal128-2.json +793 -0
  62. data/spec/spec_tests/data/corpus/decimal128-3.json +1771 -0
  63. data/spec/spec_tests/data/corpus/decimal128-4.json +117 -0
  64. data/spec/spec_tests/data/corpus/decimal128-5.json +402 -0
  65. data/spec/spec_tests/data/corpus/decimal128-6.json +119 -0
  66. data/spec/spec_tests/data/corpus/decimal128-7.json +323 -0
  67. data/spec/spec_tests/data/corpus/document.json +36 -0
  68. data/spec/spec_tests/data/corpus/double.json +87 -0
  69. data/spec/spec_tests/data/corpus/int32.json +43 -0
  70. data/spec/spec_tests/data/corpus/int64.json +43 -0
  71. data/spec/spec_tests/data/corpus/maxkey.json +12 -0
  72. data/spec/spec_tests/data/corpus/minkey.json +12 -0
  73. data/spec/spec_tests/data/corpus/multi-type-deprecated.json +15 -0
  74. data/spec/spec_tests/data/corpus/multi-type.json +11 -0
  75. data/spec/spec_tests/data/corpus/null.json +12 -0
  76. data/spec/spec_tests/data/corpus/oid.json +28 -0
  77. data/spec/spec_tests/data/corpus/regex.json +65 -0
  78. data/spec/spec_tests/data/corpus/string.json +72 -0
  79. data/spec/spec_tests/data/corpus/symbol.json +80 -0
  80. data/spec/spec_tests/data/corpus/timestamp.json +24 -0
  81. data/spec/spec_tests/data/corpus/top.json +240 -0
  82. data/spec/spec_tests/data/corpus/undefined.json +15 -0
  83. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/array.json +0 -0
  84. data/spec/{support/corpus-tests/failures → spec_tests/data/corpus_legacy}/binary.json +0 -0
  85. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/boolean.json +0 -0
  86. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/code.json +1 -1
  87. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/code_w_scope.json +1 -1
  88. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/document.json +1 -1
  89. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/double.json +1 -1
  90. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/datetime.json +0 -0
  91. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/dbpointer.json +0 -0
  92. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/int64.json +0 -0
  93. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/failures/symbol.json +0 -0
  94. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/int32.json +1 -1
  95. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/maxkey.json +1 -1
  96. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/minkey.json +1 -1
  97. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/null.json +1 -1
  98. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/oid.json +0 -0
  99. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/regex.json +1 -1
  100. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/string.json +0 -0
  101. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/timestamp.json +1 -1
  102. data/spec/{support/corpus-tests → spec_tests/data/corpus_legacy}/top.json +0 -0
  103. data/spec/{support/corpus-tests/failures → spec_tests/data/corpus_legacy}/undefined.json +0 -0
  104. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-1.json +0 -0
  105. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-2.json +0 -0
  106. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-3.json +0 -0
  107. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-4.json +0 -0
  108. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-5.json +0 -0
  109. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-6.json +0 -0
  110. data/spec/{support/driver-spec-tests → spec_tests/data}/decimal128/decimal128-7.json +0 -0
  111. metadata +170 -94
  112. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8c8a8456a3da6e860243257d9bb9feeb63257f21422a15150df244c8013830d
4
- data.tar.gz: 9dafda0d9652f1528fbce70db3fa6ab2645275dbb292f3f1bc8171adde2e5b0c
3
+ metadata.gz: d691529718f7129a9ee438d11e85f2abc13fe81c03a43d48f528a21e05bd9e1b
4
+ data.tar.gz: 5ab219503040fe8826b1d58bcc51bdad454fa84420609474b6315c18f54a5db0
5
5
  SHA512:
6
- metadata.gz: ea4a57864469d857227855434271646d2a2c168d927f24fbfd8d7f8a86709744db36d52a52567f25b2b5fa6f2aa83c7d67032b9d2bb5a773d36490dc30eb5a61
7
- data.tar.gz: 0bc99ae57ae76c93ccc2b7be6a23804486ec20a9e38bb82c206baf69e8dd13eaeb4a5fe2290d8e8eefcbf552193d48c2a0579cfcd4f939005535f526e6967d73
6
+ metadata.gz: b00a7c8078bf9d05a94ecb6d4052eb195df77115a6d11568d84db513da65ace9ec15ce91ca403680d1a029f615ac80e41b4e98aee44133c3a4b488d56f6075fc
7
+ data.tar.gz: 83a6195af39949c91379888f7c97d244b42248824de696248fb958825725a711b6ea414da93b3bac7f581c55999b33c633114e2d057366ea2e1d9c838a1002f4
@@ -1,2 +1 @@
1
- b)�GPkM�����EV_s�Q��MD<�����@A|�zYs6��>g|e-2N����5_�&Q��M��k!��L�Y�Ȕ����ntvI}UWc.#��ONG'�����-/�y���q��l�5Z{�G���;��S��g���1�"�Uڋ!�Ƒ��/� z
2
- S^�T�J/N�����}~!�*Ȏ<�����"�Aq�:K�iF�b1���� ��%\TCW�5xڊ����KFsW��M�*��4P��;KK
1
+ ��nQH@�y|d����E5�.�!�<ҙ(L�����1����+R8lD|襣��2m$�E�
data.tar.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- BSON [![Build Status](https://secure.travis-ci.org/mongodb/bson-ruby.png?branch=master&.png)](http://travis-ci.org/mongodb/bson-ruby) [![Code Climate](https://codeclimate.com/github/mongodb/bson-ruby.png)](https://codeclimate.com/github/mongodb/bson-ruby) [![Coverage Status](https://coveralls.io/repos/mongodb/bson-ruby/badge.png?branch=master)](https://coveralls.io/r/mongodb/bson-ruby?branch=master) [![Inline docs](http://inch-ci.org/github/mongodb/bson-ruby.svg?branch=master)](http://inch-ci.org/github/mongodb/bson-ruby)
1
+ BSON [![Build Status](https://secure.travis-ci.org/mongodb/bson-ruby.svg?branch=master)](http://travis-ci.org/mongodb/bson-ruby)
2
+ [![Build status Windows](https://ci.appveyor.com/api/projects/status/p5aqko7umsx351nm?svg=true)](https://ci.appveyor.com/project/p-mongo/bson-ruby/branch/master)
3
+ [![Code Climate](https://codeclimate.com/github/mongodb/bson-ruby.svg)](https://codeclimate.com/github/mongodb/bson-ruby)
4
+ [![Coverage Status](https://coveralls.io/repos/mongodb/bson-ruby/badge.svg?branch=master)](https://coveralls.io/r/mongodb/bson-ruby?branch=master)
5
+ [![Inline docs](http://inch-ci.org/github/mongodb/bson-ruby.svg?branch=master)](http://inch-ci.org/github/mongodb/bson-ruby)
2
6
  ====
3
7
 
4
8
  An implementation of the BSON specification in Ruby.
Binary file
@@ -57,6 +57,7 @@ module BSON
57
57
  end
58
58
 
59
59
  require "bson/config"
60
+ require "bson/error"
60
61
  require "bson/registry"
61
62
  require "bson/specialized"
62
63
  require "bson/json"
@@ -70,8 +71,10 @@ require "bson/code"
70
71
  require "bson/code_with_scope"
71
72
  require "bson/date"
72
73
  require "bson/date_time"
74
+ require "bson/db_pointer"
73
75
  require "bson/decimal128"
74
76
  require "bson/document"
77
+ require "bson/ext_json"
75
78
  require "bson/false_class"
76
79
  require "bson/float"
77
80
  require "bson/hash"
@@ -85,26 +85,44 @@ module BSON
85
85
  map { |value| value.to_bson_normalized_value }
86
86
  end
87
87
 
88
+ # Converts this object to a representation directly serializable to
89
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json.rst).
90
+ #
91
+ # This method recursively invokes +as_extended_json+ with the provided
92
+ # options on each array element.
93
+ #
94
+ # @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
95
+ # (default is canonical extended JSON)
96
+ #
97
+ # @return [ Array ] This array converted to extended json representation.
98
+ def as_extended_json(**options)
99
+ map do |item|
100
+ item.as_extended_json(**options)
101
+ end
102
+ end
103
+
88
104
  module ClassMethods
89
105
 
90
106
  # Deserialize the array from BSON.
91
107
  #
92
108
  # @param [ ByteBuffer ] buffer The byte buffer.
93
109
  #
110
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
111
+ #
94
112
  # @return [ Array ] The decoded array.
95
113
  #
96
114
  # @see http://bsonspec.org/#/specification
97
115
  #
98
116
  # @since 2.0.0
99
- def from_bson(buffer)
117
+ def from_bson(buffer, **options)
100
118
  if buffer.respond_to?(:get_array)
101
- buffer.get_array
119
+ buffer.get_array(**options)
102
120
  else
103
121
  array = new
104
122
  buffer.get_int32 # throw away the length
105
123
  while (type = buffer.get_byte) != NULL_BYTE
106
124
  buffer.get_cstring
107
- array << BSON::Registry.get(type).from_bson(buffer)
125
+ array << BSON::Registry.get(type).from_bson(buffer, **options)
108
126
  end
109
127
  array
110
128
  end
@@ -31,6 +31,12 @@ module BSON
31
31
 
32
32
  # The mappings of subtypes to their single byte identifiers.
33
33
  #
34
+ # @note subtype 6 (ciphertext) is used for the Client-Side Encryption
35
+ # feature. Data represented by this subtype is often encrypted, but
36
+ # may also be plaintext. All instances of this subtype necessary for
37
+ # Client-Side Encryption will be created internally by the Ruby driver.
38
+ # An application should not create new BSON::Binary objects of this subtype.
39
+ #
34
40
  # @since 2.0.0
35
41
  SUBTYPES = {
36
42
  :generic => 0.chr,
@@ -39,6 +45,7 @@ module BSON
39
45
  :uuid_old => 3.chr,
40
46
  :uuid => 4.chr,
41
47
  :md5 => 5.chr,
48
+ :ciphertext => 6.chr,
42
49
  :user => 128.chr
43
50
  }.freeze
44
51
 
@@ -94,8 +101,31 @@ module BSON
94
101
  # @return [ Hash ] The binary as a JSON hash.
95
102
  #
96
103
  # @since 2.0.0
104
+ # @deprecated Use as_extended_json instead.
97
105
  def as_json(*args)
98
- { "$binary" => Base64.encode64(data), "$type" => type }
106
+ as_extended_json
107
+ end
108
+
109
+ # Converts this object to a representation directly serializable to
110
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json.rst).
111
+ #
112
+ # @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
113
+ # (default is canonical extended JSON)
114
+ #
115
+ # @return [ Hash ] The extended json representation.
116
+ def as_extended_json(**options)
117
+ subtype = SUBTYPES[type].each_byte.map { |c| c.to_s(16) }.join
118
+ if subtype.length == 1
119
+ subtype = "0#{subtype}"
120
+ end
121
+
122
+ value = Base64.encode64(data).strip
123
+
124
+ if options[:mode] == :legacy
125
+ { "$binary" => value, "$type" => subtype }
126
+ else
127
+ { "$binary" => {'base64' => value, "subType" => subtype }}
128
+ end
99
129
  end
100
130
 
101
131
  # Instantiate the new binary object.
@@ -228,14 +258,22 @@ module BSON
228
258
  #
229
259
  # @param [ ByteBuffer ] buffer The byte buffer.
230
260
  #
261
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
262
+ #
231
263
  # @return [ Binary ] The decoded binary data.
232
264
  #
233
265
  # @see http://bsonspec.org/#/specification
234
266
  #
235
267
  # @since 2.0.0
236
- def self.from_bson(buffer)
268
+ def self.from_bson(buffer, **options)
237
269
  length = buffer.get_int32
238
- type = TYPES[buffer.get_byte]
270
+ type_byte = buffer.get_byte
271
+ type = TYPES[type_byte]
272
+ if type.nil?
273
+ raise Error::UnsupportedBinarySubtype,
274
+ "BSON data contains unsupported binary subtype #{'0x%02x' % type_byte.ord}"
275
+ end
276
+
239
277
  length = buffer.get_int32 if type == :old
240
278
  data = buffer.get_bytes(length)
241
279
  new(data, type)
@@ -31,12 +31,14 @@ module BSON
31
31
  #
32
32
  # @param [ ByteBuffer ] buffer The byte buffer.
33
33
  #
34
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
35
+ #
34
36
  # @return [ TrueClass, FalseClass ] The decoded boolean.
35
37
  #
36
38
  # @see http://bsonspec.org/#/specification
37
39
  #
38
40
  # @since 2.0.0
39
- def self.from_bson(buffer)
41
+ def self.from_bson(buffer, **options)
40
42
  buffer.get_byte == TrueClass::TRUE_BYTE
41
43
  end
42
44
 
@@ -55,7 +55,19 @@ module BSON
55
55
  # @return [ Hash ] The code as a JSON hash.
56
56
  #
57
57
  # @since 2.0.0
58
+ # @deprecated Use as_extended_json instead.
58
59
  def as_json(*args)
60
+ as_extended_json
61
+ end
62
+
63
+ # Converts this object to a representation directly serializable to
64
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json.rst).
65
+ #
66
+ # @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
67
+ # (default is canonical extended JSON)
68
+ #
69
+ # @return [ Hash ] The extended json representation.
70
+ def as_extended_json(**options)
59
71
  { "$code" => javascript }
60
72
  end
61
73
 
@@ -89,12 +101,14 @@ module BSON
89
101
  #
90
102
  # @param [ ByteBuffer ] buffer The byte buffer.
91
103
  #
104
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
105
+ #
92
106
  # @return [ TrueClass, FalseClass ] The decoded code.
93
107
  #
94
108
  # @see http://bsonspec.org/#/specification
95
109
  #
96
110
  # @since 2.0.0
97
- def self.from_bson(buffer)
111
+ def self.from_bson(buffer, **options)
98
112
  new(buffer.get_string)
99
113
  end
100
114
 
@@ -59,8 +59,20 @@ module BSON
59
59
  # @return [ Hash ] The code with scope as a JSON hash.
60
60
  #
61
61
  # @since 2.0.0
62
+ # @deprecated Use as_extended_json instead.
62
63
  def as_json(*args)
63
- { "$code" => javascript, "$scope" => scope }
64
+ as_extended_json
65
+ end
66
+
67
+ # Converts this object to a representation directly serializable to
68
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json.rst).
69
+ #
70
+ # @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
71
+ # (default is canonical extended JSON)
72
+ #
73
+ # @return [ Hash ] The extended json representation.
74
+ def as_extended_json(**options)
75
+ { "$code" => javascript, "$scope" => scope.as_extended_json(**options) }
64
76
  end
65
77
 
66
78
  # Instantiate the new code with scope.
@@ -99,14 +111,16 @@ module BSON
99
111
  #
100
112
  # @param [ ByteBuffer ] buffer The byte buffer.
101
113
  #
114
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
115
+ #
102
116
  # @return [ TrueClass, FalseClass ] The decoded code with scope.
103
117
  #
104
118
  # @see http://bsonspec.org/#/specification
105
119
  #
106
120
  # @since 2.0.0
107
- def self.from_bson(buffer)
121
+ def self.from_bson(buffer, **options)
108
122
  buffer.get_int32 # Throw away the total length.
109
- new(buffer.get_string, ::Hash.from_bson(buffer))
123
+ new(buffer.get_string, ::Hash.from_bson(buffer, **options))
110
124
  end
111
125
 
112
126
  # Register this type when the module is loaded.
@@ -0,0 +1,104 @@
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
+ module BSON
16
+
17
+ # Injects behaviour for encoding and decoding DBPointer values to and from
18
+ # raw bytes as specified by the BSON spec.
19
+ #
20
+ # @see http://bsonspec.org/#/specification
21
+ class DbPointer
22
+ include JSON
23
+
24
+ # A DBPointer is type 0x0C in the BSON spec.
25
+ BSON_TYPE = 0x0C.chr.force_encoding(BINARY).freeze
26
+
27
+ # Create a new DBPointer object.
28
+ #
29
+ # @param [ String ] ref The database collection name.
30
+ # @param [ BSON::ObjectId ] id The DBPointer id.
31
+ def initialize(ref, id)
32
+ @ref = ref
33
+ @id = id
34
+ end
35
+
36
+ # Return the collection name.
37
+ #
38
+ # @return [ String ] The database collection name.
39
+ attr_reader :ref
40
+
41
+ # Return the DbPointer's id.
42
+ #
43
+ # @return [ BSON::ObjectId ] The id of the DbPointer instance
44
+ attr_reader :id
45
+
46
+ # Determine if this DBPointer object is equal to another object.
47
+ #
48
+ # @param [ Object ] other The object to compare against.
49
+ #
50
+ # @return [ true | false ] If the objects are equal
51
+ def ==(other)
52
+ return false unless other.is_a?(DbPointer)
53
+ ref == other.ref && id == other.id
54
+ end
55
+
56
+ # Get the DBPointer as JSON hash data
57
+ #
58
+ # @return [ Hash ] The DBPointer as a JSON hash.
59
+ #
60
+ # @deprecated Use as_extended_json instead.
61
+ def as_json(*args)
62
+ as_extended_json
63
+ end
64
+
65
+ # Converts this object to a representation directly serializable to
66
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json.rst).
67
+ #
68
+ # @option options [ true | false ] :relaxed Whether to produce relaxed
69
+ # extended JSON representation.
70
+ #
71
+ # @return [ Hash ] The extended json representation.
72
+ def as_extended_json(**options)
73
+ {'$dbPointer' => { "$ref" => ref, '$id' => id.as_extended_json }}
74
+ end
75
+
76
+ # Encode the DBPointer.
77
+ #
78
+ # @return [ BSON::ByteBuffer ] The buffer with the encoded object.
79
+ #
80
+ # @see http://bsonspec.org/#/specification
81
+ def to_bson(buffer = ByteBuffer.new, validating_keys = Config.validating_keys?)
82
+ buffer.put_string(ref)
83
+ id.to_bson(buffer, validating_keys)
84
+ buffer
85
+ end
86
+
87
+ # Deserialize a DBPointer from BSON.
88
+ #
89
+ # @param [ ByteBuffer ] buffer The byte buffer.
90
+ # @param [ Hash ] options
91
+ #
92
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
93
+ #
94
+ # @return [ BSON::DbPointer ] The decoded DBPointer.
95
+ #
96
+ # @see http://bsonspec.org/#/specification
97
+ def self.from_bson(buffer, **options)
98
+ new(buffer.get_string, ObjectId.from_bson(buffer, **options))
99
+ end
100
+
101
+ # Register this type when the module is loaded.
102
+ Registry.register(BSON_TYPE, self)
103
+ end
104
+ end
@@ -63,7 +63,19 @@ module BSON
63
63
  # @return [ Hash ] The number as a JSON hash.
64
64
  #
65
65
  # @since 4.2.0
66
+ # @deprecated Use as_extended_json instead.
66
67
  def as_json(*args)
68
+ as_extended_json
69
+ end
70
+
71
+ # Converts this object to a representation directly serializable to
72
+ # Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json.rst).
73
+ #
74
+ # @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
75
+ # (default is canonical extended JSON)
76
+ #
77
+ # @return [ Hash ] The extended json representation.
78
+ def as_extended_json(**options)
67
79
  { EXTENDED_JSON_KEY => to_s }
68
80
  end
69
81
 
@@ -197,10 +209,12 @@ module BSON
197
209
  #
198
210
  # @param [ ByteBuffer ] buffer The byte buffer.
199
211
  #
212
+ # @option options [ nil | :bson ] :mode Decoding mode to use.
213
+ #
200
214
  # @return [ BSON::Decimal128 ] The decimal object.
201
215
  #
202
216
  # @since 4.2.0
203
- def from_bson(buffer)
217
+ def from_bson(buffer, **options)
204
218
  from_bits(*buffer.get_decimal128_bytes.unpack('Q<*'))
205
219
  end
206
220
 
@@ -0,0 +1,17 @@
1
+ module BSON
2
+ # Base exception class for all BSON-related errors.
3
+ #
4
+ # @note Many existing exceptions raised by bson-ruby do not derive from
5
+ # this base class. This will change in the next major version (5.0).
6
+ class Error < StandardError
7
+
8
+ # Exception raised when Extended JSON parsing fails.
9
+ class ExtJSONParseError < Error
10
+ end
11
+
12
+ # Exception raised when decoding BSON and the data contains an
13
+ # unsupported binary subtype.
14
+ class UnsupportedBinarySubtype < Error
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,374 @@
1
+ # Copyright (C) 2019 MongoDB Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'json'
16
+
17
+ module BSON
18
+
19
+ # This module contains methods for parsing Extended JSON 2.0.
20
+ # https://github.com/mongodb/specifications/blob/master/source/extended-json.rst
21
+ module ExtJSON
22
+
23
+ # Parses JSON in a string into a Ruby object tree.
24
+ #
25
+ # There are two strategies that this method can follow. If the canonical
26
+ # strategy is used which is the default, this method returns BSON types
27
+ # as much as possible. This allows the resulting object tree to be
28
+ # serialized back to extended JSON or to BSON while preserving the types.
29
+ # The relaxed strategy, enabled by passing {emit_relaxed: true} option,
30
+ # returns native Ruby types as much as possible which makes the resulting
31
+ # object tree easier to work with but may lose type information.
32
+ #
33
+ # Please note the following aspects of this method when emitting relaxed
34
+ # object trees:
35
+ #
36
+ # 1. $numberInt and $numberLong inputs produce Integer instances.
37
+ # 2. $regularExpression inputs produce BSON Regexp instances. This may
38
+ # change in a future version of bson-ruby to produce Ruby Regexp
39
+ # instances, potentially depending on regular expression options.
40
+ # 3. $numberDecimal inputs produce BSON Decimal128 instances. This may
41
+ # change in a future version of bson-ruby to produce Ruby BigDecimal
42
+ # instances instead.
43
+ #
44
+ # This method accepts canonical extended JSON, relaxed extended JSON and
45
+ # JSON without type information as well as a mix of the above.
46
+ #
47
+ # @note This method uses Ruby standard library's JSON.parse method to
48
+ # perform JSON parsing. As the JSON.parse method accepts inputs other
49
+ # than hashes, so does this method and therefore this method can return
50
+ # objects of any type.
51
+ #
52
+ # @param [ String ] str The string to parse.
53
+ #
54
+ # @option options [ nil | :bson ] :mode Which types to emit
55
+ #
56
+ # @return [ Object ] Parsed object tree.
57
+ module_function def parse(str, **options)
58
+ parse_obj(::JSON.parse(str), **options)
59
+ end
60
+
61
+ # Transforms a Ruby object tree containing extended JSON type hashes
62
+ # into a Ruby object tree with said hashes replaced by BSON or Ruby native
63
+ # types.
64
+ #
65
+ # @example Convert extended JSON type hashes:
66
+ # BSON::ExtJSON.parse_obj('foo' => {'$numberLong' => '42'})
67
+ # => {"foo"=>#<BSON::Int64:0x000055e55f4d40f0 @value=42>}
68
+ #
69
+ # @example Convert a non-hash value:
70
+ # BSON::ExtJSON.parse_obj('$numberLong' => '42')
71
+ # => #<BSON::Int64:0x000055e55f4e6ed0 @value=42>
72
+ #
73
+ # There are two strategies that this method can follow. If the canonical
74
+ # strategy is used which is the default, this method returns BSON types
75
+ # as much as possible. This allows the resulting object tree to be
76
+ # serialized back to extended JSON or to BSON while preserving the types.
77
+ # The relaxed strategy, enabled by passing {emit_relaxed: true} option,
78
+ # returns native Ruby types as much as possible which makes the resulting
79
+ # object tree easier to work with but may lose type information.
80
+ #
81
+ # Please note the following aspects of this method when emitting relaxed
82
+ # object trees:
83
+ #
84
+ # 1. $numberInt and $numberLong inputs produce Integer instances.
85
+ # 2. $regularExpression inputs produce BSON Regexp instances. This may
86
+ # change in a future version of bson-ruby to produce Ruby Regexp
87
+ # instances, potentially depending on regular expression options.
88
+ # 3. $numberDecimal inputs produce BSON Decimal128 instances. This may
89
+ # change in a future version of bson-ruby to produce Ruby BigDecimal
90
+ # instances instead.
91
+ #
92
+ # This method accepts object trees resulting from parsing canonical
93
+ # extended JSON, relaxed extended JSON and JSON without type information
94
+ # as well as a mix of the above.
95
+ #
96
+ # @note This method accepts any types as input, not just Hash instances.
97
+ # Consequently, it can return values of any type.
98
+ #
99
+ # @param [ Object ] value The object tree to convert.
100
+ #
101
+ # @option options [ nil | :bson ] :mode Which types to emit
102
+ #
103
+ # @return [ Object ] Converted object tree.
104
+ module_function def parse_obj(value, **options)
105
+ # TODO implement :ruby and :ruby! modes
106
+ unless [nil, :bson].include?(options[:mode])
107
+ raise ArgumentError, "Invalid value for :mode option: #{options[:mode].inspect}"
108
+ end
109
+
110
+ case value
111
+ when String, TrueClass, FalseClass, NilClass, Numeric
112
+ value
113
+ when Hash
114
+ parse_hash(value, **options)
115
+ when Array
116
+ value.map do |item|
117
+ parse_obj(item, **options)
118
+ end
119
+ else
120
+ raise Error::ExtJSONParseError, "Unknown value type: #{value}"
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ RESERVED_KEYS = %w(
127
+ $oid $symbol $numberInt $numberLong $numberDouble $numberDecimal
128
+ $binary $code $scope $timestamp $regularExpression $dbPointer
129
+ $date $minKey $maxKey $undefined
130
+ ).freeze
131
+
132
+ RESERVED_KEYS_HASH = Hash[RESERVED_KEYS.map do |key|
133
+ [key, true]
134
+ end].freeze
135
+
136
+ module_function def parse_hash(hash, **options)
137
+ if hash.empty?
138
+ return {}
139
+ end
140
+
141
+ if hash.key?('$ref')
142
+ # Legacy dbref handling.
143
+ # Note that according to extended json spec, only hash values (but
144
+ # not the top-level BSON document itself) may be of type "dbref".
145
+ # This code applies to both hash values and the hash overall; however,
146
+ # since we do not have DBRef as a distinct type, applying the below
147
+ # logic to top level hashes doesn't cause harm.
148
+ hash = hash.dup
149
+ ref = hash.delete('$ref')
150
+ # $ref can be a string value or an ObjectId
151
+ unless ref.is_a?(String)
152
+ raise Error::ExtJSONParseError, "Invalid $ref value: #{ref}"
153
+ end
154
+ # $id, if present, can be anything
155
+ id = hash.delete('$id')
156
+ if id.is_a?(Hash)
157
+ id = parse_hash(id)
158
+ end
159
+ # Preserve $id value as it was, do not convert either to ObjectId
160
+ # or to a string. But if the value was in {'$oid' => ...} format,
161
+ # the value is converted to an ObjectId instance so that
162
+ # serialization to BSON later on works correctly.
163
+ out = {'$ref' => ref, '$id' => id}
164
+ if hash.key?('$db')
165
+ # $db must always be a string, if provided
166
+ db = hash.delete('$db')
167
+ unless db.is_a?(String)
168
+ raise Error::ExtJSONParseError, "Invalid $db value: #{db}"
169
+ end
170
+ out['$db'] = db
171
+ end
172
+ return out.update(hash)
173
+ end
174
+
175
+ if hash.length == 1
176
+ key, value = hash.first
177
+ return case key
178
+ when '$oid'
179
+ ObjectId.from_string(value)
180
+ when '$symbol'
181
+ Symbol::Raw.new(value)
182
+ when '$numberInt'
183
+ unless value.is_a?(String)
184
+ raise Error::ExtJSONParseError, "$numberInt value is of an incorrect type: #{value}"
185
+ end
186
+ value.to_i
187
+ when '$numberLong'
188
+ unless value.is_a?(String)
189
+ raise Error::ExtJSONParseError, "$numberLong value is of an incorrect type: #{value}"
190
+ end
191
+ value = value.to_i
192
+ if options[:mode] != :bson
193
+ value
194
+ else
195
+ Int64.new(value)
196
+ end
197
+ when '$numberDouble'
198
+ # This handles string to double conversion as well as inf/-inf/nan
199
+ unless value.is_a?(String)
200
+ raise Error::ExtJSONParseError, "Invalid $numberDouble value: #{value}"
201
+ end
202
+ BigDecimal(value).to_f
203
+ when '$numberDecimal'
204
+ # TODO consider returning BigDecimal here instead of Decimal128
205
+ Decimal128.new(value)
206
+ when '$binary'
207
+ unless value.is_a?(Hash)
208
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{value}"
209
+ end
210
+ unless value.keys.sort == %w(base64 subType)
211
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{value}"
212
+ end
213
+ encoded_value = value['base64']
214
+ unless encoded_value.is_a?(String)
215
+ raise Error::ExtJSONParseError, "Invalid base64 value in $binary: #{value}"
216
+ end
217
+ subtype = value['subType']
218
+ unless subtype.is_a?(String)
219
+ raise Error::ExtJSONParseError, "Invalid subType value in $binary: #{value}"
220
+ end
221
+ create_binary(encoded_value, subtype)
222
+ when '$code'
223
+ unless value.is_a?(String)
224
+ raise Error::ExtJSONParseError, "Invalid $code value: #{value}"
225
+ end
226
+ Code.new(value)
227
+ when '$timestamp'
228
+ unless value.keys.sort == %w(i t)
229
+ raise Error::ExtJSONParseError, "Invalid $timestamp value: #{value}"
230
+ end
231
+ t = value['t']
232
+ unless t.is_a?(Integer)
233
+ raise Error::ExtJSONParseError, "Invalid t value: #{value}"
234
+ end
235
+ i = value['i']
236
+ unless i.is_a?(Integer)
237
+ raise Error::ExtJSONParseError, "Invalid i value: #{value}"
238
+ end
239
+ Timestamp.new(t, i)
240
+ when '$regularExpression'
241
+ unless value.keys.sort == %w(options pattern)
242
+ raise Error::ExtJSONParseError, "Invalid $regularExpression value: #{value}"
243
+ end
244
+ # TODO consider returning Ruby regular expression object here
245
+ create_regexp(value['pattern'], value['options'])
246
+ when '$dbPointer'
247
+ unless value.keys.sort == %w($id $ref)
248
+ raise Error::ExtJSONParseError, "Invalid $dbPointer value: #{value}"
249
+ end
250
+ DbPointer.new(value['$ref'], parse_hash(value['$id']))
251
+ when '$date'
252
+ case value
253
+ when String
254
+ ::Time.parse(value)
255
+ when Hash
256
+ unless value.keys.sort == %w($numberLong)
257
+ raise Error::ExtJSONParseError, "Invalid value for $date: #{value}"
258
+ end
259
+ ::Time.at(value.values.first.to_i.to_f / 1000)
260
+ else
261
+ raise Error::ExtJSONParseError, "Invalid value for $date: #{value}"
262
+ end
263
+ when '$minKey'
264
+ unless value == 1
265
+ raise Error::ExtJSONParseError, "Invalid $minKey value: #{value}"
266
+ end
267
+ MinKey.new
268
+ when '$maxKey'
269
+ unless value == 1
270
+ raise Error::ExtJSONParseError, "Invalid $maxKey value: #{value}"
271
+ end
272
+ MaxKey.new
273
+ when '$undefined'
274
+ unless value == true
275
+ raise Error::ExtJSONParseError, "Invalid $undefined value: #{value}"
276
+ end
277
+ Undefined.new
278
+ else
279
+ map_hash(hash, **options)
280
+ end
281
+ end
282
+
283
+ if hash.length == 2
284
+ sorted_keys = hash.keys.sort
285
+ first_key = sorted_keys.first
286
+ last_key = sorted_keys.last
287
+
288
+ if first_key == '$code'
289
+ unless sorted_keys == %w($code $scope)
290
+ raise Error::ExtJSONParseError, "Invalid $code value: #{hash}"
291
+ end
292
+ unless hash['$code'].is_a?(String)
293
+ raise Error::ExtJSONParseError, "Invalid $code value: #{value}"
294
+ end
295
+
296
+ return CodeWithScope.new(hash['$code'], map_hash(hash['$scope']))
297
+ end
298
+
299
+ if first_key == '$binary'
300
+ unless sorted_keys == %w($binary $type)
301
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{hash}"
302
+ end
303
+ unless hash['$binary'].is_a?(String)
304
+ raise Error::ExtJSONParseError, "Invalid $binary value: #{value}"
305
+ end
306
+ unless hash['$type'].is_a?(String)
307
+ raise Error::ExtJSONParseError, "Invalid $binary subtype: #{hash['$type']}"
308
+ end
309
+
310
+ return create_binary(hash['$binary'], hash['$type'])
311
+ end
312
+
313
+ if last_key == '$regex'
314
+ unless sorted_keys == %w($options $regex)
315
+ raise Error::ExtJSONParseError, "Invalid $regex value: #{hash}"
316
+ end
317
+
318
+ if hash['$regex'].is_a?(Hash)
319
+ return {
320
+ '$regex' => parse_hash(hash['$regex']),
321
+ '$options' => hash['$options']
322
+ }
323
+ end
324
+
325
+ unless hash['$regex'].is_a?(String)
326
+ raise Error::ExtJSONParseError, "Invalid $regex pattern: #{hash['$regex']}"
327
+ end
328
+ unless hash['$options'].is_a?(String)
329
+ raise Error::ExtJSONParseError, "Invalid $regex options: #{hash['$options']}"
330
+ end
331
+
332
+ return create_regexp(hash['$regex'], hash['$options'])
333
+ end
334
+
335
+ verify_no_reserved_keys(hash, **options)
336
+ end
337
+
338
+ verify_no_reserved_keys(hash, **options)
339
+ end
340
+
341
+ module_function def verify_no_reserved_keys(hash, **options)
342
+ if hash.length > RESERVED_KEYS.length
343
+ if RESERVED_KEYS.any? { |key| hash.key?(key) }
344
+ raise Error::ExtJSONParseError, "Hash uses reserved keys but does not match a known type: #{hash}"
345
+ end
346
+ else
347
+ if hash.keys.any? { |key| RESERVED_KEYS_HASH.key?(key) }
348
+ raise Error::ExtJSONParseError, "Hash uses reserved keys but does not match a known type: #{hash}"
349
+ end
350
+ end
351
+ map_hash(hash, **options)
352
+ end
353
+
354
+ module_function def map_hash(hash, **options)
355
+ ::Hash[hash.map do |key, value|
356
+ [key, parse_obj(value, **options)]
357
+ end]
358
+ end
359
+
360
+ module_function def create_binary(encoded_value, encoded_subtype)
361
+ subtype = encoded_subtype.hex
362
+ type = Binary::TYPES[subtype.chr]
363
+ unless type
364
+ # Requires https://jira.mongodb.org/browse/RUBY-2056
365
+ raise NotImplementedError, "Binary subtype #{encoded_subtype} is not currently supported"
366
+ end
367
+ Binary.new(Base64.decode64(encoded_value), type)
368
+ end
369
+
370
+ module_function def create_regexp(pattern, options)
371
+ Regexp::Raw.new(pattern, options)
372
+ end
373
+ end
374
+ end