From bac8def659e44581f509c56bec4eebbc94d0b938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars-Christian=20F=C3=BCrstenberg?= Date: Tue, 21 Mar 2023 10:27:15 +0100 Subject: [PATCH] Feature add changelog.md to the package (#19) closes #16 * Update README.md * Feature encoding matrix values (#13) closes #9 * Switched matrix encoding from row-major to column-major * Fixed matrix encoding bug. Added more tests. * Added utf-8 bom to json model files (#14) closes #11 * Added utf-8 bom to json model files * Updated changelog * Feature fallback strategy for newer rexs versions (#15) closes #8 * Refactoring * Load latest model version if requested is unknown * Fixed model registry problem * Feature update thirdparty components (#18) closes #7 * Updated all thirdparty components * Update README.md * Added changelog to package --- CHANGELOG.md | 12 +++++ CMakeLists.txt | 8 ++- README.md | 18 ++++--- cmake/fetch_cli11.cmake | 2 +- cmake/fetch_doctest.cmake | 2 +- cmake/fetch_fmt.cmake | 2 +- cmake/fetch_json.cmake | 2 +- cmake/fetch_miniz.cmake | 5 +- cmake/fetch_pugixml.cmake | 2 +- cmake/fetch_valijson.cmake | 2 +- include/rexsapi/CodedValue.hxx | 41 +++++++++------ include/rexsapi/JsonModelLoader.hxx | 20 ++++--- include/rexsapi/JsonSerializer.hxx | 4 +- include/rexsapi/JsonValueDecoder.hxx | 12 ++--- include/rexsapi/XMLModelLoader.hxx | 26 +++++---- include/rexsapi/XMLValueDecoder.hxx | 19 +++---- include/rexsapi/database/ModelRegistry.hxx | 38 +++++++++++--- include/rexsapi/database/XMLModelLoader.hxx | 11 ++-- test/CodedValuesTest.cxx | 58 ++++++++++++++++++--- test/JsonModelLoaderTest.cxx | 39 +++++++++----- test/JsonSchemaValidatorTest.cxx | 2 +- test/TestModelHelper.hxx | 10 ++++ test/XMLModelLoaderTest.cxx | 42 ++++++++++----- test/database/ModelRegistryTest.cxx | 24 +++++++-- test/main.cpp | 4 +- 25 files changed, 288 insertions(+), 117 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a5d855..ee0427d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.0] + +### Changed + +- Switched matrix encoding from row-major to column-major order (#9) +- JSON model files will now be written with a UTF-8 BOM (#11) +- The model registry can be configured to load the latest available model version, if the requested version is not + known. If no appropriate language can be found, english will be chosen. (#8) +- In relaxed mode latest available model version loading will be active (#8) +- Updated all thirdparty components (#7) +- Add changelog to the package (#16) + ## [1.0.0] - 2022-11-09 ### Added diff --git a/CMakeLists.txt b/CMakeLists.txt index ba34f85..7f4ebe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(rexsapi VERSION 1.0.0 LANGUAGES CXX) +project(rexsapi VERSION 1.1.0 LANGUAGES CXX) include(FetchContent) @@ -76,3 +76,9 @@ if(BUILD_WITH_TOOLS) add_subdirectory(tools) endif( ) + +install( + FILES + ${CMAKE_SOURCE_DIR}/CHANGELOG.md + DESTINATION . +) diff --git a/README.md b/README.md index b33b59b..2b8f41e 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,10 @@ Relations ... ``` +# Changelog + +See CHANGELOG.md for detailed changelog information. + # Integration The library is header only and can be easily integrated into existing projects. Using CMake is the recommended way to use the library. However, the library also comes as a zip package which can be used without CMake. You have to set the C++ standard of your project to C++17 in order to build with the library. @@ -277,13 +281,13 @@ The library is header only. A build is only necessary if you want to run the tes REXSapi uses the following thirdparty open source software -- [cli11 2.2.0](https://github.com/CLIUtils/CLI11) -- [fmt 8.1.1](https://github.com/fmtlib/fmt) -- [nlohmann json 3.10.5](https://github.com/nlohmann/json) -- [miniz 2.2.0](https://github.com/richgel999/miniz) -- [pugixml 1.12.1](https://github.com/zeux/pugixml) -- [valijson 0.6](https://github.com/tristanpenman/valijson) -- [doctest 2.4.8](https://github.com/doctest/doctest) +- [cli11 2.3.2](https://github.com/CLIUtils/CLI11) +- [fmt 9.1.0](https://github.com/fmtlib/fmt) +- [nlohmann json 3.11.2](https://github.com/nlohmann/json) +- [miniz 3.0.2](https://github.com/richgel999/miniz) +- [pugixml 1.13](https://github.com/zeux/pugixml) +- [valijson 1.0](https://github.com/tristanpenman/valijson) +- [doctest 2.4.10](https://github.com/doctest/doctest) # License REXsapi is licensed under the Apache-2.0 license. diff --git a/cmake/fetch_cli11.cmake b/cmake/fetch_cli11.cmake index b1088a9..31257a1 100644 --- a/cmake/fetch_cli11.cmake +++ b/cmake/fetch_cli11.cmake @@ -1,6 +1,6 @@ FetchContent_Declare( cli11 - URL https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.2.0.tar.gz + URL https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.3.2.tar.gz ) FetchContent_MakeAvailable(cli11) diff --git a/cmake/fetch_doctest.cmake b/cmake/fetch_doctest.cmake index dcf1f55..6292bed 100644 --- a/cmake/fetch_doctest.cmake +++ b/cmake/fetch_doctest.cmake @@ -1,7 +1,7 @@ FetchContent_Declare( doctest GIT_REPOSITORY https://github.com/doctest/doctest - GIT_TAG v2.4.9 + GIT_TAG v2.4.10 ) FetchContent_GetProperties(doctest) diff --git a/cmake/fetch_fmt.cmake b/cmake/fetch_fmt.cmake index 0500cdf..c79c74a 100644 --- a/cmake/fetch_fmt.cmake +++ b/cmake/fetch_fmt.cmake @@ -1,6 +1,6 @@ FetchContent_Declare( fmt - URL https://github.com/fmtlib/fmt/archive/refs/tags/8.1.1.tar.gz + URL https://github.com/fmtlib/fmt/archive/refs/tags/9.1.0.tar.gz ) FetchContent_GetProperties(fmt) diff --git a/cmake/fetch_json.cmake b/cmake/fetch_json.cmake index 6b2976a..bc9c620 100644 --- a/cmake/fetch_json.cmake +++ b/cmake/fetch_json.cmake @@ -1,6 +1,6 @@ FetchContent_Declare( json - URL https://github.com/nlohmann/json/archive/refs/tags/v3.10.5.tar.gz + URL https://github.com/nlohmann/json/archive/refs/tags/v3.11.2.tar.gz ) FetchContent_GetProperties(json) diff --git a/cmake/fetch_miniz.cmake b/cmake/fetch_miniz.cmake index 065e92d..388c108 100644 --- a/cmake/fetch_miniz.cmake +++ b/cmake/fetch_miniz.cmake @@ -1,6 +1,6 @@ FetchContent_Declare( miniz - URL https://github.com/richgel999/miniz/archive/refs/tags/2.2.0.tar.gz + URL https://github.com/richgel999/miniz/archive/refs/tags/3.0.2.tar.gz ) FetchContent_GetProperties(miniz) @@ -32,6 +32,9 @@ if(NOT miniz_POPULATED) string(REPLACE "#include \"${REPLACE_STRING}.h\"" "" AMAL_MINIZ_C "${AMAL_MINIZ_C}") endforeach() + string(REPLACE "static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };" "" AMAL_MINIZ_C "${AMAL_MINIZ_C}") + string(REPLACE "static const mz_uint s_tdefl_num_probes[11];" "static inline const mz_uint s_tdefl_num_probes[11] = {0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500};" AMAL_MINIZ_C "${AMAL_MINIZ_C}") + string(CONCAT AMAL_MINIZ_H "#define MINIZ_EXPORT\n" "${AMAL_MINIZ_H}") string(CONCAT AMAL_MINIZ_H "${AMAL_MINIZ_H}" "\n#ifndef MINIZ_HEADER_FILE_ONLY\n" diff --git a/cmake/fetch_pugixml.cmake b/cmake/fetch_pugixml.cmake index bac037d..38a2783 100644 --- a/cmake/fetch_pugixml.cmake +++ b/cmake/fetch_pugixml.cmake @@ -1,7 +1,7 @@ FetchContent_Declare( pugixml GIT_REPOSITORY https://github.com/zeux/pugixml - GIT_TAG v1.12.1 + GIT_TAG v1.13 ) if(NOT pugixml_POPULATED) diff --git a/cmake/fetch_valijson.cmake b/cmake/fetch_valijson.cmake index 08dd029..c65460a 100644 --- a/cmake/fetch_valijson.cmake +++ b/cmake/fetch_valijson.cmake @@ -1,6 +1,6 @@ FetchContent_Declare( valijson - URL https://github.com/tristanpenman/valijson/archive/refs/tags/v0.6.tar.gz + URL https://github.com/tristanpenman/valijson/archive/refs/tags/v1.0.tar.gz ) FetchContent_GetProperties(valijson) diff --git a/include/rexsapi/CodedValue.hxx b/include/rexsapi/CodedValue.hxx index 172be9b..d644d25 100644 --- a/include/rexsapi/CodedValue.hxx +++ b/include/rexsapi/CodedValue.hxx @@ -33,7 +33,7 @@ namespace rexsapi::detail static std::string toCodedValueString(TCodedValueType type); - template::value || std::is_floating_point::value>* = nullptr> + template || std::is_floating_point_v>* = nullptr> class TCodedValueArray { public: @@ -59,18 +59,19 @@ namespace rexsapi::detail } }; - template::value || std::is_floating_point::value>* = nullptr> + template || std::is_floating_point_v>* = nullptr> class TCodedValueMatrix { public: static std::string encode(const TMatrix& matrix) { - const auto count = matrix.m_Values.size() * matrix.m_Values.size() * sizeof(T); + const auto count = matrix.m_Values.size() * matrix.m_Values[0].size(); std::vector array; array.reserve(count); - for (const auto& row : matrix.m_Values) { - for (const auto& column : row) { - array.emplace_back(column); + + for (size_t c = 0; c < matrix.m_Values[0].size(); ++c) { + for (size_t r = 0; r < matrix.m_Values.size(); ++r) { + array.emplace_back(matrix.m_Values[r][c]); } } const auto* data = reinterpret_cast(array.data()); @@ -78,19 +79,25 @@ namespace rexsapi::detail return base64Encode(data, len); } - static TMatrix decode(std::string_view value) + static TMatrix decode(std::string_view value, size_t columns, size_t rows) { TMatrix matrix; const auto data = base64Decode(value); const auto count = data.size() / sizeof(T); - const auto elementCount = static_cast(::sqrt(static_cast(count))); const auto values = reinterpret_cast(data.data()); - matrix.m_Values.reserve(elementCount); - for (size_t row = 0; row < elementCount; ++row) { + if (count != columns * rows) { + throw TException{ + fmt::format("matrix does not have the correct element count: {} rows: {} columns: {}", count, rows, columns)}; + } + if (count == 0) { + throw TException{"matrix does not have any elements"}; + } + matrix.m_Values.reserve(rows); + for (size_t row = 0; row < rows; ++row) { std::vector col; - col.reserve(elementCount); - for (size_t column = 0; column < elementCount; ++column) { - col.emplace_back(values[(row * elementCount) + column]); + col.reserve(columns); + for (size_t column = 0; column < columns; ++column) { + col.emplace_back(values[row + (rows * column)]); } matrix.m_Values.emplace_back(std::move(col)); } @@ -141,7 +148,7 @@ namespace rexsapi::detail return std::make_pair(TCodedValueMatrix::encode(matrix), TCodedValueType::Float64); } if (type == TCodeType::Optimized) { - TMatrix tmp{matrix}; + const TMatrix tmp{matrix}; return std::make_pair(TCodedValueMatrix::encode(tmp), TCodedValueType::Float32); } throw TException{"should never reach this"}; @@ -150,7 +157,7 @@ namespace rexsapi::detail template<> inline std::pair encodeMatrix(const TMatrix& matrix, TCodeType) { - TMatrix tmp{matrix}; + const TMatrix tmp{matrix}; return std::make_pair(TCodedValueMatrix::encode(tmp), TCodedValueType::Int32); } @@ -222,12 +229,12 @@ namespace rexsapi::detail template struct TCodedValueMatrixDecoder { - static TValue decode(std::string_view value) + static TValue decode(std::string_view value, size_t columns, size_t rows) { if (!std::is_same_v::Type>) { throw TException{"coded value type does not correspond to attribute value type"}; } - auto result = TCodedValueMatrix::Type>::decode(value); + auto result = TCodedValueMatrix::Type>::decode(value, columns, rows); TValue val{TMatrix{result}}; val.coded(getCodedType(TCodedValueType{T2::value})); return val; diff --git a/include/rexsapi/JsonModelLoader.hxx b/include/rexsapi/JsonModelLoader.hxx index 2636b4e..e643497 100644 --- a/include/rexsapi/JsonModelLoader.hxx +++ b/include/rexsapi/JsonModelLoader.hxx @@ -105,8 +105,7 @@ namespace rexsapi { try { const json j = json::parse(buffer); - std::vector errors; - if (!m_Validator.validate(j, errors)) { + if (std::vector errors; !m_Validator.validate(j, errors)) { for (const auto& error : errors) { result.addError(TError{TErrorLevel::CRIT, error}); } @@ -118,12 +117,19 @@ namespace rexsapi language = j["/model/applicationLanguage"_json_pointer].get(); } - TModelInfo info{j["/model/applicationId"_json_pointer].get(), - j["/model/applicationVersion"_json_pointer].get(), - j["/model/date"_json_pointer].get(), - TRexsVersion{j["/model/version"_json_pointer].get()}, language}; + const TModelInfo info{j["/model/applicationId"_json_pointer].get(), + j["/model/applicationVersion"_json_pointer].get(), + j["/model/date"_json_pointer].get(), + TRexsVersion{j["/model/version"_json_pointer].get()}, language}; - const auto& dbModel = registry.getModel(info.getVersion(), language.has_value() ? *language : "en"); + const auto& dbModel = + registry.getModel(info.getVersion(), language.value_or("en"), m_Mode.getMode() == TMode::STRICT_MODE); + + if (dbModel.getVersion() != info.getVersion()) { + result.addError( + TError{TErrorLevel::WARN, fmt::format("exact database model for version not available, using {}", + dbModel.getVersion().asString())}); + } detail::ComponentMapping componentMapping; TComponents components = getComponents(result, componentMapping, dbModel, j); diff --git a/include/rexsapi/JsonSerializer.hxx b/include/rexsapi/JsonSerializer.hxx index e7cb1be..c65c01d 100644 --- a/include/rexsapi/JsonSerializer.hxx +++ b/include/rexsapi/JsonSerializer.hxx @@ -81,9 +81,11 @@ namespace rexsapi * @param doc The json obejct containing the REXS json model * @throws TException if the file cannot be written */ - void serialize(const ordered_json& doc) + void serialize(const ordered_json& doc) const { + constexpr static uint8_t bom[] = {0xEF, 0xBB, 0xBF}; std::ofstream stream{m_File}; + stream.write(reinterpret_cast(bom), sizeof(bom)); stream << doc.dump(m_Indent); stream.flush(); if (!stream) { diff --git a/include/rexsapi/JsonValueDecoder.hxx b/include/rexsapi/JsonValueDecoder.hxx index 5121b30..eb97ee9 100644 --- a/include/rexsapi/JsonValueDecoder.hxx +++ b/include/rexsapi/JsonValueDecoder.hxx @@ -402,23 +402,23 @@ namespace rexsapi::detail case detail::TCodedValueType::None: throw TException{"unknown code"}; case detail::TCodedValueType::Int32: { - value = - detail::TCodedValueMatrixDecoder>::decode(val); + value = detail::TCodedValueMatrixDecoder< + Type, Enum2type>::decode(val, columns, rows); break; } case detail::TCodedValueType::Float32: { value = detail::TCodedValueMatrixDecoder< - Type, Enum2type>::decode(val); + Type, Enum2type>::decode(val, columns, rows); break; } case detail::TCodedValueType::Float64: { value = detail::TCodedValueMatrixDecoder< - Type, Enum2type>::decode(val); + Type, Enum2type>::decode(val, columns, rows); break; } } - if (value.getValue>().m_Values.size() != rows) { + if (value.getValue>().m_Values.size() != rows || + value.getValue>().m_Values[0].size() != columns) { throw TException{"decoded matrix size does not correspond to configured size"}; } return std::make_pair(std::move(value), TDecoderResult::SUCCESS); diff --git a/include/rexsapi/XMLModelLoader.hxx b/include/rexsapi/XMLModelLoader.hxx index 2b44b9c..b670b53 100644 --- a/include/rexsapi/XMLModelLoader.hxx +++ b/include/rexsapi/XMLModelLoader.hxx @@ -96,22 +96,28 @@ namespace rexsapi } const auto rexsModel = *doc.select_nodes("/model").begin(); - auto language = detail::getStringAttribute(rexsModel, "applicationLanguage", ""); - TModelInfo info{detail::getStringAttribute(rexsModel, "applicationId"), - detail::getStringAttribute(rexsModel, "applicationVersion"), - detail::getStringAttribute(rexsModel, "date"), - TRexsVersion{detail::getStringAttribute(rexsModel, "version")}, - language.empty() ? std::optional{} : language}; - - const auto& dbModel = registry.getModel(info.getVersion(), language.empty() ? "en" : language); - detail::ComponentMapping componentsMapping; + const auto language = detail::getStringAttribute(rexsModel, "applicationLanguage", ""); + const TModelInfo info{detail::getStringAttribute(rexsModel, "applicationId"), + detail::getStringAttribute(rexsModel, "applicationVersion"), + detail::getStringAttribute(rexsModel, "date"), + TRexsVersion{detail::getStringAttribute(rexsModel, "version")}, + language.empty() ? std::optional{} : language}; + + const auto& dbModel = + registry.getModel(info.getVersion(), language.empty() ? "en" : language, m_Mode.getMode() == TMode::STRICT_MODE); + + if (dbModel.getVersion() != info.getVersion()) { + result.addError(TError{TErrorLevel::WARN, fmt::format("exact database model for version not available, using {}", + dbModel.getVersion().asString())}); + } + detail::ComponentMapping componentsMapping; TComponents components; components.reserve(10); std::set usedComponents; for (const auto& component : doc.select_nodes("/model/components/component")) { - auto componentId = detail::getStringAttribute(component, "id"); + const auto componentId = detail::getStringAttribute(component, "id"); std::string componentName = detail::getStringAttribute(component, "name", ""); try { const auto& componentType = dbModel.findComponentById(detail::getStringAttribute(component, "type")); diff --git a/include/rexsapi/XMLValueDecoder.hxx b/include/rexsapi/XMLValueDecoder.hxx index f2829e2..1c0e28b 100644 --- a/include/rexsapi/XMLValueDecoder.hxx +++ b/include/rexsapi/XMLValueDecoder.hxx @@ -264,38 +264,39 @@ namespace rexsapi::detail std::pair onDecode(const std::optional& enumValue, const pugi::xml_node& node) const override { + size_t rows = 0; + size_t columns = 0; const auto child = node.first_child(); TValue value; const auto codedType = rexsapi::detail::codedValueFromString(detail::getStringAttribute(child, "code")); + if (codedType != detail::TCodedValueType::None) { + rows = convertToUint64(detail::getStringAttribute(child, "rows")); + columns = convertToUint64(detail::getStringAttribute(child, "columns")); + } switch (codedType) { case detail::TCodedValueType::None: return TMatrixDecoder::onDecode(enumValue, node); case detail::TCodedValueType::Int32: { value = detail::TCodedValueMatrixDecoder< typename TMatrixDecoder::type, - detail::Enum2type>::decode(child.child_value()); + detail::Enum2type>::decode(child.child_value(), columns, rows); break; } case detail::TCodedValueType::Float32: { value = detail::TCodedValueMatrixDecoder< typename TMatrixDecoder::type, - detail::Enum2type>::decode(child.child_value()); + detail::Enum2type>::decode(child.child_value(), columns, rows); break; } case detail::TCodedValueType::Float64: { value = detail::TCodedValueMatrixDecoder< typename TMatrixDecoder::type, - detail::Enum2type>::decode(child.child_value()); + detail::Enum2type>::decode(child.child_value(), columns, rows); break; } } if (codedType != detail::TCodedValueType::None) { - const auto rows = convertToUint64(detail::getStringAttribute(child, "rows")); - const auto columns = convertToUint64(detail::getStringAttribute(child, "columns")); - if (rows != columns) { - throw TException{"matrix rows != columns"}; - } - if (value.getValue::type>>().m_Values.size() != rows) { + if (value.getValue::type>>().m_Values.size() != rows || value.getValue::type>>().m_Values[0].size() != columns) { throw TException{"decoded matrix size does not correspond to configured size"}; } } diff --git a/include/rexsapi/database/ModelRegistry.hxx b/include/rexsapi/database/ModelRegistry.hxx index 4c9cf98..08e407f 100644 --- a/include/rexsapi/database/ModelRegistry.hxx +++ b/include/rexsapi/database/ModelRegistry.hxx @@ -19,6 +19,7 @@ #include + /** @file */ namespace rexsapi::database @@ -45,10 +46,13 @@ namespace rexsapi::database * * @param version The database model version to retrieve * @param language The language of the database model to retrieve + * @param strict Determines if the latest available model version shall be returned if an exact one was not found. + * If set to true, will throw an exception if the exact version could not be found. * @return const TModel& to the found database model - * @throws TException if the specific version or language is not available + * @throws TException if the specific version or language is not available and strict mode was set to true */ - [[nodiscard]] const TModel& getModel(const TRexsVersion& version, const std::string& language) const; + [[nodiscard]] const TModel& getModel(const TRexsVersion& version, const std::string& language, + bool strict = true) const; /** * @brief Creates a model registry @@ -78,18 +82,38 @@ namespace rexsapi::database // Implementation ///////////////////////////////////////////////////////////////////////////// - inline const TModel& TModelRegistry::getModel(const TRexsVersion& version, const std::string& language) const + inline const TModel& TModelRegistry::getModel(const TRexsVersion& version, const std::string& language, + bool strict) const { const auto it = std::find_if(m_Models.begin(), m_Models.end(), [&version, &language](const auto& model) { return model.getVersion() == version && model.getLanguage() == language; }); - if (it == m_Models.end()) { - throw TException{ - fmt::format("cannot find a database model for version '{}' and locale '{}'", version.asString(), language)}; + if (it != m_Models.end()) { + return *it; + } + + if (!strict) { + // no exact model was found, find the latest model for the given language + // otherwise choose english + const TRexsVersion baseVersion{1, 0}; + const TModel* baseModel{nullptr}; + std::for_each(m_Models.begin(), m_Models.end(), [&baseModel, &baseVersion, &language](const auto& model) mutable { + if (model.getLanguage() == language && model.getVersion() >= (baseModel ? baseModel->getVersion() : baseVersion)) { + baseModel = &model; + } + if (model.getLanguage() == "en" && model.getVersion() > (baseModel ? baseModel->getVersion() : baseVersion)) { + baseModel = &model; + } + }); + + if (baseModel) { + return *baseModel; + } } - return *it; + throw TException{ + fmt::format("cannot find a database model for version '{}' and locale '{}'", version.asString(), language)}; } template diff --git a/include/rexsapi/database/XMLModelLoader.hxx b/include/rexsapi/database/XMLModelLoader.hxx index bfd6383..bc17ab3 100644 --- a/include/rexsapi/database/XMLModelLoader.hxx +++ b/include/rexsapi/database/XMLModelLoader.hxx @@ -85,7 +85,8 @@ namespace rexsapi::database TXmlModelLoader::load(const std::function& callback) const { return m_ResourceLoader.load([this, &callback](TResult& result, std::vector& buffer) { - pugi::xml_document doc = rexsapi::detail::loadXMLDocument(result, buffer, TXSDSchemaValidator{m_SchemaLoader}); + const pugi::xml_document doc = + rexsapi::detail::loadXMLDocument(result, buffer, TXSDSchemaValidator{m_SchemaLoader}); if (!result) { return; } @@ -163,12 +164,12 @@ namespace rexsapi::database TIntervalEndpoint min; TIntervalEndpoint max; - if (auto att = node.node().attribute("rangeMin"); !att.empty()) { - auto open = rexsapi::detail::getBoolAttribute(node, "rangeMinIntervalOpen", true); + if (const auto att = node.node().attribute("rangeMin"); !att.empty()) { + const auto open = rexsapi::detail::getBoolAttribute(node, "rangeMinIntervalOpen", true); min = TIntervalEndpoint{convertToDouble(att.value()), open ? TIntervalType::OPEN : TIntervalType::CLOSED}; } - if (auto att = node.node().attribute("rangeMax"); !att.empty()) { - auto open = rexsapi::detail::getBoolAttribute(node, "rangeMaxIntervalOpen", true); + if (const auto att = node.node().attribute("rangeMax"); !att.empty()) { + const auto open = rexsapi::detail::getBoolAttribute(node, "rangeMaxIntervalOpen", true); max = TIntervalEndpoint{convertToDouble(att.value()), open ? TIntervalType::OPEN : TIntervalType::CLOSED}; } diff --git a/test/CodedValuesTest.cxx b/test/CodedValuesTest.cxx index 281c40c..be92c50 100644 --- a/test/CodedValuesTest.cxx +++ b/test/CodedValuesTest.cxx @@ -50,7 +50,7 @@ TEST_CASE("Coded values test") { rexsapi::TMatrix matrix{{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}}}; const auto encoded = rexsapi::detail::TCodedValueMatrix::encode(matrix); - const auto decoded = rexsapi::detail::TCodedValueMatrix::decode(encoded); + const auto decoded = rexsapi::detail::TCodedValueMatrix::decode(encoded, 3, 3); REQUIRE(decoded.m_Values.size() == 3); REQUIRE(decoded.m_Values[0].size() == 3); CHECK(decoded.m_Values[0][0] == doctest::Approx{1.0}); @@ -65,7 +65,26 @@ TEST_CASE("Coded values test") CHECK(decoded.m_Values[2][1] == doctest::Approx{8.0}); CHECK(decoded.m_Values[2][2] == doctest::Approx{9.0}); CHECK(encoded == - "AAAAAAAA8D8AAAAAAAAAQAAAAAAAAAhAAAAAAAAAEEAAAAAAAAAUQAAAAAAAABhAAAAAAAAAHEAAAAAAAAAgQAAAAAAAACJA"); + "AAAAAAAA8D8AAAAAAAAQQAAAAAAAABxAAAAAAAAAAEAAAAAAAAAUQAAAAAAAACBAAAAAAAAACEAAAAAAAAAYQAAAAAAAACJA"); + } + + SUBCASE("int64 matrix") + { + rexsapi::TMatrix matrix{{{1, 2, 3, 4}, {5, 6, 7, 8}}}; + const auto encoded = rexsapi::detail::TCodedValueMatrix::encode(matrix); + const auto decoded = rexsapi::detail::TCodedValueMatrix::decode(encoded, 4, 2); + REQUIRE(decoded.m_Values.size() == 2); + REQUIRE(decoded.m_Values[0].size() == 4); + CHECK(decoded.m_Values[0][0] == 1); + CHECK(decoded.m_Values[0][1] == 2); + CHECK(decoded.m_Values[0][2] == 3); + CHECK(decoded.m_Values[0][3] == 4); + REQUIRE(decoded.m_Values[1].size() == 4); + CHECK(decoded.m_Values[1][0] == 5); + CHECK(decoded.m_Values[1][1] == 6); + CHECK(decoded.m_Values[1][2] == 7); + CHECK(decoded.m_Values[1][3] == 8); + CHECK(encoded == "AQAAAAAAAAAFAAAAAAAAAAIAAAAAAAAABgAAAAAAAAADAAAAAAAAAAcAAAAAAAAABAAAAAAAAAAIAAAAAAAAAA=="); } SUBCASE("Encode int64 array") @@ -99,10 +118,32 @@ TEST_CASE("Coded values test") SUBCASE("Encode double matrix") { - const auto [value, type] = rexsapi::detail::encodeMatrix( - rexsapi::TMatrix{{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}}}, rexsapi::TCodeType::Default); - CHECK(type == rexsapi::detail::TCodedValueType::Float64); - CHECK(value == "AAAAAAAA8D8AAAAAAAAAQAAAAAAAAAhAAAAAAAAAEEAAAAAAAAAUQAAAAAAAABhAAAAAAAAAHEAAAAAAAAAgQAAAAAAAACJA"); + { + const auto [value, type] = rexsapi::detail::encodeMatrix( + rexsapi::TMatrix{{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}}}, rexsapi::TCodeType::Default); + CHECK(type == rexsapi::detail::TCodedValueType::Float64); + CHECK(value == + "AAAAAAAA8D8AAAAAAAAQQAAAAAAAABxAAAAAAAAAAEAAAAAAAAAUQAAAAAAAACBAAAAAAAAACEAAAAAAAAAYQAAAAAAAACJA"); + } + + { + const auto [value, type] = rexsapi::detail::encodeMatrix( + rexsapi::TMatrix{{{1.0, 5.0, 54.125738867291}, {4.0, 3.0, 0.0}, {2.0, 6.0, -259.10672159143496}}}, + rexsapi::TCodeType::Default); + CHECK(type == rexsapi::detail::TCodedValueType::Float64); + CHECK(value == + "AAAAAAAA8D8AAAAAAAAQQAAAAAAAAABAAAAAAAAAFEAAAAAAAAAIQAAAAAAAABhA62wRNhgQS0AAAAAAAAAAANgPsyG1MXDA"); + } + } + + SUBCASE("Encode integer matrix") + { + { + const auto [value, type] = rexsapi::detail::encodeMatrix( + rexsapi::TMatrix{{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}}, rexsapi::TCodeType::Default); + CHECK(type == rexsapi::detail::TCodedValueType::Int32); + CHECK(value == "AQAAAAQAAAAHAAAAAgAAAAUAAAAIAAAAAwAAAAYAAAAJAAAA"); + } } SUBCASE("Encode double matrix optimized") @@ -110,13 +151,14 @@ TEST_CASE("Coded values test") const auto [value, type] = rexsapi::detail::encodeMatrix( rexsapi::TMatrix{{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}}}, rexsapi::TCodeType::Optimized); CHECK(type == rexsapi::detail::TCodedValueType::Float32); - CHECK(value == "AACAPwAAAEAAAEBAAACAQAAAoEAAAMBAAADgQAAAAEEAABBB"); + CHECK(value == "AACAPwAAgEAAAOBAAAAAQAAAoEAAAABBAABAQAAAwEAAABBB"); } SUBCASE("Encode matrix failure") { CHECK_THROWS(rexsapi::detail::encodeMatrix( rexsapi::TMatrix{{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}}}, rexsapi::TCodeType::None)); + CHECK_THROWS(rexsapi::detail::encodeMatrix(rexsapi::TMatrix{{}}, rexsapi::TCodeType::None)); } } @@ -153,7 +195,7 @@ TEST_CASE("Coded value decoder test") { auto val = rexsapi::detail::TCodedValueMatrixDecoder< double, rexsapi::detail::Enum2type>:: - decode("AAAAAAAA8D8AAAAAAAAAQAAAAAAAAAhAAAAAAAAAEEAAAAAAAAAUQAAAAAAAABhAAAAAAAAAHEAAAAAAAAAgQAAAAAAAACJA"); + decode("AAAAAAAA8D8AAAAAAAAQQAAAAAAAABxAAAAAAAAAAEAAAAAAAAAUQAAAAAAAACBAAAAAAAAACEAAAAAAAAAYQAAAAAAAACJA", 3, 3); CHECK(val.coded() == rexsapi::TCodeType::Default); CHECK_NOTHROW(val.getValue()); } diff --git a/test/JsonModelLoaderTest.cxx b/test/JsonModelLoaderTest.cxx index 9686fcc..9b24f54 100644 --- a/test/JsonModelLoaderTest.cxx +++ b/test/JsonModelLoaderTest.cxx @@ -35,18 +35,8 @@ namespace path}; return loader.load(mode, result, registry); } -} - -TEST_CASE("Json model loader test") -{ - rexsapi::TFileJsonSchemaLoader schemaLoader{projectDir() / "models" / "rexs-schema.json"}; - rexsapi::TJsonSchemaValidator validator{schemaLoader}; - rexsapi::TResult result; - const auto registry = createModelRegistry(); - SUBCASE("Load valid document from buffer") - { - std::string buffer = R"({ + const std::string MemModel = R"({ "model":{ "applicationId":"Bearinx", "applicationVersion":"12.0.8823", @@ -211,9 +201,19 @@ TEST_CASE("Json model loader test") } } })"; +} + +TEST_CASE("Json model loader test") +{ + rexsapi::TFileJsonSchemaLoader schemaLoader{projectDir() / "models" / "rexs-schema.json"}; + rexsapi::TJsonSchemaValidator validator{schemaLoader}; + rexsapi::TResult result; + const auto registry = createModelRegistry(); + SUBCASE("Load valid document from buffer") + { rexsapi::detail::TBufferModelLoader loader{validator, - buffer}; + MemModel}; auto model = loader.load(rexsapi::TMode::RELAXED_MODE, result, registry); CHECK_FALSE(result); REQUIRE(result.getErrors().size() == 4); @@ -503,4 +503,19 @@ TEST_CASE("Json model loader test") CHECK(result.isCritical()); CHECK_FALSE(model); } + + SUBCASE("Load valid document with unkown version") + { + std::string buffer{MemModel}; + replace(buffer, R"("version":"1.4")", R"("version":"1.99")"); + rexsapi::detail::TBufferModelLoader loader{validator, + buffer}; + auto model = loader.load(rexsapi::TMode::RELAXED_MODE, result, registry); + CHECK_FALSE(result); + CHECK_FALSE(result.isCritical()); + REQUIRE(model); + result.reset(); + + CHECK_THROWS((void)loader.load(rexsapi::TMode::STRICT_MODE, result, registry)); + } } diff --git a/test/JsonSchemaValidatorTest.cxx b/test/JsonSchemaValidatorTest.cxx index d0bf0b5..649ce56 100644 --- a/test/JsonSchemaValidatorTest.cxx +++ b/test/JsonSchemaValidatorTest.cxx @@ -147,7 +147,7 @@ TEST_CASE("Json schema validator test") CHECK(errors.empty()); CHECK_FALSE(validator.validate(rexsapi::json::parse(invalidValue), errors)); - CHECK(errors.size() == 5); + CHECK(errors.size() == 6); } SUBCASE("Valid complex schema") diff --git a/test/TestModelHelper.hxx b/test/TestModelHelper.hxx index 19b3458..9e37ae6 100644 --- a/test/TestModelHelper.hxx +++ b/test/TestModelHelper.hxx @@ -67,3 +67,13 @@ public: private: const rexsapi::TComponent& m_Component; }; + +static inline bool replace(std::string& str, std::string_view from, std::string_view to) +{ + size_t startPos = str.find(from); + if (startPos == std::string::npos) { + return false; + } + str.replace(startPos, from.length(), to); + return true; +} diff --git a/test/XMLModelLoaderTest.cxx b/test/XMLModelLoaderTest.cxx index 7de59b9..fd2a53b 100644 --- a/test/XMLModelLoaderTest.cxx +++ b/test/XMLModelLoaderTest.cxx @@ -35,18 +35,8 @@ namespace rexsapi::detail::TFileModelLoader loader{validator, path}; return loader.load(mode, result, registry); } -} - -TEST_CASE("XML model loader test") -{ - const auto registry = createModelRegistry(); - rexsapi::TFileXsdSchemaLoader schemaLoader{projectDir() / "models" / "rexs-schema.xsd"}; - rexsapi::TXSDSchemaValidator validator{schemaLoader}; - rexsapi::TResult result; - SUBCASE("Load model from buffer") - { - std::string buffer = R"( + const std::string MemModel = R"( @@ -132,9 +122,19 @@ TEST_CASE("XML model loader test") )"; +} + +TEST_CASE("XML model loader test") +{ + const auto registry = createModelRegistry(); + rexsapi::TFileXsdSchemaLoader schemaLoader{projectDir() / "models" / "rexs-schema.xsd"}; + rexsapi::TXSDSchemaValidator validator{schemaLoader}; + rexsapi::TResult result; + SUBCASE("Load model from buffer") + { rexsapi::detail::TBufferModelLoader loader{validator, - buffer}; + MemModel}; auto model = loader.load(rexsapi::TMode::STRICT_MODE, result, registry); CHECK_FALSE(result); REQUIRE(result.getErrors().size() == 1); @@ -459,4 +459,22 @@ TEST_CASE("XML model loader test") CHECK(result.hasIssues()); CHECK(model); } + + SUBCASE("Load valid document with unkown version") + { + std::string buffer{MemModel}; + replace(buffer, R"(version="1.4")", R"(version="1.99")"); + { + rexsapi::detail::TBufferModelLoader loader{validator, + buffer}; + auto model = loader.load(rexsapi::TMode::RELAXED_MODE, result, registry); + CHECK(result); + REQUIRE(model); + } + result.reset(); + + rexsapi::detail::TBufferModelLoader loader{validator, + buffer}; + CHECK_THROWS((void)loader.load(rexsapi::TMode::STRICT_MODE, result, registry)); + } } diff --git a/test/database/ModelRegistryTest.cxx b/test/database/ModelRegistryTest.cxx index 25b8e88..ad673fe 100644 --- a/test/database/ModelRegistryTest.cxx +++ b/test/database/ModelRegistryTest.cxx @@ -22,9 +22,9 @@ TEST_CASE("Test rexs model registry") { - rexsapi::TFileXsdSchemaLoader schemaLoader{projectDir() / "models" / "rexs-dbmodel.xsd"}; - rexsapi::database::TFileResourceLoader resourceLoader{projectDir() / "models"}; - rexsapi::database::TXmlModelLoader modelLoader{resourceLoader, schemaLoader}; + const rexsapi::TFileXsdSchemaLoader schemaLoader{projectDir() / "models" / "rexs-dbmodel.xsd"}; + const rexsapi::database::TFileResourceLoader resourceLoader{projectDir() / "models"}; + const rexsapi::database::TXmlModelLoader modelLoader{resourceLoader, schemaLoader}; const auto [registry, success] = rexsapi::database::TModelRegistry::createModelRegistry(modelLoader); REQUIRE(success); @@ -47,7 +47,21 @@ TEST_CASE("Test rexs model registry") { CHECK_THROWS_WITH((void)registry.getModel(rexsapi::TRexsVersion{"1.4"}, "es"), "cannot find a database model for version '1.4' and locale 'es'"); - CHECK_THROWS_WITH((void)registry.getModel(rexsapi::TRexsVersion{"1.99"}, "en"), - "cannot find a database model for version '1.99' and locale 'en'"); + CHECK_THROWS_WITH((void)registry.getModel(rexsapi::TRexsVersion{"1.99"}, "es"), + "cannot find a database model for version '1.99' and locale 'es'"); + } + + SUBCASE("Get non existing model non-strict mode") + { + const auto& model = registry.getModel(rexsapi::TRexsVersion{"1.99"}, "de", false); + CHECK(model.getVersion() == rexsapi::TRexsVersion{"1.4"}); + CHECK(model.getLanguage() == "de"); + } + + SUBCASE("Get non existing model non-strict mode with unknown language") + { + const auto& model = registry.getModel(rexsapi::TRexsVersion{"1.99"}, "es", false); + CHECK(model.getVersion() == rexsapi::TRexsVersion{"1.4"}); + CHECK(model.getLanguage() == "en"); } } diff --git a/test/main.cpp b/test/main.cpp index 8022ad3..a38da25 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -16,7 +16,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + #define REXSAPI_MINIZ_IMPL #include - -#include