diff --git a/CMakeLists.txt b/CMakeLists.txt index 90195b73..9ddb7ab4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ if (REFLECTCPP_AVRO) list(APPEND REFLECT_CPP_SOURCES src/reflectcpp_avro.cpp ) + find_package(jansson CONFIG REQUIRED) if(REFLECTCPP_USE_VCPKG) target_include_directories(reflectcpp SYSTEM PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include") if (MSVC) @@ -111,6 +112,7 @@ if (REFLECTCPP_AVRO) target_link_libraries(reflectcpp PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/libavro${CMAKE_STATIC_LIBRARY_SUFFIX}") endif () endif() + target_link_libraries(reflectcpp PRIVATE jansson::jansson) endif () if (REFLECTCPP_BSON) diff --git a/include/rfl/avro/Reader.hpp b/include/rfl/avro/Reader.hpp index 3f5e4f46..92e878e5 100644 --- a/include/rfl/avro/Reader.hpp +++ b/include/rfl/avro/Reader.hpp @@ -80,18 +80,30 @@ struct Reader { if (type == AVRO_DOUBLE) { double result = 0.0; const auto err = avro_value_get_double(_var.val_, &result); + if (err) { + return Error("Could not cast to double."); + } return static_cast(result); } else if (type == AVRO_INT32) { int32_t result = 0; - avro_value_get_int(_var.val_, &result); + const auto err = avro_value_get_int(_var.val_, &result); + if (err) { + return Error("Could not cast to int32."); + } return static_cast(result); } else if (type == AVRO_INT64) { int64_t result = 0; - avro_value_get_long(_var.val_, &result); + const auto err = avro_value_get_long(_var.val_, &result); + if (err) { + return Error("Could not cast to int64."); + } return static_cast(result); } else if (type == AVRO_FLOAT) { - double result = 0.0; + float result = 0.0; const auto err = avro_value_get_float(_var.val_, &result); + if (err) { + return Error("Could not cast to float."); + } return static_cast(result); } return rfl::Error( diff --git a/include/rfl/avro/SchemaImpl.hpp b/include/rfl/avro/SchemaImpl.hpp index 9276fb68..2f2fa50f 100644 --- a/include/rfl/avro/SchemaImpl.hpp +++ b/include/rfl/avro/SchemaImpl.hpp @@ -28,7 +28,7 @@ class SchemaImpl { const std::string& json_str() const { return json_str_; } /// The interface used to create new values. - const avro_value_iface_t* iface() const { return iface_; }; + avro_value_iface_t* iface() const { return iface_; }; private: /// The JSON string used to create the schema. diff --git a/include/rfl/avro/read.hpp b/include/rfl/avro/read.hpp index 28eed4e7..ebf41515 100644 --- a/include/rfl/avro/read.hpp +++ b/include/rfl/avro/read.hpp @@ -11,6 +11,7 @@ #include "../internal/wrap_in_rfl_array_t.hpp" #include "Parser.hpp" #include "Reader.hpp" +#include "Schema.hpp" namespace rfl::avro { @@ -26,20 +27,20 @@ auto read(const InputVarType& _obj) { /// Parses an object from AVRO using reflection. template -Result> read(const char* _bytes, - const size_t _size) { - AvroParser parser; - InputVarType doc; - avro_parser_init(std::bit_cast(_bytes), _size, 0, &parser, - &doc.val_); - auto result = read(doc); +Result> read( + const char* _bytes, const size_t _size, const Schema& _schema) noexcept { + avro_reader_t avro_reader = avro_reader_memory(_bytes, _size); + avro_value_t root; + avro_generic_value_new(_schema.iface(), &root); + auto result = read(InputVarType{&root}); + avro_reader_free(avro_reader); return result; } /// Parses an object from AVRO using reflection. template -auto read(const std::vector& _bytes, const auto& _schema) { - return read(_bytes.data(), _bytes.size()); +auto read(const std::vector& _bytes, const Schema& _schema) { + return read(_bytes.data(), _bytes.size(), _schema); } /// Parses an object from a stream. diff --git a/include/rfl/avro/write.hpp b/include/rfl/avro/write.hpp index 6341f7bd..53c99682 100644 --- a/include/rfl/avro/write.hpp +++ b/include/rfl/avro/write.hpp @@ -14,14 +14,17 @@ #include "../parsing/Parent.hpp" #include "Parser.hpp" #include "Schema.hpp" +#include "Writer.hpp" namespace rfl::avro { /// Returns AVRO bytes. template std::vector write(const auto& _obj, const auto& _schema) noexcept { - static_assert(std::is_same, - typename decltype(_schema)::Type>(), + using T = std::remove_cvref_t; + using U = typename std::remove_cvref_t::Type; + using ParentType = parsing::Parent; + static_assert(std::is_same(), "The schema must be compatible with the type to write."); std::vector buffer(4096); avro_value_t root; @@ -29,12 +32,12 @@ std::vector write(const auto& _obj, const auto& _schema) noexcept { const auto writer = Writer(&root); Parser>::write(writer, _obj, typename ParentType::Root{}); - avro_writer_t avro_writer; - avro_writer_memory(buffer.data(), buffer.size()); + avro_writer_t avro_writer = avro_writer_memory(buffer.data(), buffer.size()); // TODO: Handle cases in which the buffer isn't large enough. avro_value_write(avro_writer, &root); - const auto len = avro_writer_tell(writer); + const auto len = avro_writer_tell(avro_writer); avro_value_decref(&root); + avro_writer_free(avro_writer); buffer.resize(len); return buffer; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1d0660dd..d99a20a2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,7 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O2") if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std:c++20") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -Werror -ggdb -ftemplate-backtrace-limit=0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -ggdb -ftemplate-backtrace-limit=0") endif() if (REFLECTCPP_JSON) diff --git a/tests/avro/test_readme_example.cpp b/tests/avro/test_readme_example.cpp deleted file mode 100644 index 4a63f464..00000000 --- a/tests/avro/test_readme_example.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include -#include - -#include "write_and_read.hpp" - -namespace test_readme_example { - -using Age = rfl::Validator, rfl::Maximum<130>>; - -struct Person { - rfl::Rename<"firstName", std::string> first_name; - rfl::Rename<"lastName", std::string> last_name = "Simpson"; - std::string town = "Springfield"; - rfl::Timestamp<"%Y-%m-%d"> birthday; - Age age; - rfl::Email email; - std::vector child; -}; - -TEST(avro, test_readme_example) { - const auto bart = Person{.first_name = "Bart", - .birthday = "1987-04-19", - .age = 10, - .email = "bart@simpson.com"}; - - const auto lisa = Person{.first_name = "Lisa", - .birthday = "1987-04-19", - .age = 8, - .email = "lisa@simpson.com"}; - - const auto maggie = Person{.first_name = "Maggie", - .birthday = "1987-04-19", - .age = 0, - .email = "maggie@simpson.com"}; - - const auto homer = Person{.first_name = "Homer", - .birthday = "1987-04-19", - .age = 45, - .email = "homer@simpson.com", - .child = std::vector({bart, lisa, maggie})}; - - write_and_read(homer); -} -} // namespace test_readme_example diff --git a/tests/avro/test_tutorial_example.cpp b/tests/avro/test_tutorial_example.cpp new file mode 100644 index 00000000..72ca6245 --- /dev/null +++ b/tests/avro/test_tutorial_example.cpp @@ -0,0 +1,46 @@ +#include + +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +/// The basic example from the Avro C tutorial. +namespace test_tutorial_example { + +const char PERSON_SCHEMA[] = + "{\"type\":\"record\",\ + \"name\":\"Person\",\ + \"fields\":[\ + {\"name\": \"ID\", \"type\": \"long\"},\ + {\"name\": \"First\", \"type\": \"string\"},\ + {\"name\": \"Last\", \"type\": \"string\"},\ + {\"name\": \"Phone\", \"type\": \"string\"},\ + {\"name\": \"Age\", \"type\": \"int\"}]}"; + +struct Person { + size_t ID; + std::string First; + std::string Last; + std::string Phone; + int Age; +}; + +TEST(avro, test_tutorial_example) { + const auto person = Person{.ID = 1, + .First = "Randal", + .Last = "Graves", + .Phone = "(555) 123-5678", + .Age = 30}; + const auto schema = rfl::avro::Schema::from_json(PERSON_SCHEMA); + const auto serialized1 = rfl::avro::write(person, schema.value()); + const auto res = rfl::avro::read(serialized1, schema.value()); + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().value().what(); + const auto serialized2 = rfl::avro::write(res.value(), schema.value()); + EXPECT_EQ(serialized1, serialized2); +} +} // namespace test_tutorial_example