Skip to content

Commit 394452e

Browse files
authored
🔀 Merge pull request #302 from nevans/config/add-versioned_defaults
🔧 Add versioned defaults
2 parents fa78037 + be8d5cd commit 394452e

File tree

2 files changed

+170
-4
lines changed

2 files changed

+170
-4
lines changed

lib/net/imap/config.rb

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,112 @@ class IMAP
5454
# plain_client.config.inherited?(:debug) # => true
5555
# plain_client.config.debug? # => false
5656
#
57+
# == Versioned defaults
58+
#
59+
# The effective default configuration for a specific +x.y+ version of
60+
# +net-imap+ can be loaded with the +config+ keyword argument to
61+
# Net::IMAP.new. Requesting default configurations for previous versions
62+
# enables extra backward compatibility with those versions:
63+
#
64+
# client = Net::IMAP.new(hostname, config: 0.3)
65+
# client.config.sasl_ir # => false
66+
# client.config.responses_without_block # => :silence_deprecation_warning
67+
#
68+
# client = Net::IMAP.new(hostname, config: 0.4)
69+
# client.config.sasl_ir # => true
70+
# client.config.responses_without_block # => :silence_deprecation_warning
71+
#
72+
# client = Net::IMAP.new(hostname, config: 0.5)
73+
# client.config.sasl_ir # => true
74+
# client.config.responses_without_block # => :warn
75+
#
76+
# client = Net::IMAP.new(hostname, config: :future)
77+
# client.config.sasl_ir # => true
78+
# client.config.responses_without_block # => :raise
79+
#
80+
# The versioned default configs inherit certain specific config options from
81+
# Config.global, for example #debug:
82+
#
83+
# client = Net::IMAP.new(hostname, config: 0.4)
84+
# Net::IMAP.debug = false
85+
# client.config.debug? # => false
86+
#
87+
# Net::IMAP.debug = true
88+
# client.config.debug? # => true
89+
#
90+
# === Named defaults
91+
# In addition to +x.y+ version numbers, the following aliases are supported:
92+
#
93+
# [+:default+]
94+
# An alias for +:current+.
95+
#
96+
# >>>
97+
# *NOTE*: This is _not_ the same as Config.default. It inherits some
98+
# attributes from Config.global, for example: #debug.
99+
# [+:current+]
100+
# An alias for the current +x.y+ version's defaults.
101+
# [+:next+]
102+
# The _planned_ config for the next +x.y+ version.
103+
# [+:future+]
104+
# The _planned_ eventual config for some future +x.y+ version.
105+
#
106+
# For example, to raise exceptions for all current deprecations:
107+
# client = Net::IMAP.new(hostname, config: :future)
108+
# client.responses # raises an ArgumentError
57109
#
58110
# == Thread Safety
59111
#
60112
# *NOTE:* Updates to config objects are not synchronized for thread-safety.
61113
#
62114
class Config
115+
# Array of attribute names that are _not_ loaded by #load_defaults.
116+
DEFAULT_TO_INHERIT = %i[debug].freeze
117+
private_constant :DEFAULT_TO_INHERIT
118+
63119
# The default config, which is hardcoded and frozen.
64120
def self.default; @default end
65121

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

69-
def self.[](config) # :nodoc: unfinished API
125+
# A hash of hard-coded configurations, indexed by version number.
126+
def self.version_defaults; @version_defaults end
127+
@version_defaults = {}
128+
129+
# :call-seq:
130+
# Net::IMAP::Config[number] -> versioned config
131+
# Net::IMAP::Config[symbol] -> named config
132+
# Net::IMAP::Config[hash] -> new frozen config
133+
# Net::IMAP::Config[config] -> same config
134+
#
135+
# Given a version number, returns the default configuration for the target
136+
# version. See Config@Versioned+defaults.
137+
#
138+
# Given a version name, returns the default configuration for the target
139+
# version. See Config@Named+defaults.
140+
#
141+
# Given a Hash, creates a new _frozen_ config which inherits from
142+
# Config.global. Use Config.new for an unfrozen config.
143+
#
144+
# Given a config, returns that same config.
145+
def self.[](config)
70146
if config.is_a?(Config) || config.nil? && global.nil?
71147
config
148+
elsif config.respond_to?(:to_hash)
149+
new(global, **config).freeze
72150
else
73-
raise TypeError, "no implicit conversion of %s to %s" % [
74-
config.class, Config
75-
]
151+
version_defaults.fetch(config) {
152+
case config
153+
when Numeric
154+
raise RangeError, "unknown config version: %p" % [config]
155+
when Symbol
156+
raise KeyError, "unknown config name: %p" % [config]
157+
else
158+
raise TypeError, "no implicit conversion of %s to %s" % [
159+
config.class, Config
160+
]
161+
end
162+
}
76163
end
77164
end
78165

@@ -198,6 +285,31 @@ def to_h; data.members.to_h { [_1, send(_1)] } end
198285

199286
@global = default.new
200287

288+
version_defaults[0.4] = Config[
289+
default.to_h.reject {|k,v| DEFAULT_TO_INHERIT.include?(k) }
290+
]
291+
292+
version_defaults[0] = Config[0.4].dup.update(
293+
sasl_ir: false,
294+
).freeze
295+
version_defaults[0.0] = Config[0]
296+
version_defaults[0.1] = Config[0]
297+
version_defaults[0.2] = Config[0]
298+
version_defaults[0.3] = Config[0]
299+
300+
version_defaults[0.5] = Config[0.4].dup.update(
301+
responses_without_block: :warn,
302+
).freeze
303+
304+
version_defaults[:default] = Config[0.4]
305+
version_defaults[:current] = Config[0.4]
306+
version_defaults[:next] = Config[0.5]
307+
308+
version_defaults[:future] = Config[0.5].dup.update(
309+
responses_without_block: :raise,
310+
).freeze
311+
312+
version_defaults.freeze
201313
end
202314
end
203315
end

test/net/imap/test_config.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,64 @@ class ConfigTest < Test::Unit::TestCase
135135
assert_equal false, child.debug?
136136
end
137137

138+
test ".version_defaults are all frozen, and inherit debug from global" do
139+
Config.version_defaults.each do |name, config|
140+
assert [0, Float, Symbol].any? { _1 === name }
141+
assert_kind_of Config, config
142+
assert config.frozen?, "#{name} isn't frozen"
143+
assert config.inherited?(:debug), "#{name} doesn't inherit debug"
144+
assert_same Config.global, config.parent
145+
end
146+
end
147+
148+
test ".[] for all x.y versions" do
149+
original = Config[0]
150+
assert_kind_of Config, original
151+
assert_same original, Config[0.0]
152+
assert_same original, Config[0.1]
153+
assert_same original, Config[0.2]
154+
assert_same original, Config[0.3]
155+
assert_kind_of Config, Config[0.4]
156+
assert_kind_of Config, Config[0.5]
157+
end
158+
159+
test ".[] range errors" do
160+
assert_raise(RangeError) do Config[0.01] end
161+
assert_raise(RangeError) do Config[0.11] end
162+
assert_raise(RangeError) do Config[0.111] end
163+
assert_raise(RangeError) do Config[0.9] end
164+
assert_raise(RangeError) do Config[1] end
165+
end
166+
167+
test ".[] key errors" do
168+
assert_raise(KeyError) do Config[:nonexistent] end
169+
end
170+
171+
test ".[] with symbol names" do
172+
assert_same Config[0.4], Config[:current]
173+
assert_same Config[0.4], Config[:default]
174+
assert_same Config[0.5], Config[:next]
175+
assert_kind_of Config, Config[:future]
176+
end
177+
178+
test ".[] with a hash" do
179+
config = Config[{responses_without_block: :raise, sasl_ir: false}]
180+
assert config.frozen?
181+
refute config.sasl_ir?
182+
assert config.inherited?(:debug)
183+
refute config.inherited?(:sasl_ir)
184+
assert_same Config.global, config.parent
185+
assert_same :raise, config.responses_without_block
186+
end
187+
138188
test ".new always sets a parent" do
139189
assert_same Config.global, Config.new.parent
140190
assert_same Config.default, Config.new(Config.default).parent
141191
assert_same Config.global, Config.new(Config.global).parent
192+
assert_same Config[0.4], Config.new(0.4).parent
193+
assert_same Config[0.5], Config.new(:next).parent
194+
assert_equal true, Config.new({debug: true}, debug: false).parent.debug?
195+
assert_equal true, Config.new({debug: true}, debug: false).parent.frozen?
142196
end
143197

144198
test "#freeze" do

0 commit comments

Comments
 (0)