Motivation
The primary motivation is to simplify backward compatibility concerns while still allowing deprecation and breaking changes. Users should be able to opt-in to new (incompatible) behaviors before they become the default, opt-out of new (incompatible) behaviors after they become the default, and load defaults by version number (similarly to how Rails handles upgrades via Rails::Application::Configuration#load_defaults).
The secondary motivation is to scope all config options. It should be possible to globally update defaults for client options and method parameters. But client configuration should be able to override any global configuration. A simple mechanism for customizing configuration inheritance should be available.
Another motivation is to avoid a proliferation of configuration methods on Net::IMAP, both global config attributes (class methods) and client config attributes (instance methods). Currently, the number of config options is low, but that number will go up to handle options for backward and forward compatibility.
An implementation goal, not originally in this proposal but discovered during #291: Basic type checking and coercion should be simple. For example, the timeouts are already being assigned using @foo_timout = Integer(foo_timout), so the config options for them should do that too.
Non-goals
The following were considered, but are not goals for this issue (maybe in future PRs):
- client-specific configuration (such as
host and port).
- Reading config from ENV, Thread locals, Fiber locals, or Fiber storage.
- Thread safety (config is usually only changed up-front or during client construction)
- Ractor safety
Proposal
Net::IMAP::Config will have attribute readers/writers for all config options.
All config objects (except for the default config) will inherit from a #parent config object. All configs will have a hard-coded (frozen) Config.default as their final ancestor, and Config.default will be the only config with parent.nil?. Net::IMAP.config will be a global config (not frozen), which inherits from default.
Every client will create its own unique Config in #initialize, and all unknown keyword args will be forwarded to the client config attributes. A config keyword arg will set the client's parent config. This would allow a client to use defaults without inheriting from the (mutuble) global config, or to inherit from a custom ENV or Fiber.storage based config, which might itself inherit from the global config. With no further customization, the config chain is client -> global -> default.
Version compatibility defaults will be provided as hard-coded frozen configs, which override most values but inherit from global in order inherit options like debug or logger. If a client opts in to using specific version defaults, the config chain will be something like client -> defaults_for(0.4) -> global -> default. Alternatively, these defaults can be copied into an existing config: config.load_defaults(0.4).
The current default config should be stable; it should only change when config options are added or when the minor version number is bumped. Backward compatibility defaults should only change as config options are added. Deprecated config options should only be dropped when the minor version is bumped.
Config Options
Existing global settings
Existing client settings
open_timeout
idle_response_timeout
Existing method parameters
#authenticate
sasl_ir
sasl_registry -> registry kwarg
#id
#idle
idle_timeout -> timeout argument
idle_response_handler -> response_handler block argument
#select/#examine
select_condstore -> condstore kwarg
Ideas for future config options
- Deprecation options can be assigned one of:
:silence | :warn | :raise
- Default
response_handlers to attach before the client connects
- Automatically call
#enable after authenticating.
- Disable auto-connect in
#initialize (similar to net-pop and net-smtp, which use a #start method to connect)
- Parser options:
parser: {**options} or parser: ResponseParserClass
logger, to enable logging differently from Net::IMAP.debug
- Ability to select or merge a config by hostname, e.g for server-specific
ssl, parser workarounds, etc.
Motivation
The primary motivation is to simplify backward compatibility concerns while still allowing deprecation and breaking changes. Users should be able to opt-in to new (incompatible) behaviors before they become the default, opt-out of new (incompatible) behaviors after they become the default, and load defaults by version number (similarly to how Rails handles upgrades via Rails::Application::Configuration#load_defaults).
The secondary motivation is to scope all config options. It should be possible to globally update defaults for client options and method parameters. But client configuration should be able to override any global configuration. A simple mechanism for customizing configuration inheritance should be available.
Another motivation is to avoid a proliferation of configuration methods on Net::IMAP, both global config attributes (class methods) and client config attributes (instance methods). Currently, the number of config options is low, but that number will go up to handle options for backward and forward compatibility.
An implementation goal, not originally in this proposal but discovered during #291: Basic type checking and coercion should be simple. For example, the timeouts are already being assigned using
@foo_timout = Integer(foo_timout), so the config options for them should do that too.Non-goals
The following were considered, but are not goals for this issue (maybe in future PRs):
hostandport).Proposal
Net::IMAP::Configwill have attribute readers/writers for all config options.All config objects (except for the default config) will inherit from a
#parentconfig object. All configs will have a hard-coded (frozen)Config.defaultas their final ancestor, andConfig.defaultwill be the only config withparent.nil?.Net::IMAP.configwill be a global config (not frozen), which inherits fromdefault.Every client will create its own unique Config in
#initialize, and all unknown keyword args will be forwarded to the client config attributes. Aconfigkeyword arg will set the client's parent config. This would allow a client to use defaults without inheriting from the (mutuble) global config, or to inherit from a custom ENV or Fiber.storage based config, which might itself inherit from the global config. With no further customization, the config chain isclient -> global -> default.Version compatibility defaults will be provided as hard-coded frozen configs, which override most values but inherit from
globalin order inherit options likedebugorlogger. If a client opts in to using specific version defaults, the config chain will be something likeclient -> defaults_for(0.4) -> global -> default. Alternatively, these defaults can be copied into an existing config:config.load_defaults(0.4).The current default config should be stable; it should only change when config options are added or when the minor version number is bumped. Backward compatibility defaults should only change as config options are added. Deprecated config options should only be dropped when the minor version is bumped.
Config Options
Existing global settings
debugExisting client settings
open_timeoutidle_response_timeoutExisting method parameters
#authenticatesasl_irsasl_registry->registrykwarg#idclient_id#idleidle_timeout->timeoutargumentidle_response_handler->response_handlerblock argument#select/#examineselect_condstore->condstorekwargIdeas for future config options
:silence | :warn | :raise#responsesusage #97responses_without_block#293deprecated_client_thread#searchparameters for nested keys lists, validation, implicit RawData, etc.default_ssl, or some mechanism for configuringssldefault_ssl_context_paramsto provided defaults for#starttlsand#initialize(ssl: true)auto_starttls- Automatically call#starttlson plaintext connections (similar tonet-smtp)enforce_capabilities- Honoring server-reported and client-enabled capabilities #49STARTTLScapability #134LOGINDISABLEDcapability: MUST not allowLOGIN#32utf7-UTF-7should be automatically encoded/decoded #30response_handlersto attach before the client connects#enableafter authenticating.enable_utf8#initialize(similar tonet-popandnet-smtp, which use a#startmethod to connect)parser: {**options}orparser: ResponseParserClasslogger, to enable logging differently fromNet::IMAP.debugssl, parser workarounds, etc.