diff --git a/cpp/evolution/v0/generated/yardl/detail/binary/serializers.h b/cpp/evolution/v0/generated/yardl/detail/binary/serializers.h index e2707f10..1dcc75e7 100644 --- a/cpp/evolution/v0/generated/yardl/detail/binary/serializers.h +++ b/cpp/evolution/v0/generated/yardl/detail/binary/serializers.h @@ -14,10 +14,6 @@ #include "../../yardl.h" #include "coded_stream.h" -#if __cplusplus >= 202002L -#include -#endif - namespace yardl::binary { /** @@ -177,11 +173,7 @@ inline void WriteDate(CodedOutputStream& stream, yardl::Date const& value) { inline void ReadDate(CodedInputStream& stream, yardl::Date& value) { int64_t days; ReadInteger(stream, days); -#if __cplusplus < 202002L value = yardl::Date(date::days(days)); -#else - value = yardl::Date{std::chrono::days{days}}; -#endif } inline void WriteTime(CodedOutputStream& stream, yardl::Time const& value) { @@ -202,13 +194,9 @@ inline void WriteDateTime(CodedOutputStream& stream, yardl::DateTime const& valu inline void ReadDateTime(CodedInputStream& stream, yardl::DateTime& value) { int64_t ns; ReadInteger(stream, ns); -#if __cplusplus < 202002L - value = yardl::DateTime(date::sys_time(std::chrono::nanoseconds(ns))); -#else value = yardl::DateTime{ std::chrono::time_point(std::chrono::nanoseconds(ns))}; -#endif } template WriteElement> diff --git a/cpp/evolution/v0/generated/yardl/detail/hdf5/ddl.h b/cpp/evolution/v0/generated/yardl/detail/hdf5/ddl.h index f0d757a2..8bf8d5c9 100644 --- a/cpp/evolution/v0/generated/yardl/detail/hdf5/ddl.h +++ b/cpp/evolution/v0/generated/yardl/detail/hdf5/ddl.h @@ -37,15 +37,9 @@ static inline H5::PredType const& SizeTypeDdl() { * of days since the epoch. */ static inline H5::DataType DateTypeDdl() { -#if __cplusplus < 202002L static_assert(sizeof(yardl::Date) == sizeof(int32_t)); static_assert(std::is_same_v); return H5::PredType::NATIVE_INT32; -#else - static_assert(sizeof(yardl::Date) == sizeof(int64_t)); - static_assert(std::is_same_v); - return H5::PredType::NATIVE_INT64; -#endif } /** diff --git a/cpp/evolution/v0/generated/yardl/detail/ndjson/serializers.h b/cpp/evolution/v0/generated/yardl/detail/ndjson/serializers.h index 1bd729d3..db5dcd92 100644 --- a/cpp/evolution/v0/generated/yardl/detail/ndjson/serializers.h +++ b/cpp/evolution/v0/generated/yardl/detail/ndjson/serializers.h @@ -125,25 +125,12 @@ struct adl_serializer { template <> struct adl_serializer { static void to_json(ordered_json& j, yardl::Date const& value) { -#if __cplusplus < 202002L j = date::format("%F", value); -#else - date::local_days ld(value.time_since_epoch()); - j = date::format("%F", ld); -#endif } static void from_json(ordered_json const& j, yardl::Date& value) { std::stringstream ss{j.get()}; - -#if __cplusplus < 202002L ss >> date::parse("%F", value); -#else - date::local_days ld; - ss >> date::parse("%F", ld); - value = yardl::Date(ld.time_since_epoch()); -#endif - if (ss.fail()) { throw std::runtime_error("invalid date format"); } diff --git a/cpp/evolution/v0/generated/yardl/yardl.h b/cpp/evolution/v0/generated/yardl/yardl.h index aa8eb682..4a074ac7 100644 --- a/cpp/evolution/v0/generated/yardl/yardl.h +++ b/cpp/evolution/v0/generated/yardl/yardl.h @@ -5,11 +5,7 @@ #include -#if __cplusplus < 202002L -// This functionality is part of the standard library as of C++20 #include -#endif - #include #include #include @@ -46,17 +42,10 @@ using NDArray = xt::xtensor; template using DynamicNDArray = xt::xarray; -#if __cplusplus < 202002L /** * @brief Represents a date as a number of days since the epoch. */ using Date = date::local_days; -#else -/** - * @brief Represents a date as a number of days since the epoch. - */ -using Date = std::chrono::local_days; -#endif /** * @brief Represents a time of day as the number of nanoseconds since midnight. @@ -66,8 +55,7 @@ using Time = std::chrono::duration; /** * @brief Represents a datetime as the number of nanoseconds since the epoch. */ -using DateTime = std::chrono::time_point>; +using DateTime = std::chrono::time_point; /** * @brief The same as size_t when it is 64 bits, otherwise uint64_t. diff --git a/cpp/evolution/v1/generated/yardl/detail/binary/serializers.h b/cpp/evolution/v1/generated/yardl/detail/binary/serializers.h index e2707f10..1dcc75e7 100644 --- a/cpp/evolution/v1/generated/yardl/detail/binary/serializers.h +++ b/cpp/evolution/v1/generated/yardl/detail/binary/serializers.h @@ -14,10 +14,6 @@ #include "../../yardl.h" #include "coded_stream.h" -#if __cplusplus >= 202002L -#include -#endif - namespace yardl::binary { /** @@ -177,11 +173,7 @@ inline void WriteDate(CodedOutputStream& stream, yardl::Date const& value) { inline void ReadDate(CodedInputStream& stream, yardl::Date& value) { int64_t days; ReadInteger(stream, days); -#if __cplusplus < 202002L value = yardl::Date(date::days(days)); -#else - value = yardl::Date{std::chrono::days{days}}; -#endif } inline void WriteTime(CodedOutputStream& stream, yardl::Time const& value) { @@ -202,13 +194,9 @@ inline void WriteDateTime(CodedOutputStream& stream, yardl::DateTime const& valu inline void ReadDateTime(CodedInputStream& stream, yardl::DateTime& value) { int64_t ns; ReadInteger(stream, ns); -#if __cplusplus < 202002L - value = yardl::DateTime(date::sys_time(std::chrono::nanoseconds(ns))); -#else value = yardl::DateTime{ std::chrono::time_point(std::chrono::nanoseconds(ns))}; -#endif } template WriteElement> diff --git a/cpp/evolution/v1/generated/yardl/detail/hdf5/ddl.h b/cpp/evolution/v1/generated/yardl/detail/hdf5/ddl.h index f0d757a2..8bf8d5c9 100644 --- a/cpp/evolution/v1/generated/yardl/detail/hdf5/ddl.h +++ b/cpp/evolution/v1/generated/yardl/detail/hdf5/ddl.h @@ -37,15 +37,9 @@ static inline H5::PredType const& SizeTypeDdl() { * of days since the epoch. */ static inline H5::DataType DateTypeDdl() { -#if __cplusplus < 202002L static_assert(sizeof(yardl::Date) == sizeof(int32_t)); static_assert(std::is_same_v); return H5::PredType::NATIVE_INT32; -#else - static_assert(sizeof(yardl::Date) == sizeof(int64_t)); - static_assert(std::is_same_v); - return H5::PredType::NATIVE_INT64; -#endif } /** diff --git a/cpp/evolution/v1/generated/yardl/detail/ndjson/serializers.h b/cpp/evolution/v1/generated/yardl/detail/ndjson/serializers.h index 1bd729d3..db5dcd92 100644 --- a/cpp/evolution/v1/generated/yardl/detail/ndjson/serializers.h +++ b/cpp/evolution/v1/generated/yardl/detail/ndjson/serializers.h @@ -125,25 +125,12 @@ struct adl_serializer { template <> struct adl_serializer { static void to_json(ordered_json& j, yardl::Date const& value) { -#if __cplusplus < 202002L j = date::format("%F", value); -#else - date::local_days ld(value.time_since_epoch()); - j = date::format("%F", ld); -#endif } static void from_json(ordered_json const& j, yardl::Date& value) { std::stringstream ss{j.get()}; - -#if __cplusplus < 202002L ss >> date::parse("%F", value); -#else - date::local_days ld; - ss >> date::parse("%F", ld); - value = yardl::Date(ld.time_since_epoch()); -#endif - if (ss.fail()) { throw std::runtime_error("invalid date format"); } diff --git a/cpp/evolution/v1/generated/yardl/yardl.h b/cpp/evolution/v1/generated/yardl/yardl.h index aa8eb682..4a074ac7 100644 --- a/cpp/evolution/v1/generated/yardl/yardl.h +++ b/cpp/evolution/v1/generated/yardl/yardl.h @@ -5,11 +5,7 @@ #include -#if __cplusplus < 202002L -// This functionality is part of the standard library as of C++20 #include -#endif - #include #include #include @@ -46,17 +42,10 @@ using NDArray = xt::xtensor; template using DynamicNDArray = xt::xarray; -#if __cplusplus < 202002L /** * @brief Represents a date as a number of days since the epoch. */ using Date = date::local_days; -#else -/** - * @brief Represents a date as a number of days since the epoch. - */ -using Date = std::chrono::local_days; -#endif /** * @brief Represents a time of day as the number of nanoseconds since midnight. @@ -66,8 +55,7 @@ using Time = std::chrono::duration; /** * @brief Represents a datetime as the number of nanoseconds since the epoch. */ -using DateTime = std::chrono::time_point>; +using DateTime = std::chrono::time_point; /** * @brief The same as size_t when it is 64 bits, otherwise uint64_t. diff --git a/cpp/evolution/v2/generated/yardl/detail/binary/serializers.h b/cpp/evolution/v2/generated/yardl/detail/binary/serializers.h index e2707f10..1dcc75e7 100644 --- a/cpp/evolution/v2/generated/yardl/detail/binary/serializers.h +++ b/cpp/evolution/v2/generated/yardl/detail/binary/serializers.h @@ -14,10 +14,6 @@ #include "../../yardl.h" #include "coded_stream.h" -#if __cplusplus >= 202002L -#include -#endif - namespace yardl::binary { /** @@ -177,11 +173,7 @@ inline void WriteDate(CodedOutputStream& stream, yardl::Date const& value) { inline void ReadDate(CodedInputStream& stream, yardl::Date& value) { int64_t days; ReadInteger(stream, days); -#if __cplusplus < 202002L value = yardl::Date(date::days(days)); -#else - value = yardl::Date{std::chrono::days{days}}; -#endif } inline void WriteTime(CodedOutputStream& stream, yardl::Time const& value) { @@ -202,13 +194,9 @@ inline void WriteDateTime(CodedOutputStream& stream, yardl::DateTime const& valu inline void ReadDateTime(CodedInputStream& stream, yardl::DateTime& value) { int64_t ns; ReadInteger(stream, ns); -#if __cplusplus < 202002L - value = yardl::DateTime(date::sys_time(std::chrono::nanoseconds(ns))); -#else value = yardl::DateTime{ std::chrono::time_point(std::chrono::nanoseconds(ns))}; -#endif } template WriteElement> diff --git a/cpp/evolution/v2/generated/yardl/detail/hdf5/ddl.h b/cpp/evolution/v2/generated/yardl/detail/hdf5/ddl.h index f0d757a2..8bf8d5c9 100644 --- a/cpp/evolution/v2/generated/yardl/detail/hdf5/ddl.h +++ b/cpp/evolution/v2/generated/yardl/detail/hdf5/ddl.h @@ -37,15 +37,9 @@ static inline H5::PredType const& SizeTypeDdl() { * of days since the epoch. */ static inline H5::DataType DateTypeDdl() { -#if __cplusplus < 202002L static_assert(sizeof(yardl::Date) == sizeof(int32_t)); static_assert(std::is_same_v); return H5::PredType::NATIVE_INT32; -#else - static_assert(sizeof(yardl::Date) == sizeof(int64_t)); - static_assert(std::is_same_v); - return H5::PredType::NATIVE_INT64; -#endif } /** diff --git a/cpp/evolution/v2/generated/yardl/detail/ndjson/serializers.h b/cpp/evolution/v2/generated/yardl/detail/ndjson/serializers.h index 1bd729d3..db5dcd92 100644 --- a/cpp/evolution/v2/generated/yardl/detail/ndjson/serializers.h +++ b/cpp/evolution/v2/generated/yardl/detail/ndjson/serializers.h @@ -125,25 +125,12 @@ struct adl_serializer { template <> struct adl_serializer { static void to_json(ordered_json& j, yardl::Date const& value) { -#if __cplusplus < 202002L j = date::format("%F", value); -#else - date::local_days ld(value.time_since_epoch()); - j = date::format("%F", ld); -#endif } static void from_json(ordered_json const& j, yardl::Date& value) { std::stringstream ss{j.get()}; - -#if __cplusplus < 202002L ss >> date::parse("%F", value); -#else - date::local_days ld; - ss >> date::parse("%F", ld); - value = yardl::Date(ld.time_since_epoch()); -#endif - if (ss.fail()) { throw std::runtime_error("invalid date format"); } diff --git a/cpp/evolution/v2/generated/yardl/yardl.h b/cpp/evolution/v2/generated/yardl/yardl.h index aa8eb682..4a074ac7 100644 --- a/cpp/evolution/v2/generated/yardl/yardl.h +++ b/cpp/evolution/v2/generated/yardl/yardl.h @@ -5,11 +5,7 @@ #include -#if __cplusplus < 202002L -// This functionality is part of the standard library as of C++20 #include -#endif - #include #include #include @@ -46,17 +42,10 @@ using NDArray = xt::xtensor; template using DynamicNDArray = xt::xarray; -#if __cplusplus < 202002L /** * @brief Represents a date as a number of days since the epoch. */ using Date = date::local_days; -#else -/** - * @brief Represents a date as a number of days since the epoch. - */ -using Date = std::chrono::local_days; -#endif /** * @brief Represents a time of day as the number of nanoseconds since midnight. @@ -66,8 +55,7 @@ using Time = std::chrono::duration; /** * @brief Represents a datetime as the number of nanoseconds since the epoch. */ -using DateTime = std::chrono::time_point>; +using DateTime = std::chrono::time_point; /** * @brief The same as size_t when it is 64 bits, otherwise uint64_t. diff --git a/cpp/test/CMakeLists.txt b/cpp/test/CMakeLists.txt index d5acc703..1aae56b1 100644 --- a/cpp/test/CMakeLists.txt +++ b/cpp/test/CMakeLists.txt @@ -14,8 +14,16 @@ add_executable(tests roundtrip_test.cc ) +# This additional test must be compiled with C++20 to test compatibility with +# the C++17 version of the generated library +add_library(abi_test OBJECT + abi_test.cc +) +set_property(TARGET abi_test PROPERTY CXX_STANDARD 20) + target_link_libraries( tests + abi_test fmt gtest gtest_main diff --git a/cpp/test/abi_test.cc b/cpp/test/abi_test.cc new file mode 100644 index 00000000..554c3e52 --- /dev/null +++ b/cpp/test/abi_test.cc @@ -0,0 +1,26 @@ +#include + +#include + +#include "generated/binary/protocols.h" + +TEST(ABITest, OptionalDate) { + using namespace test_model; + + std::ostringstream oss; + + binary::ProtocolWithOptionalDateWriter w(oss); + RecordWithOptionalDate rec1; + w.WriteRecord(rec1); + w.Close(); + + std::string data = oss.str(); + std::istringstream iss(data); + + binary::ProtocolWithOptionalDateReader r(iss); + std::optional rec2_opt; + r.ReadRecord(rec2_opt); + r.Close(); + + EXPECT_TRUE(rec2_opt.has_value()); +} diff --git a/cpp/test/generated/binary/protocols.cc b/cpp/test/generated/binary/protocols.cc index b8262407..0ee26231 100644 --- a/cpp/test/generated/binary/protocols.cc +++ b/cpp/test/generated/binary/protocols.cc @@ -580,6 +580,15 @@ struct IsTriviallySerializable { offsetof(__T__, int_field) < offsetof(__T__, sizeof_field) && offsetof(__T__, sizeof_field) < offsetof(__T__, if_field); }; +template <> +struct IsTriviallySerializable { + using __T__ = test_model::RecordWithOptionalDate; + static constexpr bool value = + std::is_standard_layout_v<__T__> && + IsTriviallySerializable::value && + (sizeof(__T__) == (sizeof(__T__::date_field))); +}; + #ifndef _MSC_VER #pragma GCC diagnostic pop // #pragma GCC diagnostic ignored "-Winvalid-offsetof" #endif @@ -2694,6 +2703,24 @@ template ReadU, typename V, yardl::binary:: yardl::binary::ReadEnum(stream, value.if_field); } +[[maybe_unused]] void WriteRecordWithOptionalDate(yardl::binary::CodedOutputStream& stream, test_model::RecordWithOptionalDate const& value) { + if constexpr (yardl::binary::IsTriviallySerializable::value) { + yardl::binary::WriteTriviallySerializable(stream, value); + return; + } + + yardl::binary::WriteOptional(stream, value.date_field); +} + +[[maybe_unused]] void ReadRecordWithOptionalDate(yardl::binary::CodedInputStream& stream, test_model::RecordWithOptionalDate& value) { + if constexpr (yardl::binary::IsTriviallySerializable::value) { + yardl::binary::ReadTriviallySerializable(stream, value); + return; + } + + yardl::binary::ReadOptional(stream, value.date_field); +} + } // namespace void BenchmarkFloat256x256Writer::WriteFloat256x256Impl(yardl::FixedNDArray const& value) { @@ -4342,5 +4369,25 @@ void ProtocolWithKeywordStepsReader::CloseImpl() { stream_.VerifyFinished(); } +void ProtocolWithOptionalDateWriter::WriteRecordImpl(std::optional const& value) { + yardl::binary::WriteOptional(stream_, value); +} + +void ProtocolWithOptionalDateWriter::Flush() { + stream_.Flush(); +} + +void ProtocolWithOptionalDateWriter::CloseImpl() { + stream_.Flush(); +} + +void ProtocolWithOptionalDateReader::ReadRecordImpl(std::optional& value) { + yardl::binary::ReadOptional(stream_, value); +} + +void ProtocolWithOptionalDateReader::CloseImpl() { + stream_.VerifyFinished(); +} + } // namespace test_model::binary diff --git a/cpp/test/generated/binary/protocols.h b/cpp/test/generated/binary/protocols.h index f11a7b00..739c962a 100644 --- a/cpp/test/generated/binary/protocols.h +++ b/cpp/test/generated/binary/protocols.h @@ -1496,4 +1496,40 @@ class ProtocolWithKeywordStepsReader : public test_model::ProtocolWithKeywordSte size_t current_block_remaining_ = 0; }; +// Binary writer for the ProtocolWithOptionalDate protocol. +class ProtocolWithOptionalDateWriter : public test_model::ProtocolWithOptionalDateWriterBase, yardl::binary::BinaryWriter { + public: + ProtocolWithOptionalDateWriter(std::ostream& stream, Version version = Version::Current) + : yardl::binary::BinaryWriter(stream, test_model::ProtocolWithOptionalDateWriterBase::SchemaFromVersion(version)), version_(version) {} + + ProtocolWithOptionalDateWriter(std::string file_name, Version version = Version::Current) + : yardl::binary::BinaryWriter(file_name, test_model::ProtocolWithOptionalDateWriterBase::SchemaFromVersion(version)), version_(version) {} + + void Flush() override; + + protected: + void WriteRecordImpl(std::optional const& value) override; + void CloseImpl() override; + + Version version_; +}; + +// Binary reader for the ProtocolWithOptionalDate protocol. +class ProtocolWithOptionalDateReader : public test_model::ProtocolWithOptionalDateReaderBase, yardl::binary::BinaryReader { + public: + ProtocolWithOptionalDateReader(std::istream& stream) + : yardl::binary::BinaryReader(stream), version_(test_model::ProtocolWithOptionalDateReaderBase::VersionFromSchema(schema_read_)) {} + + ProtocolWithOptionalDateReader(std::string file_name) + : yardl::binary::BinaryReader(file_name), version_(test_model::ProtocolWithOptionalDateReaderBase::VersionFromSchema(schema_read_)) {} + + Version GetVersion() { return version_; } + + protected: + void ReadRecordImpl(std::optional& value) override; + void CloseImpl() override; + + Version version_; +}; + } // namespace test_model::binary diff --git a/cpp/test/generated/factories.cc b/cpp/test/generated/factories.cc index 3f038ae1..d21e6c63 100644 --- a/cpp/test/generated/factories.cc +++ b/cpp/test/generated/factories.cc @@ -959,4 +959,32 @@ std::unique_ptr CreateReader +std::unique_ptr CreateWriter(Format format, std::string const& filename) { + switch (format) { + case Format::kHdf5: + return std::make_unique(filename); + case Format::kBinary: + return std::make_unique(filename); + case Format::kNDJson: + return std::make_unique(filename); + default: + throw std::runtime_error("Unknown format"); + } +} + +template<> +std::unique_ptr CreateReader(Format format, std::string const& filename) { + switch (format) { + case Format::kHdf5: + return std::make_unique(filename); + case Format::kBinary: + return std::make_unique(filename); + case Format::kNDJson: + return std::make_unique(filename); + default: + throw std::runtime_error("Unknown format"); + } +} + } diff --git a/cpp/test/generated/hdf5/protocols.cc b/cpp/test/generated/hdf5/protocols.cc index 9fc67f52..fb802534 100644 --- a/cpp/test/generated/hdf5/protocols.cc +++ b/cpp/test/generated/hdf5/protocols.cc @@ -1114,6 +1114,19 @@ struct _Inner_RecordWithKeywordFields { test_model::EnumWithKeywordSymbols if_field; }; +struct _Inner_RecordWithOptionalDate { + _Inner_RecordWithOptionalDate() {} + _Inner_RecordWithOptionalDate(test_model::RecordWithOptionalDate const& o) + : date_field(o.date_field) { + } + + void ToOuter (test_model::RecordWithOptionalDate& o) const { + yardl::hdf5::ToOuter(date_field, o.date_field); + } + + yardl::hdf5::InnerOptional date_field; +}; + [[maybe_unused]] H5::CompType GetSmallBenchmarkRecordHdf5Ddl() { using RecordType = test_model::SmallBenchmarkRecord; H5::CompType t(sizeof(RecordType)); @@ -1543,6 +1556,13 @@ template return t; } +[[maybe_unused]] H5::CompType GetRecordWithOptionalDateHdf5Ddl() { + using RecordType = test_model::hdf5::_Inner_RecordWithOptionalDate; + H5::CompType t(sizeof(RecordType)); + t.insertMember("dateField", HOFFSET(RecordType, date_field), yardl::hdf5::OptionalTypeDdl(yardl::hdf5::DateTypeDdl())); + return t; +} + } // namespace BenchmarkFloat256x256Writer::BenchmarkFloat256x256Writer(std::string path) @@ -3679,5 +3699,21 @@ void ProtocolWithKeywordStepsReader::ReadFloatImpl(test_model::EnumWithKeywordSy yardl::hdf5::ReadScalarDataset(group_, "float", test_model::hdf5::GetEnumWithKeywordSymbolsHdf5Ddl(), value); } +ProtocolWithOptionalDateWriter::ProtocolWithOptionalDateWriter(std::string path) + : yardl::hdf5::Hdf5Writer::Hdf5Writer(path, "ProtocolWithOptionalDate", schema_) { +} + +void ProtocolWithOptionalDateWriter::WriteRecordImpl(std::optional const& value) { + yardl::hdf5::WriteScalarDataset, std::optional>(group_, "record", yardl::hdf5::OptionalTypeDdl(test_model::hdf5::GetRecordWithOptionalDateHdf5Ddl()), value); +} + +ProtocolWithOptionalDateReader::ProtocolWithOptionalDateReader(std::string path) + : yardl::hdf5::Hdf5Reader::Hdf5Reader(path, "ProtocolWithOptionalDate", schema_) { +} + +void ProtocolWithOptionalDateReader::ReadRecordImpl(std::optional& value) { + yardl::hdf5::ReadScalarDataset, std::optional>(group_, "record", yardl::hdf5::OptionalTypeDdl(test_model::hdf5::GetRecordWithOptionalDateHdf5Ddl()), value); +} + } // namespace test_model::hdf5 diff --git a/cpp/test/generated/hdf5/protocols.h b/cpp/test/generated/hdf5/protocols.h index ab19bb93..e52afe99 100644 --- a/cpp/test/generated/hdf5/protocols.h +++ b/cpp/test/generated/hdf5/protocols.h @@ -1185,5 +1185,26 @@ class ProtocolWithKeywordStepsReader : public test_model::ProtocolWithKeywordSte std::unique_ptr int_dataset_state_; }; +// HDF5 writer for the ProtocolWithOptionalDate protocol. +class ProtocolWithOptionalDateWriter : public test_model::ProtocolWithOptionalDateWriterBase, public yardl::hdf5::Hdf5Writer { + public: + ProtocolWithOptionalDateWriter(std::string path); + + protected: + void WriteRecordImpl(std::optional const& value) override; + + private: +}; + +// HDF5 reader for the ProtocolWithOptionalDate protocol. +class ProtocolWithOptionalDateReader : public test_model::ProtocolWithOptionalDateReaderBase, public yardl::hdf5::Hdf5Reader { + public: + ProtocolWithOptionalDateReader(std::string path); + + void ReadRecordImpl(std::optional& value) override; + + private: +}; + } // namespace test_model diff --git a/cpp/test/generated/mocks.cc b/cpp/test/generated/mocks.cc index 3556f281..79d9a17e 100644 --- a/cpp/test/generated/mocks.cc +++ b/cpp/test/generated/mocks.cc @@ -4320,6 +4320,63 @@ class TestProtocolWithKeywordStepsWriterBase : public ProtocolWithKeywordStepsWr MockProtocolWithKeywordStepsWriter mock_writer_; bool close_called_ = false; }; + +class MockProtocolWithOptionalDateWriter : public ProtocolWithOptionalDateWriterBase { + public: + void WriteRecordImpl (std::optional const& value) override { + if (WriteRecordImpl_expected_values_.empty()) { + throw std::runtime_error("Unexpected call to WriteRecordImpl"); + } + if (WriteRecordImpl_expected_values_.front() != value) { + throw std::runtime_error("Unexpected argument value for call to WriteRecordImpl"); + } + WriteRecordImpl_expected_values_.pop(); + } + + std::queue> WriteRecordImpl_expected_values_; + + void ExpectWriteRecordImpl (std::optional const& value) { + WriteRecordImpl_expected_values_.push(value); + } + + void Verify() { + if (!WriteRecordImpl_expected_values_.empty()) { + throw std::runtime_error("Expected call to WriteRecordImpl was not received"); + } + } +}; + +class TestProtocolWithOptionalDateWriterBase : public ProtocolWithOptionalDateWriterBase { + public: + TestProtocolWithOptionalDateWriterBase(std::unique_ptr writer, std::function()> create_reader) : writer_(std::move(writer)), create_reader_(create_reader) { + } + + ~TestProtocolWithOptionalDateWriterBase() { + if (!close_called_ && !std::uncaught_exceptions()) { + ADD_FAILURE() << "Close() needs to be called on 'TestProtocolWithOptionalDateWriterBase' to verify mocks"; + } + } + + protected: + void WriteRecordImpl(std::optional const& value) override { + writer_->WriteRecord(value); + mock_writer_.ExpectWriteRecordImpl(value); + } + + void CloseImpl() override { + close_called_ = true; + writer_->Close(); + std::unique_ptr reader = create_reader_(); + reader->CopyTo(mock_writer_); + mock_writer_.Verify(); + } + + private: + std::unique_ptr writer_; + std::function()> create_reader_; + MockProtocolWithOptionalDateWriter mock_writer_; + bool close_called_ = false; +}; } // namespace } // namespace test_model @@ -4596,4 +4653,12 @@ std::unique_ptr CreateValidating ); } +template<> +std::unique_ptr CreateValidatingWriter(Format format, std::string const& filename) { + return std::make_unique( + CreateWriter(format, filename), + [format, filename](){ return CreateReader(format, filename);} + ); +} + } diff --git a/cpp/test/generated/model.json b/cpp/test/generated/model.json index 4f6dcebe..d28eddd0 100644 --- a/cpp/test/generated/model.json +++ b/cpp/test/generated/model.json @@ -4096,6 +4096,20 @@ } ] } + }, + { + "record": { + "name": "RecordWithOptionalDate", + "fields": [ + { + "name": "dateField", + "type": [ + null, + "date" + ] + } + ] + } } ], "protocols": [ @@ -5365,6 +5379,18 @@ "type": "TestModel.EnumWithKeywordSymbols" } ] + }, + { + "name": "ProtocolWithOptionalDate", + "sequence": [ + { + "name": "record", + "type": [ + null, + "TestModel.RecordWithOptionalDate" + ] + } + ] } ] } diff --git a/cpp/test/generated/ndjson/protocols.cc b/cpp/test/generated/ndjson/protocols.cc index bd02b87c..905ff8f8 100644 --- a/cpp/test/generated/ndjson/protocols.cc +++ b/cpp/test/generated/ndjson/protocols.cc @@ -200,6 +200,9 @@ void from_json(ordered_json const& j, test_model::EnumWithKeywordSymbols& value) void to_json(ordered_json& j, test_model::RecordWithKeywordFields const& value); void from_json(ordered_json const& j, test_model::RecordWithKeywordFields& value); +void to_json(ordered_json& j, test_model::RecordWithOptionalDate const& value); +void from_json(ordered_json const& j, test_model::RecordWithOptionalDate& value); + } // namespace test_model NLOHMANN_JSON_NAMESPACE_BEGIN @@ -2593,6 +2596,19 @@ void from_json(ordered_json const& j, test_model::RecordWithKeywordFields& value } } +void to_json(ordered_json& j, test_model::RecordWithOptionalDate const& value) { + j = ordered_json::object(); + if (yardl::ndjson::ShouldSerializeFieldValue(value.date_field)) { + j.push_back({"dateField", value.date_field}); + } +} + +void from_json(ordered_json const& j, test_model::RecordWithOptionalDate& value) { + if (auto it = j.find("dateField"); it != j.end()) { + it->get_to(value.date_field); + } +} + } // namespace test_model namespace test_model::ndjson { @@ -3868,5 +3884,25 @@ void ProtocolWithKeywordStepsReader::CloseImpl() { VerifyFinished(); } +void ProtocolWithOptionalDateWriter::WriteRecordImpl(std::optional const& value) { + ordered_json json_value = value; + yardl::ndjson::WriteProtocolValue(stream_, "record", json_value);} + +void ProtocolWithOptionalDateWriter::Flush() { + stream_.flush(); +} + +void ProtocolWithOptionalDateWriter::CloseImpl() { + stream_.flush(); +} + +void ProtocolWithOptionalDateReader::ReadRecordImpl(std::optional& value) { + yardl::ndjson::ReadProtocolValue(stream_, line_, "record", true, unused_step_, value); +} + +void ProtocolWithOptionalDateReader::CloseImpl() { + VerifyFinished(); +} + } // namespace test_model::ndjson diff --git a/cpp/test/generated/ndjson/protocols.h b/cpp/test/generated/ndjson/protocols.h index 5a336a26..67f9c027 100644 --- a/cpp/test/generated/ndjson/protocols.h +++ b/cpp/test/generated/ndjson/protocols.h @@ -1339,4 +1339,38 @@ class ProtocolWithKeywordStepsReader : public test_model::ProtocolWithKeywordSte void CloseImpl() override; }; +// NDJSON writer for the ProtocolWithOptionalDate protocol. +class ProtocolWithOptionalDateWriter : public test_model::ProtocolWithOptionalDateWriterBase, yardl::ndjson::NDJsonWriter { + public: + ProtocolWithOptionalDateWriter(std::ostream& stream) + : yardl::ndjson::NDJsonWriter(stream, schema_) { + } + + ProtocolWithOptionalDateWriter(std::string file_name) + : yardl::ndjson::NDJsonWriter(file_name, schema_) { + } + + void Flush() override; + + protected: + void WriteRecordImpl(std::optional const& value) override; + void CloseImpl() override; +}; + +// NDJSON reader for the ProtocolWithOptionalDate protocol. +class ProtocolWithOptionalDateReader : public test_model::ProtocolWithOptionalDateReaderBase, yardl::ndjson::NDJsonReader { + public: + ProtocolWithOptionalDateReader(std::istream& stream) + : yardl::ndjson::NDJsonReader(stream, schema_) { + } + + ProtocolWithOptionalDateReader(std::string file_name) + : yardl::ndjson::NDJsonReader(file_name, schema_) { + } + + protected: + void ReadRecordImpl(std::optional& value) override; + void CloseImpl() override; +}; + } // namespace test_model::ndjson diff --git a/cpp/test/generated/protocols.cc b/cpp/test/generated/protocols.cc index 2375176b..72e4d661 100644 --- a/cpp/test/generated/protocols.cc +++ b/cpp/test/generated/protocols.cc @@ -6802,4 +6802,94 @@ void ProtocolWithKeywordStepsReaderBase::CopyTo(ProtocolWithKeywordStepsWriterBa writer.WriteFloat(value); } } + +namespace { +void ProtocolWithOptionalDateWriterBaseInvalidState(uint8_t attempted, [[maybe_unused]] bool end, uint8_t current) { + std::string expected_method; + switch (current) { + case 0: expected_method = "WriteRecord()"; break; + } + std::string attempted_method; + switch (attempted) { + case 0: attempted_method = "WriteRecord()"; break; + case 1: attempted_method = "Close()"; break; + } + throw std::runtime_error("Expected call to " + expected_method + " but received call to " + attempted_method + " instead."); +} + +void ProtocolWithOptionalDateReaderBaseInvalidState(uint8_t attempted, uint8_t current) { + auto f = [](uint8_t i) -> std::string { + switch (i/2) { + case 0: return "ReadRecord()"; + case 1: return "Close()"; + default: return ""; + } + }; + throw std::runtime_error("Expected call to " + f(current) + " but received call to " + f(attempted) + " instead."); +} + +} // namespace + +std::string ProtocolWithOptionalDateWriterBase::schema_ = R"({"protocol":{"name":"ProtocolWithOptionalDate","sequence":[{"name":"record","type":[null,"TestModel.RecordWithOptionalDate"]}]},"types":[{"name":"RecordWithOptionalDate","fields":[{"name":"dateField","type":[null,"date"]}]}]})"; + +std::vector ProtocolWithOptionalDateWriterBase::previous_schemas_ = { +}; + +std::string ProtocolWithOptionalDateWriterBase::SchemaFromVersion(Version version) { + switch (version) { + case Version::Current: return ProtocolWithOptionalDateWriterBase::schema_; break; + default: throw std::runtime_error("The version does not correspond to any schema supported by protocol ProtocolWithOptionalDate."); + } + +} +void ProtocolWithOptionalDateWriterBase::WriteRecord(std::optional const& value) { + if (unlikely(state_ != 0)) { + ProtocolWithOptionalDateWriterBaseInvalidState(0, false, state_); + } + + WriteRecordImpl(value); + state_ = 1; +} + +void ProtocolWithOptionalDateWriterBase::Close() { + if (unlikely(state_ != 1)) { + ProtocolWithOptionalDateWriterBaseInvalidState(1, false, state_); + } + + CloseImpl(); +} + +std::string ProtocolWithOptionalDateReaderBase::schema_ = ProtocolWithOptionalDateWriterBase::schema_; + +std::vector ProtocolWithOptionalDateReaderBase::previous_schemas_ = ProtocolWithOptionalDateWriterBase::previous_schemas_; + +Version ProtocolWithOptionalDateReaderBase::VersionFromSchema(std::string const& schema) { + if (schema == ProtocolWithOptionalDateWriterBase::schema_) { + return Version::Current; + } + throw std::runtime_error("The schema does not match any version supported by protocol ProtocolWithOptionalDate."); +} +void ProtocolWithOptionalDateReaderBase::ReadRecord(std::optional& value) { + if (unlikely(state_ != 0)) { + ProtocolWithOptionalDateReaderBaseInvalidState(0, state_); + } + + ReadRecordImpl(value); + state_ = 2; +} + +void ProtocolWithOptionalDateReaderBase::Close() { + if (unlikely(state_ != 2)) { + ProtocolWithOptionalDateReaderBaseInvalidState(2, state_); + } + + CloseImpl(); +} +void ProtocolWithOptionalDateReaderBase::CopyTo(ProtocolWithOptionalDateWriterBase& writer) { + { + std::optional value; + ReadRecord(value); + writer.WriteRecord(value); + } +} } // namespace test_model diff --git a/cpp/test/generated/protocols.h b/cpp/test/generated/protocols.h index c8c3edb8..de067e7b 100644 --- a/cpp/test/generated/protocols.h +++ b/cpp/test/generated/protocols.h @@ -2810,4 +2810,60 @@ class ProtocolWithKeywordStepsReaderBase { private: uint8_t state_ = 0; }; + +// Abstract writer for the ProtocolWithOptionalDate protocol. +class ProtocolWithOptionalDateWriterBase { + public: + // Ordinal 0. + void WriteRecord(std::optional const& value); + + // Optionaly close this writer before destructing. Validates that all steps were completed. + void Close(); + + virtual ~ProtocolWithOptionalDateWriterBase() = default; + + // Flushes all buffered data. + virtual void Flush() {} + + protected: + virtual void WriteRecordImpl(std::optional const& value) = 0; + virtual void CloseImpl() {} + + static std::string schema_; + + static std::vector previous_schemas_; + + static std::string SchemaFromVersion(Version version); + + private: + uint8_t state_ = 0; + + friend class ProtocolWithOptionalDateReaderBase; +}; + +// Abstract reader for the ProtocolWithOptionalDate protocol. +class ProtocolWithOptionalDateReaderBase { + public: + // Ordinal 0. + void ReadRecord(std::optional& value); + + // Optionaly close this writer before destructing. Validates that all steps were completely read. + void Close(); + + void CopyTo(ProtocolWithOptionalDateWriterBase& writer); + + virtual ~ProtocolWithOptionalDateReaderBase() = default; + + protected: + virtual void ReadRecordImpl(std::optional& value) = 0; + virtual void CloseImpl() {} + static std::string schema_; + + static std::vector previous_schemas_; + + static Version VersionFromSchema(const std::string& schema); + + private: + uint8_t state_ = 0; +}; } // namespace test_model diff --git a/cpp/test/generated/translator_impl.cc b/cpp/test/generated/translator_impl.cc index 0fbdfd1f..63fdd49f 100644 --- a/cpp/test/generated/translator_impl.cc +++ b/cpp/test/generated/translator_impl.cc @@ -392,6 +392,17 @@ void TranslateStream(std::string const& protocol_name, yardl::testing::Format in reader->CopyTo(*writer); return; } + if (protocol_name == "ProtocolWithOptionalDate") { + auto reader = input_format == yardl::testing::Format::kBinary + ? std::unique_ptr(new test_model::binary::ProtocolWithOptionalDateReader(input)) + : std::unique_ptr(new test_model::ndjson::ProtocolWithOptionalDateReader(input)); + + auto writer = output_format == yardl::testing::Format::kBinary + ? std::unique_ptr(new test_model::binary::ProtocolWithOptionalDateWriter(output)) + : std::unique_ptr(new test_model::ndjson::ProtocolWithOptionalDateWriter(output)); + reader->CopyTo(*writer); + return; + } throw std::runtime_error("Unsupported protocol " + protocol_name); } } // namespace yardl::testing diff --git a/cpp/test/generated/types.h b/cpp/test/generated/types.h index 35a59b83..bd462697 100644 --- a/cpp/test/generated/types.h +++ b/cpp/test/generated/types.h @@ -1451,5 +1451,17 @@ struct RecordWithKeywordFields { } }; +struct RecordWithOptionalDate { + std::optional date_field{}; + + bool operator==(const RecordWithOptionalDate& other) const { + return date_field == other.date_field; + } + + bool operator!=(const RecordWithOptionalDate& other) const { + return !(*this == other); + } +}; + } // namespace test_model diff --git a/cpp/test/roundtrip_test.cc b/cpp/test/roundtrip_test.cc index b5449451..2916aa3c 100644 --- a/cpp/test/roundtrip_test.cc +++ b/cpp/test/roundtrip_test.cc @@ -11,11 +11,7 @@ using namespace test_model; using namespace yardl; using namespace yardl::testing; -#if __cplusplus < 202002L using year = date::year; -#else -using year = std::chrono::year; -#endif namespace { diff --git a/matlab/generated/+test_model/+binary/ProtocolWithOptionalDateReader.m b/matlab/generated/+test_model/+binary/ProtocolWithOptionalDateReader.m new file mode 100644 index 00000000..1c1830bc --- /dev/null +++ b/matlab/generated/+test_model/+binary/ProtocolWithOptionalDateReader.m @@ -0,0 +1,22 @@ +% This file was generated by the "yardl" tool. DO NOT EDIT. + +classdef ProtocolWithOptionalDateReader < yardl.binary.BinaryProtocolReader & test_model.ProtocolWithOptionalDateReaderBase + % Binary reader for the ProtocolWithOptionalDate protocol + properties (Access=protected) + record_serializer + end + + methods + function self = ProtocolWithOptionalDateReader(filename) + self@test_model.ProtocolWithOptionalDateReaderBase(); + self@yardl.binary.BinaryProtocolReader(filename, test_model.ProtocolWithOptionalDateReaderBase.schema); + self.record_serializer = yardl.binary.OptionalSerializer(test_model.binary.RecordWithOptionalDateSerializer()); + end + end + + methods (Access=protected) + function value = read_record_(self) + value = self.record_serializer.read(self.stream_); + end + end +end diff --git a/matlab/generated/+test_model/+binary/ProtocolWithOptionalDateWriter.m b/matlab/generated/+test_model/+binary/ProtocolWithOptionalDateWriter.m new file mode 100644 index 00000000..7c0705b2 --- /dev/null +++ b/matlab/generated/+test_model/+binary/ProtocolWithOptionalDateWriter.m @@ -0,0 +1,22 @@ +% This file was generated by the "yardl" tool. DO NOT EDIT. + +classdef ProtocolWithOptionalDateWriter < yardl.binary.BinaryProtocolWriter & test_model.ProtocolWithOptionalDateWriterBase + % Binary writer for the ProtocolWithOptionalDate protocol + properties (Access=protected) + record_serializer + end + + methods + function self = ProtocolWithOptionalDateWriter(filename) + self@test_model.ProtocolWithOptionalDateWriterBase(); + self@yardl.binary.BinaryProtocolWriter(filename, test_model.ProtocolWithOptionalDateWriterBase.schema); + self.record_serializer = yardl.binary.OptionalSerializer(test_model.binary.RecordWithOptionalDateSerializer()); + end + end + + methods (Access=protected) + function write_record_(self, value) + self.record_serializer.write(self.stream_, value); + end + end +end diff --git a/matlab/generated/+test_model/+binary/RecordWithOptionalDateSerializer.m b/matlab/generated/+test_model/+binary/RecordWithOptionalDateSerializer.m new file mode 100644 index 00000000..71ff50ee --- /dev/null +++ b/matlab/generated/+test_model/+binary/RecordWithOptionalDateSerializer.m @@ -0,0 +1,24 @@ +% This file was generated by the "yardl" tool. DO NOT EDIT. + +classdef RecordWithOptionalDateSerializer < yardl.binary.RecordSerializer + methods + function self = RecordWithOptionalDateSerializer() + field_serializers{1} = yardl.binary.OptionalSerializer(yardl.binary.DateSerializer); + self@yardl.binary.RecordSerializer('test_model.RecordWithOptionalDate', field_serializers); + end + + function write(self, outstream, value) + arguments + self + outstream (1,1) yardl.binary.CodedOutputStream + value (1,1) test_model.RecordWithOptionalDate + end + self.write_(outstream, value.date_field); + end + + function value = read(self, instream) + fields = self.read_(instream); + value = test_model.RecordWithOptionalDate(date_field=fields{1}); + end + end +end diff --git a/matlab/generated/+test_model/+testing/MockProtocolWithOptionalDateWriter.m b/matlab/generated/+test_model/+testing/MockProtocolWithOptionalDateWriter.m new file mode 100644 index 00000000..f51855f1 --- /dev/null +++ b/matlab/generated/+test_model/+testing/MockProtocolWithOptionalDateWriter.m @@ -0,0 +1,36 @@ +% This file was generated by the "yardl" tool. DO NOT EDIT. + +classdef MockProtocolWithOptionalDateWriter < matlab.mixin.Copyable & test_model.ProtocolWithOptionalDateWriterBase + properties + testCase_ + expected_record + end + + methods + function self = MockProtocolWithOptionalDateWriter(testCase) + self.testCase_ = testCase; + self.expected_record = yardl.None; + end + + function expect_write_record_(self, value) + self.expected_record = yardl.Optional(value); + end + + function verify(self) + self.testCase_.verifyEqual(self.expected_record, yardl.None, "Expected call to write_record_ was not received"); + end + end + + methods (Access=protected) + function write_record_(self, value) + self.testCase_.verifyTrue(self.expected_record.has_value(), "Unexpected call to write_record_"); + self.testCase_.verifyEqual(value, self.expected_record.value, "Unexpected argument value for call to write_record_"); + self.expected_record = yardl.None; + end + + function close_(self) + end + function end_stream_(self) + end + end +end diff --git a/matlab/generated/+test_model/+testing/TestProtocolWithOptionalDateWriter.m b/matlab/generated/+test_model/+testing/TestProtocolWithOptionalDateWriter.m new file mode 100644 index 00000000..f5fd372f --- /dev/null +++ b/matlab/generated/+test_model/+testing/TestProtocolWithOptionalDateWriter.m @@ -0,0 +1,61 @@ +% This file was generated by the "yardl" tool. DO NOT EDIT. + +classdef TestProtocolWithOptionalDateWriter < test_model.ProtocolWithOptionalDateWriterBase + properties (Access = private) + writer_ + create_reader_ + mock_writer_ + close_called_ + filename_ + format_ + end + + methods + function self = TestProtocolWithOptionalDateWriter(testCase, format, create_writer, create_reader) + self.filename_ = tempname(); + self.format_ = format; + self.writer_ = create_writer(self.filename_); + self.create_reader_ = create_reader; + self.mock_writer_ = test_model.testing.MockProtocolWithOptionalDateWriter(testCase); + self.close_called_ = false; + end + + function delete(self) + delete(self.filename_); + if ~self.close_called_ + % ADD_FAILURE() << ...; + throw(yardl.RuntimeError("Close() must be called on 'TestProtocolWithOptionalDateWriter' to verify mocks")); + end + end + end + + methods (Access=protected) + function write_record_(self, value) + self.writer_.write_record(value); + self.mock_writer_.expect_write_record_(value); + end + + function close_(self) + self.close_called_ = true; + self.writer_.close(); + mock_copy = copy(self.mock_writer_); + + reader = self.create_reader_(self.filename_); + reader.copy_to(self.mock_writer_); + reader.close(); + self.mock_writer_.verify(); + self.mock_writer_.close(); + + translated = invoke_translator(self.filename_, self.format_, self.format_); + reader = self.create_reader_(translated); + reader.copy_to(mock_copy); + reader.close(); + mock_copy.verify(); + mock_copy.close(); + delete(translated); + end + + function end_stream_(self) + end + end +end diff --git a/matlab/generated/+test_model/ProtocolWithOptionalDateReaderBase.m b/matlab/generated/+test_model/ProtocolWithOptionalDateReaderBase.m new file mode 100644 index 00000000..32f1b914 --- /dev/null +++ b/matlab/generated/+test_model/ProtocolWithOptionalDateReaderBase.m @@ -0,0 +1,63 @@ +% This file was generated by the "yardl" tool. DO NOT EDIT. + +classdef ProtocolWithOptionalDateReaderBase < handle + properties (Access=protected) + state_ + end + + methods + function self = ProtocolWithOptionalDateReaderBase() + self.state_ = 0; + end + + function close(self) + self.close_(); + if self.state_ ~= 1 + expected_method = self.state_to_method_name_(self.state_); + throw(yardl.ProtocolError("Protocol reader closed before all data was consumed. Expected call to '%s'.", expected_method)); + end + end + + % Ordinal 0 + function value = read_record(self) + if self.state_ ~= 0 + self.raise_unexpected_state_(0); + end + + value = self.read_record_(); + self.state_ = 1; + end + + function copy_to(self, writer) + writer.write_record(self.read_record()); + end + end + + methods (Static) + function res = schema() + res = test_model.ProtocolWithOptionalDateWriterBase.schema; + end + end + + methods (Abstract, Access=protected) + read_record_(self) + + close_(self) + end + + methods (Access=private) + function raise_unexpected_state_(self, actual) + actual_method = self.state_to_method_name_(actual); + expected_method = self.state_to_method_name_(self.state_); + throw(yardl.ProtocolError("Expected call to '%s' but received call to '%s'.", expected_method, actual_method)); + end + + function name = state_to_method_name_(self, state) + if state == 0 + name = "read_record"; + else + name = ""; + end + end + end +end diff --git a/matlab/generated/+test_model/ProtocolWithOptionalDateWriterBase.m b/matlab/generated/+test_model/ProtocolWithOptionalDateWriterBase.m new file mode 100644 index 00000000..a70cc40b --- /dev/null +++ b/matlab/generated/+test_model/ProtocolWithOptionalDateWriterBase.m @@ -0,0 +1,61 @@ +% This file was generated by the "yardl" tool. DO NOT EDIT. + +% Abstract writer for protocol ProtocolWithOptionalDate +classdef (Abstract) ProtocolWithOptionalDateWriterBase < handle + properties (Access=protected) + state_ + end + + methods + function self = ProtocolWithOptionalDateWriterBase() + self.state_ = 0; + end + + function close(self) + self.close_(); + if self.state_ ~= 1 + expected_method = self.state_to_method_name_(self.state_); + throw(yardl.ProtocolError("Protocol writer closed before all steps were called. Expected call to '%s'.", expected_method)); + end + end + + % Ordinal 0 + function write_record(self, value) + if self.state_ ~= 0 + self.raise_unexpected_state_(0); + end + + self.write_record_(value); + self.state_ = 1; + end + end + + methods (Static) + function res = schema() + res = string('{"protocol":{"name":"ProtocolWithOptionalDate","sequence":[{"name":"record","type":[null,"TestModel.RecordWithOptionalDate"]}]},"types":[{"name":"RecordWithOptionalDate","fields":[{"name":"dateField","type":[null,"date"]}]}]}'); + end + end + + methods (Abstract, Access=protected) + write_record_(self, value) + + end_stream_(self) + close_(self) + end + + methods (Access=private) + function raise_unexpected_state_(self, actual) + expected_method = self.state_to_method_name_(self.state_); + actual_method = self.state_to_method_name_(actual); + throw(yardl.ProtocolError("Expected call to '%s' but received call to '%s'", expected_method, actual_method)); + end + + function name = state_to_method_name_(self, state) + if state == 0 + name = "write_record"; + else + name = ''; + end + end + end +end diff --git a/matlab/generated/+test_model/RecordWithOptionalDate.m b/matlab/generated/+test_model/RecordWithOptionalDate.m new file mode 100644 index 00000000..f888cf9b --- /dev/null +++ b/matlab/generated/+test_model/RecordWithOptionalDate.m @@ -0,0 +1,41 @@ +% This file was generated by the "yardl" tool. DO NOT EDIT. + +classdef RecordWithOptionalDate < handle + properties + date_field + end + + methods + function self = RecordWithOptionalDate(kwargs) + arguments + kwargs.date_field = yardl.None; + end + self.date_field = kwargs.date_field; + end + + function res = eq(self, other) + res = ... + isa(other, "test_model.RecordWithOptionalDate") && ... + isequal(self.date_field, other.date_field); + end + + function res = ne(self, other) + res = ~self.eq(other); + end + end + + methods (Static) + function z = zeros(varargin) + elem = test_model.RecordWithOptionalDate(); + if nargin == 0 + z = elem; + return; + end + sz = [varargin{:}]; + if isscalar(sz) + sz = [sz, sz]; + end + z = reshape(repelem(elem, prod(sz)), sz); + end + end +end diff --git a/models/test/unittests.yml b/models/test/unittests.yml index 599f3ea6..778e34cc 100644 --- a/models/test/unittests.yml +++ b/models/test/unittests.yml @@ -729,3 +729,12 @@ ProtocolWithKeywordSteps: !protocol items: RecordWithKeywordFields float: EnumWithKeywordSymbols # END delibrately using C++ keywords and macros as identitiers + + +RecordWithOptionalDate: !record + fields: + dateField: date? + +ProtocolWithOptionalDate: !protocol + sequence: + record: RecordWithOptionalDate? diff --git a/python/test_model/__init__.py b/python/test_model/__init__.py index 25ff324e..1b993eea 100644 --- a/python/test_model/__init__.py +++ b/python/test_model/__init__.py @@ -92,6 +92,7 @@ def _parse_version(version: str) -> _Tuple[int, ...]: RecordWithNDArrays, RecordWithNDArraysSingleDimension, RecordWithNamedFixedArrays, + RecordWithOptionalDate, RecordWithOptionalFields, RecordWithOptionalGenericField, RecordWithOptionalGenericUnionField, @@ -166,6 +167,8 @@ def _parse_version(version: str) -> _Tuple[int, ...]: ProtocolWithComputedFieldsWriterBase, ProtocolWithKeywordStepsReaderBase, ProtocolWithKeywordStepsWriterBase, + ProtocolWithOptionalDateReaderBase, + ProtocolWithOptionalDateWriterBase, ScalarOptionalsReaderBase, ScalarOptionalsWriterBase, ScalarsReaderBase, @@ -236,6 +239,8 @@ def _parse_version(version: str) -> _Tuple[int, ...]: BinaryProtocolWithComputedFieldsWriter, BinaryProtocolWithKeywordStepsReader, BinaryProtocolWithKeywordStepsWriter, + BinaryProtocolWithOptionalDateReader, + BinaryProtocolWithOptionalDateWriter, BinaryScalarOptionalsReader, BinaryScalarOptionalsWriter, BinaryScalarsReader, @@ -306,6 +311,8 @@ def _parse_version(version: str) -> _Tuple[int, ...]: NDJsonProtocolWithComputedFieldsWriter, NDJsonProtocolWithKeywordStepsReader, NDJsonProtocolWithKeywordStepsWriter, + NDJsonProtocolWithOptionalDateReader, + NDJsonProtocolWithOptionalDateWriter, NDJsonScalarOptionalsReader, NDJsonScalarOptionalsWriter, NDJsonScalarsReader, diff --git a/python/test_model/binary.py b/python/test_model/binary.py index 1dd4085d..60df44a0 100644 --- a/python/test_model/binary.py +++ b/python/test_model/binary.py @@ -1245,6 +1245,29 @@ def _read_int(self) -> collections.abc.Iterable[RecordWithKeywordFields]: def _read_float(self) -> EnumWithKeywordSymbols: return _binary.EnumSerializer(_binary.int32_serializer, EnumWithKeywordSymbols).read(self._stream) +class BinaryProtocolWithOptionalDateWriter(_binary.BinaryProtocolWriter, ProtocolWithOptionalDateWriterBase): + """Binary writer for the ProtocolWithOptionalDate protocol.""" + + + def __init__(self, stream: typing.Union[typing.BinaryIO, str]) -> None: + ProtocolWithOptionalDateWriterBase.__init__(self) + _binary.BinaryProtocolWriter.__init__(self, stream, ProtocolWithOptionalDateWriterBase.schema) + + def _write_record(self, value: typing.Optional[RecordWithOptionalDate]) -> None: + _binary.OptionalSerializer(RecordWithOptionalDateSerializer()).write(self._stream, value) + + +class BinaryProtocolWithOptionalDateReader(_binary.BinaryProtocolReader, ProtocolWithOptionalDateReaderBase): + """Binary writer for the ProtocolWithOptionalDate protocol.""" + + + def __init__(self, stream: typing.Union[io.BufferedReader, io.BytesIO, typing.BinaryIO, str]) -> None: + ProtocolWithOptionalDateReaderBase.__init__(self) + _binary.BinaryProtocolReader.__init__(self, stream, ProtocolWithOptionalDateReaderBase.schema) + + def _read_record(self) -> typing.Optional[RecordWithOptionalDate]: + return _binary.OptionalSerializer(RecordWithOptionalDateSerializer()).read(self._stream) + class SmallBenchmarkRecordSerializer(_binary.RecordSerializer[SmallBenchmarkRecord]): def __init__(self) -> None: super().__init__([("a", _binary.float64_serializer), ("b", _binary.float32_serializer), ("c", _binary.float32_serializer)]) @@ -1983,3 +2006,21 @@ def read(self, stream: _binary.CodedInputStream) -> RecordWithKeywordFields: return RecordWithKeywordFields(int_=field_values[0], sizeof=field_values[1], if_=field_values[2]) +class RecordWithOptionalDateSerializer(_binary.RecordSerializer[RecordWithOptionalDate]): + def __init__(self) -> None: + super().__init__([("date_field", _binary.OptionalSerializer(_binary.date_serializer))]) + + def write(self, stream: _binary.CodedOutputStream, value: RecordWithOptionalDate) -> None: + if isinstance(value, np.void): + self.write_numpy(stream, value) + return + self._write(stream, value.date_field) + + def write_numpy(self, stream: _binary.CodedOutputStream, value: np.void) -> None: + self._write(stream, value['date_field']) + + def read(self, stream: _binary.CodedInputStream) -> RecordWithOptionalDate: + field_values = self._read(stream) + return RecordWithOptionalDate(date_field=field_values[0]) + + diff --git a/python/test_model/ndjson.py b/python/test_model/ndjson.py index 91a8168f..5b44daa8 100644 --- a/python/test_model/ndjson.py +++ b/python/test_model/ndjson.py @@ -2420,6 +2420,46 @@ def from_json_to_numpy(self, json_object: object) -> np.void: ) # type:ignore +class RecordWithOptionalDateConverter(_ndjson.JsonConverter[RecordWithOptionalDate, np.void]): + def __init__(self) -> None: + self._date_field_converter = _ndjson.OptionalConverter(_ndjson.date_converter) + super().__init__(np.dtype([ + ("date_field", self._date_field_converter.overall_dtype()), + ])) + + def to_json(self, value: RecordWithOptionalDate) -> object: + if not isinstance(value, RecordWithOptionalDate): # pyright: ignore [reportUnnecessaryIsInstance] + raise TypeError("Expected 'RecordWithOptionalDate' instance") + json_object = {} + + if value.date_field is not None: + json_object["dateField"] = self._date_field_converter.to_json(value.date_field) + return json_object + + def numpy_to_json(self, value: np.void) -> object: + if not isinstance(value, np.void): # pyright: ignore [reportUnnecessaryIsInstance] + raise TypeError("Expected 'np.void' instance") + json_object = {} + + if (field_val := value["date_field"]) is not None: + json_object["dateField"] = self._date_field_converter.numpy_to_json(field_val) + return json_object + + def from_json(self, json_object: object) -> RecordWithOptionalDate: + if not isinstance(json_object, dict): + raise TypeError("Expected 'dict' instance") + return RecordWithOptionalDate( + date_field=self._date_field_converter.from_json(json_object.get("dateField")), + ) + + def from_json_to_numpy(self, json_object: object) -> np.void: + if not isinstance(json_object, dict): + raise TypeError("Expected 'dict' instance") + return ( + self._date_field_converter.from_json_to_numpy(json_object.get("dateField")), + ) # type:ignore + + class NDJsonBenchmarkFloat256x256Writer(_ndjson.NDJsonProtocolWriter, BenchmarkFloat256x256WriterBase): """NDJson writer for the BenchmarkFloat256x256 protocol.""" @@ -4100,3 +4140,30 @@ def _read_float(self) -> EnumWithKeywordSymbols: converter = _ndjson.EnumConverter(EnumWithKeywordSymbols, np.int32, enum_with_keyword_symbols_name_to_value_map, enum_with_keyword_symbols_value_to_name_map) return converter.from_json(json_object) +class NDJsonProtocolWithOptionalDateWriter(_ndjson.NDJsonProtocolWriter, ProtocolWithOptionalDateWriterBase): + """NDJson writer for the ProtocolWithOptionalDate protocol.""" + + + def __init__(self, stream: typing.Union[typing.TextIO, str]) -> None: + ProtocolWithOptionalDateWriterBase.__init__(self) + _ndjson.NDJsonProtocolWriter.__init__(self, stream, ProtocolWithOptionalDateWriterBase.schema) + + def _write_record(self, value: typing.Optional[RecordWithOptionalDate]) -> None: + converter = _ndjson.OptionalConverter(RecordWithOptionalDateConverter()) + json_value = converter.to_json(value) + self._write_json_line({"record": json_value}) + + +class NDJsonProtocolWithOptionalDateReader(_ndjson.NDJsonProtocolReader, ProtocolWithOptionalDateReaderBase): + """NDJson writer for the ProtocolWithOptionalDate protocol.""" + + + def __init__(self, stream: typing.Union[io.BufferedReader, typing.TextIO, str]) -> None: + ProtocolWithOptionalDateReaderBase.__init__(self) + _ndjson.NDJsonProtocolReader.__init__(self, stream, ProtocolWithOptionalDateReaderBase.schema) + + def _read_record(self) -> typing.Optional[RecordWithOptionalDate]: + json_object = self._read_json_line("record", True) + converter = _ndjson.OptionalConverter(RecordWithOptionalDateConverter()) + return converter.from_json(json_object) + diff --git a/python/test_model/protocols.py b/python/test_model/protocols.py index 778b8c71..6a5b57de 100644 --- a/python/test_model/protocols.py +++ b/python/test_model/protocols.py @@ -6771,3 +6771,129 @@ def _state_to_method_name(self, state: int) -> str: return 'read_float' return "" +class ProtocolWithOptionalDateWriterBase(abc.ABC): + """Abstract writer for the ProtocolWithOptionalDate protocol.""" + + + def __init__(self) -> None: + self._state = 0 + + schema = r"""{"protocol":{"name":"ProtocolWithOptionalDate","sequence":[{"name":"record","type":[null,"TestModel.RecordWithOptionalDate"]}]},"types":[{"name":"RecordWithOptionalDate","fields":[{"name":"dateField","type":[null,"date"]}]}]}""" + + def close(self) -> None: + self._close() + if self._state != 2: + expected_method = self._state_to_method_name((self._state + 1) & ~1) + raise ProtocolError(f"Protocol writer closed before all steps were called. Expected to call to '{expected_method}'.") + + def __enter__(self): + return self + + def __exit__(self, exc_type: typing.Optional[type[BaseException]], exc: typing.Optional[BaseException], traceback: object) -> None: + try: + self.close() + except Exception as e: + if exc is None: + raise e + + def write_record(self, value: typing.Optional[RecordWithOptionalDate]) -> None: + """Ordinal 0""" + + if self._state != 0: + self._raise_unexpected_state(0) + + self._write_record(value) + self._state = 2 + + @abc.abstractmethod + def _write_record(self, value: typing.Optional[RecordWithOptionalDate]) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def _close(self) -> None: + pass + + @abc.abstractmethod + def _end_stream(self) -> None: + pass + + def _raise_unexpected_state(self, actual: int) -> None: + expected_method = self._state_to_method_name(self._state) + actual_method = self._state_to_method_name(actual) + raise ProtocolError(f"Expected to call to '{expected_method}' but received call to '{actual_method}'.") + + def _state_to_method_name(self, state: int) -> str: + if state == 0: + return 'write_record' + return "" + +class ProtocolWithOptionalDateReaderBase(abc.ABC): + """Abstract reader for the ProtocolWithOptionalDate protocol.""" + + + def __init__(self) -> None: + self._state = 0 + + def close(self) -> None: + self._close() + if self._state != 2: + if self._state % 2 == 1: + previous_method = self._state_to_method_name(self._state - 1) + raise ProtocolError(f"Protocol reader closed before all data was consumed. The iterable returned by '{previous_method}' was not fully consumed.") + else: + expected_method = self._state_to_method_name(self._state) + raise ProtocolError(f"Protocol reader closed before all data was consumed. Expected call to '{expected_method}'.") + + + schema = ProtocolWithOptionalDateWriterBase.schema + + def __enter__(self): + return self + + def __exit__(self, exc_type: typing.Optional[type[BaseException]], exc: typing.Optional[BaseException], traceback: object) -> None: + try: + self.close() + except Exception as e: + if exc is None: + raise e + + @abc.abstractmethod + def _close(self) -> None: + raise NotImplementedError() + + def read_record(self) -> typing.Optional[RecordWithOptionalDate]: + """Ordinal 0""" + + if self._state != 0: + self._raise_unexpected_state(0) + + value = self._read_record() + self._state = 2 + return value + + def copy_to(self, writer: ProtocolWithOptionalDateWriterBase) -> None: + writer.write_record(self.read_record()) + + @abc.abstractmethod + def _read_record(self) -> typing.Optional[RecordWithOptionalDate]: + raise NotImplementedError() + + T = typing.TypeVar('T') + def _wrap_iterable(self, iterable: collections.abc.Iterable[T], final_state: int) -> collections.abc.Iterable[T]: + yield from iterable + self._state = final_state + + def _raise_unexpected_state(self, actual: int) -> None: + actual_method = self._state_to_method_name(actual) + if self._state % 2 == 1: + previous_method = self._state_to_method_name(self._state - 1) + raise ProtocolError(f"Received call to '{actual_method}' but the iterable returned by '{previous_method}' was not fully consumed.") + else: + expected_method = self._state_to_method_name(self._state) + raise ProtocolError(f"Expected to call to '{expected_method}' but received call to '{actual_method}'.") + + def _state_to_method_name(self, state: int) -> str: + if state == 0: + return 'read_record' + return "" + diff --git a/python/test_model/types.py b/python/test_model/types.py index e37288fd..63de6bac 100644 --- a/python/test_model/types.py +++ b/python/test_model/types.py @@ -1939,6 +1939,27 @@ def __repr__(self) -> str: return f"RecordWithKeywordFields(int={repr(self.int_)}, sizeof={repr(self.sizeof)}, if={repr(self.if_)})" +class RecordWithOptionalDate: + date_field: typing.Optional[datetime.date] + + def __init__(self, *, + date_field: typing.Optional[datetime.date] = None, + ): + self.date_field = date_field + + def __eq__(self, other: object) -> bool: + return ( + isinstance(other, RecordWithOptionalDate) + and self.date_field == other.date_field + ) + + def __str__(self) -> str: + return f"RecordWithOptionalDate(dateField={self.date_field})" + + def __repr__(self) -> str: + return f"RecordWithOptionalDate(dateField={repr(self.date_field)})" + + class AcquisitionOrImage: Acquisition: typing.ClassVar[type["AcquisitionOrImageUnionCase[SimpleAcquisition]"]] Image: typing.ClassVar[type["AcquisitionOrImageUnionCase[image.Image[np.float32]]"]] @@ -2087,6 +2108,7 @@ def _mk_get_dtype(): dtype_map.setdefault(RecordNotUsedInProtocol, np.dtype([('u1', np.dtype(np.object_)), ('u2', np.dtype(np.object_))], align=True)) dtype_map.setdefault(EnumWithKeywordSymbols, np.dtype(np.int32)) dtype_map.setdefault(RecordWithKeywordFields, np.dtype([('int_', np.dtype(np.object_)), ('sizeof', np.dtype(np.object_)), ('if_', get_dtype(EnumWithKeywordSymbols))], align=True)) + dtype_map.setdefault(RecordWithOptionalDate, np.dtype([('date_field', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.datetime64))], align=True))], align=True)) dtype_map.setdefault(AcquisitionOrImage, np.dtype(np.object_)) dtype_map.setdefault(StringOrInt32, np.dtype(np.object_)) dtype_map.setdefault(Int32OrSimpleRecord, np.dtype(np.object_)) diff --git a/tooling/internal/cpp/include/detail/binary/serializers.h b/tooling/internal/cpp/include/detail/binary/serializers.h index e2707f10..1dcc75e7 100644 --- a/tooling/internal/cpp/include/detail/binary/serializers.h +++ b/tooling/internal/cpp/include/detail/binary/serializers.h @@ -14,10 +14,6 @@ #include "../../yardl.h" #include "coded_stream.h" -#if __cplusplus >= 202002L -#include -#endif - namespace yardl::binary { /** @@ -177,11 +173,7 @@ inline void WriteDate(CodedOutputStream& stream, yardl::Date const& value) { inline void ReadDate(CodedInputStream& stream, yardl::Date& value) { int64_t days; ReadInteger(stream, days); -#if __cplusplus < 202002L value = yardl::Date(date::days(days)); -#else - value = yardl::Date{std::chrono::days{days}}; -#endif } inline void WriteTime(CodedOutputStream& stream, yardl::Time const& value) { @@ -202,13 +194,9 @@ inline void WriteDateTime(CodedOutputStream& stream, yardl::DateTime const& valu inline void ReadDateTime(CodedInputStream& stream, yardl::DateTime& value) { int64_t ns; ReadInteger(stream, ns); -#if __cplusplus < 202002L - value = yardl::DateTime(date::sys_time(std::chrono::nanoseconds(ns))); -#else value = yardl::DateTime{ std::chrono::time_point(std::chrono::nanoseconds(ns))}; -#endif } template WriteElement> diff --git a/tooling/internal/cpp/include/detail/hdf5/ddl.h b/tooling/internal/cpp/include/detail/hdf5/ddl.h index f0d757a2..8bf8d5c9 100644 --- a/tooling/internal/cpp/include/detail/hdf5/ddl.h +++ b/tooling/internal/cpp/include/detail/hdf5/ddl.h @@ -37,15 +37,9 @@ static inline H5::PredType const& SizeTypeDdl() { * of days since the epoch. */ static inline H5::DataType DateTypeDdl() { -#if __cplusplus < 202002L static_assert(sizeof(yardl::Date) == sizeof(int32_t)); static_assert(std::is_same_v); return H5::PredType::NATIVE_INT32; -#else - static_assert(sizeof(yardl::Date) == sizeof(int64_t)); - static_assert(std::is_same_v); - return H5::PredType::NATIVE_INT64; -#endif } /** diff --git a/tooling/internal/cpp/include/detail/ndjson/serializers.h b/tooling/internal/cpp/include/detail/ndjson/serializers.h index 1bd729d3..db5dcd92 100644 --- a/tooling/internal/cpp/include/detail/ndjson/serializers.h +++ b/tooling/internal/cpp/include/detail/ndjson/serializers.h @@ -125,25 +125,12 @@ struct adl_serializer { template <> struct adl_serializer { static void to_json(ordered_json& j, yardl::Date const& value) { -#if __cplusplus < 202002L j = date::format("%F", value); -#else - date::local_days ld(value.time_since_epoch()); - j = date::format("%F", ld); -#endif } static void from_json(ordered_json const& j, yardl::Date& value) { std::stringstream ss{j.get()}; - -#if __cplusplus < 202002L ss >> date::parse("%F", value); -#else - date::local_days ld; - ss >> date::parse("%F", ld); - value = yardl::Date(ld.time_since_epoch()); -#endif - if (ss.fail()) { throw std::runtime_error("invalid date format"); } diff --git a/tooling/internal/cpp/include/yardl.h b/tooling/internal/cpp/include/yardl.h index aa8eb682..4a074ac7 100644 --- a/tooling/internal/cpp/include/yardl.h +++ b/tooling/internal/cpp/include/yardl.h @@ -5,11 +5,7 @@ #include -#if __cplusplus < 202002L -// This functionality is part of the standard library as of C++20 #include -#endif - #include #include #include @@ -46,17 +42,10 @@ using NDArray = xt::xtensor; template using DynamicNDArray = xt::xarray; -#if __cplusplus < 202002L /** * @brief Represents a date as a number of days since the epoch. */ using Date = date::local_days; -#else -/** - * @brief Represents a date as a number of days since the epoch. - */ -using Date = std::chrono::local_days; -#endif /** * @brief Represents a time of day as the number of nanoseconds since midnight. @@ -66,8 +55,7 @@ using Time = std::chrono::duration; /** * @brief Represents a datetime as the number of nanoseconds since the epoch. */ -using DateTime = std::chrono::time_point>; +using DateTime = std::chrono::time_point; /** * @brief The same as size_t when it is 64 bits, otherwise uint64_t.