diff --git a/CMakeLists.txt b/CMakeLists.txt index e75c277f..2213e769 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,8 +21,11 @@ include(CTest) include(FetchContent) include(GNUInstallDirs) include(CMakeDependentOption) + include(CMakePackageConfigHelpers) +option(MORPHEUS_ENABLE_TESTS "Build Morpheus tests" OFF) +option(MORPHEUS_ENABLE_EXAMPLES "Build Morpheus examples" OFF) cmake_dependent_option(MORPHEUS_CODE_COVERAGE "Enable code coverage" ON "\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\" OR \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\"" OFF) cmake_dependent_option(MORPHEUS_INCLUDE_NATVIS "Enable inclusion of a natvis files for debugging" ON "\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"MSVC\"" OFF) diff --git a/conanfile.py b/conanfile.py index f6050952..ece484c6 100644 --- a/conanfile.py +++ b/conanfile.py @@ -22,7 +22,7 @@ from conan import ConanFile from conan.errors import ConanException, ConanInvalidConfiguration from conan.tools.build import check_min_cppstd -from conan.tools.cmake import cmake_layout, CMake, CMakeDeps +from conan.tools.cmake import cmake_layout, CMake, CMakeDeps, CMakeToolchain from conan.tools.files import copy from conan.tools.scm import Version from conan.tools.files import load @@ -56,23 +56,24 @@ class Morpheus(ConanFile): "shared": [True, False], "fPIC": [True, False], "tools": [True, False], - "build_docs": [True, False] + "build_docs": [True, False], + "build_tests": [True, False] } default_options = { "shared": False, "fPIC": True, "tools": True, - "build_docs": False + "build_docs": False, + "build_tests": True } exports_sources = ["CMakeLists.txt", "LICENSE", "version.txt", "cmake/*", "examples/*" "libraries/*"] - generators = "CMakeDeps", "CMakeToolchain" + #generators = "CMakeDeps", "CMakeToolchain" requires = ( "boost/1.82.0", "ctre/3.8", "fmt/[^10]", "glbinding/3.1.0", "glew/2.2.0", - "gtest/1.13.0", "magic_enum/0.8.2", "ms-gsl/4.0.0", "rapidjson/cci.20220822", @@ -93,7 +94,12 @@ def set_version(self): def build_requirements(self): self.tool_requires("ninja/1.11.1") - self.test_requires("catch2/3.4.0") + + if self.options.build_tests: + self.test_requires("gtest/1.13.0") + #self.test_requires("rapidcheck/cci.20220514", options={'enable_catch':'True'}) + #self.requires("catch2/3.3.2", override=True, test=True) # Should be 'test_requires' but that does not allow override which we need for catch2. + self.test_requires("catch2/3.4.0") if get_cmake_version() < Version("3.27.0"): self.tool_requires("cmake/3.27.0") @@ -129,7 +135,7 @@ def _minimum_compilers_version(self): "apple-clang": "13" } - def configure(self): + def validate(self): if self.settings.compiler.get_safe("cppstd"): check_min_cppstd(self, self._minimum_cpp_standard) min_version = self._minimum_compilers_version.get( @@ -146,6 +152,15 @@ def configure(self): self.name, self._minimum_cpp_standard, self.settings.compiler, self.settings.compiler.version)) + + def generate(self): + tc = CMakeToolchain(self) + tc.variables["MORPHEUS_ENABLE_TESTS"] = self.options.build_tests + tc.variables["MORPHEUS_BUILD_DOCUMENTATION"] = self.options.build_docs + tc.generate() + + tc = CMakeDeps(self) + tc.generate() # def generate(self): # tc = CMakeToolchain(self, generator=os.getenv("CONAN_CMAKE_GENERATOR")) diff --git a/libraries/core/mocking/morpheus/core/serialisation/mock/reader.hpp b/libraries/core/mocking/morpheus/core/serialisation/mock/reader.hpp index 89eb24c0..ad72cce2 100644 --- a/libraries/core/mocking/morpheus/core/serialisation/mock/reader.hpp +++ b/libraries/core/mocking/morpheus/core/serialisation/mock/reader.hpp @@ -77,6 +77,14 @@ class Reader { return read(T{}); } + + /* template + T readSequence() + requires requires(Reader r) { r.read(T{}); } + { + return read(T{}); + } + */ }; } // namespace morpheus::serialisation::mock \ No newline at end of file diff --git a/libraries/core/src/morpheus/core/concurrency/generator.hpp b/libraries/core/src/morpheus/core/concurrency/generator.hpp index 143926fa..c3ca9aba 100644 --- a/libraries/core/src/morpheus/core/concurrency/generator.hpp +++ b/libraries/core/src/morpheus/core/concurrency/generator.hpp @@ -26,7 +26,9 @@ struct Generator using reference = std::conditional_t, T, const value_type&>; using pointer = std::add_pointer_t; - Generator(handle_type h) : coro(h) {} + Generator(handle_type h) + : coro(h) + {} /// Destroys the generator. ~Generator() @@ -38,7 +40,9 @@ struct Generator Generator(const Generator&) = delete; Generator& operator=(const Generator&) = delete; - Generator(Generator&& rhs) noexcept : coro(std::exchange(rhs.coro, {})) {} + Generator(Generator&& rhs) noexcept + : coro(std::exchange(rhs.coro, {})) + {} Generator& operator=(Generator&& rhs) noexcept { @@ -54,7 +58,10 @@ struct Generator auto get_return_object() { return Generator{handle_type::from_promise(*this)}; } - auto return_void() { return coro_ns::suspend_never{}; } + // auto return_void() { + // return coro_ns::suspend_never{}; + // } + void return_void() noexcept {} auto yield_value(const T value) { @@ -73,8 +80,8 @@ struct Generator { public: using value_type = Generator::value_type; ///< Value type pointed to by the iterator. - using reference = Generator::reference; ///< Reference to the underlying value type pointed to by the iterator. - using pointer = Generator::pointer; ///< Pointer to the underlying value type pointed to by the iterator. + using reference = Generator::reference; ///< Reference to the underlying value type pointed to by the iterator. + using pointer = Generator::pointer; ///< Pointer to the underlying value type pointed to by the iterator. using difference_type = std::ptrdiff_t; using iterator_category = std::input_iterator_tag; @@ -82,7 +89,9 @@ struct Generator iterator(iterator const& rhs) noexcept = default; iterator& operator=(iterator const& other) noexcept = default; - iterator(iterator&& rhs) noexcept : handle(std::exchange(rhs.handle, {})) {} + iterator(iterator&& rhs) noexcept + : handle(std::exchange(rhs.handle, {})) + {} iterator& operator=(iterator&& other) noexcept { @@ -112,7 +121,9 @@ struct Generator private: friend Generator; - explicit iterator(coro_ns::coroutine_handle h) noexcept : handle(h) {} + explicit iterator(coro_ns::coroutine_handle h) noexcept + : handle(h) + {} coro_ns::coroutine_handle handle; }; diff --git a/libraries/core/src/morpheus/core/serialisation/adapters/std/CMakeLists.txt b/libraries/core/src/morpheus/core/serialisation/adapters/std/CMakeLists.txt index bc35eb73..782dca6e 100644 --- a/libraries/core/src/morpheus/core/serialisation/adapters/std/CMakeLists.txt +++ b/libraries/core/src/morpheus/core/serialisation/adapters/std/CMakeLists.txt @@ -1,6 +1,7 @@ target_sources(MorpheusCore PUBLIC array.hpp + chrono.hpp deque.hpp expected.hpp forward_list.hpp @@ -9,8 +10,10 @@ target_sources(MorpheusCore monostate.hpp optional.hpp pair.hpp + ranges.hpp set.hpp source_location.hpp + span.hpp tuple.hpp unique_ptr.hpp unordered_set.hpp diff --git a/libraries/core/src/morpheus/core/serialisation/adapters/std/expected.hpp b/libraries/core/src/morpheus/core/serialisation/adapters/std/expected.hpp index d865cb75..38cbd7c8 100644 --- a/libraries/core/src/morpheus/core/serialisation/adapters/std/expected.hpp +++ b/libraries/core/src/morpheus/core/serialisation/adapters/std/expected.hpp @@ -37,4 +37,4 @@ T deserialise(Serialiser& serialiser) exp_ns::unexpected(serialiser.template deserialise("error"))); } -} // namespace morpheus::serialisation::detail \ No newline at end of file +} // namespace morpheus::serialisation::detail diff --git a/libraries/core/src/morpheus/core/serialisation/adapters/std/pair.hpp b/libraries/core/src/morpheus/core/serialisation/adapters/std/pair.hpp index 72ebc145..a4a681ea 100644 --- a/libraries/core/src/morpheus/core/serialisation/adapters/std/pair.hpp +++ b/libraries/core/src/morpheus/core/serialisation/adapters/std/pair.hpp @@ -17,10 +17,11 @@ concept IsStdPair = meta::IsSpecialisationOf; template void serialise(Serialiser& serialiser, std::pair const& value) { - serialiser.writer().beginSequence(std::tuple_size>::value); - serialiser.serialise(value.first); - serialiser.serialise(value.second); - serialiser.writer().endSequence(); + serialiser.serialise(std::tuple_size>::value, [&]() + { + serialiser.serialise(value.first); + serialiser.serialise(value.second); + }); } template diff --git a/libraries/core/src/morpheus/core/serialisation/adapters/std/ranges.hpp b/libraries/core/src/morpheus/core/serialisation/adapters/std/ranges.hpp index f94dcd22..524f070e 100644 --- a/libraries/core/src/morpheus/core/serialisation/adapters/std/ranges.hpp +++ b/libraries/core/src/morpheus/core/serialisation/adapters/std/ranges.hpp @@ -1,7 +1,6 @@ #pragma once #include "morpheus/core/conformance/ranges.hpp" -#include "morpheus/core/meta/concepts/string.hpp" #include "morpheus/core/serialisation/concepts/read_serialisable.hpp" #include "morpheus/core/serialisation/concepts/read_serialiser.hpp" #include "morpheus/core/serialisation/concepts/write_serialisable.hpp" @@ -31,4 +30,18 @@ void serialise(Serialiser& serialiser, IsRange auto const& range) serialiser.writer().endSequence(); } -} // namespace morpheus::serialisation::detail \ No newline at end of file +template +T deserialise(Serialiser& serialiser) +{ + auto const scope = makeScopedSequence(serialiser.reader()); + auto sequenceGenerator = serialiser.reader().template readSequence(); + // We should be using the for_range_t constructor create the container but there is no support for this in Gcc 12 and Clang 15. + // T sequence(std::from_range_t, sequenceGenerator); + T sequence; + for (auto& entry : sequenceGenerator) { + sequence.push_back(entry); + } + return sequence; +} + +} // namespace morpheus::serialisation::detail diff --git a/libraries/core/src/morpheus/core/serialisation/adapters/std/span.hpp b/libraries/core/src/morpheus/core/serialisation/adapters/std/span.hpp new file mode 100644 index 00000000..e316afcb --- /dev/null +++ b/libraries/core/src/morpheus/core/serialisation/adapters/std/span.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "morpheus/core/serialisation/adapters/std/ranges.hpp" + +#include + +namespace morpheus::serialisation::detail +{ + +template +inline constexpr bool isEnabledForRangeSerialisation> = true; + +/// std::span is handled by the native interface. +template <> +inline constexpr bool isEnabledForRangeSerialisation> = false; + +} // namespace morpheus::serialisation::detail diff --git a/libraries/core/src/morpheus/core/serialisation/adapters/std/tuple.hpp b/libraries/core/src/morpheus/core/serialisation/adapters/std/tuple.hpp index 65b2ac5c..c9912889 100644 --- a/libraries/core/src/morpheus/core/serialisation/adapters/std/tuple.hpp +++ b/libraries/core/src/morpheus/core/serialisation/adapters/std/tuple.hpp @@ -17,12 +17,12 @@ template void serialise(Serialiser& serialiser, std::tuple const& value) { constexpr auto size = std::tuple_size>::value; - serialiser.writer().beginSequence(size); - [&] (std::index_sequence) - { - (serialiser.serialise(std::get(value)), ...); - }(std::make_index_sequence()); - serialiser.writer().endSequence(); + serialiser.serialise(size, + [&] (std::index_sequence) + { + return [&] { (serialiser.serialise(std::get(value)), ...); }; + }(std::make_index_sequence()) + ); } template diff --git a/libraries/core/src/morpheus/core/serialisation/adapters/std/variant.hpp b/libraries/core/src/morpheus/core/serialisation/adapters/std/variant.hpp index 29cf9dd6..1355c425 100644 --- a/libraries/core/src/morpheus/core/serialisation/adapters/std/variant.hpp +++ b/libraries/core/src/morpheus/core/serialisation/adapters/std/variant.hpp @@ -53,21 +53,25 @@ template void serialise(Serialiser& serialiser, std::variant const& value) { serialiser.writer().beginComposite(); - if (value.valueless_by_exception()) [[unlikely]] +// serialiser.serialise([&]() { - serialiser.serialise("index", static_cast(std::variant_npos)); - } - else - { - if (serialiser.writer().isTextual()) + if (value.valueless_by_exception()) [[unlikely]] { - serialiser.serialise("type", TypeListNames>::name(value.index())); + serialiser.serialise("index", static_cast(std::variant_npos)); } else - serialiser.serialise("index", static_cast(value.index())); + { + if (serialiser.writer().isTextual()) + { + serialiser.serialise("type", TypeListNames>::name(value.index())); + } + else + serialiser.serialise("index", static_cast(value.index())); - std::visit([&serialiser](auto const& value) { serialiser.serialise("value", value); }, value); + std::visit([&serialiser](auto const& value) { return serialiser.serialise("value", value); }, value); + } } + //); serialiser.writer().endComposite(); } diff --git a/libraries/core/src/morpheus/core/serialisation/adapters/std/vector.hpp b/libraries/core/src/morpheus/core/serialisation/adapters/std/vector.hpp index 374480fe..5aa9dd28 100644 --- a/libraries/core/src/morpheus/core/serialisation/adapters/std/vector.hpp +++ b/libraries/core/src/morpheus/core/serialisation/adapters/std/vector.hpp @@ -10,4 +10,8 @@ namespace morpheus::serialisation::detail template inline constexpr bool isEnabledForRangeSerialisation> = true; +/// std::vector> is handled by the native interface. +template <> +inline constexpr bool isEnabledForRangeSerialisation> = false; + } // namespace morpheus::serialisation::detail diff --git a/libraries/core/src/morpheus/core/serialisation/json_reader.cpp b/libraries/core/src/morpheus/core/serialisation/json_reader.cpp index 39c4c403..d5410fbe 100644 --- a/libraries/core/src/morpheus/core/serialisation/json_reader.cpp +++ b/libraries/core/src/morpheus/core/serialisation/json_reader.cpp @@ -123,10 +123,19 @@ struct JsonExtracter : rapidjson::BaseReaderHandler, JsonExtra JsonReader::EventValue mCurrent; ///< Current json event. }; - +void JsonReader::repeatCurrent() +{ + MORPHEUS_ASSERT(!mRepeat); + mRepeat = true; +} JsonReader::EventValue JsonReader::getNext() { + if (mRepeat) { + mRepeat = false; + return mExtractor->mCurrent; + } + using namespace rapidjson; mJsonReader.IterativeParseNext(mStream, *mExtractor); if (mJsonReader.HasParseError()) { diff --git a/libraries/core/src/morpheus/core/serialisation/json_reader.hpp b/libraries/core/src/morpheus/core/serialisation/json_reader.hpp index eddc21d7..2d0f2471 100644 --- a/libraries/core/src/morpheus/core/serialisation/json_reader.hpp +++ b/libraries/core/src/morpheus/core/serialisation/json_reader.hpp @@ -2,9 +2,13 @@ #include "morpheus/core/base/assert.hpp" #include "morpheus/core/base/cold.hpp" +#include "morpheus/core/base/scoped_action.hpp" +#include "morpheus/core/concurrency/generator.hpp" #include "morpheus/core/functional/overload.hpp" #include "morpheus/core/memory/polymorphic_value.hpp" #include "morpheus/core/serialisation/exceptions.hpp" +#include "morpheus/core/serialisation/concepts/reader_archtype.hpp" +#include "morpheus/core/serialisation/read_serialiser_decl.hpp" #include @@ -33,6 +37,7 @@ namespace morpheus::serialisation /// Read in objects from an underlying json representation. class MORPHEUSCORE_EXPORT JsonReader { + enum class FundamentalType : std::uint32_t { Boolean, @@ -83,6 +88,27 @@ class MORPHEUSCORE_EXPORT JsonReader /// \copydoc morpheus::serialisation::concepts::ReaderArchtype::endNullable() void endNullable(); + template + requires requires(concepts::ReaderArchtype& r) { + { + r.template read() + } -> std::same_as; + } + concurrency::Generator readSequence() + { + for (;;) { + auto const [event, next] = getNext(); + repeatCurrent(); + + if (event == Event::EndSequence) { + co_return; + } + + MORPHEUS_ASSERT(event == Event::Value); + co_yield read(); + } + } + // clang-format off /// Read a boolean from the serialisation. template @@ -164,12 +190,14 @@ class MORPHEUSCORE_EXPORT JsonReader using EventValue = std::tuple; [[nodiscard]] EventValue getNext(); + void repeatCurrent(); memory::polymorphic_value mSourceStream; /// Owned input stream containing the Json source. - rapidjson::IStreamWrapper mStream; + rapidjson::IStreamWrapper mStream; rapidjson::Reader mJsonReader; std::unique_ptr mExtractor; bool mValidate = true; + bool mRepeat = false; }; } // namespace morpheus::serialisation \ No newline at end of file diff --git a/libraries/core/src/morpheus/core/serialisation/read_serialiser.hpp b/libraries/core/src/morpheus/core/serialisation/read_serialiser.hpp index 4a6a1c6b..22f9a2ab 100644 --- a/libraries/core/src/morpheus/core/serialisation/read_serialiser.hpp +++ b/libraries/core/src/morpheus/core/serialisation/read_serialiser.hpp @@ -25,4 +25,26 @@ template return serialisation::deserialise.template operator(), T>(*this); } +template +template +[[nodiscard]] T ReadSerialiser::deserialise(std::size_t size, std::invocable auto f) +{ + auto const sequence = makeScopedSequence(*this, size); + +} + +template +template +[[nodiscard]] T ReadSerialiser::deserialise(std::invocable auto f) +{ + +} + +template +template +[[nodiscard]] T ReadSerialiser::deserialise(bool const null, std::invocable auto f) +{ + +} + } \ No newline at end of file diff --git a/libraries/core/src/morpheus/core/serialisation/read_serialiser_decl.hpp b/libraries/core/src/morpheus/core/serialisation/read_serialiser_decl.hpp index df7642d8..6a6be351 100644 --- a/libraries/core/src/morpheus/core/serialisation/read_serialiser_decl.hpp +++ b/libraries/core/src/morpheus/core/serialisation/read_serialiser_decl.hpp @@ -14,48 +14,52 @@ namespace morpheus::serialisation auto makeScopedValue(concepts::Reader auto& reader, std::string_view const key) { - return ScopedAction([&reader, key] { reader.beginValue(key); }, [&reader] { reader.endValue(); } ); + return ScopedAction([&reader, key] { reader.beginValue(key); }, [&reader] { reader.endValue(); }); } auto makeScopedSequence(concepts::Reader auto& reader, std::optional const size = std::nullopt) { - return ScopedAction([&reader] { return reader.beginSequence(); }, [&reader] { reader.endSequence(); } ); + return ScopedAction([&reader] { return reader.beginSequence(); }, [&reader] { reader.endSequence(); }); } auto makeScopedComposite(concepts::Reader auto& reader) { - return ScopedAction([&reader] { reader.beginComposite(); }, [&reader] { reader.endComposite(); } ); + return ScopedAction([&reader] { reader.beginComposite(); }, [&reader] { reader.endComposite(); }); } auto makeScopedNullable(concepts::Reader auto& reader) { - return ScopedAction([&reader] { return reader.beginNullable(); }, [&reader] { reader.endNullable(); } ); + return ScopedAction([&reader] { return reader.beginNullable(); }, [&reader] { reader.endNullable(); }); } /// \class ReadSerialiser /// \tparam ReaderType The type of underlying reader for serialisation. -template +template class ReadSerialiser { public: /// Constructs a ReadSerialiser when the underlying reader supports default construction. - template - ReadSerialiser() noexcept(meta::concepts::DefaultNothrowConstructible) + template + ReadSerialiser() noexcept(meta::concepts::DefaultNothrowConstructible) {} - template - requires(std::is_constructible_v) + template + requires(std::is_constructible_v) ReadSerialiser(Args&&... args) noexcept(meta::concepts::NothrowConstructible) - : mReader(std::forward(args)...) + : mReader(std::forward(args)...) {} #if (__cpp_explicit_this_parameter >= 202110) /// Access the underlying reader. - template - [[nodiscard]] auto& reader(this Self&& self) noexcept { return self.mReader; } + template + [[nodiscard]] auto& reader(this Self&& self) noexcept + { + return self.mReader; + } #else /// Access the underlying reader. [[nodiscard]] ReaderType& reader() noexcept { return mReader; } + /// Access the underlying reader. [[nodiscard]] ReaderType const& reader() const noexcept { return mReader; } #endif // (__cpp_explicit_this_parameter >= 202110) @@ -71,18 +75,39 @@ class ReadSerialiser /// Deserialise a single value /// \tparam T The underlying type of value to deserialise. /// \return The deserialises value. - template + template [[nodiscard]] T deserialise(); /// Deserialise a key value pair /// \tparam T The underlying type of value to deserialise. /// \param[in] key The key to serialise. /// \return The deserialises value. - template + template [[nodiscard]] T deserialise(std::string_view const key); + + /// Deserialise a sequence of values + /// \tparam T The underlying type of sequence to deserialise. + /// \param[in] size The number of entries in the sequence to serialise. + /// \param[in] f The command deserialising the sequence of values. + template + [[nodiscard]] T deserialise(std::size_t size, std::invocable auto f); + + /// Deserialise a related set of values in a composite. + /// \tparam T The underlying type of composite to deserialise. + /// \param[in] f The command deserialising the composite values. + template + [[nodiscard]] T deserialise(std::invocable auto f); + + /// Deserialise a nullable value + /// \tparam T The underlying type of nullable to deserialise. + /// \param[in] null If the nullable value is null or set. + /// \param[in] f The command deserialising the nullable values. + template + [[nodiscard]] T deserialise( + bool const null, std::invocable auto f = [] {}); ///@} private: ReaderType mReader; ///< The underlying reading for serialising fundamental types. }; -} \ No newline at end of file +} // namespace morpheus::serialisation \ No newline at end of file diff --git a/libraries/core/src/morpheus/core/serialisation/write_serialiser.hpp b/libraries/core/src/morpheus/core/serialisation/write_serialiser.hpp index feef76f8..4c55a85b 100644 --- a/libraries/core/src/morpheus/core/serialisation/write_serialiser.hpp +++ b/libraries/core/src/morpheus/core/serialisation/write_serialiser.hpp @@ -7,19 +7,43 @@ namespace morpheus::serialisation { template -template -void WriteSerialiser::serialise(T const& value) +void WriteSerialiser::serialise(auto const& value) { serialisation::serialise(*this, value); } template -template -void WriteSerialiser::serialise(std::string_view const key, T const& value) +void WriteSerialiser::serialise(std::string_view const key, auto const& value) { mWriter.beginValue(key); serialisation::serialise(*this, value); mWriter.endValue(); } +template +auto WriteSerialiser::serialise(std::size_t size, std::invocable auto f) +{ + mWriter.beginSequence(size); + f(); + mWriter.endSequence(); +} + +/* +template +auto WriteSerialiser::serialise(std::invocable auto f) +{ + mWriter.beginComposite(); + f(); + mWriter.endComposite(); +} +*/ + +template +auto WriteSerialiser::serialise(bool const null, std::invocable auto f) +{ + mWriter.beginNullable(null); + f(); + mWriter.endNullable(); +} + } \ No newline at end of file diff --git a/libraries/core/src/morpheus/core/serialisation/write_serialiser_decl.hpp b/libraries/core/src/morpheus/core/serialisation/write_serialiser_decl.hpp index 1f5fadc7..f263fa86 100644 --- a/libraries/core/src/morpheus/core/serialisation/write_serialiser_decl.hpp +++ b/libraries/core/src/morpheus/core/serialisation/write_serialiser_decl.hpp @@ -3,6 +3,7 @@ #include "morpheus/core/meta/concepts/constructible.hpp" #include "morpheus/core/serialisation/concepts/writer.hpp" +#include #include #include #include @@ -51,17 +52,27 @@ class WriteSerialiser ///@{ /// Serialise a single value - /// \tparam T The underlying type of value to serialise. /// \param[in] value The value to serialise. - template - void serialise(T const& value); + void serialise(auto const& value); /// Serialise a key value pair - /// \tparam T The underlying type of value to serialise. /// \param[in] key The key to serialise. /// \param[in] value The value to serialise. - template - void serialise(std::string_view const key, T const& value); + void serialise(std::string_view const key, auto const& value); + + /// Serialise a sequence of values + /// \param[in] size The number of entries in the sequence to serialise. + /// \param[in] f The command serialising the sequence of values. + auto serialise(std::size_t size, std::invocable auto f); + + /// Serialise a related set of values in a composite. + /// \param[in] f The command serialising the composite values. +// auto serialise(std::invocable auto f); + + /// Serialise a nullable value + /// \param[in] null If the nullable value is null or set. + /// \param[in] f The command serialising the nullable values. + auto serialise(bool const null, std::invocable auto f = []{}); ///@} private: WriterType mWriter; ///< The underlying writer for serialising fundamental types. diff --git a/libraries/core/tests/meta/is_string.tests.cpp b/libraries/core/tests/meta/is_string.tests.cpp index 51868b9a..4bf0bbfd 100644 --- a/libraries/core/tests/meta/is_string.tests.cpp +++ b/libraries/core/tests/meta/is_string.tests.cpp @@ -1,6 +1,5 @@ #include "morpheus/core/meta/concepts/string.hpp" #include "morpheus/core/meta/is_string.hpp" - #include namespace morpheus::meta diff --git a/libraries/core/tests/serialisation/adapters/std/ranges.tests.cpp b/libraries/core/tests/serialisation/adapters/std/ranges.tests.cpp index 89863a14..4cf00950 100644 --- a/libraries/core/tests/serialisation/adapters/std/ranges.tests.cpp +++ b/libraries/core/tests/serialisation/adapters/std/ranges.tests.cpp @@ -1,4 +1,6 @@ + #include "morpheus/core/conformance/ranges.hpp" +#include "morpheus/core/meta/concepts/string.hpp" #include "morpheus/core/serialisation/adapters/std/array.hpp" #include "morpheus/core/serialisation/adapters/std/deque.hpp" #include "morpheus/core/serialisation/adapters/std/forward_list.hpp" @@ -13,9 +15,12 @@ #include #include +// #include -#include #include +#include +#include +#include #include namespace morpheus::serialisation @@ -24,23 +29,28 @@ namespace morpheus::serialisation using namespace ::testing; using namespace std::literals::string_view_literals; +TEST_CASE("Verify serialisation of ranges", "[morpheus.serialisation.ranges]") +{ + STATIC_REQUIRE(detail::IsRange>); + STATIC_REQUIRE(meta::IsStringView); +} + TEMPLATE_TEST_CASE("Verify serialisation of sequence containers std::ranges", "[morpheus.serialisation.ranges.serialise.sequence_containers]", (std::array), std::deque, std::list, std::set, std::unordered_set, std::vector) { GIVEN("A range of values") { - TestType container([] - { - std::initializer_list const values = {1, 2, 3, 4, 5}; - if constexpr (std::is_constructible_v) - { - return values; - } - else + TestType container( + [] { - return TestType{1, 2, 3, 4, 5}; - } - }()); + std::initializer_list const values = {1, 2, 3, 4, 5}; + if constexpr (std::is_constructible_v) { + return values; + } + else { + return TestType{1, 2, 3, 4, 5}; + } + }()); THEN("Expect the following sequence of operations on the underlying writer") { @@ -56,57 +66,33 @@ TEMPLATE_TEST_CASE("Verify serialisation of sequence containers std::ranges", "[ } /* -TEST_CASE("Verify deserialisation of std::ranges", "[morpheus.serialisation.ranges.deserialise]") +TEMPLATE_TEST_CASE("Verify deserialisation of std::ranges", "[morpheus.serialisation.ranges.deserialise]", std::deque, std::list, std::set, + std::unordered_set, std::vector) { - GIVEN("Expected contents of a std::expected holding a value") + GIVEN("Expected contents of a given range to be deserialised") { - constexpr std::int64_t actualValue = 10; + std::vector const range{1, 2, 3, 4, 5}; THEN("Expect the following sequence of operations on the underlying writer") { InSequence seq; MockedReadSerialiser serialiser; - EXPECT_CALL(serialiser.reader(), beginComposite()).Times(1); - EXPECT_CALL(serialiser.reader(), beginValue("state"sv)).Times(1); - EXPECT_CALL(serialiser.reader(), read(An())).WillOnce(Return(true)); - EXPECT_CALL(serialiser.reader(), endValue()).Times(1); - EXPECT_CALL(serialiser.reader(), beginValue("value"sv)).Times(1); - EXPECT_CALL(serialiser.reader(), read(An())).WillOnce(Return(actualValue)); - EXPECT_CALL(serialiser.reader(), endValue()).Times(1); - EXPECT_CALL(serialiser.reader(), endComposite()).Times(1); - WHEN("Serialising the std::expected") - { - using ExpectedType = exp_ns::expected; - auto const expected = serialiser.deserialise(); - REQUIRE(expected.value() == actualValue); + EXPECT_CALL(serialiser.reader(), beginSequence()).Times(1); + for (auto const& value : range) { + EXPECT_CALL(serialiser.reader(), beginValue("value"sv)).Times(1); + EXPECT_CALL(serialiser.reader(), read(An())).WillOnce(Return(value)); + EXPECT_CALL(serialiser.reader(), endValue()).Times(1); } - } - } - GIVEN("Expected contents of a std::expected holding an error") - { - std::string const actualValue = "This string is an error"; + EXPECT_CALL(serialiser.reader(), endSequence()).Times(1); - THEN("Expect the following sequence of operations on the underlying writer") - { - InSequence seq; - MockedReadSerialiser serialiser; - EXPECT_CALL(serialiser.reader(), beginComposite()).Times(1); - EXPECT_CALL(serialiser.reader(), beginValue("state"sv)).Times(1); - EXPECT_CALL(serialiser.reader(), read(An())).WillOnce(Return(false)); - EXPECT_CALL(serialiser.reader(), endValue()).Times(1); - EXPECT_CALL(serialiser.reader(), beginValue("error"sv)).Times(1); - EXPECT_CALL(serialiser.reader(), read(An())).WillOnce(Return(actualValue)); - EXPECT_CALL(serialiser.reader(), endValue()).Times(1); - EXPECT_CALL(serialiser.reader(), endComposite()).Times(1); - - WHEN("Deserialising the std::expected") + WHEN("Serialising the std::expected") { - using ExpectedType = exp_ns::expected; - auto const expected = serialiser.deserialise(); - REQUIRE(expected.error() == actualValue); + auto const expected = serialiser.deserialise(); + REQUIRE(expected == TestType(range.begin(), range.end())); } } } } */ -} // namespace morpheus::serialisation \ No newline at end of file + +} // namespace morpheus::serialisation diff --git a/libraries/core/tests/serialisation/json_reader.tests.cpp b/libraries/core/tests/serialisation/json_reader.tests.cpp index 79acff46..6de7d181 100644 --- a/libraries/core/tests/serialisation/json_reader.tests.cpp +++ b/libraries/core/tests/serialisation/json_reader.tests.cpp @@ -1,6 +1,8 @@ #include "morpheus/core/conformance/format.hpp" +#include "morpheus/core/conformance/ranges.hpp" #include "morpheus/core/serialisation/adapters/aggregate.hpp" #include "morpheus/core/serialisation/adapters/std/chrono.hpp" +#include "morpheus/core/serialisation/adapters/std/list.hpp" #include "morpheus/core/serialisation/adapters/std/monostate.hpp" #include "morpheus/core/serialisation/adapters/std/optional.hpp" #include "morpheus/core/serialisation/adapters/std/pair.hpp" @@ -98,7 +100,7 @@ TEST_CASE("Create and then copy a reader and read from the copied stream", "[mor } } -TEST_CASE("Json reader providess basic reader functionality", "[morpheus.serialisation.json_reader.fundamental]") +TEST_CASE("Json reader provides basic reader functionality", "[morpheus.serialisation.json_reader.fundamental]") { GIVEN("A Json stream") { @@ -236,6 +238,14 @@ TEST_CASE("Json reader can read simple composite types from underlying test repr } } +TEST_CASE("Json reader can read sequence types from underlying text representation", "[morpheus.serialisation.json_reader.read_sequence]") +{ + using namespace std::string_literals; + std::vector const expectedValues{ 0,1,2,3,4,5 }; + std::vector const actualValues = test::deserialise>("[0,1,2,3,4,5]"); + REQUIRE(actualValues == expectedValues); +} + template struct ContainsType { @@ -328,6 +338,7 @@ TEST_CASE("Json reader can read std types from underlying text representation", REQUIRE(test::deserialise(R"("12m")") == std::chrono::months{12}); } REQUIRE(test::deserialise(R"({})") == std::monostate{}); + REQUIRE(test::deserialise>(R"([1, 2, 3, 4, 5])") == std::list{1, 2, 3, 4, 5}); REQUIRE(test::deserialise>(R"(100)") == std::optional{100}); REQUIRE(test::deserialise>(R"(null)") == std::optional{}); REQUIRE(test::deserialise>(R"([50,true])") == std::pair{50, true}); @@ -335,6 +346,7 @@ TEST_CASE("Json reader can read std types from underlying text representation", REQUIRE(test::deserialise>(R"([75,true,"Example"])") == std::tuple{75, true, "Example"}); // REQUIRE(test::deserialise>(R"({"type":"bool","value":true})") == std::variant{true}); REQUIRE(*test::deserialise>(R"(50)") == 50); + REQUIRE(test::deserialise>(R"([1, 2, 3, 4, 5])") == std::vector{1, 2, 3, 4, 5}); } TEST_CASE("Error handling test cases for unexpected errors in the input Json stream", "[morpheus.serialisation.json_reader.error_handling]") diff --git a/libraries/core/tests/serialisation/json_writer.tests.cpp b/libraries/core/tests/serialisation/json_writer.tests.cpp index 82f9fd59..1557f0fe 100644 --- a/libraries/core/tests/serialisation/json_writer.tests.cpp +++ b/libraries/core/tests/serialisation/json_writer.tests.cpp @@ -5,6 +5,7 @@ #include "morpheus/core/serialisation/adapters/std/monostate.hpp" #include "morpheus/core/serialisation/adapters/std/optional.hpp" #include "morpheus/core/serialisation/adapters/std/pair.hpp" +#include "morpheus/core/serialisation/adapters/std/ranges.hpp" #include "morpheus/core/serialisation/adapters/std/tuple.hpp" #include "morpheus/core/serialisation/adapters/std/unique_ptr.hpp" #include "morpheus/core/serialisation/adapters/std/variant.hpp" @@ -30,7 +31,7 @@ std::string serialise(T const& value) { std::ostringstream oss; JsonWriteSerialiser serialiser{oss}; - serialiser.serialise(value); + serialiser.serialise(value); return oss.str(); } @@ -273,7 +274,8 @@ TEST_CASE("Json writer can write std types to underlying text representation", " REQUIRE(test::serialise(std::tuple{75, true, "Example"}) == R"([75,true,"Example"])"); REQUIRE(test::serialise(std::make_unique(123)) == R"(123)"); REQUIRE(test::serialise(std::variant{true}) == R"({"type":"bool","value":true})"); - REQUIRE(test::serialise(std::vector{1,2,3,4,5}) == R"([1,2,3,4,5])"); + REQUIRE(test::serialise(std::vector{0,1,2,3,4,5}) == R"([0,1,2,3,4,5])"); + static_assert(ranges::range>); } } // namespace morpheus::serialisation \ No newline at end of file