diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index c3deff908561..f33977b4ada9 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -81,24 +81,45 @@ class Socket end class Error < Socket::Error - getter error_code : Int32 + @[Deprecated("Use `#os_error` instead")] + def error_code : Int32 + os_error.not_nil!.value.to_i32! + end + + @[Deprecated("Use `.from_os_error` instead")] + def self.new(error_code : Int32, message, domain) + from_os_error(message, Errno.new(error_code), domain: domain) + end - def self.new(error_code, domain) - new error_code, error_string(error_code), domain + @[Deprecated("Use `.from_os_error` instead")] + def self.new(error_code : Int32, domain) + new error_code, nil, domain: domain end - def initialize(@error_code, message, domain) - super("Hostname lookup for #{domain} failed: #{message}") + protected def self.new_from_os_error(message : String, os_error, *, domain, type, service, protocol, **opts) + new(message, **opts) end - def self.error_string(error_code) - {% if flag?(:win32) %} - # gai_strerror is defined as a macro in WS2tcpip.h, we can just use - # WinError for this - return WinError.new(error_code.to_u32).message - {% else %} - String.new(LibC.gai_strerror(error_code)) - {% end %} + def self.build_message(message, *, domain, **opts) + "Hostname lookup for #{domain} failed" + end + + def self.os_error_message(os_error : Errno, *, type, service, protocol, **opts) + case os_error.value + when LibC::EAI_NONAME + "No address found" + when LibC::EAI_SOCKTYPE + "The requested socket type #{type} protocol #{protocol} is not supported" + when LibC::EAI_SERVICE + "The requested service #{service} is not available for the requested socket type #{type}" + else + {% unless flag?(:win32) %} + # There's no need for a special win32 branch because the os_error on Windows + # is of type WinError, which wouldn't match this overload anyways. + + String.new(LibC.gai_strerror(os_error.value)) + {% end %} + end end end @@ -128,17 +149,14 @@ class Socket end {% end %} - case ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) - when 0 - # success - when LibC::EAI_NONAME - raise Error.new(ret, "No address found", domain) - when LibC::EAI_SOCKTYPE - raise Error.new(ret, "The requested socket type #{type} protocol #{protocol} is not supported", domain) - when LibC::EAI_SERVICE - raise Error.new(ret, "The requested service #{service} is not available for the requested socket type #{type}", domain) - else - raise Error.new(ret, domain) + ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) + unless ret.zero? + error = {% if flag?(:win32) %} + WinError.new(ret.to_u32!) + {% else %} + Errno.new(ret) + {% end %} + raise Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) end begin diff --git a/src/system_error.cr b/src/system_error.cr index e2d7b1f732c6..41cfe6d63022 100644 --- a/src/system_error.cr +++ b/src/system_error.cr @@ -32,6 +32,11 @@ # This is a factory method and by default it creates an instance # of the current class. It can be overridden to generate different # classes based on the `os_error` value or keyword arguments. +# * `protected def os_error_message(os_error : Errno | WinError | Nil, **opts) : String?` +# Returns the respective error message for *os_error*. +# By default it returns the result of `Errno#message` or `WinError#message`. +# This method can be overridden for customization of the error message based +# on *or_error* and *opts*. module SystemError macro included extend ::SystemError::ClassMethods @@ -53,9 +58,9 @@ module SystemError message = self.build_message(message, **opts) message = if message - "#{message}: #{os_error.message}" + "#{message}: #{os_error_message(os_error, **opts)}" else - os_error.message + os_error_message(os_error, **opts) end self.new_from_os_error(message, os_error, **opts).tap do |e| @@ -84,6 +89,15 @@ module SystemError message end + # Returns the respective error message for *os_error*. + # + # By default it returns the result of `Errno#message` or `WinError#message`. + # This method can be overridden for customization of the error message based + # on *or_error* and *\*\*opts*. + protected def os_error_message(os_error : Errno | WinError | Nil, **opts) : String? + os_error.try &.message + end + # Creates an instance of the exception that wraps a system error. # # This is a factory method and by default it creates an instance