Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/tutorials/ruby-driver-create-client.txt
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,13 @@ Ruby Options
- ``Float``
- 10

* - ``:wrapping_libraries``
- Information about libraries such as ODMs that are wrapping the driver.
Specify the lower level libraries first. Allowed hash keys: :name,
:version, :platform. Example: ``[name: 'Mongoid', version: '7.1.2']``
- ``Array<Hash>``
- none

* - ``:write``
- Deprecated. Equivalent to ``:write_concern`` option. If both ``:write``
and ``:write_concern`` are specified, their values must be identical.
Expand Down
35 changes: 35 additions & 0 deletions lib/mongo/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class Client
:truncate_logs,
:user,
:wait_queue_timeout,
:wrapping_libraries,
:write,
:write_concern,
:zlib_compression_level,
Expand Down Expand Up @@ -375,6 +376,10 @@ def hash
# @option options [ String ] :user The user name.
# @option options [ Float ] :wait_queue_timeout The time to wait, in
# seconds, in the connection pool for a connection to be checked in.
# @option options [ Array<Hash> ] :wrapping_libraries Information about
# libraries such as ODMs that are wrapping the driver, to be added to
# metadata sent to the server. Specify the lower level libraries first.
# Allowed hash keys: :name, :version, :platform.
# @option options [ Hash ] :write Deprecated. Equivalent to :write_concern
# option.
# @option options [ Hash ] :write_concern The write concern options.
Expand Down Expand Up @@ -1160,6 +1165,36 @@ def validate_options!(addresses = nil)
raise ArgumentError, ":bg_error_backtrace option value must be true, false, nil or a positive integer: #{value}"
end
end

if libraries = options[:wrapping_libraries]
unless Array === libraries
raise ArgumentError, ":wrapping_libraries must be an array of hashes: #{libraries}"
end

libraries = libraries.map do |library|
Utils.shallow_symbolize_keys(library)
end

libraries.each do |library|
unless Hash === library
raise ArgumentError, ":wrapping_libraries element is not a hash: #{library}"
end

if library.empty?
raise ArgumentError, ":wrapping_libraries element is empty"
end

unless (library.keys - %i(name platform version)).empty?
raise ArgumentError, ":wrapping_libraries element has invalid keys (allowed keys: :name, :platform, :version): #{library}"
end

library.each do |key, value|
if value.include?('|')
raise ArgumentError, ":wrapping_libraries element value cannot include '|': #{value}"
end
end
end
end
end

# Validates all authentication-related options after they are set on the client
Expand Down
30 changes: 27 additions & 3 deletions lib/mongo/server/app_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,28 @@ class AppMetadata
# the metadata printed to the mongod logs upon establishing a connection
# in server versions >= 3.4.
# @option options [ String ] :user The user name.
# @option options [ Array<Hash> ] :wrapping_libraries Information about
# libraries such as ODMs that are wrapping the driver. Specify the
# lower level libraries first. Allowed hash keys: :name, :version,
# :platform.
#
# @since 2.4.0
def initialize(options)
@app_name = options[:app_name].to_s if options[:app_name]
@platform = options[:platform]
@compressors = options[:compressors] || []
@wrapping_libraries = options[:wrapping_libraries]

if options[:user] && !options[:auth_mech]
auth_db = options[:auth_source] || 'admin'
@request_auth_mech = "#{auth_db}.#{options[:user]}"
end
end

# @return [ Array<Hash> | nil ] Information about libraries wrapping
# the driver.
attr_reader :wrapping_libraries

# Get the bytes of the ismaster message including this metadata.
#
# @api private
Expand Down Expand Up @@ -140,9 +149,17 @@ def document
end

def driver_doc
names = [DRIVER_NAME]
versions = [Mongo::VERSION]
if wrapping_libraries
wrapping_libraries.each do |library|
names << library[:name] || ''
versions << library[:version] || ''
end
end
{
name: DRIVER_NAME,
version: Mongo::VERSION
name: names.join('|'),
version: versions.join('|'),
}
end

Expand Down Expand Up @@ -175,12 +192,19 @@ def platform
ruby_versions = ["Ruby #{RUBY_VERSION}"]
platforms = [RUBY_PLATFORM]
end
[
platform = [
@platform,
*ruby_versions,
*platforms,
RbConfig::CONFIG['build'],
].compact.join(', ')
platforms = [platform]
if wrapping_libraries
wrapping_libraries.each do |library|
platforms << library[:platform] || ''
end
end
platforms.join('|')
end
end
end
Expand Down
112 changes: 112 additions & 0 deletions spec/mongo/client_construction_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,118 @@
end
end
=end

context ':wrapping_libraries option' do
let(:options) do
{wrapping_libraries: wrapping_libraries}
end

context 'valid input' do
context 'symbol keys' do
let(:wrapping_libraries) do
[name: 'Mongoid', version: '7.1.2'].freeze
end

it 'works' do
client.options[:wrapping_libraries].should == ['name' => 'Mongoid', 'version' => '7.1.2']
end
end

context 'string keys' do
let(:wrapping_libraries) do
['name' => 'Mongoid', 'version' => '7.1.2'].freeze
end

it 'works' do
client.options[:wrapping_libraries].should == ['name' => 'Mongoid', 'version' => '7.1.2']
end
end

context 'Redacted keys' do
let(:wrapping_libraries) do
[Mongo::Options::Redacted.new(name: 'Mongoid', version: '7.1.2')].freeze
end

it 'works' do
client.options[:wrapping_libraries].should == ['name' => 'Mongoid', 'version' => '7.1.2']
end
end

context 'two libraries' do
let(:wrapping_libraries) do
[
{name: 'Mongoid', version: '7.1.2'},
{name: 'Rails', version: '4.0', platform: 'Foobar'},
].freeze
end

it 'works' do
client.options[:wrapping_libraries].should == [
{'name' => 'Mongoid', 'version' => '7.1.2'},
{'name' => 'Rails', 'version' => '4.0', 'platform' => 'Foobar'},
]
end
end

context 'empty array' do
let(:wrapping_libraries) do
[]
end

it 'works' do
client.options[:wrapping_libraries].should == []
end
end

context 'empty array' do
let(:wrapping_libraries) do
nil
end

it 'works' do
client.options[:wrapping_libraries].should be nil
end
end
end

context 'valid input' do
context 'hash given instead of an array' do
let(:wrapping_libraries) do
{name: 'Mongoid', version: '7.1.2'}.freeze
end

it 'is rejected' do
lambda do
client
end.should raise_error(ArgumentError, /:wrapping_libraries must be an array of hashes/)
end
end

context 'invalid keys' do
let(:wrapping_libraries) do
[name: 'Mongoid', invalid: '7.1.2'].freeze
end

it 'is rejected' do
lambda do
client
end.should raise_error(ArgumentError, /:wrapping_libraries element has invalid keys/)
end
end

context 'value includes |' do
let(:wrapping_libraries) do
[name: 'Mongoid|on|Rails', version: '7.1.2'].freeze
end

it 'is rejected' do
lambda do
client
end.should raise_error(ArgumentError, /:wrapping_libraries element value cannot include '|'/)
end
end
end
end
end

context 'when making a block client' do
Expand Down
80 changes: 80 additions & 0 deletions spec/mongo/server/app_metadata_shared.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,84 @@
end
end
end

context 'when wrapping libraries are specified' do
let(:app_metadata) do
described_class.new(wrapping_libraries: wrapping_libraries)
end

context 'one' do
let(:wrapping_libraries) { [wrapping_library] }

context 'no fields' do
let(:wrapping_library) do
{}
end

it 'adds empty strings' do
document[:client][:driver][:name].should == 'mongo-ruby-driver|'
document[:client][:driver][:version].should == "#{Mongo::VERSION}|"
document[:client][:platform].should =~ /\AJ?Ruby[^|]+\|\z/
end
end

context 'some fields' do
let(:wrapping_library) do
{name: 'Mongoid'}
end

it 'adds the fields' do
document[:client][:driver][:name].should == 'mongo-ruby-driver|Mongoid'
document[:client][:driver][:version].should == "#{Mongo::VERSION}|"
document[:client][:platform].should =~ /\AJ?Ruby[^|]+\|\z/
end
end

context 'all fields' do
let(:wrapping_library) do
{name: 'Mongoid', version: '7.1.2', platform: 'OS9000'}
end

it 'adds the fields' do
document[:client][:driver][:name].should == 'mongo-ruby-driver|Mongoid'
document[:client][:driver][:version].should == "#{Mongo::VERSION}|7.1.2"
document[:client][:platform].should =~ /\AJ?Ruby[^|]+\|OS9000\z/
end
end
end

context 'two' do
context 'some fields' do
let(:wrapping_libraries) do
[
{name: 'Mongoid', version: '42'},
# All libraries should be specifying their versions, in theory,
# but test not specifying a version.
{version: '4.0', platform: 'OS9000'},
]
end

it 'adds the fields' do
document[:client][:driver][:name].should == 'mongo-ruby-driver|Mongoid|'
document[:client][:driver][:version].should == "#{Mongo::VERSION}|42|4.0"
document[:client][:platform].should =~ /\AJ?Ruby[^|]+\|\|OS9000\z/
end
end

context 'a realistic Mongoid & Rails wrapping' do
let(:wrapping_libraries) do
[
{name: 'Mongoid', version: '7.1.2'},
{name: 'Rails', version: '6.0.3'},
]
end

it 'adds the fields' do
document[:client][:driver][:name].should == 'mongo-ruby-driver|Mongoid|Rails'
document[:client][:driver][:version].should == "#{Mongo::VERSION}|7.1.2|6.0.3"
document[:client][:platform].should =~ /\AJ?Ruby[^|]+\|\|\z/
end
end
end
end
end