mikage-dev/source/framework/exceptions.hpp
2024-03-08 10:54:13 +01:00

90 lines
2.9 KiB
C++

#pragma once
#include <cassert>
#include <stdexcept>
#include <fmt/core.h>
namespace Mikage {
namespace Exceptions {
std::string generate_backtrace();
/**
* Exception to throw upon violation internal contracts that are expected to
* hold regardless of invalid emulation or user inputs.
*
* ValidateContract provides a drop-in replacement for assert that throws
* exceptions of this kind.
*/
struct ContractViolated : std::runtime_error {
ContractViolated(std::string_view condition, std::string_view function, std::string_view file, int line)
: std::runtime_error(FormatMessage(condition, function, file, line) + generate_backtrace()) {
}
static std::string FormatMessage(std::string_view condition, std::string_view function, std::string_view file, int line);
};
/**
* Exception to throw when the given situation is valid but handling it is not
* implemented currently.
*
* Examples of this are unhandled (but valid) enum inputs to emulated APIs or
* unhandled corner cases in internal libraries.
*
* This exception may also be used in situations where it's not entirely clear
* whether the behavior may or may not be technically valid.
*/
struct NotImplemented : std::runtime_error {
template<typename... T>
NotImplemented(const char* message, T&&... ts) : std::runtime_error(fmt::format(fmt::runtime(message), std::forward<T>(ts)...) + "\n" + generate_backtrace()) {
}
};
/**
* Exception to throw when the given situation is invalid, such as by violating
* preconditions of an emulated API.
*
* Examples of this are out-of-range parameters and invalid enum values.
*
* When it's not entirely clear whether a situation should be considered
* valid or not, prefer NotImplemented.
*/
struct Invalid : std::runtime_error {
template<typename... T>
Invalid(const char* message, T&&... ts) : std::runtime_error(fmt::format(fmt::runtime(message), std::forward<T>(ts)...) + "\n" + generate_backtrace()) {
}
};
/**
* Exception to throw when the user provides invalid input, for example from
* a command-line argument or a configuration file.
*
* Emulation logic should never use this.
*/
struct InvalidUserInput : std::runtime_error {
template<typename... T>
InvalidUserInput(const char* message, T&&... ts) : std::runtime_error(fmt::format(fmt::runtime(message), std::forward<T>(ts)...) + "\n" + generate_backtrace()) {
}
};
} // namespace Exceptions
} // namespace Mikage
// ValidateContract is implemented as a macro so that we can stringify the failed condition
#ifdef NDEBUG
#define ValidateContract(cond) do { if (!(cond)) { \
throw Mikage::Exceptions::ContractViolated(#cond, __PRETTY_FUNCTION__, __FILE__, __LINE__); \
} } while (false)
#else
// Use assert() directly in debug mode
#define ValidateContract(cond) do { if (!(cond)) { \
fputs(Mikage::Exceptions::generate_backtrace().c_str(), stderr); \
assert((cond)); \
} } while (false)
#endif