diff --git a/spec/std/winerror_spec.cr b/spec/std/winerror_spec.cr new file mode 100644 index 000000000000..d03f4e55941f --- /dev/null +++ b/spec/std/winerror_spec.cr @@ -0,0 +1,46 @@ +require "spec" + +describe WinError do + it ".value" do + {% if flag?(:win32) %} + WinError.value = WinError::ERROR_SUCCESS + WinError.value.should eq WinError::ERROR_SUCCESS + WinError.value = WinError::ERROR_BROKEN_PIPE + WinError.value.should eq WinError::ERROR_BROKEN_PIPE + {% else %} + expect_raises(NotImplementedError) do + WinError.value = WinError::ERROR_SUCCESS + end + expect_raises(NotImplementedError) do + WinError.value + end + {% end %} + end + + it ".wsa_value" do + {% if flag?(:win32) %} + WinError.wsa_value = WinError::ERROR_SUCCESS + WinError.wsa_value.should eq WinError::ERROR_SUCCESS + WinError.wsa_value = WinError::WSAEBADF + WinError.wsa_value.should eq WinError::WSAEBADF + {% else %} + expect_raises(NotImplementedError) do + WinError.wsa_value = WinError::ERROR_SUCCESS + end + expect_raises(NotImplementedError) do + WinError.wsa_value + end + {% end %} + end + + it "#message" do + message = WinError::ERROR_SUCCESS.message + {% if flag?(:win32) %} + # Not testing for specific content because the result is locale-specific + # and currently the message uses only default `LANGID`. + message.empty?.should be_false + {% else %} + message.should eq "" + {% end %} + end +end diff --git a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr index ab70d6e37138..b41c407f43ca 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr @@ -18,5 +18,9 @@ lib LibC fun htons(hostshort : UShort) : UShort fun ntohs(netshort : UShort) : UShort + fun WSAStartup(wVersionRequired : WORD, lpWSAData : WSAData*) : Int + + fun WSASetLastError(iError : Int) : Void + fun WSAGetLastError : Int end diff --git a/src/system_error.cr b/src/system_error.cr index 41cfe6d63022..6b5ce9f3d2ef 100644 --- a/src/system_error.cr +++ b/src/system_error.cr @@ -108,15 +108,15 @@ module SystemError end # Builds an instance of the exception from the current windows error value (`WinError.value`). + # # The system message corresponding to the OS error value amends the *message*. # Additional keyword arguments are forwarded to the exception initializer `.new_from_os_error`. def from_winerror(message : String?, **opts) from_os_error(message, WinError.value, **opts) end - # Builds an instance of the exception from a `WinError` + # Builds an instance of the exception from the current Windows Socket API error value (`WinError.wsa_value`). # - # By default it takes the current `WinError` value (see `WinError.value`). # The system message corresponding to the OS error value amends the *message*. # Additional keyword arguments are forwarded to the exception initializer. def from_wsa_error(message : String? = nil, **opts) diff --git a/src/winerror.cr b/src/winerror.cr index f5e8ecb8dc25..3f8316d05577 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -1,6 +1,7 @@ {% if flag?(:win32) %} require "c/winbase" require "c/errhandlingapi" + require "c/winsock2" {% end %} # `WinError` represents Windows' [System Error Codes](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes#system-error-codes-1). @@ -17,6 +18,42 @@ enum WinError : UInt32 {% end %} end + # Sets the value of [`SetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setlasterror) + # which signifies the error code of the previously called win32 function. + # + # Raises `NotImplementedError` on non-win32 platforms. + def self.value=(winerror : self) + {% if flag?(:win32) %} + LibC.SetLastError(winerror.value) + {% else %} + raise NotImplementedError.new("WinError.value=") + {% end %} + end + + # Returns the value of [`WSAGetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsagetlasterror) + # which is used to retrieve the error code of the previously called Windows Socket API function. + # + # Raises `NotImplementedError` on non-win32 platforms. + def self.wsa_value + {% if flag?(:win32) %} + WinError.new LibC.WSAGetLastError.to_u32! + {% else %} + raise NotImplementedError.new("WinError.wsa_value") + {% end %} + end + + # Sets the value of [`WSASetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasetlasterror) + # which signifies the error code of the previously called Windows Socket API function. + # + # Raises `NotImplementedError` on non-win32 platforms. + def self.wsa_value=(winerror : self) + {% if flag?(:win32) %} + LibC.WSASetLastError(winerror.value) + {% else %} + raise NotImplementedError.new("WinError.value=") + {% end %} + end + # Returns the system error message associated with this error code. # # The message is retrieved via [`FormatMessageW`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagew)