Skip to content

Commit

Permalink
🔧 Add default configs for each x.y version
Browse files Browse the repository at this point in the history
  • Loading branch information
nevans committed Jun 22, 2024
1 parent a3e1385 commit 7dfb1f8
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 3 deletions.
74 changes: 71 additions & 3 deletions lib/net/imap/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,66 @@ class IMAP
# plain_client.config.inherited?(:debug) # => true
# plain_client.config.debug? # => false
#
# == Versioned defaults
#
# The effective default configuration for a specific +x.y+ version of
# +net-imap+ can be loaded with the +config+ keyword argument to
# Net::IMAP.new. Requesting default configurations for previous versions
# enables extra backward compatibility with those versions:
#
# client = Net::IMAP.new(hostname, config: 0.3)
# client.config.sasl_ir # => false
# client.config.responses_without_block # => :silence_deprecation_warning
#
# client = Net::IMAP.new(hostname, config: 0.4)
# client.config.sasl_ir # => true
# client.config.responses_without_block # => :silence_deprecation_warning
#
# client = Net::IMAP.new(hostname, config: 0.5)
# client.config.sasl_ir # => true
# client.config.responses_without_block # => :warn
#
# The versioned default configs inherit certain specific config options from
# Config.global, for example #debug:
#
# client = Net::IMAP.new(hostname, config: 0.4)
# Net::IMAP.debug = false
# client.config.debug? # => false
#
# Net::IMAP.debug = true
# client.config.debug? # => true
#
# == Thread Safety
#
# *NOTE:* Updates to config objects are not synchronized for thread-safety.
#
class Config
# Array of attribute names that are _not_ loaded by #load_defaults.
DEFAULT_TO_INHERIT = %i[debug].freeze
private_constant :DEFAULT_TO_INHERIT

# The default config, which is hardcoded and frozen.
def self.default; @default end

# The global config object. Also available from Net::IMAP.config.
def self.global; @global end

# A hash of hard-coded configurations, indexed by version number.
def self.version_defaults; @version_defaults end
@version_defaults = {}

# :call-seq:
# Net::IMAP::Config[number] -> versioned config
# Net::IMAP::Config[symbol] -> named config
# Net::IMAP::Config[hash] -> new frozen config
# Net::IMAP::Config[config] -> same config
#
# Given a version number, returns the default configuration for the target
# version. See Config@Versioned+defaults.
#
# Given a version name, returns the default configuration for the target
# version. See Config@Named+defaults.
#
# Given a Hash, creates a new _frozen_ config which inherits from
# Config.global. Use Config.new for an unfrozen config.
#
Expand All @@ -80,9 +124,16 @@ def self.[](config)
elsif config.respond_to?(:to_hash)
new(global, **config).freeze
else
raise TypeError, "no implicit conversion of %s to %s" % [
config.class, Config
]
version_defaults.fetch(config) {
case config
when Numeric
raise RangeError, "unknown config version: %p" % [config]
else
raise TypeError, "no implicit conversion of %s to %s" % [
config.class, Config
]
end
}
end
end

Expand Down Expand Up @@ -204,6 +255,23 @@ def to_h; data.members.to_h { [_1, send(_1)] } end

@global = default.new

version_defaults[0.4] = Config[
default.to_h.reject {|k,v| DEFAULT_TO_INHERIT.include?(k) }
]

version_defaults[0] = Config[0.4].dup.update(
sasl_ir: false,
).freeze
version_defaults[0.0] = Config[0]
version_defaults[0.1] = Config[0]
version_defaults[0.2] = Config[0]
version_defaults[0.3] = Config[0]

version_defaults[0.5] = Config[0.4].dup.update(
responses_without_block: :warn,
).freeze

version_defaults.freeze
end
end
end
30 changes: 30 additions & 0 deletions test/net/imap/test_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,35 @@ class ConfigTest < Test::Unit::TestCase
assert_equal false, child.debug?
end

test ".version_defaults are all frozen, and inherit debug from global" do
Config.version_defaults.each do |name, config|
assert [0, Float, Symbol].any? { _1 === name }
assert_kind_of Config, config
assert config.frozen?, "#{name} isn't frozen"
assert config.inherited?(:debug), "#{name} doesn't inherit debug"
assert_same Config.global, config.parent
end
end

test ".[] for all x.y versions" do
original = Config[0]
assert_kind_of Config, original
assert_same original, Config[0.0]
assert_same original, Config[0.1]
assert_same original, Config[0.2]
assert_same original, Config[0.3]
assert_kind_of Config, Config[0.4]
assert_kind_of Config, Config[0.5]
end

test ".[] range errors" do
assert_raise(RangeError) do Config[0.01] end
assert_raise(RangeError) do Config[0.11] end
assert_raise(RangeError) do Config[0.111] end
assert_raise(RangeError) do Config[0.9] end
assert_raise(RangeError) do Config[1] end
end

test ".[] with a hash" do
config = Config[{responses_without_block: :raise, sasl_ir: false}]
assert config.frozen?
Expand All @@ -149,6 +178,7 @@ class ConfigTest < Test::Unit::TestCase
assert_same Config.global, Config.new.parent
assert_same Config.default, Config.new(Config.default).parent
assert_same Config.global, Config.new(Config.global).parent
assert_same Config[0.4], Config.new(0.4).parent
assert_equal true, Config.new({debug: true}, debug: false).parent.debug?
assert_equal true, Config.new({debug: true}, debug: false).parent.frozen?
end
Expand Down

0 comments on commit 7dfb1f8

Please sign in to comment.