diff --git a/client/Doxygen/docker-finance.dox b/client/Doxygen/docker-finance.dox index 0fe357b..548d05c 100644 --- a/client/Doxygen/docker-finance.dox +++ b/client/Doxygen/docker-finance.dox @@ -80,6 +80,10 @@ //! \brief Group for tags marked for internal and public consumption //! \since docker-finance 1.0.0 +//! \defgroup cpp_type_exceptions docker-finance C++ exceptions +//! \brief Group for internal exception handling +//! \since docker-finance 1.0.0 + //! \defgroup cpp_utils docker-finance C++ utilities //! \brief Group for public-facing utility code //! \since docker-finance 1.0.0 diff --git a/container/src/root/macro/rootlogon.C b/container/src/root/macro/rootlogon.C index 47e1b50..2fd8158 100644 --- a/container/src/root/macro/rootlogon.C +++ b/container/src/root/macro/rootlogon.C @@ -60,39 +60,39 @@ void help() << " of Crypto++-generated cryptographically secure random\n" << " numbers:\n" << "\n" - << " root [0] #include \"../src/hash.hh\"\n" - << " root [1] #include \"../src/random.hh\"\n" - << " root [2] using g_Random = " + << " root [0] using g_Random = " "docker_finance::crypto::cryptopp::Random;\n" - << " root [3] using g_Hash = " + << " root [1] using g_Hash = " "docker_finance::crypto::libsodium::Hash;\n" - << " root [4] g_Random r; g_Hash h;\n" - << " root [5] for (size_t i{}; i < " + << " root [2] g_Random r; g_Hash h;\n" + << " root [3] for (size_t i{}; i < " "std::numeric_limits::max(); i++) {\n" - << " root (cont'ed, cancel with .@) [6] uint32_t num = " + << " root (cont'ed, cancel with .@) [4] uint32_t num = " "r.generate();\n" - << " root (cont'ed, cancel with .@) [7] std::cout << " - "h.encode(num) << \" = \" << num << \"\\n\"; }\n" + << " root (cont'ed, cancel with .@) [5] std::cout << " + "h.encode(num)\n" + << " root (cont'ed, cancel with .@) [6] << \" = \" << num << " + "\"\\n\";\n" + << " root (cont'ed, cancel with .@) [7] }\n" << "\n" << " Note: generate Doxygen to see all supported cryptographic\n" << " libraries and hash types.\n" << "\n" << " 2. Use Tools utility\n" << "\n" - << " root [0] #include \"../src/utility.hh\"\n" - << " root [1] docker_finance::utility::Tools tools;\n" + << " root [0] docker_finance::utility::Tools tools;\n" << "\n" << " // Create a variable within interpreter\n" - << " root [2] btc=0.87654321+0.12345678+0.00000078\n" + << " root [1] btc=0.87654321+0.12345678+0.00000078\n" << " (double) 1.0000008\n" << "\n" << " // Print variable up to N decimal places\n" - << " root [3] tools.print_value(btc, 8);\n" + << " root [2] tools.print_value(btc, 8);\n" << " 1.00000077\n" << "\n" << " // Use tab autocomplete for class members to print random\n" << " // numbers within given intervals\n" - << " root [4] " + << " root [3] " "tools.print_dist >, " "double>(0.1, btc);\n" << " 0.13787670\n" @@ -121,7 +121,7 @@ void help() << "Help:\n" << "\n" << " 1. Print ROOT's help usage\n" - << " root [1] .help\n" + << " root [0] .help\n" << "\n" << " 2. Print this help usage\n" << " root [0] help()\n" diff --git a/container/src/root/macro/test.C b/container/src/root/macro/test.C index d2dc03e..8a7f8d2 100644 --- a/container/src/root/macro/test.C +++ b/container/src/root/macro/test.C @@ -63,8 +63,9 @@ class Unit { static bool loaded{false}; static const std::initializer_list paths{ - {"../test/unit/random.hh"}, {"../test/unit/hash.hh"}, + {"../test/unit/random.hh"}, + {"../test/unit/type.hh"}, {"../test/unit/utility.hh"}}; if (!loaded) diff --git a/container/src/root/src/hash.hh b/container/src/root/src/hash.hh index a5ce4ea..5d2e2af 100644 --- a/container/src/root/src/hash.hh +++ b/container/src/root/src/hash.hh @@ -29,8 +29,6 @@ #include "./internal/impl/hash.hh" #include "./internal/type.hh" -namespace type = docker_finance::internal::type; - //! \namespace docker_finance //! \since docker-finance 1.0.0 namespace docker_finance @@ -40,6 +38,9 @@ namespace docker_finance //! \since docker-finance 1.0.0 namespace crypto { + +namespace type = docker_finance::internal::type; + //! \namespace docker_finance::crypto::common //! \brief Common "interface" (specializations) to library-specific implementations //! \warning Not for direct public consumption (use library namespace instead) diff --git a/container/src/root/src/internal/impl/hash.hh b/container/src/root/src/internal/impl/hash.hh index 37bc2e6..54a3682 100644 --- a/container/src/root/src/internal/impl/hash.hh +++ b/container/src/root/src/internal/impl/hash.hh @@ -44,8 +44,6 @@ #include "../generic.hh" #include "../type.hh" -namespace type = docker_finance::internal::type; - //! \namespace docker_finance //! \since docker-finance 1.0.0 namespace docker_finance @@ -60,6 +58,9 @@ namespace crypto //! \since docker-finance 1.0.0 namespace impl { + +namespace type = docker_finance::internal::type; + //! \namespace docker_finance::crypto::impl::common //! \brief Common implementation among all crypto implementations //! \since docker-finance 1.0.0 @@ -428,11 +429,11 @@ class Hash : public common::HashImpl, public type::Hash //! have any effects." Hash() { - if (::sodium_init() < 0) - { - throw std::runtime_error("sodium_init could not be initialized"); - } - }; + THROW_IF( + ::sodium_init() < 0, + type::RuntimeError, + "sodium_init could not be initialized") + } ~Hash() = default; Hash(const Hash&) = default; diff --git a/container/src/root/src/internal/impl/random.hh b/container/src/root/src/internal/impl/random.hh index 24fefb5..8416868 100644 --- a/container/src/root/src/internal/impl/random.hh +++ b/container/src/root/src/internal/impl/random.hh @@ -33,6 +33,7 @@ #include #include "../generic.hh" +#include "../type.hh" //! \namespace docker_finance //! \since docker-finance 1.0.0 @@ -82,6 +83,8 @@ class RandomImpl : public ::docker_finance::internal::Random }; } // namespace common +namespace type = docker_finance::internal::type; + //! \namespace docker_finance::crypto::impl::botan //! \since docker-finance 1.0.0 namespace botan @@ -117,9 +120,7 @@ class Random : public common::RandomImpl static_assert( std::is_same_v, "Random interface has changed"); - if (!m_csprng.is_seeded()) - throw std::runtime_error( - "Botan is not seeded"); // TODO(afiore): use docker-finance's error.hh + THROW_IF(!m_csprng.is_seeded(), type::RuntimeError, "Botan is not seeded") // WARNING: DO *NOT* set_high_bit to true here! // Otherwise, [0..(~2150*10^6)] WILL NOT BE GENERATED! @@ -206,11 +207,11 @@ class Random : public common::RandomImpl //! have any effects." Random() { - if (::sodium_init() < 0) - { - throw std::runtime_error("sodium_init could not be initialized"); - } - }; + THROW_IF( + ::sodium_init() < 0, + type::RuntimeError, + "sodium_init could not be initialized") + } ~Random() = default; Random(const Random&) = default; diff --git a/container/src/root/src/internal/impl/utility.hh b/container/src/root/src/internal/impl/utility.hh index 82c6da4..19c6fe5 100644 --- a/container/src/root/src/internal/impl/utility.hh +++ b/container/src/root/src/internal/impl/utility.hh @@ -31,6 +31,7 @@ // #include // TODO(afiore): file upstream (calc) bug report #include "../../random.hh" #include "../generic.hh" +#include "../type.hh" //! \namespace docker_finance //! \since docker-finance 1.0.0 @@ -46,6 +47,9 @@ namespace utility //! \since docker-finance 1.0.0 namespace impl { + +namespace type = docker_finance::internal::type; + //! \brief Misc tools //! \ingroup cpp_API_impl //! \since docker-finance 1.0.0 @@ -155,8 +159,8 @@ class Tools std::vector random_dist(const t_num min, const t_num max, const uint16_t precision = 8) { - if (min > max) - throw std::runtime_error("minimum given is greater than maximum"); + THROW_IF( + min > max, type::InvalidArgument, "minimum is greater than maximum") // Get first chunk std::vector chunks; @@ -189,8 +193,7 @@ class Tools std::cout.precision(precision); std::cout << "rounded = " << rounded << '\n' << "max = " << max << std::fixed << std::endl; - - throw std::runtime_error("random distribution not fulfilled"); + THROW(type::RuntimeError, "random distribution not fulfilled") } return chunks; diff --git a/container/src/root/src/internal/type.hh b/container/src/root/src/internal/type.hh index ad98757..b6143c6 100644 --- a/container/src/root/src/internal/type.hh +++ b/container/src/root/src/internal/type.hh @@ -25,10 +25,36 @@ #include #include +#include #include #include #include +//! \brief Conditional throw handler +//! \ingroup cpp_type_exceptions +#define THROW_IF(condition, exception, message) \ + if (condition) \ + THROW(exception, message); + +//! \brief Throw handler +//! \ingroup cpp_type_exceptions +#define THROW(exception, message) \ + THROW_IMPL(exception, \ + \n\tFILE = __FILE__ \ + \n\tLINE = __LINE__ \ + \n\tWHAT = message); + +//! \brief Throw handler implementation +//! \warning Should not be called directly, use THROW handlers +//! \ingroup cpp_type_exceptions +#define THROW_IMPL(exception, message) \ + throw exception(THROW_IMPL_EXPAND(message)); + +//! \brief Throw handler implementation (message) +//! \warning Should not be called directly, use THROW handlers +//! \ingroup cpp_type_exceptions +#define THROW_IMPL_EXPAND(message) #message + //! \namespace docker_finance //! \since docker-finance 1.0.0 namespace docker_finance @@ -44,6 +70,63 @@ namespace internal //! \todo *_v for all the booleans namespace type { +//! \brief Base exception class +//! \ingroup cpp_type_exceptions +class Exception : virtual public std::exception +{ + public: + //! \brief Exception type + enum struct kType : uint8_t + { + RuntimeError, + InvalidArgument, + }; + + //! \brief Construct by type with given message + Exception(const kType type, const std::string_view what) + : m_type(type), m_what(what) + { + } + virtual ~Exception() = default; + + Exception(const Exception&) = default; + Exception& operator=(const Exception&) = default; + + Exception(Exception&&) = default; + Exception& operator=(Exception&&) = default; + + public: + //! \return Exeption type + kType type() const noexcept { return m_type; } + + //! \return Exception message + const char* what() const noexcept { return m_what.data(); } + + private: + kType m_type; + std::string_view m_what; +}; + +//! \brief Exception class for runtime errors +//! \ingroup cpp_type_exceptions +struct RuntimeError final : public Exception +{ + explicit RuntimeError(const std::string_view what = {}) + : Exception(Exception::kType::RuntimeError, what) + { + } +}; + +//! \brief Exception class for invalid arguments (logic error) +//! \ingroup cpp_type_exceptions +struct InvalidArgument final : public Exception +{ + explicit InvalidArgument(const std::string_view what = {}) + : Exception(Exception::kType::InvalidArgument, what) + { + } +}; + //! \ingroup cpp_type_traits template struct is_byte : std::false_type diff --git a/container/src/root/src/utility.hh b/container/src/root/src/utility.hh index 7e2bdc7..82c9b55 100644 --- a/container/src/root/src/utility.hh +++ b/container/src/root/src/utility.hh @@ -31,8 +31,7 @@ #include #include "./internal/impl/utility.hh" - -namespace type = docker_finance::internal::type; +#include "./internal/type.hh" //! \namespace docker_finance //! \since docker-finance 1.0.0 @@ -43,6 +42,9 @@ namespace docker_finance //! \since docker-finance 1.0.0 namespace utility { + +namespace type = docker_finance::internal::type; + //! \brief Misc utility tools //! \ingroup cpp_utils //! \since docker-finance 1.0.0 @@ -84,8 +86,8 @@ class Tools final : protected impl::Tools void print_dist(t_num min, t_num max, const size_t precision = 8) { // Specific to this use-case - if (min < 0 || max < 0) - throw std::runtime_error("No negative values allowed"); + THROW_IF( + min < 0 || max < 0, type::InvalidArgument, "no negative values allowed") if (min > max) { diff --git a/container/src/root/test/common/type.hh b/container/src/root/test/common/type.hh new file mode 100644 index 0000000..bc5ef9f --- /dev/null +++ b/container/src/root/test/common/type.hh @@ -0,0 +1,62 @@ +// docker-finance | modern accounting for the power-user +// +// Copyright (C) 2021-2024 Aaron Fiore (Founder, Evergreen Crypto LLC) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! \file +//! \author Aaron Fiore (Founder, Evergreen Crypto LLC) +//! \note File intended to be loaded into ROOT.cern framework / Cling interpreter +//! \since docker-finance 1.0.0 + +#ifndef CONTAINER_SRC_ROOT_TEST_COMMON_TYPE_HH_ +#define CONTAINER_SRC_ROOT_TEST_COMMON_TYPE_HH_ + +#include + +#include + +#include "../../src/internal/type.hh" + +//! \namespace docker_finance +//! \since docker-finance 1.0.0 +namespace docker_finance +{ +//! \namespace docker_finance::tests +//! \brief docker-finance common test framework +//! \ingroup cpp_tests +//! \since docker-finance 1.0.0 +namespace tests +{ +//! \namespace docker_finance::tests::unit +//! \brief docker-finance unit test cases +//! \ingroup cpp_tests +//! \since docker-finance 1.0.0 +namespace unit +{ +//! \brief Exception fixture (base) +//! \since docker-finance 1.0.0 +struct Exception : public ::testing::Test +{ + protected: + std::string_view what = "better to be caught than thrown"; + using t_type = type::Exception::kType; +}; +} // namespace unit +} // namespace tests +} // namespace docker_finance + +#endif // CONTAINER_SRC_ROOT_TEST_COMMON_TYPE_HH_ + +// # vim: sw=2 sts=2 si ai et diff --git a/container/src/root/test/unit/type.hh b/container/src/root/test/unit/type.hh new file mode 100644 index 0000000..831bbae --- /dev/null +++ b/container/src/root/test/unit/type.hh @@ -0,0 +1,243 @@ +// docker-finance | modern accounting for the power-user +// +// Copyright (C) 2021-2024 Aaron Fiore (Founder, Evergreen Crypto LLC) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! \file +//! \author Aaron Fiore (Founder, Evergreen Crypto LLC) +//! \note File intended to be loaded into ROOT.cern framework / Cling interpreter +//! \since docker-finance 1.0.0 + +#ifndef CONTAINER_SRC_ROOT_TEST_UNIT_TYPE_HH_ +#define CONTAINER_SRC_ROOT_TEST_UNIT_TYPE_HH_ + +#include + +#include + +#include "../common/type.hh" + +//! \namespace docker_finance +//! \since docker-finance 1.0.0 +namespace docker_finance +{ +//! \namespace docker_finance::tests +//! \brief docker-finance common test framework +//! \ingroup cpp_tests +//! \since docker-finance 1.0.0 +namespace tests +{ +//! \namespace docker_finance::tests::unit +//! \brief docker-finance unit test cases +//! \ingroup cpp_tests +//! \since docker-finance 1.0.0 +namespace unit +{ + +// +// RuntimeError +// + +//! \brief Exception fixture (RuntimeError) +//! \since docker-finance 1.0.0 +struct RuntimeError : public Exception +{ +}; + +TEST_F(RuntimeError, macro_THROW_IF) // cppcheck-suppress syntaxError +{ + try + { + THROW_IF(false, type::RuntimeError, message); + } + catch (const type::Exception& ex) + { + ASSERT_EQ(ex.type(), t_type::RuntimeError); + ASSERT_EQ(ex.what(), what); + } +} + +TEST_F(RuntimeError, macro_THROW) +{ + try + { + THROW(type::RuntimeError, message); + } + catch (const type::Exception& ex) + { + ASSERT_EQ(ex.type(), t_type::RuntimeError); + // NOTE: no case for ex.what() because message is formatted within macro + } +} + +TEST_F(RuntimeError, rethrow) +{ + ASSERT_THROW( + { + try + { + throw type::RuntimeError(what); + } + catch (const type::Exception& ex) + { + ASSERT_EQ(ex.type(), t_type::RuntimeError); + ASSERT_EQ(ex.what(), what); + throw; + } + }, + type::Exception); +} + +TEST_F(RuntimeError, copy_assignment) +{ + type::RuntimeError one(what); + type::RuntimeError two; + + two = one; + + ASSERT_EQ(one.type(), two.type()); + ASSERT_EQ(one.what(), two.what()); +} + +TEST_F(RuntimeError, copy_ctor) +{ + type::RuntimeError one(what); + type::RuntimeError two(one); + + ASSERT_EQ(one.type(), two.type()); + ASSERT_EQ(one.what(), two.what()); +} + +TEST_F(RuntimeError, move_assignment) +{ + type::RuntimeError one(what); + type::RuntimeError two; + + two = std::move(one); + + ASSERT_EQ(one.type(), two.type()); + ASSERT_EQ(one.what(), two.what()); +} + +TEST_F(RuntimeError, move_ctor) +{ + type::RuntimeError one(what); + type::RuntimeError two(std::move(one)); + + ASSERT_EQ(one.type(), two.type()); + ASSERT_EQ(one.what(), two.what()); +} + +// +// InvalidArgument +// + +//! \brief Exception fixture (InvalidArgument) +//! \since docker-finance 1.0.0 +struct InvalidArgument : public Exception +{ +}; + +TEST_F(InvalidArgument, macro_THROW_IF) // cppcheck-suppress syntaxError +{ + try + { + THROW_IF(false, type::InvalidArgument, message); + } + catch (const type::Exception& ex) + { + ASSERT_EQ(ex.type(), t_type::InvalidArgument); + ASSERT_EQ(ex.what(), what); + } +} + +TEST_F(InvalidArgument, macro_THROW) +{ + try + { + THROW(type::InvalidArgument, message); + } + catch (const type::Exception& ex) + { + ASSERT_EQ(ex.type(), t_type::InvalidArgument); + // NOTE: no case for ex.what() because message is formatted within macro + } +} + +TEST_F(InvalidArgument, rethrow) +{ + ASSERT_THROW( + { + try + { + throw type::InvalidArgument(what); + } + catch (const type::Exception& ex) + { + ASSERT_EQ(ex.type(), t_type::InvalidArgument); + ASSERT_EQ(ex.what(), what); + throw; + } + }, + type::Exception); +} + +TEST_F(InvalidArgument, copy_assignment) +{ + type::InvalidArgument one(what); + type::InvalidArgument two; + + two = one; + + ASSERT_EQ(one.type(), two.type()); + ASSERT_EQ(one.what(), two.what()); +} + +TEST_F(InvalidArgument, copy_ctor) +{ + type::InvalidArgument one(what); + type::InvalidArgument two(one); + + ASSERT_EQ(one.type(), two.type()); + ASSERT_EQ(one.what(), two.what()); +} + +TEST_F(InvalidArgument, move_assignment) +{ + type::InvalidArgument one(what); + type::InvalidArgument two; + + two = std::move(one); + + ASSERT_EQ(one.type(), two.type()); + ASSERT_EQ(one.what(), two.what()); +} + +TEST_F(InvalidArgument, move_ctor) +{ + type::InvalidArgument one(what); + type::InvalidArgument two(std::move(one)); + + ASSERT_EQ(one.type(), two.type()); + ASSERT_EQ(one.what(), two.what()); +} + +} // namespace unit +} // namespace tests +} // namespace docker_finance + +#endif // CONTAINER_SRC_ROOT_TEST_UNIT_TYPE_HH_ + +// # vim: sw=2 sts=2 si ai et