From 955afc3182df2dbfa4ca9e46237485398104090d Mon Sep 17 00:00:00 2001
From: Connor McLaughlin <stenzek@gmail.com>
Date: Fri, 19 Mar 2021 01:51:39 +1000
Subject: [PATCH] Common: Add Error helper class

---
 src/common/CMakeLists.txt |   2 +
 src/common/error.cpp      | 359 ++++++++++++++++++++++++++++++++++++++
 src/common/error.h        |  97 ++++++++++
 3 files changed, 458 insertions(+)
 create mode 100644 src/common/error.cpp
 create mode 100644 src/common/error.h

diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 220395860..9824b7aa2 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -27,6 +27,8 @@ add_library(common
   crash_handler.cpp
   crash_handler.h
   dimensional_array.h
+  error.cpp
+  error.h
   event.cpp
   event.h
   fifo_queue.h
diff --git a/src/common/error.cpp b/src/common/error.cpp
new file mode 100644
index 000000000..56af691fe
--- /dev/null
+++ b/src/common/error.cpp
@@ -0,0 +1,359 @@
+#include "error.h"
+#include <cstdlib>
+#include <cstring>
+#include <type_traits>
+
+// Platform-specific includes
+#if defined(_WIN32)
+#include "windows_headers.h"
+static_assert(std::is_same<DWORD, unsigned long>::value, "DWORD is unsigned long");
+static_assert(std::is_same<HRESULT, long>::value, "HRESULT is long");
+#endif
+
+namespace Common {
+
+Error::Error() : m_type(Type::None)
+{
+  m_error.none = 0;
+}
+
+Error::Error(const Error& c)
+{
+  m_type = c.m_type;
+  std::memcpy(&m_error, &c.m_error, sizeof(m_error));
+  m_code_string.AppendString(c.m_code_string);
+  m_message.AppendString(c.m_message);
+}
+
+Error::~Error() = default;
+
+void Error::Clear()
+{
+  m_type = Type::None;
+  m_error.none = 0;
+  m_code_string.Clear();
+  m_message.Clear();
+}
+
+void Error::SetErrno(int err)
+{
+  m_type = Type::Errno;
+  m_error.errno_f = err;
+
+  m_code_string.Format("%i", err);
+
+#ifdef _MSC_VER
+  strerror_s(m_message.GetWriteableCharArray(), m_message.GetBufferSize(), err);
+  m_message.UpdateSize();
+#else
+  const char* message = std::strerror(err);
+  if (message)
+    m_message = message;
+  else
+    m_message = StaticString("<Could not get error message>");
+#endif
+}
+
+void Error::SetSocket(int err)
+{
+// Socket errors are win32 errors on windows
+#ifdef _WIN32
+  SetWin32(err);
+#else
+  SetErrno(err);
+#endif
+}
+
+void Error::SetMessage(const char* msg)
+{
+  m_type = Type::User;
+  m_error.user = 0;
+  m_code_string.Clear();
+  m_message = msg;
+}
+
+void Error::SetUser(int err, const char* msg)
+{
+  m_type = Type::User;
+  m_error.user = err;
+  m_code_string.Format("%d", err);
+  m_message = msg;
+}
+
+void Error::SetUser(const char* code, const char* message)
+{
+  m_type = Type::User;
+  m_error.user = 0;
+  m_code_string = code;
+  m_message = message;
+}
+
+void Error::SetUserFormatted(int err, const char* format, ...)
+{
+  std::va_list ap;
+  va_start(ap, format);
+
+  m_type = Type::User;
+  m_error.user = err;
+  m_code_string.Format("%d", err);
+  m_message.FormatVA(format, ap);
+  va_end(ap);
+}
+
+void Error::SetUserFormatted(const char* code, const char* format, ...)
+{
+  std::va_list ap;
+  va_start(ap, format);
+
+  m_type = Type::User;
+  m_error.user = 0;
+  m_code_string = code;
+  m_message.FormatVA(format, ap);
+  va_end(ap);
+}
+
+void Error::SetFormattedMessage(const char* format, ...)
+{
+  std::va_list ap;
+  va_start(ap, format);
+
+  m_type = Type::User;
+  m_error.user = 0;
+  m_code_string.Clear();
+  m_message.FormatVA(format, ap);
+  va_end(ap);
+}
+
+#ifdef _WIN32
+
+void Error::SetWin32(unsigned long err)
+{
+  m_type = Type::Win32;
+  m_error.win32 = err;
+  m_code_string.Format("%u", static_cast<u32>(err));
+  m_message.Clear();
+
+  const DWORD r = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, m_error.win32, 0, m_message.GetWriteableCharArray(),
+                                 m_message.GetWritableBufferSize(), NULL);
+  if (r > 0)
+  {
+    m_message.Resize(r);
+    m_message.RStrip();
+  }
+  else
+  {
+    m_message = "<Could not resolve system error ID>";
+  }
+}
+
+void Error::SetHResult(long err)
+{
+  m_type = Type::HResult;
+  m_error.win32 = err;
+  m_code_string.Format("%08X", static_cast<u32>(err));
+  m_message.Clear();
+
+  const DWORD r = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, m_error.win32, 0, m_message.GetWriteableCharArray(),
+                                 m_message.GetWritableBufferSize(), NULL);
+  if (r > 0)
+  {
+    m_message.Resize(r);
+    m_message.RStrip();
+  }
+  else
+  {
+    m_message = "<Could not resolve system error ID>";
+  }
+}
+
+#endif
+
+// constructors
+Error Error::CreateNone()
+{
+  Error ret;
+  ret.Clear();
+  return ret;
+}
+
+Error Error::CreateErrno(int err)
+{
+  Error ret;
+  ret.SetErrno(err);
+  return ret;
+}
+
+Error Error::CreateSocket(int err)
+{
+  Error ret;
+  ret.SetSocket(err);
+  return ret;
+}
+
+Error Error::CreateMessage(const char* msg)
+{
+  Error ret;
+  ret.SetMessage(msg);
+  return ret;
+}
+
+Error Error::CreateUser(int err, const char* msg)
+{
+  Error ret;
+  ret.SetUser(err, msg);
+  return ret;
+}
+
+Error Error::CreateUser(const char* code, const char* message)
+{
+  Error ret;
+  ret.SetUser(code, message);
+  return ret;
+}
+
+Error Error::CreateMessageFormatted(const char* format, ...)
+{
+  std::va_list ap;
+  va_start(ap, format);
+
+  Error ret;
+  ret.m_type = Type::User;
+  ret.m_message.FormatVA(format, ap);
+
+  va_end(ap);
+
+  return ret;
+}
+
+Error Error::CreateUserFormatted(int err, const char* format, ...)
+{
+  std::va_list ap;
+  va_start(ap, format);
+
+  Error ret;
+  ret.m_type = Type::User;
+  ret.m_error.user = err;
+  ret.m_code_string.Format("%d", err);
+  ret.m_message.FormatVA(format, ap);
+
+  va_end(ap);
+
+  return ret;
+}
+
+Error Error::CreateUserFormatted(const char* code, const char* format, ...)
+{
+  std::va_list ap;
+  va_start(ap, format);
+
+  Error ret;
+  ret.m_type = Type::User;
+  ret.m_error.user = 0;
+  ret.m_code_string = code;
+  ret.m_message.FormatVA(format, ap);
+
+  va_end(ap);
+
+  return ret;
+}
+
+#ifdef _WIN32
+Error Error::CreateWin32(unsigned long err)
+{
+  Error ret;
+  ret.SetWin32(err);
+  return ret;
+}
+
+Error Error::CreateHResult(long err)
+{
+  Error ret;
+  ret.SetHResult(err);
+  return ret;
+}
+
+#endif
+
+Error& Error::operator=(const Error& e)
+{
+  m_type = e.m_type;
+  std::memcpy(&m_error, &e.m_error, sizeof(m_error));
+  m_code_string.Clear();
+  m_code_string.AppendString(e.m_code_string);
+  m_message.Clear();
+  m_message.AppendString(e.m_message);
+  return *this;
+}
+
+bool Error::operator==(const Error& e) const
+{
+  switch (m_type)
+  {
+    case Type::None:
+      return true;
+
+    case Type::Errno:
+      return m_error.errno_f == e.m_error.errno_f;
+
+    case Type::Socket:
+      return m_error.socketerr == e.m_error.socketerr;
+
+    case Type::User:
+      return m_error.user == e.m_error.user;
+
+#ifdef _WIN32
+    case Type::Win32:
+      return m_error.win32 == e.m_error.win32;
+
+    case Type::HResult:
+      return m_error.hresult == e.m_error.hresult;
+#endif
+  }
+
+  return false;
+}
+
+bool Error::operator!=(const Error& e) const
+{
+  switch (m_type)
+  {
+    case Type::None:
+      return false;
+
+    case Type::Errno:
+      return m_error.errno_f != e.m_error.errno_f;
+
+    case Type::Socket:
+      return m_error.socketerr != e.m_error.socketerr;
+
+    case Type::User:
+      return m_error.user != e.m_error.user;
+
+#ifdef _WIN32
+    case Type::Win32:
+      return m_error.win32 != e.m_error.win32;
+
+    case Type::HResult:
+      return m_error.hresult != e.m_error.hresult;
+#endif
+  }
+
+  return true;
+}
+
+SmallString Error::GetCodeAndMessage() const
+{
+  SmallString ret;
+  GetCodeAndMessage(ret);
+  return ret;
+}
+
+void Error::GetCodeAndMessage(String& dest) const
+{
+  if (m_code_string.IsEmpty())
+    dest.Assign(m_message);
+  else
+    dest.Format("[%s]: %s", m_code_string.GetCharArray(), m_message.GetCharArray());
+}
+
+} // namespace Common
\ No newline at end of file
diff --git a/src/common/error.h b/src/common/error.h
new file mode 100644
index 000000000..aac45bb9b
--- /dev/null
+++ b/src/common/error.h
@@ -0,0 +1,97 @@
+#pragma once
+#include "string.h"
+#include "types.h"
+
+namespace Common {
+
+// this class provides enough storage room for all of these types
+class Error
+{
+public:
+  Error();
+  Error(const Error& e);
+  ~Error();
+
+  enum class Type
+  {
+    None = 0,   // Set by default constructor, returns 'No Error'.
+    Errno = 1,  // Error that is set by system functions, such as open().
+    Socket = 2, // Error that is set by socket functions, such as socket(). On Unix this is the same as errno.
+    User = 3,   // When translated, will return 'User Error %u' if no message is specified.
+    Win32 = 4,  // Error that is returned by some Win32 functions, such as RegOpenKeyEx. Also used by other APIs through
+               // GetLastError().
+    HResult = 5, // Error that is returned by Win32 COM methods, e.g. S_OK.
+  };
+
+  ALWAYS_INLINE Type GetType() const { return m_type; }
+  ALWAYS_INLINE int GetErrnoCode() const { return m_error.errno_f; }
+  ALWAYS_INLINE int GetSocketCode() const { return m_error.socketerr; }
+  ALWAYS_INLINE int GetUserCode() const { return m_error.user; }
+#ifdef _WIN32
+  ALWAYS_INLINE unsigned long GetWin32Code() const { return m_error.win32; }
+  ALWAYS_INLINE long GetHResultCode() const { return m_error.hresult; }
+#endif
+
+  // get code, e.g. "0x00000002"
+  ALWAYS_INLINE const String& GetCodeString() const { return m_code_string; }
+
+  // get description, e.g. "File not Found"
+  ALWAYS_INLINE const String& GetMessage() const { return m_message; }
+
+  // setter functions
+  void Clear();
+  void SetErrno(int err);
+  void SetSocket(int err);
+  void SetMessage(const char* msg);
+  void SetFormattedMessage(const char* format, ...);
+  void SetUser(int err, const char* msg);
+  void SetUser(const char* code, const char* message);
+  void SetUserFormatted(int err, const char* format, ...);
+  void SetUserFormatted(const char* code, const char* format, ...);
+#ifdef _WIN32
+  void SetWin32(unsigned long err);
+  void SetHResult(long err);
+#endif
+
+  // constructors
+  static Error CreateNone();
+  static Error CreateErrno(int err);
+  static Error CreateSocket(int err);
+  static Error CreateMessage(const char* msg);
+  static Error CreateMessageFormatted(const char* format, ...);
+  static Error CreateUser(int err, const char* msg);
+  static Error CreateUser(const char* code, const char* message);
+  static Error CreateUserFormatted(int err, const char* format, ...);
+  static Error CreateUserFormatted(const char* code, const char* format, ...);
+#ifdef _WIN32
+  static Error CreateWin32(unsigned long err);
+  static Error CreateHResult(long err);
+#endif
+
+  // get code and description, e.g. "[0x00000002]: File not Found"
+  SmallString GetCodeAndMessage() const;
+  void GetCodeAndMessage(String& dest) const;
+
+  // operators
+  Error& operator=(const Error& e);
+  bool operator==(const Error& e) const;
+  bool operator!=(const Error& e) const;
+
+private:
+  Type m_type = Type::None;
+  union
+  {
+    int none;
+    int errno_f; // renamed from errno to avoid conflicts with #define'd errnos.
+    int socketerr;
+    int user;
+#ifdef _WIN32
+    unsigned long win32;
+    long hresult;
+#endif
+  } m_error{};
+  StackString<16> m_code_string;
+  TinyString m_message;
+};
+
+} // namespace Common
\ No newline at end of file