mongo 2.13.1 → 2.14.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -4
  4. data/lib/mongo.rb +9 -0
  5. data/lib/mongo/address/ipv4.rb +1 -1
  6. data/lib/mongo/address/ipv6.rb +1 -1
  7. data/lib/mongo/bulk_write.rb +17 -0
  8. data/lib/mongo/caching_cursor.rb +74 -0
  9. data/lib/mongo/client.rb +47 -8
  10. data/lib/mongo/cluster.rb +3 -3
  11. data/lib/mongo/cluster/topology/single.rb +1 -1
  12. data/lib/mongo/collection.rb +26 -0
  13. data/lib/mongo/collection/view.rb +24 -20
  14. data/lib/mongo/collection/view/aggregation.rb +25 -4
  15. data/lib/mongo/collection/view/builder/find_command.rb +38 -18
  16. data/lib/mongo/collection/view/explainable.rb +27 -8
  17. data/lib/mongo/collection/view/iterable.rb +72 -12
  18. data/lib/mongo/collection/view/readable.rb +12 -2
  19. data/lib/mongo/collection/view/writable.rb +15 -1
  20. data/lib/mongo/crypt/encryption_io.rb +6 -6
  21. data/lib/mongo/cursor.rb +1 -0
  22. data/lib/mongo/database.rb +6 -0
  23. data/lib/mongo/error.rb +2 -0
  24. data/lib/mongo/error/invalid_read_concern.rb +28 -0
  25. data/lib/mongo/error/server_certificate_revoked.rb +22 -0
  26. data/lib/mongo/error/unsupported_option.rb +14 -12
  27. data/lib/mongo/lint.rb +2 -1
  28. data/lib/mongo/logger.rb +3 -3
  29. data/lib/mongo/operation.rb +2 -0
  30. data/lib/mongo/operation/aggregate/result.rb +9 -8
  31. data/lib/mongo/operation/collections_info/result.rb +2 -0
  32. data/lib/mongo/operation/delete/bulk_result.rb +2 -0
  33. data/lib/mongo/operation/delete/result.rb +3 -0
  34. data/lib/mongo/operation/explain/command.rb +4 -0
  35. data/lib/mongo/operation/explain/legacy.rb +4 -0
  36. data/lib/mongo/operation/explain/op_msg.rb +6 -0
  37. data/lib/mongo/operation/explain/result.rb +3 -0
  38. data/lib/mongo/operation/find/legacy/result.rb +2 -0
  39. data/lib/mongo/operation/find/result.rb +3 -0
  40. data/lib/mongo/operation/get_more/result.rb +3 -0
  41. data/lib/mongo/operation/indexes/result.rb +5 -0
  42. data/lib/mongo/operation/insert/bulk_result.rb +5 -0
  43. data/lib/mongo/operation/insert/result.rb +5 -0
  44. data/lib/mongo/operation/list_collections/result.rb +5 -0
  45. data/lib/mongo/operation/map_reduce/result.rb +10 -0
  46. data/lib/mongo/operation/parallel_scan/result.rb +4 -0
  47. data/lib/mongo/operation/result.rb +35 -6
  48. data/lib/mongo/operation/shared/bypass_document_validation.rb +1 -0
  49. data/lib/mongo/operation/shared/causal_consistency_supported.rb +1 -0
  50. data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +2 -0
  51. data/lib/mongo/operation/shared/executable.rb +1 -0
  52. data/lib/mongo/operation/shared/idable.rb +2 -1
  53. data/lib/mongo/operation/shared/limited.rb +1 -0
  54. data/lib/mongo/operation/shared/object_id_generator.rb +1 -0
  55. data/lib/mongo/operation/shared/result/aggregatable.rb +1 -0
  56. data/lib/mongo/operation/shared/sessions_supported.rb +1 -0
  57. data/lib/mongo/operation/shared/specifiable.rb +1 -0
  58. data/lib/mongo/operation/shared/write.rb +1 -0
  59. data/lib/mongo/operation/shared/write_concern_supported.rb +1 -0
  60. data/lib/mongo/operation/update/legacy/result.rb +7 -0
  61. data/lib/mongo/operation/update/result.rb +8 -0
  62. data/lib/mongo/operation/users_info/result.rb +3 -0
  63. data/lib/mongo/query_cache.rb +242 -0
  64. data/lib/mongo/retryable.rb +8 -1
  65. data/lib/mongo/server.rb +5 -1
  66. data/lib/mongo/server/connection_common.rb +2 -2
  67. data/lib/mongo/server/connection_pool.rb +3 -0
  68. data/lib/mongo/server/monitor.rb +1 -1
  69. data/lib/mongo/server/monitor/connection.rb +3 -3
  70. data/lib/mongo/server/pending_connection.rb +2 -2
  71. data/lib/mongo/server/push_monitor.rb +1 -1
  72. data/lib/mongo/server_selector/base.rb +5 -1
  73. data/lib/mongo/session.rb +3 -0
  74. data/lib/mongo/socket.rb +6 -4
  75. data/lib/mongo/socket/ocsp_cache.rb +97 -0
  76. data/lib/mongo/socket/ocsp_verifier.rb +368 -0
  77. data/lib/mongo/socket/ssl.rb +45 -24
  78. data/lib/mongo/srv/monitor.rb +7 -13
  79. data/lib/mongo/srv/resolver.rb +14 -10
  80. data/lib/mongo/timeout.rb +2 -0
  81. data/lib/mongo/uri.rb +21 -390
  82. data/lib/mongo/uri/options_mapper.rb +582 -0
  83. data/lib/mongo/uri/srv_protocol.rb +3 -2
  84. data/lib/mongo/utils.rb +12 -1
  85. data/lib/mongo/version.rb +1 -1
  86. data/spec/NOTES.aws-auth.md +12 -7
  87. data/spec/README.md +56 -1
  88. data/spec/integration/bulk_write_spec.rb +48 -0
  89. data/spec/integration/client_authentication_options_spec.rb +55 -28
  90. data/spec/integration/connection_pool_populator_spec.rb +3 -1
  91. data/spec/integration/cursor_reaping_spec.rb +53 -17
  92. data/spec/integration/ocsp_connectivity_spec.rb +26 -0
  93. data/spec/integration/ocsp_verifier_cache_spec.rb +188 -0
  94. data/spec/integration/ocsp_verifier_spec.rb +334 -0
  95. data/spec/integration/query_cache_spec.rb +1045 -0
  96. data/spec/integration/query_cache_transactions_spec.rb +179 -0
  97. data/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb +1 -0
  98. data/spec/integration/retryable_writes/shared/performs_legacy_retries.rb +2 -0
  99. data/spec/integration/sdam_error_handling_spec.rb +68 -0
  100. data/spec/integration/server_selection_spec.rb +36 -0
  101. data/spec/integration/srv_monitoring_spec.rb +38 -3
  102. data/spec/integration/srv_spec.rb +56 -0
  103. data/spec/lite_spec_helper.rb +3 -1
  104. data/spec/mongo/address_spec.rb +1 -1
  105. data/spec/mongo/caching_cursor_spec.rb +70 -0
  106. data/spec/mongo/client_construction_spec.rb +54 -1
  107. data/spec/mongo/client_spec.rb +40 -0
  108. data/spec/mongo/cluster/topology/single_spec.rb +14 -5
  109. data/spec/mongo/cluster_spec.rb +3 -0
  110. data/spec/mongo/collection/view/explainable_spec.rb +87 -4
  111. data/spec/mongo/collection/view/map_reduce_spec.rb +2 -0
  112. data/spec/mongo/collection_spec.rb +60 -0
  113. data/spec/mongo/crypt/auto_decryption_context_spec.rb +1 -1
  114. data/spec/mongo/crypt/auto_encryption_context_spec.rb +1 -1
  115. data/spec/mongo/crypt/explicit_decryption_context_spec.rb +1 -1
  116. data/spec/mongo/crypt/explicit_encryption_context_spec.rb +1 -1
  117. data/spec/mongo/database_spec.rb +44 -0
  118. data/spec/mongo/error/no_server_available_spec.rb +1 -1
  119. data/spec/mongo/logger_spec.rb +13 -11
  120. data/spec/mongo/query_cache_spec.rb +279 -0
  121. data/spec/mongo/server/connection_pool_spec.rb +7 -3
  122. data/spec/mongo/server/connection_spec.rb +14 -7
  123. data/spec/mongo/socket/ssl_spec.rb +1 -1
  124. data/spec/mongo/socket_spec.rb +1 -1
  125. data/spec/mongo/uri/srv_protocol_spec.rb +64 -33
  126. data/spec/mongo/uri_option_parsing_spec.rb +11 -11
  127. data/spec/mongo/uri_spec.rb +68 -41
  128. data/spec/mongo/utils_spec.rb +39 -0
  129. data/spec/runners/auth.rb +3 -0
  130. data/spec/runners/connection_string.rb +35 -124
  131. data/spec/spec_tests/cmap_spec.rb +7 -3
  132. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +0 -1
  133. data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
  134. data/spec/spec_tests/data/cmap/pool-checkout-connection.yml +6 -2
  135. data/spec/spec_tests/data/cmap/pool-create-min-size.yml +3 -0
  136. data/spec/spec_tests/data/connection_string/valid-warnings.yml +24 -0
  137. data/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml +1 -3
  138. data/spec/spec_tests/data/sdam_monitoring/standalone.yml +2 -2
  139. data/spec/spec_tests/data/sdam_monitoring/standalone_repeated.yml +2 -2
  140. data/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml +2 -2
  141. data/spec/spec_tests/data/sdam_monitoring/standalone_to_rs_with_me_mismatch.yml +2 -2
  142. data/spec/spec_tests/data/uri_options/auth-options.yml +25 -0
  143. data/spec/spec_tests/data/uri_options/compression-options.yml +6 -3
  144. data/spec/spec_tests/data/uri_options/read-preference-options.yml +24 -0
  145. data/spec/spec_tests/data/uri_options/ruby-connection-options.yml +1 -0
  146. data/spec/spec_tests/data/uri_options/tls-options.yml +160 -4
  147. data/spec/spec_tests/dns_seedlist_discovery_spec.rb +9 -1
  148. data/spec/spec_tests/uri_options_spec.rb +31 -33
  149. data/spec/support/certificates/atlas-ocsp-ca.crt +28 -0
  150. data/spec/support/certificates/atlas-ocsp.crt +41 -0
  151. data/spec/support/client_registry_macros.rb +11 -2
  152. data/spec/support/common_shortcuts.rb +45 -0
  153. data/spec/support/constraints.rb +23 -0
  154. data/spec/support/lite_constraints.rb +24 -0
  155. data/spec/support/matchers.rb +16 -0
  156. data/spec/support/ocsp +1 -0
  157. data/spec/support/session_registry.rb +52 -0
  158. data/spec/support/spec_config.rb +22 -0
  159. data/spec/support/utils.rb +19 -1
  160. metadata +38 -3
  161. metadata.gz.sig +0 -0
@@ -0,0 +1,582 @@
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 Mongo
16
+ class URI
17
+
18
+ # Performs mapping between URI options and Ruby options.
19
+ #
20
+ # This class contains:
21
+ #
22
+ # - The mapping defining how URI options are converted to Ruby options.
23
+ # - The mapping from downcased URI option names to canonical-cased URI
24
+ # option names.
25
+ # - Methods to perform conversion of URI option values to Ruby option
26
+ # values (the convert_* methods). These generally warn and return nil
27
+ # when input given is invalid.
28
+ # - Methods to perform conversion of Ruby option values to standardized
29
+ # MongoClient options (revert_* methods). These assume the input is valid
30
+ # and generally do not perform validation.
31
+ #
32
+ # URI option names are case insensitive. Ruby options are specified as
33
+ # symbols (though in Client options use indifferent access).
34
+ #
35
+ # @api private
36
+ class OptionsMapper
37
+
38
+ include Loggable
39
+
40
+ # Instantates the options mapper.
41
+ #
42
+ # @option opts [ Logger ] :logger A custom logger to use.
43
+ def initialize(**opts)
44
+ @options = opts
45
+ end
46
+
47
+ # @return [ Hash ] The options.
48
+ attr_reader :options
49
+
50
+ # Adds an option to the uri options hash.
51
+ #
52
+ # Acquires a target for the option based on group.
53
+ # Transforms the value.
54
+ # Merges the option into the target.
55
+ #
56
+ # @param [ String ] key URI option name.
57
+ # @param [ String ] value The value of the option.
58
+ # @param [ Hash ] uri_options The base option target.
59
+ def add_uri_option(key, value, uri_options)
60
+ strategy = URI_OPTION_MAP[key.downcase]
61
+ if strategy.nil?
62
+ log_warn("Unsupported URI option '#{key}' on URI '#{@string}'. It will be ignored.")
63
+ return
64
+ end
65
+
66
+ group = strategy[:group]
67
+ target = if group
68
+ uri_options[group] || {}
69
+ else
70
+ uri_options
71
+ end
72
+ value = apply_transform(key, value, strategy[:type])
73
+ # Sometimes the value here would be nil, for example if we are processing
74
+ # read preference tags or auth mechanism properties and all of the
75
+ # data within is invalid. Ignore such options.
76
+ unless value.nil?
77
+ merge_uri_option(target, value, strategy[:name])
78
+ end
79
+
80
+ if group && !target.empty? && !uri_options.key?(group)
81
+ uri_options[group] = target
82
+ end
83
+ end
84
+
85
+ # Converts Ruby options provided to "standardized MongoClient options".
86
+ #
87
+ # @param [ Hash ] opts Ruby options to convert.
88
+ #
89
+ # @return [ Hash ] Standardized MongoClient options.
90
+ def ruby_to_smc(opts)
91
+ rv = {}
92
+ URI_OPTION_MAP.each do |uri_key, spec|
93
+ if spec[:group]
94
+ v = opts[spec[:group]]
95
+ v = v && v[spec[:name]]
96
+ else
97
+ v = opts[spec[:name]]
98
+ end
99
+ unless v.nil?
100
+ if spec[:type]
101
+ v = send("revert_#{spec[:type]}", v)
102
+ end
103
+ canonical_key = URI_OPTION_CANONICAL_NAMES[uri_key]
104
+ unless canonical_key
105
+ raise ArgumentError, "Option #{uri_key} is not known"
106
+ end
107
+ rv[canonical_key] = v
108
+ end
109
+ end
110
+ # For options that default to true, remove the value if it is true.
111
+ %w(retryReads retryWrites).each do |k|
112
+ if rv[k]
113
+ rv.delete(k)
114
+ end
115
+ end
116
+ # Remove auth source when it is $external for mechanisms that default
117
+ # (or require) that auth source.
118
+ if %w(MONGODB-AWS).include?(rv['authMechanism']) && rv['authSource'] == '$external'
119
+ rv.delete('authSource')
120
+ end
121
+ # ssl and tls are aliases, remove ssl ones
122
+ rv.delete('ssl')
123
+ # TODO remove authSource if it is the same as the database,
124
+ # requires this method to know the database specified in the client.
125
+ rv
126
+ end
127
+
128
+ private
129
+
130
+ # Applies URI value transformation by either using the default cast
131
+ # or a transformation appropriate for the given type.
132
+ #
133
+ # @param key [String] URI option name.
134
+ # @param value [String] The value to be transformed.
135
+ # @param type [Symbol] The transform method.
136
+ def apply_transform(key, value, type)
137
+ if type
138
+ send("convert_#{type}", key, value)
139
+ else
140
+ value
141
+ end
142
+ end
143
+
144
+ # Merges a new option into the target.
145
+ #
146
+ # If the option exists at the target destination the merge will
147
+ # be an addition.
148
+ #
149
+ # Specifically required to append an additional tag set
150
+ # to the array of tag sets without overwriting the original.
151
+ #
152
+ # @param target [Hash] The destination.
153
+ # @param value [Object] The value to be merged.
154
+ # @param name [Symbol] The name of the option.
155
+ def merge_uri_option(target, value, name)
156
+ if target.key?(name)
157
+ if REPEATABLE_OPTIONS.include?(name)
158
+ target[name] += value
159
+ else
160
+ log_warn("Repeated option key: #{name}.")
161
+ end
162
+ else
163
+ target.merge!(name => value)
164
+ end
165
+ end
166
+
167
+ # Hash for storing map of URI option parameters to conversion strategies
168
+ URI_OPTION_MAP = {}
169
+
170
+ # @return [ Hash<String, String> ] Map from lowercased to canonical URI
171
+ # option names.
172
+ URI_OPTION_CANONICAL_NAMES = {}
173
+
174
+ # Simple internal dsl to register a MongoDB URI option in the URI_OPTION_MAP.
175
+ #
176
+ # @param uri_key [String] The MongoDB URI option to register.
177
+ # @param name [Symbol] The name of the option in the driver.
178
+ # @param extra [Hash] Extra options.
179
+ # * :group [Symbol] Nested hash where option will go.
180
+ # * :type [Symbol] Name of function to transform value.
181
+ def self.uri_option(uri_key, name, **extra)
182
+ URI_OPTION_MAP[uri_key.downcase] = { name: name }.update(extra)
183
+ URI_OPTION_CANONICAL_NAMES[uri_key.downcase] = uri_key
184
+ end
185
+
186
+ # Replica Set Options
187
+ uri_option 'replicaSet', :replica_set
188
+
189
+ # Timeout Options
190
+ uri_option 'connectTimeoutMS', :connect_timeout, type: :ms
191
+ uri_option 'socketTimeoutMS', :socket_timeout, type: :ms
192
+ uri_option 'serverSelectionTimeoutMS', :server_selection_timeout, type: :ms
193
+ uri_option 'localThresholdMS', :local_threshold, type: :ms
194
+ uri_option 'heartbeatFrequencyMS', :heartbeat_frequency, type: :ms
195
+ uri_option 'maxIdleTimeMS', :max_idle_time, type: :ms
196
+
197
+ # Write Options
198
+ uri_option 'w', :w, group: :write_concern, type: :w
199
+ uri_option 'journal', :j, group: :write_concern, type: :bool
200
+ uri_option 'fsync', :fsync, group: :write_concern, type: :bool
201
+ uri_option 'wTimeoutMS', :wtimeout, group: :write_concern, type: :integer
202
+
203
+ # Read Options
204
+ uri_option 'readPreference', :mode, group: :read, type: :read_mode
205
+ uri_option 'readPreferenceTags', :tag_sets, group: :read, type: :read_tags
206
+ uri_option 'maxStalenessSeconds', :max_staleness, group: :read, type: :max_staleness
207
+
208
+ # Pool options
209
+ uri_option 'minPoolSize', :min_pool_size, type: :integer
210
+ uri_option 'maxPoolSize', :max_pool_size, type: :integer
211
+ uri_option 'waitQueueTimeoutMS', :wait_queue_timeout, type: :ms
212
+
213
+ # Security Options
214
+ uri_option 'ssl', :ssl, type: :repeated_bool
215
+ uri_option 'tls', :ssl, type: :repeated_bool
216
+ uri_option 'tlsAllowInvalidCertificates', :ssl_verify_certificate,
217
+ type: :inverse_bool
218
+ uri_option 'tlsAllowInvalidHostnames', :ssl_verify_hostname,
219
+ type: :inverse_bool
220
+ uri_option 'tlsCAFile', :ssl_ca_cert
221
+ uri_option 'tlsCertificateKeyFile', :ssl_cert
222
+ uri_option 'tlsCertificateKeyFilePassword', :ssl_key_pass_phrase
223
+ uri_option 'tlsInsecure', :ssl_verify, type: :inverse_bool
224
+ uri_option 'tlsDisableOCSPEndpointCheck', :ssl_verify_ocsp_endpoint,
225
+ type: :inverse_bool
226
+
227
+ # Topology options
228
+ uri_option 'directConnection', :direct_connection, type: :bool
229
+ uri_option 'connect', :connect, type: :symbol
230
+
231
+ # Auth Options
232
+ uri_option 'authSource', :auth_source
233
+ uri_option 'authMechanism', :auth_mech, type: :auth_mech
234
+ uri_option 'authMechanismProperties', :auth_mech_properties, type: :auth_mech_props
235
+
236
+ # Client Options
237
+ uri_option 'appName', :app_name
238
+ uri_option 'compressors', :compressors, type: :array
239
+ uri_option 'readConcernLevel', :level, group: :read_concern, type: :symbol
240
+ uri_option 'retryReads', :retry_reads, type: :bool
241
+ uri_option 'retryWrites', :retry_writes, type: :bool
242
+ uri_option 'zlibCompressionLevel', :zlib_compression_level, type: :zlib_compression_level
243
+
244
+ # Converts +value+ to a boolean.
245
+ #
246
+ # Returns true for 'true', false for 'false', otherwise nil.
247
+ #
248
+ # @param [ String ] name Name of the URI option being processed.
249
+ # @param value [ String ] URI option value.
250
+ #
251
+ # @return [ true | false | nil ] Converted value.
252
+ def convert_bool(name, value)
253
+ case value
254
+ when "true", 'TRUE'
255
+ true
256
+ when "false", 'FALSE'
257
+ false
258
+ else
259
+ log_warn("invalid boolean option for #{name}: #{value}")
260
+ nil
261
+ end
262
+ end
263
+
264
+ def revert_bool(value)
265
+ value
266
+ end
267
+
268
+ # Converts the value into a boolean and returns it wrapped in an array.
269
+ #
270
+ # @param name [ String ] Name of the URI option being processed.
271
+ # @param value [ String ] URI option value.
272
+ #
273
+ # @return [ Array<true | false> ] The boolean value parsed and wraped
274
+ # in an array.
275
+ def convert_repeated_bool(name, value)
276
+ [convert_bool(name, value)]
277
+ end
278
+
279
+ def revert_repeated_bool(value)
280
+ value
281
+ end
282
+
283
+ # Parses a boolean value and returns its inverse.
284
+ #
285
+ # @param [ String ] name Name of the URI option being processed.
286
+ # @param value [ String ] The URI option value.
287
+ #
288
+ # @return [ true | false | nil ] The inverse of the boolean value parsed out, otherwise nil
289
+ # (and a warning will be logged).
290
+ def convert_inverse_bool(name, value)
291
+ b = convert_bool(name, value)
292
+
293
+ if b.nil?
294
+ nil
295
+ else
296
+ !b
297
+ end
298
+ end
299
+
300
+ def revert_inverse_bool(value)
301
+ !value
302
+ end
303
+
304
+ # Converts +value+ into an integer.
305
+ #
306
+ # If the value is not a valid integer, warns and returns nil.
307
+ #
308
+ # @param [ String ] name Name of the URI option being processed.
309
+ # @param value [ String ] URI option value.
310
+ #
311
+ # @return [ nil | Integer ] Converted value.
312
+ def convert_integer(name, value)
313
+ unless /\A\d+\z/ =~ value
314
+ log_warn("#{value} is not a valid integer for #{name}")
315
+ return nil
316
+ end
317
+
318
+ value.to_i
319
+ end
320
+
321
+ def revert_integer(value)
322
+ value
323
+ end
324
+
325
+ # Ruby's convention is to provide timeouts in seconds, not milliseconds and
326
+ # to use fractions where more precision is necessary. The connection string
327
+ # options are always in MS so we provide an easy conversion type.
328
+ #
329
+ # @param [ String ] name Name of the URI option being processed.
330
+ # @param [ Integer ] value The millisecond value.
331
+ #
332
+ # @return [ Float ] The seconds value.
333
+ #
334
+ # @since 2.0.0
335
+ def convert_ms(name, value)
336
+ unless /\A-?\d+(\.\d+)?\z/ =~ value
337
+ log_warn("Invalid ms value for #{name}: #{value}")
338
+ return nil
339
+ end
340
+
341
+ if value[0] == '-'
342
+ log_warn("#{name} cannot be a negative number")
343
+ return nil
344
+ end
345
+
346
+ value.to_f / 1000
347
+ end
348
+
349
+ def revert_ms(value)
350
+ (value * 1000).round
351
+ end
352
+
353
+ # Converts +value+ into a symbol.
354
+ #
355
+ # @param [ String ] name Name of the URI option being processed.
356
+ # @param value [ String ] URI option value.
357
+ #
358
+ # @return [ Symbol ] Converted value.
359
+ def convert_symbol(name, value)
360
+ value.to_sym
361
+ end
362
+
363
+ def revert_symbol(value)
364
+ value.to_s
365
+ end
366
+
367
+ # Extract values from the string and put them into an array.
368
+ #
369
+ # @param [ String ] name Name of the URI option being processed.
370
+ # @param [ String ] value The string to build an array from.
371
+ #
372
+ # @return [ Array ] The array built from the string.
373
+ def convert_array(name, value)
374
+ value.split(',')
375
+ end
376
+
377
+ def revert_array(value)
378
+ value
379
+ end
380
+
381
+ # Authentication mechanism transformation.
382
+ #
383
+ # @param [ String ] name Name of the URI option being processed.
384
+ # @param value [String] The authentication mechanism.
385
+ #
386
+ # @return [Symbol] The transformed authentication mechanism.
387
+ def convert_auth_mech(name, value)
388
+ (AUTH_MECH_MAP[value.upcase] || value).tap do |mech|
389
+ log_warn("#{value} is not a valid auth mechanism") unless mech
390
+ end
391
+ end
392
+
393
+ def revert_auth_mech(value)
394
+ found = AUTH_MECH_MAP.detect do |k, v|
395
+ v == value
396
+ end
397
+ if found
398
+ found.first
399
+ else
400
+ raise ArgumentError, "Unknown auth mechanism #{value}"
401
+ end
402
+ end
403
+
404
+ # Auth mechanism properties extractor.
405
+ #
406
+ # @param [ String ] name Name of the URI option being processed.
407
+ # @param value [ String ] The auth mechanism properties string.
408
+ #
409
+ # @return [ Hash ] The auth mechanism properties hash.
410
+ def convert_auth_mech_props(name, value)
411
+ properties = hash_extractor('authMechanismProperties', value)
412
+ if properties
413
+ properties.each do |k, v|
414
+ if k.to_s.downcase == 'canonicalize_host_name' && v
415
+ properties[k] = (v.downcase == 'true')
416
+ end
417
+ end
418
+ end
419
+ properties
420
+ end
421
+
422
+ def revert_auth_mech_props(value)
423
+ value
424
+ end
425
+
426
+ # Parses the max staleness value, which must be either "0" or an integer
427
+ # greater or equal to 90.
428
+ #
429
+ # @param [ String ] name Name of the URI option being processed.
430
+ # @param value [ String ] The max staleness string.
431
+ #
432
+ # @return [ Integer | nil ] The max staleness integer parsed out if it is valid, otherwise nil
433
+ # (and a warning will be logged).
434
+ def convert_max_staleness(name, value)
435
+ if /\A-?\d+\z/ =~ value
436
+ int = value.to_i
437
+
438
+ if int == -1
439
+ int = nil
440
+ end
441
+
442
+ if int && (int >= 0 && int < 90 || int < 0)
443
+ log_warn("max staleness should be either 0 or greater than 90: #{value}")
444
+ int = nil
445
+ end
446
+
447
+ return int
448
+ end
449
+
450
+ log_warn("Invalid max staleness value: #{value}")
451
+ nil
452
+ end
453
+
454
+ def revert_max_staleness(value)
455
+ value
456
+ end
457
+
458
+ # Read preference mode transformation.
459
+ #
460
+ # @param [ String ] name Name of the URI option being processed.
461
+ # @param value [String] The read mode string value.
462
+ #
463
+ # @return [Symbol] The read mode symbol.
464
+ def convert_read_mode(name, value)
465
+ READ_MODE_MAP[value.downcase] || value
466
+ end
467
+
468
+ def revert_read_mode(value)
469
+ value.to_s.gsub(/_(\w)/) { $1.upcase }
470
+ end
471
+
472
+ # Read preference tags transformation.
473
+ #
474
+ # @param [ String ] name Name of the URI option being processed.
475
+ # @param value [String] The string representing tag set.
476
+ #
477
+ # @return [Array<Hash>] Array with tag set.
478
+ def convert_read_tags(name, value)
479
+ converted = convert_read_set(name, value)
480
+ if converted
481
+ [converted]
482
+ else
483
+ nil
484
+ end
485
+ end
486
+
487
+ def revert_read_tags(value)
488
+ value
489
+ end
490
+
491
+ # Read preference tag set extractor.
492
+ #
493
+ # @param [ String ] name Name of the URI option being processed.
494
+ # @param value [String] The tag set string.
495
+ #
496
+ # @return [Hash] The tag set hash.
497
+ def convert_read_set(name, value)
498
+ hash_extractor('readPreferenceTags', value)
499
+ end
500
+
501
+ # Converts +value+ as a write concern.
502
+ #
503
+ # If +value+ is the word "majority", returns the symbol :majority.
504
+ # If +value+ is a number, returns the number as an integer.
505
+ # Otherwise returns the string +value+ unchanged.
506
+ #
507
+ # @param [ String ] name Name of the URI option being processed.
508
+ # @param value [ String ] URI option value.
509
+ #
510
+ # @return [ Integer | Symbol | String ] Converted value.
511
+ def convert_w(name, value)
512
+ case value
513
+ when 'majority'
514
+ :majority
515
+ when /\A[0-9]+\z/
516
+ value.to_i
517
+ else
518
+ value
519
+ end
520
+ end
521
+
522
+ def revert_w(value)
523
+ case value
524
+ when Symbol
525
+ value.to_s
526
+ else
527
+ value
528
+ end
529
+ end
530
+
531
+ # Parses the zlib compression level.
532
+ #
533
+ # @param [ String ] name Name of the URI option being processed.
534
+ # @param value [ String ] The zlib compression level string.
535
+ #
536
+ # @return [ Integer | nil ] The compression level value if it is between -1 and 9 (inclusive),
537
+ # otherwise nil (and a warning will be logged).
538
+ def convert_zlib_compression_level(name, value)
539
+ if /\A-?\d+\z/ =~ value
540
+ i = value.to_i
541
+
542
+ if i >= -1 && i <= 9
543
+ return i
544
+ end
545
+ end
546
+
547
+ log_warn("#{value} is not a valid zlibCompressionLevel")
548
+ nil
549
+ end
550
+
551
+ def revert_zlib_compression_level(value)
552
+ value
553
+ end
554
+
555
+ # Extract values from the string and put them into a nested hash.
556
+ #
557
+ # @param [ String ] name Name of the URI option being processed.
558
+ # @param value [ String ] The string to build a hash from.
559
+ #
560
+ # @return [ Hash ] The hash built from the string.
561
+ def hash_extractor(name, value)
562
+ h = {}
563
+ value.split(',').each do |tag|
564
+ k, v = tag.split(':')
565
+ if v.nil?
566
+ log_warn("Invalid hash value for #{name}: key `#{k}` does not have a value: #{value}")
567
+ next
568
+ end
569
+
570
+ h[k.to_sym] = v
571
+ end
572
+ if h.empty?
573
+ nil
574
+ else
575
+ h
576
+ end
577
+ end
578
+
579
+ end
580
+
581
+ end
582
+ end