diff --git a/conanfile.py b/conanfile.py index f399a6445ef3a8f396ae08fc85fe3688fd80f5cc..a773f501212cb23d3649aae31c644bd66a259516 100644 --- a/conanfile.py +++ b/conanfile.py @@ -14,10 +14,10 @@ class CaosdbConan(ConanFile): default_options = {"shared": False, "fPIC": True} generators = "cmake" requires = [ - ("boost/1.77.0"), ("grpc/1.39.1"), ] build_requires = [ + ("boost/1.77.0"), ("gtest/1.11.0"), ] exports = ("*.cmake", "*CMakeLists.txt", "*.in", diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index eea5b43ec1abbe81632d548a0fd3d6d02e034df1..fd3083fe290b51f78c12838c94bb3cf5b7a5a066 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* Removed boost from the headers. Boost is only a build dependency from now on. + ### Deprecated ### Removed diff --git a/include/caosdb/configuration.h b/include/caosdb/configuration.h index 092522d1bb3a30980036e7c6acb71ddb8b78be5c..053888672ab3e1649a060553264fe8d26fda9907 100644 --- a/include/caosdb/configuration.h +++ b/include/caosdb/configuration.h @@ -25,27 +25,20 @@ #include "caosdb/authentication.h" // for Authenticator, PlainPassw... #include "caosdb/certificate_provider.h" // for CertificateProvider, path #include "caosdb/exceptions.h" // for ConfigurationError -#include "caosdb/logging.h" // for SinkConfiguration, Loggin... #include "caosdb/utility.h" // for load_json_file #include <google/protobuf/arena.h> // for Arena #include <google/protobuf/extension_set.h> // for Arena #include <grpcpp/security/credentials.h> // for ChannelCredentials -#include <boost/json/object.hpp> // for object -#include <boost/json/value.hpp> // for value -#include <boost/json/value_ref.hpp> // for array, object -// IWYU pragma: no_include "boost/json/fwd.hpp" -#include <filesystem> // for path, exists -#include <iosfwd> // for ostream -#include <memory> // for shared_ptr, unique_ptr -#include <string> // for string +#include <filesystem> // for path, exists +#include <iosfwd> // for ostream +#include <memory> // for shared_ptr, unique_ptr +#include <string> // for string namespace caosdb::configuration { -using boost::json::array; -using boost::json::object; -using boost::json::value; using caosdb::authentication::Authenticator; using caosdb::authentication::PlainPasswordAuthenticator; using caosdb::exceptions::ConfigurationError; +using caosdb::utility::JsonValue; using caosdb::utility::load_json_file; using google::protobuf::Arena; using grpc::ChannelCredentials; @@ -102,69 +95,6 @@ public: [[nodiscard]] auto ToString() const -> std::string override; }; -/** - * Helper class (no state, just member functions) which should only be used by - * the ConfigurationManager to initialize the logging framework from the stored - * configuration. - */ -class LoggingConfigurationHelper { -public: - friend class ConfigurationManager; - -private: - auto ConvertLogLevel(const std::string &string_level) const -> int; - auto CreateConsoleSinkConfiguration(const object &from, const std::string &name, int level) const - -> std::shared_ptr<caosdb::logging::SinkConfiguration>; - auto CreateSyslogSinkConfiguration(const object &from, const std::string &name, int level) const - -> std::shared_ptr<caosdb::logging::SinkConfiguration>; - auto CreateFileSinkConfiguration(const object &from, const std::string &name, int level) const - -> std::shared_ptr<caosdb::logging::SinkConfiguration>; - auto CreateSinkConfiguration(const object &from, const std::string &name, int default_level) const - -> std::shared_ptr<caosdb::logging::SinkConfiguration>; - auto CreateLoggingConfiguration(const object &from) const - -> caosdb::logging::LoggingConfiguration; -}; - -/** - * Helper class (no state, just member functions) which should only be used by - * the ConfigurationManager to construct Connection instances from the stored - * configuration. - */ -class ConnectionConfigurationHelper { -public: - friend class ConfigurationManager; - -private: - /** - * @param from - a single connection configuration. - */ - auto CreateCertificateProvider(const object &from) const -> std::unique_ptr<CertificateProvider>; - - /** - * @param from - a single connection configuration. - */ - auto CreateAuthenticator(const object &from) const -> std::unique_ptr<Authenticator>; - - /** - * @param from - a single connection configuration. - */ - auto CreateConnectionConfiguration(const bool tls, const std::string &host, const int port, - const CertificateProvider *certificate_provider, - const Authenticator *authenticator) const - -> std::unique_ptr<ConnectionConfiguration>; - - /** - * @param from - a single connection configuration. - */ - auto IsTls(const object &from) const -> bool; - - /** - * @param from - a single connection configuration. - */ - auto CreateConnectionConfiguration(const object &from) const - -> std::unique_ptr<ConnectionConfiguration>; -}; - /** * Reads the configuration file and keeps the configuration. Singleton. * @@ -225,13 +155,12 @@ public: private: Arena arena; - value json_configuration; - ConnectionConfigurationHelper connection_configuration_helper; - LoggingConfigurationHelper logging_configuration_helper; + JsonValue json_configuration; - inline ConfigurationManager(){ - // InitializeDefaults(); - }; + inline ConfigurationManager() + : json_configuration(nullptr){ + // InitializeDefaults(); + }; /** * Initialize this ConfigurationManager with the defaults. @@ -242,22 +171,6 @@ private: */ auto InitializeDefaults() -> int; - /** - * Return a json object representing the current configuration. - */ - auto GetConfiguration() const -> const object &; - - /** - * Return the connection configurations. - */ - auto GetConnections() const -> const object &; - - /** - * Return the configuration for the connection with the given name (as a json - * object). - */ - auto GetConnection(const std::string &name) const -> const object &; - /** * Reset this ConfigurationManager. * diff --git a/include/caosdb/utility.h b/include/caosdb/utility.h index 0a4d65e883ccfb6cc8f41d1c412bef5433b89417..13e3dfeb99dc5415a733d5b6dcea852705b1662d 100644 --- a/include/caosdb/utility.h +++ b/include/caosdb/utility.h @@ -21,27 +21,15 @@ #ifndef CAOSDB_UTILS_H #define CAOSDB_UTILS_H -#include "caosdb/data_type.h" // for AtomicDataType -#include "caosdb/entity.h" // for Importance, Role -#include <boost/beast/core/detail/base64.hpp> // for encoded_size -#include <boost/beast/core/detail/base64.ipp> // for encode -#include <boost/filesystem/string_file.hpp> // for load_string_file -#include <boost/json/stream_parser.hpp> // for stream_parser -#include <boost/json/value.hpp> // for value -#include <boost/lexical_cast.hpp> // for lexical_cast -#include <cassert> // for assert -#include <cstdlib> // for getenv -#include <filesystem> // for path -#include <fstream> // for basic_istream<>::__ist... -#include <memory> // for allocator, unique_ptr -#include <stdexcept> // for logic_error -#include <string> // for string, operator+, cha... -#include <type_traits> // for underlying_type_t -#include <typeinfo> // for type_info +#include "caosdb/data_type.h" // for AtomicDataType +#include "caosdb/entity.h" // for Importance, Role +#include <cstdlib> // for getenv +#include <filesystem> // for path +#include <fstream> // for basic_istream<>::__ist... +#include <memory> // for shared_ptr +#include <string> // for string, operator+, cha... namespace caosdb::utility { -using boost::json::stream_parser; -using boost::json::value; using std::ifstream; using std::filesystem::exists; using std::filesystem::path; @@ -49,12 +37,7 @@ using std::filesystem::path; /** * @brief Get the name of the enum value. May be useful for higher-order CaosDB clients. */ -template <typename Enum> auto getEnumNameFromValue(Enum v) -> std::string { - if (std::is_same_v<std::underlying_type_t<Enum>, int>) { - return boost::lexical_cast<std::string>(static_cast<int>(v)); - } - throw std::logic_error(std::string("Enum type ") + typeid(v).name() + " not implemented."); -} +template <typename Enum> auto getEnumNameFromValue(Enum v) -> std::string; // Forward declaration of specializations template <> @@ -84,11 +67,7 @@ auto getEnumValueFromName<caosdb::entity::Role>(const std::string &name) -> caos /** * @brief Read a text file into a string and return the file's content. */ -inline auto load_string_file(const path &file_path) -> std::string { - std::string result; - boost::filesystem::load_string_file(file_path.string(), result); - return result; -} +auto load_string_file(const path &file_path) -> std::string; /** * @brief Return the environment variable KEY, or FALLBACK if it does not exist. @@ -115,37 +94,82 @@ inline auto get_env_fallback(const std::string &key, const std::string &fallback } /** - * @brief Encode string as base64 + * @brief JsonValue is a thin wrapper around a implementation specific + * third-party json object (e.g. boost). */ -inline auto base64_encode(const std::string &plain) -> std::string { - auto size_plain = plain.size(); - auto size_encoded = boost::beast::detail::base64::encoded_size(size_plain); - - std::unique_ptr<char[]> encoded(new char[size_encoded]); - boost::beast::detail::base64::encode(encoded.get(), plain.c_str(), size_plain); - - // the encoded char[] is not null terminated, so explicitely set the length - return std::string(encoded.get(), encoded.get() + size_encoded); -} - -inline auto load_json_file(const path &json_file) -> value { - assert(exists(json_file)); +class JsonValue { +public: + /** + * Default Constructor. + * + * Creates an empty wrapper where `wrapped` is nullptr. + */ + JsonValue() : JsonValue(nullptr) {} + /** + * Constructor. + * + * By calling this constructor the ownership of the `wrapped` parameter is + * transferred to this object. + */ + JsonValue(void *wrapped); + /** + * Destructor. + * + * Also deletes the `wrapped` object. + */ + ~JsonValue(); + + /** + * Copy Constructor. + * + * Also copies the `wrapped` object. + */ + JsonValue(const JsonValue &other); + + /** + * Copy Assigment. + * + * Also copies the `wrapped` object. + */ + auto operator=(const JsonValue &other) -> JsonValue &; + + /** + * Move Constructor. + * + * Also moves the `wrapped` object. + */ + JsonValue(JsonValue &&other) noexcept; + + /** + * Move Assigment. + * + * Also moves the `wrapped` object. + */ + auto operator=(JsonValue &&other) noexcept -> JsonValue &; + + /** + * Reset this object. + * + * Also deletes `wrapped` sets it to the nullptr. + */ + auto Reset() -> void; + + /** + * An object which represents a JSON value. The object's class is an + * implementation detail. + */ + std::shared_ptr<void> wrapped; +}; - constexpr auto buffer_size = std::size_t(4096); - auto stream = ifstream(json_file); - stream.exceptions(std::ios_base::badbit); - - stream_parser parser; - auto result = std::string(); - auto buffer = std::string(buffer_size, '\0'); - while (stream.read(&buffer[0], buffer_size)) { - parser.write(buffer.c_str(), stream.gcount()); - } - parser.write(buffer.c_str(), stream.gcount()); +/** + * @brief Load json object from a json file and return it. + */ +auto load_json_file(const path &json_file) -> JsonValue; - assert(parser.done()); - return parser.release(); -} +/** + * @brief Encode string as base64 + */ +auto base64_encode(const std::string &plain) -> std::string; inline auto get_home_directory() -> const path { const auto *const home = getenv("HOME"); diff --git a/src/caosdb/configuration.cpp b/src/caosdb/configuration.cpp index 41d19616acdd450aa5e8057dd1966000f46e0803..e1ffb23b3b1a2db5ecea79588c237bb36e79b1e1 100644 --- a/src/caosdb/configuration.cpp +++ b/src/caosdb/configuration.cpp @@ -24,6 +24,7 @@ #include "caosdb/constants.h" // for LIBCAOSDB_CONF... #include "caosdb/exceptions.h" // for ConfigurationE... #include "caosdb/log_level.h" // for CAOSDB_DEFAULT... +#include "caosdb/logging.h" // for SinkConfiguration, Loggin... #include "caosdb/status_code.h" // for StatusCode #include "caosdb/utility.h" // for get_home_direc... #include <boost/json/impl/object.hpp> // for object::at @@ -44,6 +45,30 @@ #include <string> // for string, operator+ #include <utility> // for move +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define WRAPPED_JSON_CONFIGURATION(obj) \ + (static_cast<value *>((obj)->json_configuration.wrapped.get())) + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define GET_CONNECTIONS \ + if (!this->json_configuration.wrapped) { \ + throw ConfigurationError("This CaosDB client has not been configured."); \ + } \ + assert(WRAPPED_JSON_CONFIGURATION(this)->is_object()); \ + const auto &configuration = WRAPPED_JSON_CONFIGURATION(this)->as_object(); \ + if (!configuration.contains("connections")) { \ + throw ConfigurationError("This CaosDB client hasn't any configured connections."); \ + } \ + const auto &connections_value = configuration.at("connections"); \ + if (connections_value.is_null()) { \ + throw ConfigurationError("This CaosDB client hasn't any configured connections."); \ + } \ + assert(connections_value.is_object()); \ + const auto &connections = connections_value.as_object(); \ + if (connections.empty()) { \ + throw ConfigurationError("This CaosDB client hasn't any configured connections."); \ + } + namespace caosdb::configuration { using boost::json::object; using boost::json::value; @@ -151,8 +176,7 @@ auto TlsConnectionConfiguration::ToString() const -> std::string { "," + this->certificate_provider + ")"; } -auto ConnectionConfigurationHelper::CreateCertificateProvider(const object &from) const - -> std::unique_ptr<CertificateProvider> { +auto CreateCertificateProvider(const object &from) -> std::unique_ptr<CertificateProvider> { std::unique_ptr<CertificateProvider> certificate_provider; if (from.contains("server_certificate_path")) { const value &path_str = from.at("server_certificate_path"); @@ -168,8 +192,7 @@ auto ConnectionConfigurationHelper::CreateCertificateProvider(const object &from return certificate_provider; } -auto ConnectionConfigurationHelper::CreateAuthenticator(const object &from) const - -> std::unique_ptr<Authenticator> { +auto CreateAuthenticator(const object &from) -> std::unique_ptr<Authenticator> { std::unique_ptr<Authenticator> authenticator; if (from.contains("authentication")) { assert(from.at("authentication").is_object()); @@ -197,9 +220,9 @@ auto ConnectionConfigurationHelper::CreateAuthenticator(const object &from) cons return authenticator; } -auto ConnectionConfigurationHelper::CreateConnectionConfiguration( - const bool tls, const std::string &host, const int port, - const CertificateProvider *certificate_provider, const Authenticator *authenticator) const +auto CreateConnectionConfiguration(const bool tls, const std::string &host, const int port, + const CertificateProvider *certificate_provider, + const Authenticator *authenticator) -> std::unique_ptr<ConnectionConfiguration> { if (tls) { if (certificate_provider != nullptr && authenticator != nullptr) { @@ -220,7 +243,7 @@ auto ConnectionConfigurationHelper::CreateConnectionConfiguration( } } -auto ConnectionConfigurationHelper::IsTls(const object &from) const -> bool { +auto IsTls(const object &from) -> bool { bool tls = true; if (from.contains("tls")) { auto tls_switch = from.at("tls"); @@ -230,8 +253,7 @@ auto ConnectionConfigurationHelper::IsTls(const object &from) const -> bool { return tls; } -auto ConnectionConfigurationHelper::CreateConnectionConfiguration(const object &from) const - -> std::unique_ptr<ConnectionConfiguration> { +auto CreateConnectionConfiguration(const object &from) -> std::unique_ptr<ConnectionConfiguration> { assert(from.contains("host")); const auto &host = from.at("host"); assert(host.is_string()); @@ -251,25 +273,19 @@ auto ConnectionConfigurationHelper::CreateConnectionConfiguration(const object & certificate_provider.get(), authenticator.get()); } -auto LoggingConfigurationHelper::CreateConsoleSinkConfiguration(const object & /*from*/, - const std::string &name, - int level) const +auto CreateConsoleSinkConfiguration(const object & /*from*/, const std::string &name, int level) -> std::shared_ptr<caosdb::logging::SinkConfiguration> { auto result = std::make_shared<ConsoleSinkConfiguration>(name, level); return result; } -auto LoggingConfigurationHelper::CreateSyslogSinkConfiguration(const object & /*from*/, - const std::string &name, - int level) const +auto CreateSyslogSinkConfiguration(const object & /*from*/, const std::string &name, int level) -> std::shared_ptr<caosdb::logging::SinkConfiguration> { auto result = std::make_shared<SyslogSinkConfiguration>(name, level); return result; } -auto LoggingConfigurationHelper::CreateFileSinkConfiguration(const object &from, - const std::string &name, - int level) const +auto CreateFileSinkConfiguration(const object &from, const std::string &name, int level) -> std::shared_ptr<caosdb::logging::SinkConfiguration> { auto result = std::make_shared<FileSinkConfiguration>(name, level); if (from.contains("directory")) { @@ -278,9 +294,21 @@ auto LoggingConfigurationHelper::CreateFileSinkConfiguration(const object &from, return result; } -auto LoggingConfigurationHelper::CreateSinkConfiguration(const object &from, - const std::string &name, - int default_level) const +auto ConvertLogLevel(const std::string &string_level) -> int { + static std::map<std::string, int> log_level_names = { + {"", CAOSDB_DEFAULT_LOG_LEVEL}, {"off", CAOSDB_LOG_LEVEL_OFF}, + {"fatal", CAOSDB_LOG_LEVEL_FATAL}, {"error", CAOSDB_LOG_LEVEL_ERROR}, + {"warn", CAOSDB_LOG_LEVEL_WARN}, {"info", CAOSDB_LOG_LEVEL_INFO}, + {"debug", CAOSDB_LOG_LEVEL_DEBUG}, {"trace", CAOSDB_LOG_LEVEL_TRACE}, + {"all", CAOSDB_LOG_LEVEL_ALL}}; + try { + return log_level_names.at(string_level); + } catch (const std::out_of_range &exc) { + throw ConfigurationError("Unknown log level: " + string_level); + } +} + +auto CreateSinkConfiguration(const object &from, const std::string &name, int default_level) -> std::shared_ptr<caosdb::logging::SinkConfiguration> { assert(from.contains("destination")); const auto &destination = std::string(from.at("destination").as_string().c_str()); @@ -299,22 +327,7 @@ auto LoggingConfigurationHelper::CreateSinkConfiguration(const object &from, } } -auto LoggingConfigurationHelper::ConvertLogLevel(const std::string &string_level) const -> int { - static std::map<std::string, int> log_level_names = { - {"", CAOSDB_DEFAULT_LOG_LEVEL}, {"off", CAOSDB_LOG_LEVEL_OFF}, - {"fatal", CAOSDB_LOG_LEVEL_FATAL}, {"error", CAOSDB_LOG_LEVEL_ERROR}, - {"warn", CAOSDB_LOG_LEVEL_WARN}, {"info", CAOSDB_LOG_LEVEL_INFO}, - {"debug", CAOSDB_LOG_LEVEL_DEBUG}, {"trace", CAOSDB_LOG_LEVEL_TRACE}, - {"all", CAOSDB_LOG_LEVEL_ALL}}; - try { - return log_level_names.at(string_level); - } catch (const std::out_of_range &exc) { - throw ConfigurationError("Unknown log level: " + string_level); - } -} - -auto LoggingConfigurationHelper::CreateLoggingConfiguration(const object &from) const - -> LoggingConfiguration { +auto CreateLoggingConfiguration(const object &from) -> LoggingConfiguration { auto default_level_str = from.contains("level") ? std::string(from.at("level").as_string().c_str()) : ""; int default_level = ConvertLogLevel(default_level_str); @@ -356,7 +369,7 @@ auto ConfigurationManager::mReset() noexcept -> int { auto ConfigurationManager::mClear() noexcept -> int { try { - json_configuration = value(nullptr); + json_configuration.Reset(); ConnectionManager::Reset(); return StatusCode::SUCCESS; } catch (const caosdb::exceptions::Exception &exc) { @@ -369,7 +382,7 @@ auto ConfigurationManager::mClear() noexcept -> int { } auto ConfigurationManager::mLoadSingleJSONConfiguration(const path &json_file) -> void { - if (!json_configuration.is_null()) { + if (json_configuration.wrapped) { throw ConfigurationError("This CaosDB client has already been configured."); } if (!exists(json_file)) { @@ -381,12 +394,17 @@ auto ConfigurationManager::mLoadSingleJSONConfiguration(const path &json_file) - auto ConfigurationManager::mGetConnectionConfiguration(const std::string &name) const -> std::unique_ptr<ConnectionConfiguration> { - auto connection_json = GetConnection(name); - return connection_configuration_helper.CreateConnectionConfiguration(connection_json); + GET_CONNECTIONS + if (connections.contains(name)) { + const auto &result_connection = connections.at(name); + assert(result_connection.is_object()); + return CreateConnectionConfiguration(result_connection.as_object()); + } + throw ConfigurationError("The connection '" + name + "' has not been defined."); } auto ConfigurationManager::mGetDefaultConnectionName() const -> std::string { - auto connections = GetConnections(); + GET_CONNECTIONS if (connections.contains("default")) { auto default_connection = connections.at("default"); if (default_connection.is_object()) { @@ -406,41 +424,6 @@ auto ConfigurationManager::mGetDefaultConnectionName() const -> std::string { throw ConfigurationError("Could not determine the default connection."); } -auto ConfigurationManager::GetConfiguration() const -> const object & { - if (json_configuration.is_null()) { - throw ConfigurationError("This CaosDB client has not been configured."); - } - assert(json_configuration.is_object()); - return json_configuration.as_object(); -} - -auto ConfigurationManager::GetConnections() const -> const object & { - const auto &configuration = GetConfiguration(); - if (!configuration.contains("connections")) { - throw ConfigurationError("This CaosDB client hasn't any configured connections."); - } - const auto &connections_value = configuration.at("connections"); - if (connections_value.is_null()) { - throw ConfigurationError("This CaosDB client hasn't any configured connections."); - } - assert(connections_value.is_object()); - const auto &connections_object = connections_value.as_object(); - if (connections_object.empty()) { - throw ConfigurationError("This CaosDB client hasn't any configured connections."); - } - return connections_object; -} - -auto ConfigurationManager::GetConnection(const std::string &name) const -> const object & { - const auto &connections = GetConnections(); - if (connections.contains(name)) { - const auto &result_connection = connections.at(name); - assert(result_connection.is_object()); - return result_connection.as_object(); - } - throw ConfigurationError("The connection '" + name + "' has not been defined."); -} - // TODO(tf) This has apparently a cognitive complexity of 34>25 (threshold). auto ConfigurationManager::InitializeDefaults() -> int { // NOLINT @@ -488,11 +471,10 @@ auto ConfigurationManager::InitializeDefaults() -> int { // NOLINT } // Logging in the configuration leads to additional content. - if (this->json_configuration.is_object() && - this->json_configuration.as_object().contains("logging")) { + if (this->json_configuration.wrapped && WRAPPED_JSON_CONFIGURATION(this)->is_object() && + WRAPPED_JSON_CONFIGURATION(this)->as_object().contains("logging")) { LoggingConfiguration logging_configuration = - logging_configuration_helper.CreateLoggingConfiguration( - json_configuration.at("logging").as_object()); + CreateLoggingConfiguration(WRAPPED_JSON_CONFIGURATION(this)->at("logging").as_object()); logging::initialize_logging(logging_configuration); } else { logging::initialize_logging_defaults(); @@ -500,7 +482,8 @@ auto ConfigurationManager::InitializeDefaults() -> int { // NOLINT "We are using the default configuration"; } - if (configuration_file_path != nullptr && this->json_configuration.is_object()) { + if (configuration_file_path != nullptr && this->json_configuration.wrapped && + WRAPPED_JSON_CONFIGURATION(this)->is_object()) { CAOSDB_LOG_INFO(logger_name) << "Loaded configuration from " << *(configuration_file_path) << "."; } diff --git a/src/caosdb/utility.cpp b/src/caosdb/utility.cpp index a4ed293adb6bd4fbfb10860e03185aeb567de163..ca7e1cbe9102763ed54763f7ca1dd271ed0b06db 100644 --- a/src/caosdb/utility.cpp +++ b/src/caosdb/utility.cpp @@ -19,18 +19,35 @@ * */ #include "caosdb/utility.h" -#include "caosdb/data_type.h" // for AtomicDataType, atomicdatatype_names -#include "caosdb/entity.h" // for Importance, Role, importance_names -#include <map> // for map, operator!=, _Rb_tree_const_iterator -#include <utility> // for pair +#include "caosdb/data_type.h" // for AtomicDataType, atomicdatatype_names +#include "caosdb/entity.h" // for Importance, Role, importance_names +#include <boost/beast/core/detail/base64.hpp> // for encoded_size +#include <boost/beast/core/detail/base64.ipp> // for encode +#include <boost/filesystem/string_file.hpp> // for load_string_file +#include <boost/json/stream_parser.hpp> // for stream_parser +#include <boost/json/value.hpp> // for value +#include <cassert> // for assert +#include <map> // for map, operator!=, _Rb_tree_const_iterator +#include <memory> // for allocator, unique_ptr +#include <stdexcept> // for logic_error +#include <type_traits> // for underlying_type_t +#include <typeinfo> // for type_info +#include <utility> // for pair namespace caosdb::utility { +using boost::json::stream_parser; +using boost::json::value; using caosdb::entity::AtomicDataType; using caosdb::entity::Importance; using caosdb::entity::Role; -// using emap = std::map<int, std::string>; // enum mapping +template <typename Enum> auto getEnumNameFromValue(Enum v) -> std::string { + if (std::is_same_v<std::underlying_type_t<Enum>, int>) { + return std::to_string(static_cast<int>(v)); + } + throw std::logic_error(std::string("Enum type ") + typeid(v).name() + " not implemented."); +} // Enum helper template specializations ////////////////////////////////////// template <> auto getEnumNameFromValue<Importance>(Importance v) -> std::string { @@ -87,4 +104,82 @@ template <> auto getEnumValueFromName<Role>(const std::string &name) -> Role { // End of template specialization ///////////////////////////////////////////// +auto load_string_file(const path &file_path) -> std::string { + std::string result; + boost::filesystem::load_string_file(file_path.string(), result); + return result; +} + +auto base64_encode(const std::string &plain) -> std::string { + auto size_plain = plain.size(); + auto size_encoded = boost::beast::detail::base64::encoded_size(size_plain); + + std::string result = std::string(size_encoded, '\0'); + boost::beast::detail::base64::encode(&result[0], plain.c_str(), size_plain); + + return result; +} + +auto _load_json_file(const path &json_file) -> value { + assert(exists(json_file)); + + constexpr auto buffer_size = std::size_t(4096); + auto stream = ifstream(json_file); + stream.exceptions(std::ios_base::badbit); + + stream_parser parser; + auto result = std::string(); + auto buffer = std::string(buffer_size, '\0'); + while (stream.read(&buffer[0], buffer_size)) { + parser.write(buffer.c_str(), stream.gcount()); + } + parser.write(buffer.c_str(), stream.gcount()); + + assert(parser.done()); + return parser.release(); +} + +auto load_json_file(const path &json_file) -> JsonValue { + return {new value(_load_json_file(json_file))}; +} + +JsonValue::JsonValue(void *wrapped) { this->wrapped.reset(static_cast<value *>(wrapped)); } + +JsonValue::~JsonValue() = default; + +auto JsonValue::Reset() -> void { + if (this->wrapped) { + this->wrapped.reset(); + } +} + +JsonValue::JsonValue(const JsonValue &other) { + if (other.wrapped) { + this->wrapped.reset(static_cast<value *>(other.wrapped.get())); + } +} + +auto JsonValue::operator=(const JsonValue &other) -> JsonValue & { + if (this != &other) { + if (other.wrapped) { + this->wrapped = std::make_shared<value>(*(static_cast<value *>(other.wrapped.get()))); + } + } + return *this; +} + +JsonValue::JsonValue(JsonValue &&other) noexcept { + if (other.wrapped) { + this->wrapped = std::move(other.wrapped); + } +} + +auto JsonValue::operator=(JsonValue &&other) noexcept -> JsonValue & { + if (this != &other) { + Reset(); + this->wrapped = std::move(other.wrapped); + } + return *this; +} + } // namespace caosdb::utility diff --git a/test/test_utility.cpp b/test/test_utility.cpp index 3a9a420cee742c1001c727a6154452ee10e8d8fb..74db29afa798ccdbf9d4be45497e1a44762d630d 100644 --- a/test/test_utility.cpp +++ b/test/test_utility.cpp @@ -20,10 +20,7 @@ * */ -#include "gmock/gmock-matchers.h" // for ElementsAre, EXPECT_THAT #include "boost/beast/core/detail/base64.hpp" // for encoded_size -#include "boost/json/object.hpp" // for object -#include "boost/json/value.hpp" // for value #include "caosdb/data_type.h" // for atomicdatatype_names #include "caosdb/entity.h" // for importance_names, role... #include "caosdb/status_code.h" // for get_status_description @@ -38,7 +35,6 @@ #include <utility> // for pair namespace caosdb::utility { -using ::testing::ElementsAre; TEST(test_utility, base64_encode) { auto test_plain = std::string("admin:caosdb"); @@ -48,17 +44,6 @@ TEST(test_utility, base64_encode) { EXPECT_EQ(test_encoded, base64_encode(test_plain)); } -TEST(test_utility, test_load_json_file) { - auto json = load_json_file(TEST_DATA_DIR + "/test.json").as_object(); - - EXPECT_EQ(json["it"], "tests"); - EXPECT_EQ(json["null values"], nullptr); - EXPECT_THAT(json["this"].as_array(), ElementsAre("is", "a", "test")); - EXPECT_THAT(json["numbers"].as_array(), ElementsAre(1, 2, 3.3)); - auto sub = json["arrays and objects"].as_object(); - EXPECT_THAT(sub["see?"].as_array(), ElementsAre(true, false)); -} - TEST(test_utility, enum_names) { // All working enums for (const auto &entry : caosdb::entity::importance_names) {