From 716804f47c932fbf54ef89df8067120b5c233b5e Mon Sep 17 00:00:00 2001 From: nick evans Date: Thu, 30 May 2024 09:55:11 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=F0=9F=8F=B7=20Add=20type=20coercio?= =?UTF-8?q?n=20for=20config=20attributes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is implemented as another module included under the other two, still based on overriding `attr_accessor`. Types are only enforced by the attr_writer methods. Fortunately, rdoc isn't confused by keyword arguments to `attr_accessor`, so the type can be added to that. Currently, only `:boolean` and `Integer` are supported, but it should be easy to add more. --- lib/net/imap/config.rb | 14 +++++-- lib/net/imap/config/attr_type_coercion.rb | 45 +++++++++++++++++++++++ test/net/imap/test_config.rb | 22 +++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 lib/net/imap/config/attr_type_coercion.rb diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index 853e23c4..a58a4ca8 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -3,6 +3,7 @@ require_relative "config/attr_accessors" require_relative "config/attr_inheritance" +require_relative "config/attr_type_coercion" module Net class IMAP @@ -48,14 +49,19 @@ def self.[](config) # :nodoc: unfinished API include AttrAccessors include AttrInheritance + include AttrTypeCoercion # The debug mode (boolean) # # | Starting with version | The default value is | # |-----------------------|----------------------| # | _original_ | +false+ | - attr_accessor :debug - alias debug? debug + attr_accessor :debug, type: :boolean + + # method: debug? + # :call-seq: debug? -> boolean + # + # Alias for #debug # Seconds to wait until a connection is opened. # @@ -65,7 +71,7 @@ def self.[](config) # :nodoc: unfinished API # | Starting with version | The default value is | # |-----------------------|----------------------| # | _original_ | +30+ seconds | - attr_accessor :open_timeout + attr_accessor :open_timeout, type: Integer # Seconds to wait until an IDLE response is received, after # the client asks to leave the IDLE state. See Net::IMAP#idle_done. @@ -73,7 +79,7 @@ def self.[](config) # :nodoc: unfinished API # | Starting with version | The default value is | # |-----------------------|----------------------| # | _original_ | +5+ seconds | - attr_accessor :idle_response_timeout + attr_accessor :idle_response_timeout, type: Integer # Creates a new config object and initialize its attribute with +attrs+. # diff --git a/lib/net/imap/config/attr_type_coercion.rb b/lib/net/imap/config/attr_type_coercion.rb new file mode 100644 index 00000000..7511193d --- /dev/null +++ b/lib/net/imap/config/attr_type_coercion.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Net + class IMAP + class Config + # Adds a +type+ keyword parameter to +attr_accessor+, which enforces + # config attributes have valid types, for example: boolean, numeric, + # enumeration, non-nullable, etc. + module AttrTypeCoercion + # :stopdoc: internal APIs only + + module Macros # :nodoc: internal API + def attr_accessor(attr, type: nil) + super(attr) + AttrTypeCoercion.attr_accessor(attr, type: type) + end + end + private_constant :Macros + + def self.included(mod) + mod.extend Macros + end + private_class_method :included + + def self.attr_accessor(attr, type: nil) + return unless type + if :boolean == type then boolean attr + elsif Integer == type then integer attr + else raise ArgumentError, "unknown type coercion %p" % [type] + end + end + + def self.boolean(attr) + define_method :"#{attr}=" do |val| super !!val end + define_method :"#{attr}?" do send attr end + end + + def self.integer(attr) + define_method :"#{attr}=" do |val| super Integer val end + end + + end + end + end +end diff --git a/test/net/imap/test_config.rb b/test/net/imap/test_config.rb index 208c09df..e14d7b74 100644 --- a/test/net/imap/test_config.rb +++ b/test/net/imap/test_config.rb @@ -28,6 +28,28 @@ class ConfigTest < Test::Unit::TestCase refute config.debug? end + test "boolean type constraints and conversion" do + config = Config.new + config.debug = 111 + assert_equal true, config.debug + config.debug = nil + assert_equal false, config.debug + end + + test "integer type constraints and conversion" do + config = Config.new + config.open_timeout = "111" + assert_equal 111, config.open_timeout + config.open_timeout = 222.0 + assert_equal 222, config.open_timeout + config.open_timeout = 333.3 + assert_equal 333, config.open_timeout + assert_raise(ArgumentError) do + config.open_timeout = "444 NaN" + end + assert_equal 333, config.open_timeout + end + test ".default" do default = Config.default assert default.equal?(Config.default)