Merge pull request #244 into master

9faebb6c container: plugins: root: fix example3()'s usage of throw handler (Aaron Fiore)
ca591691 container: root: rewrite throw handler, refactor using common types (Aaron Fiore)
fa91fd02 container: root: factor out internal types to common (Aaron Fiore)
This commit was merged in pull request #244.
This commit is contained in:
2025-11-21 11:22:14 -08:00
13 changed files with 244 additions and 168 deletions

View File

@@ -108,7 +108,7 @@ void example2()
//! \ingroup cpp_plugin_impl
void example3()
{
namespace common = ::dfi::macro::common;
namespace common = ::dfi::common;
const std::string base{
common::get_env("global_basename") + " "
@@ -122,10 +122,8 @@ void example3()
auto exec_cmd = [&make_cmd](const std::string& cmd) {
// shell 0 = success
THROW_IF(
common::exec("bash -i -c '" + make_cmd(cmd) + "'"),
std::runtime_error,
"command failed")
common::throw_ex_if<common::type::RuntimeError>(
common::exec("bash -i -c '" + make_cmd(cmd) + "'"), "command failed");
};
std::cout << "\nImporting journals...\n";

View File

@@ -0,0 +1,105 @@
// docker-finance | modern accounting for the power-user
//
// Copyright (C) 2021-2025 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 <https://www.gnu.org/licenses/>.
//! \file
//! \author Aaron Fiore (Founder, Evergreen Crypto LLC)
//! \note File intended to be loaded into ROOT.cern framework / Cling interpreter
//! \since docker-finance 1.1.0
#ifndef CONTAINER_SRC_ROOT_COMMON_TYPE_HH_
#define CONTAINER_SRC_ROOT_COMMON_TYPE_HH_
#include <stdexcept>
#include <string>
//! \namespace dfi
//! \since docker-finance 1.0.0
namespace dfi
{
//! \namespace dfi::common
//! \brief Shared common functionality
//! \since docker-finance 1.1.0
namespace common
{
//! \namespace dfi::common::type
//! \brief docker-finance defined common types
//! \since docker-finance 1.1.0
namespace type
{
//! \brief Base exception class
//! \ingroup cpp_type_exceptions
class Exception : virtual public std::exception
{
public:
//! \brief Exception type tag
enum struct kType : uint8_t
{
RuntimeError,
InvalidArgument,
};
//! \brief Construct by type tag with given message
Exception(const kType type, const std::string& 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 tag
kType type() const noexcept { return m_type; }
//! \return Exception message
const char* what() const noexcept { return m_what.c_str(); }
private:
kType m_type;
std::string m_what;
};
//! \brief Exception type for runtime errors
//! \ingroup cpp_type_exceptions
struct RuntimeError final : public Exception
{
explicit RuntimeError(const std::string& what = {})
: Exception(Exception::kType::RuntimeError, what)
{
}
};
//! \brief Exception type for invalid arguments (logic error)
//! \ingroup cpp_type_exceptions
struct InvalidArgument final : public Exception
{
explicit InvalidArgument(const std::string& what = {})
: Exception(Exception::kType::InvalidArgument, what)
{
}
};
} // namespace type
} // namespace common
} // namespace dfi
#endif // CONTAINER_SRC_ROOT_COMMON_TYPE_HH_
// # vim: sw=2 sts=2 si ai et

View File

@@ -30,6 +30,8 @@
#include <string>
#include <vector>
#include "./type.hh"
//! \namespace dfi
//! \since docker-finance 1.0.0
namespace dfi
@@ -39,6 +41,51 @@ namespace dfi
//! \since docker-finance 1.1.0
namespace common
{
//! \concept Exception
//! \brief Exception type concept for `dfi` exceptions
//! \ingroup cpp_common_impl
//! \since docker-finance 1.1.0
template <typename t_exception>
concept Exception = std::derived_from<t_exception, type::Exception>;
//! \brief Throw exception using `dfi` exception type
//! \param message Message to pass to exception type
//! \param location Source location of exception
//! \ingroup cpp_common_impl
//! \since docker-finance 1.1.0
template <Exception t_exception>
inline void throw_ex(
const std::string& message = {},
const std::source_location location = std::source_location::current())
{
// TODO(unassigned): when throwing because of heap allocation
std::string msg;
msg += std::string(" \n\tFILE = ") + location.file_name();
msg += std::string(" \n\tFUNC = ") + location.function_name();
msg += std::string(" \n\tLINE = ") + location.line();
msg += std::string(" \n\tWHAT = ") + message;
throw t_exception(msg);
}
//! \brief Throw exception using `dfi` exception type (but only on condition)
//! \param condition If boolean condition is true, throw
//! \param message Message to pass to exception type
//! \param location Source location of exception
//! \ingroup cpp_common_impl
//! \since docker-finance 1.1.0
template <Exception t_exception>
inline void throw_ex_if(
const bool condition,
const std::string& message = {},
const std::source_location location = std::source_location::current())
{
if (condition)
{
throw_ex<t_exception>(message, location);
}
}
//! \brief Wrapper to ROOT Cling commands
//! \ingroup cpp_common_impl
//! \since docker-finance 1.0.0
@@ -69,16 +116,13 @@ class Command
static void load(const std::string& path)
{
std::filesystem::path p(path);
if (!std::filesystem::exists(p))
{
throw std::runtime_error(
std::string{"'" + path + "' does not exist!"}.c_str());
}
if (!std::filesystem::is_regular_file(p))
{
throw std::runtime_error(
std::string{"'" + path + "' is not a regular file!"}.c_str());
}
throw_ex_if<type::RuntimeError>(
!std::filesystem::exists(p), "'" + path + "' does not exist!");
throw_ex_if<type::RuntimeError>(
!std::filesystem::is_regular_file(p),
"'" + path + "' is not a regular file!");
std::string cmd{".L " + path};
Command::cmd_handler({cmd});
@@ -101,9 +145,10 @@ class Command
std::string get_env(const std::string& var)
{
const auto* env = gSystem->Getenv(var.c_str());
if (!env)
throw std::runtime_error(
std::string{var + " is not set or is unavailable"}.c_str());
throw_ex_if<type::RuntimeError>(
!env, "'" + var + "' is not set or is unavailable");
return std::string{env};
}

View File

@@ -184,7 +184,7 @@ class Meta final
//! \param column Existing CSV metadata column
static void meta_sample(const std::string& column)
{
namespace common = ::dfi::macro::common;
namespace common = ::dfi::common;
// Import meta file
const std::string path{common::get_env("global_conf_meta")};
@@ -197,14 +197,12 @@ class Meta final
available += "\n\t" + col;
available += "\n";
if (!csv->HasColumn(column))
throw std::runtime_error(
std::string{"\n\nAvailable columns: " + available}.c_str());
common::throw_ex_if<common::type::RuntimeError>(
!csv->HasColumn(column), "\n\nAvailable columns: " + available);
// Aggregate all rows of given column
auto count = csv->Count();
if (!*count)
throw std::runtime_error("Empty CSV");
common::throw_ex_if<common::type::RuntimeError>(!*count, "Empty CSV");
// Prepare canvas data
Meta::CanvasData data;

View File

@@ -87,7 +87,7 @@ void load(const std::string& path)
}
else
{
throw std::runtime_error(
::dfi::common::throw_ex<::dfi::common::type::RuntimeError>(
"must be of type 'repo/<file>' or 'custom/<file>'");
}

View File

@@ -429,10 +429,8 @@ class Hash : public common::HashImpl<libsodium::Hash>, public type::Hash
//! have any effects."
Hash()
{
THROW_IF(
::sodium_init() < 0,
type::RuntimeError,
"sodium_init could not be initialized")
::dfi::common::throw_ex_if<::dfi::common::type::RuntimeError>(
::sodium_init() < 0, "sodium_init could not be initialized");
}
~Hash() = default;

View File

@@ -128,7 +128,8 @@ class Random : public common::RandomImpl<botan::Random>
|| std::is_same_v<t_random, uint32_t>,
"Invalid type (only uint16_t or uint32_t supported)");
THROW_IF(!m_csprng.is_seeded(), type::RuntimeError, "Botan is not seeded")
::dfi::common::throw_ex_if<type::RuntimeError>(
!m_csprng.is_seeded(), "Botan is not seeded");
// WARNING: DO *NOT* set_high_bit to true here!
// Otherwise, [0..(~2150*10^6)] WILL NOT BE GENERATED!
@@ -222,10 +223,8 @@ class Random : public common::RandomImpl<libsodium::Random>
//! have any effects."
Random()
{
THROW_IF(
::sodium_init() < 0,
type::RuntimeError,
"sodium_init could not be initialized")
::dfi::common::throw_ex_if<::dfi::common::type::RuntimeError>(
::sodium_init() < 0, "sodium_init could not be initialized");
}
~Random() = default;

View File

@@ -159,8 +159,8 @@ class Tools
std::vector<t_num>
random_dist(const t_num min, const t_num max, const uint16_t precision = 8)
{
THROW_IF(
min > max, type::InvalidArgument, "minimum is greater than maximum")
::dfi::common::throw_ex_if<::dfi::common::type::InvalidArgument>(
min > max, "minimum is greater than maximum");
// Get first chunk
std::vector<t_num> chunks;
@@ -193,7 +193,8 @@ class Tools
std::cout.precision(precision);
std::cout << "rounded = " << rounded << '\n'
<< "max = " << max << std::fixed << std::endl;
THROW(type::RuntimeError, "random distribution not fulfilled")
::dfi::common::throw_ex<::dfi::common::type::RuntimeError>(
"random distribution not fulfilled");
}
return chunks;

View File

@@ -30,30 +30,7 @@
#include <string_view>
#include <type_traits>
//! \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
#include "../../common/type.hh"
//! \namespace dfi
//! \since docker-finance 1.0.0
@@ -70,62 +47,20 @@ 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,
};
//! \deprecated This will be removed in the v2 API; use `dfi::common::type` namespace instead
//! \todo Remove in 2.0.0
//! \since docker-finance 1.1.0
using Exception = ::dfi::common::type::Exception;
//! \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;
//! \deprecated This will be removed in the v2 API; use `dfi::common::type` namespace instead
//! \todo Remove in 2.0.0
//! \since docker-finance 1.1.0
using RuntimeError = ::dfi::common::type::RuntimeError;
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)
{
}
};
//! \deprecated This will be removed in the v2 API; use `dfi::common::type` namespace instead
//! \todo Remove in 2.0.0
//! \since docker-finance 1.1.0
using InvalidArgument = ::dfi::common::type::InvalidArgument;
//! \ingroup cpp_type_traits
template <typename t_type>

View File

@@ -43,6 +43,7 @@ namespace dfi
namespace utility
{
namespace common = ::dfi::common;
namespace type = dfi::internal::type;
//! \brief Misc utility tools
@@ -86,8 +87,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
THROW_IF(
min < 0 || max < 0, type::InvalidArgument, "no negative values allowed")
common::throw_ex_if<common::type::InvalidArgument>(
min < 0 || max < 0, "no negative values allowed");
if (min > max)
{

View File

@@ -25,7 +25,7 @@
#include <gtest/gtest.h>
#include <string_view>
#include <string>
#include "../../src/internal/type.hh"
@@ -50,8 +50,8 @@ namespace unit
struct Exception : public ::testing::Test
{
protected:
std::string_view what = "better to be caught than thrown";
using t_type = type::Exception::kType;
std::string what = "better to be caught than thrown";
using t_type = ::dfi::common::type::Exception::kType;
};
} // namespace unit
} // namespace tests

View File

@@ -46,6 +46,8 @@ namespace tests
namespace unit
{
namespace common = ::dfi::common;
//
// RuntimeError
//
@@ -56,11 +58,11 @@ struct RuntimeError : public Exception
{
};
TEST_F(RuntimeError, macro_THROW_IF) // cppcheck-suppress syntaxError
TEST_F(RuntimeError, throw_ex_if) // cppcheck-suppress syntaxError
{
try
{
THROW_IF(false, type::RuntimeError, message);
common::throw_ex_if<common::type::RuntimeError>(false, what);
}
catch (const type::Exception& ex)
{
@@ -69,16 +71,16 @@ TEST_F(RuntimeError, macro_THROW_IF) // cppcheck-suppress syntaxError
}
}
TEST_F(RuntimeError, macro_THROW)
TEST_F(RuntimeError, throw_ex)
{
try
{
THROW(type::RuntimeError, message);
common::throw_ex<common::type::RuntimeError>(what);
}
catch (const type::Exception& ex)
{
ASSERT_EQ(ex.type(), t_type::RuntimeError);
// NOTE: no case for ex.what() because message is formatted within macro
// NOTE: no case for ex.what() because message is formatted within function
}
}
@@ -108,7 +110,7 @@ TEST_F(RuntimeError, copy_assignment)
two = one;
ASSERT_EQ(one.type(), two.type());
ASSERT_EQ(one.what(), two.what());
ASSERT_STREQ(one.what(), two.what());
}
TEST_F(RuntimeError, copy_ctor)
@@ -117,7 +119,7 @@ TEST_F(RuntimeError, copy_ctor)
type::RuntimeError two(one);
ASSERT_EQ(one.type(), two.type());
ASSERT_EQ(one.what(), two.what());
ASSERT_STREQ(one.what(), two.what());
}
TEST_F(RuntimeError, move_assignment)
@@ -128,7 +130,7 @@ TEST_F(RuntimeError, move_assignment)
two = std::move(one);
ASSERT_EQ(one.type(), two.type());
ASSERT_EQ(one.what(), two.what());
ASSERT_STRNE(one.what(), two.what());
}
TEST_F(RuntimeError, move_ctor)
@@ -137,7 +139,7 @@ TEST_F(RuntimeError, move_ctor)
type::RuntimeError two(std::move(one));
ASSERT_EQ(one.type(), two.type());
ASSERT_EQ(one.what(), two.what());
ASSERT_STRNE(one.what(), two.what());
}
//
@@ -150,11 +152,11 @@ struct InvalidArgument : public Exception
{
};
TEST_F(InvalidArgument, macro_THROW_IF) // cppcheck-suppress syntaxError
TEST_F(InvalidArgument, throw_ex_if)
{
try
{
THROW_IF(false, type::InvalidArgument, message);
common::throw_ex_if<common::type::InvalidArgument>(false, what);
}
catch (const type::Exception& ex)
{
@@ -163,16 +165,16 @@ TEST_F(InvalidArgument, macro_THROW_IF) // cppcheck-suppress syntaxError
}
}
TEST_F(InvalidArgument, macro_THROW)
TEST_F(InvalidArgument, throw_ex)
{
try
{
THROW(type::InvalidArgument, message);
common::throw_ex<common::type::InvalidArgument>(what);
}
catch (const type::Exception& ex)
{
ASSERT_EQ(ex.type(), t_type::InvalidArgument);
// NOTE: no case for ex.what() because message is formatted within macro
// NOTE: no case for ex.what() because message is formatted within function
}
}
@@ -202,7 +204,7 @@ TEST_F(InvalidArgument, copy_assignment)
two = one;
ASSERT_EQ(one.type(), two.type());
ASSERT_EQ(one.what(), two.what());
ASSERT_STREQ(one.what(), two.what());
}
TEST_F(InvalidArgument, copy_ctor)
@@ -211,7 +213,7 @@ TEST_F(InvalidArgument, copy_ctor)
type::InvalidArgument two(one);
ASSERT_EQ(one.type(), two.type());
ASSERT_EQ(one.what(), two.what());
ASSERT_STREQ(one.what(), two.what());
}
TEST_F(InvalidArgument, move_assignment)
@@ -222,7 +224,7 @@ TEST_F(InvalidArgument, move_assignment)
two = std::move(one);
ASSERT_EQ(one.type(), two.type());
ASSERT_EQ(one.what(), two.what());
ASSERT_STRNE(one.what(), two.what());
}
TEST_F(InvalidArgument, move_ctor)
@@ -231,7 +233,7 @@ TEST_F(InvalidArgument, move_ctor)
type::InvalidArgument two(std::move(one));
ASSERT_EQ(one.type(), two.type());
ASSERT_EQ(one.what(), two.what());
ASSERT_STRNE(one.what(), two.what());
}
} // namespace unit

View File

@@ -60,6 +60,9 @@ namespace tests
//! \since docker-finance 1.0.0
namespace unit
{
namespace common = ::dfi::common;
//! \brief Tools utility fixture
//! \since docker-finance 1.0.0
struct Tools : public ::testing::Test, public tests::ToolsFixture
@@ -296,50 +299,41 @@ struct CommonRawFiles : public ::testing::Test,
}
catch (...)
{
THROW(std::runtime_error, "could not generate path");
common::throw_ex<common::type::RuntimeError>("could not generate path");
}
std::ofstream file_1(path_1), file_2(path_2);
THROW_IF(
!file_1.is_open() || !file_2.is_open(),
std::runtime_error,
"could not create file");
common::throw_ex_if<common::type::RuntimeError>(
!file_1.is_open() || !file_2.is_open(), "could not create file");
file_1 << "using my_foo = int;\n";
file_1.close();
file_2 << "using my_bar = char;\n";
file_2.close();
THROW_IF(
file_1.bad() || file_2.bad(),
std::runtime_error,
"could not write to file");
common::throw_ex_if<common::type::RuntimeError>(
file_1.bad() || file_2.bad(), "could not write to file");
}
void TearDown() override
{
THROW_IF(
std::remove(path_1.c_str()),
std::runtime_error,
"could not remove file '" + path_1 + "'");
common::throw_ex_if<common::type::RuntimeError>(
std::remove(path_1.c_str()), "could not remove file '" + path_1 + "'");
THROW_IF(
std::remove(path_2.c_str()),
std::runtime_error,
"could not remove file '" + path_2 + "'");
common::throw_ex_if<common::type::RuntimeError>(
std::remove(path_2.c_str()), "could not remove file '" + path_2 + "'");
}
};
TEST_F(CommonRawFiles, RawLoadSingle)
{
ASSERT_THROW(
::dfi::common::Command::load({path_1 + "should-not-exist"}),
std::runtime_error);
common::Command::load({path_1 + "should-not-exist"}), type::RuntimeError);
gInterpreter->ProcessLine("my_foo foo;", ecode.get());
ASSERT_NE(ecode, nullptr);
EXPECT_NE(*ecode, EErrorCode::kNoError);
ASSERT_NO_THROW(::dfi::common::Command::load(path_1));
ASSERT_NO_THROW(common::Command::load(path_1));
gInterpreter->ProcessLine("my_foo foo;", ecode.get());
ASSERT_NE(ecode, nullptr);
EXPECT_EQ(*ecode, EErrorCode::kNoError);
@@ -348,14 +342,14 @@ TEST_F(CommonRawFiles, RawLoadSingle)
TEST_F(CommonRawFiles, RawLoadMultiple)
{
ASSERT_THROW(
::dfi::common::Command::load(
common::Command::load(
{{path_1 + "should-not-exist"}, {path_2 + "nor-should-this"}}),
std::runtime_error);
type::RuntimeError);
gInterpreter->ProcessLine("my_bar bar;", ecode.get());
ASSERT_NE(ecode, nullptr);
EXPECT_NE(*ecode, EErrorCode::kNoError);
ASSERT_NO_THROW(::dfi::common::Command::load({path_1, path_2}));
ASSERT_NO_THROW(common::Command::load({path_1, path_2}));
gInterpreter->ProcessLine("my_bar bar;", ecode.get());
ASSERT_NE(ecode, nullptr);
EXPECT_EQ(*ecode, EErrorCode::kNoError);
@@ -373,7 +367,7 @@ struct CommonRepoFiles : public ::testing::Test,
TEST_F(CommonRepoFiles, MacroLoad)
{
ASSERT_THROW(
::dfi::macro::load("macro/should-not/exist.C"), std::runtime_error);
::dfi::macro::load("macro/should-not/exist.C"), type::RuntimeError);
gInterpreter->ProcessLine(
"dfi::macro::common::crypto::botan::Hash h;", ecode.get());
ASSERT_NE(ecode, nullptr);
@@ -391,7 +385,7 @@ TEST_F(CommonRepoFiles, MacroLoad)
TEST_F(CommonRepoFiles, PluginLoad)
{
ASSERT_THROW(
::dfi::plugin::load("repo/should-not/exist.cc"), std::runtime_error);
::dfi::plugin::load("repo/should-not/exist.cc"), type::RuntimeError);
gInterpreter->ProcessLine(
"dfi::plugin::my_plugin_namespace::example2();", ecode.get());
ASSERT_NE(ecode, nullptr);
@@ -416,19 +410,19 @@ struct CommonFree : public ::testing::Test, public ::dfi::tests::CommonFixture
TEST_F(CommonFree, env)
{
ASSERT_THROW(::dfi::common::get_env("should-not-exist"), std::runtime_error);
ASSERT_NO_THROW(::dfi::common::get_env("DOCKER_FINANCE_VERSION"));
ASSERT_THROW(common::get_env("should-not-exist"), type::RuntimeError);
ASSERT_NO_THROW(common::get_env("DOCKER_FINANCE_VERSION"));
}
TEST_F(CommonFree, exec)
{
ASSERT_NE(::dfi::common::exec("should-not-exist"), 0);
ASSERT_EQ(::dfi::common::exec("pwd"), 0);
ASSERT_NE(common::exec("should-not-exist"), 0);
ASSERT_EQ(common::exec("pwd"), 0);
}
TEST_F(CommonFree, make_timestamp)
{
ASSERT_EQ(::dfi::common::make_timestamp().size(), 20);
ASSERT_EQ(common::make_timestamp().size(), 20);
}
} // namespace unit