diff --git a/common/cpp/include/json_parser/json_parser.h b/common/cpp/include/json_parser/json_parser.h index 8ca8ce02b4ff5d60840d61f7fab5669caee43124..7373e91a74bf5043487846b675be1ce29fc62363 100644 --- a/common/cpp/include/json_parser/json_parser.h +++ b/common/cpp/include/json_parser/json_parser.h @@ -3,6 +3,8 @@ #include <string> #include <memory> +#include <vector> +#include <string> #include "common/error.h" @@ -14,10 +16,16 @@ class JsonParser { public: JsonParser(const std::string& json, bool read_from_file); ~JsonParser(); + JsonParser(JsonParser&& other); Error GetUInt64(const std::string& name, uint64_t* val) const noexcept; Error GetString(const std::string& name, std::string* val) const noexcept; + Error GetArrayUInt64(const std::string& name, std::vector<uint64_t>* val) const noexcept; + Error GetArrayString(const std::string& name, std::vector<std::string>* val) const noexcept; + JsonParser Embedded(const std::string& name) const noexcept; private: std::unique_ptr<RapidJson> rapid_json_; + JsonParser(RapidJson* rapid_json_); + }; } diff --git a/common/cpp/src/json_parser/json_parser.cpp b/common/cpp/src/json_parser/json_parser.cpp index d9f6f49ec05cd1b166bd5b88be202030be84ebcd..1a848557881de69cf1fbcab77fcc2997e6f28d00 100644 --- a/common/cpp/src/json_parser/json_parser.cpp +++ b/common/cpp/src/json_parser/json_parser.cpp @@ -11,6 +11,16 @@ JsonParser::~JsonParser() { JsonParser::JsonParser(const std::string& json, bool read_from_file) : rapid_json_{new RapidJson(json, read_from_file)} { } +Error JsonParser::GetArrayUInt64(const std::string& name, std::vector<uint64_t>* val) const noexcept { + return rapid_json_->GetArrayUInt64(name, val); + +} + +Error JsonParser::GetArrayString(const std::string& name, std::vector<std::string>* val) const noexcept { + return rapid_json_->GetArrayString(name, val); +} + + Error JsonParser::GetUInt64(const std::string& name, uint64_t* val) const noexcept { return rapid_json_->GetUInt64(name, val); } @@ -19,6 +29,18 @@ Error JsonParser::GetString(const std::string& name, std::string* val) const noe return rapid_json_->GetString(name, val); } +JsonParser JsonParser::Embedded(const std::string& name) const noexcept { + RapidJson* rapid_json = new RapidJson(*rapid_json_.get(), name); + return JsonParser(rapid_json); +} + +JsonParser::JsonParser(RapidJson* json) : rapid_json_{json} { +} + +JsonParser::JsonParser(JsonParser&& other) { + rapid_json_ = std::move(other.rapid_json_); +} + } diff --git a/common/cpp/src/json_parser/rapid_json.cpp b/common/cpp/src/json_parser/rapid_json.cpp index 6c3a875992065fa594ae9cce578d4b5a2f9ba8a2..e3f1f6ee55a9839476b3fe9eb2eb014fe61b137f 100644 --- a/common/cpp/src/json_parser/rapid_json.cpp +++ b/common/cpp/src/json_parser/rapid_json.cpp @@ -1,4 +1,5 @@ #include "rapid_json.h" +#include "rapid_json.h" using namespace rapidjson; @@ -11,6 +12,10 @@ RapidJson::RapidJson(const std::string& json, bool read_from_file): io__{new Sys } Error RapidJson::LazyInitialize()const noexcept { + if (embedded_error_) { + return TextError(embedded_error_->Explain()); + } + if (initialized_) return nullptr; @@ -27,19 +32,26 @@ Error RapidJson::LazyInitialize()const noexcept { return TextError("Cannot parse document"); } - return nullptr; + object_ = doc_.GetObject(); + return nullptr; } hidra2::Error CheckValueType(const std::string& name, ValueType type, const Value& val) { bool res = false; switch (type) { + case ValueType::kObject: + res = val.IsObject(); + break; case ValueType::kString: res = val.IsString(); break; case ValueType::kUint64: res = val.IsInt64(); break; + case ValueType::kArray: + res = val.IsArray(); + break; } if (!res) { return TextError("wrong type: " + name); @@ -54,8 +66,8 @@ hidra2::Error RapidJson::GetValue(const std::string& name, ValueType type, Value return err; } - auto iterator = doc_.FindMember(name.c_str()); - if (iterator == doc_.MemberEnd()) { + auto iterator = object_.FindMember(name.c_str()); + if (iterator == object_.MemberEnd()) { return TextError("cannot find: " + name); } @@ -63,7 +75,6 @@ hidra2::Error RapidJson::GetValue(const std::string& name, ValueType type, Value return CheckValueType(name, type, *val); } - Error RapidJson::GetUInt64(const std::string& name, uint64_t* val) const noexcept { Value json_val; if (Error err = GetValue(name, ValueType::kUint64, &json_val)) { @@ -73,9 +84,6 @@ Error RapidJson::GetUInt64(const std::string& name, uint64_t* val) const noexcep return nullptr; } - - - Error RapidJson::GetString(const std::string& name, std::string* val) const noexcept { Value json_val; if (Error err = GetValue(name, ValueType::kString, &json_val)) { @@ -86,4 +94,47 @@ Error RapidJson::GetString(const std::string& name, std::string* val) const noex } +Error RapidJson::GetArrayUInt64(const std::string& name, std::vector<uint64_t>* val) const noexcept { + Value json_val; + if (Error err = GetValue(name, ValueType::kArray, &json_val)) { + return err; + } + + for (auto& v : json_val.GetArray()) { + if (!v.IsInt64()) { + return TextError("wrong type of array element: " + name); + } + val->push_back(v.GetInt()); + } + return nullptr; + +} + +Error RapidJson::GetArrayString(const std::string& name, std::vector<std::string>* val) const noexcept { + Value json_val; + if (Error err = GetValue(name, ValueType::kArray, &json_val)) { + return err; + } + + for (auto& v : json_val.GetArray()) { + if (!v.IsString()) { + return TextError("wrong type of array element: " + name); + } + val->push_back(v.GetString()); + } + return nullptr; + +} + + +RapidJson::RapidJson(const RapidJson& parent, const std::string& subname) { + auto err = parent.GetValue(subname, ValueType::kObject, &object_); + if (err) { + embedded_error_ = std::move(err); + return; + } + initialized_ = true; +} + + } \ No newline at end of file diff --git a/common/cpp/src/json_parser/rapid_json.h b/common/cpp/src/json_parser/rapid_json.h index 5aad1ff20133f33c2dca3ab057dc9e836c88ecb1..6f55934c2a159eb8746a1132669e6f58c0b31e70 100644 --- a/common/cpp/src/json_parser/rapid_json.h +++ b/common/cpp/src/json_parser/rapid_json.h @@ -10,21 +10,29 @@ namespace hidra2 { enum class ValueType { kUint64, - kString + kString, + kObject, + kArray }; class RapidJson { public: RapidJson(const std::string& json, bool read_from_file); + RapidJson(const RapidJson& parent, const std::string& subname); Error GetUInt64(const std::string& name, uint64_t* val) const noexcept; Error GetString(const std::string& name, std::string* val) const noexcept; + Error GetArrayUInt64(const std::string& name, std::vector<uint64_t>* val) const noexcept; + Error GetArrayString(const std::string& name, std::vector<std::string>* val) const noexcept; std::unique_ptr<IO> io__; private: mutable rapidjson::Document doc_; + mutable rapidjson::Value object_; std::string json_; bool read_from_file_; mutable bool initialized_ = false; Error LazyInitialize() const noexcept; + Error embedded_error_ = nullptr; + hidra2::Error GetValue(const std::string& name, ValueType type, rapidjson::Value* val)const noexcept; }; diff --git a/common/cpp/unittests/json_parser/test_json_parser.cpp b/common/cpp/unittests/json_parser/test_json_parser.cpp index 4de52be0bf32fe58aca700af5d4d6e4b699f6272..d4545a5a0129152ee7b03099cab941113db5be53 100644 --- a/common/cpp/unittests/json_parser/test_json_parser.cpp +++ b/common/cpp/unittests/json_parser/test_json_parser.cpp @@ -18,6 +18,7 @@ using ::testing::NiceMock; using ::testing::Return; using ::testing::SetArgPointee; using ::testing::HasSubstr; +using ::testing::ElementsAre; using hidra2::JsonParser; using hidra2::RapidJson; @@ -27,7 +28,7 @@ using hidra2::IO; namespace { -TEST(ParseString, CorrectConvertToJson) { +TEST(ParseString, SimpleConvertToJson) { std::string json = R"({"_id":2,"foo":"foo","bar":1})"; JsonParser parser{json, false}; @@ -48,6 +49,67 @@ TEST(ParseString, CorrectConvertToJson) { } +TEST(ParseString, EmbeddedConvertToJson) { + std::string json = R"({"id":{"test":2}})"; + + JsonParser parser{json, false}; + + uint64_t id; + auto err1 = parser.Embedded("id").GetUInt64("test", &id); + + ASSERT_THAT(err1, Eq(nullptr)); + ASSERT_THAT(id, Eq(2)); +} + +TEST(ParseString, DoubleEmbeddedConvertToJson) { + std::string json = R"({"id":{"test":{"test2":2}}})"; + + JsonParser parser{json, false}; + + uint64_t id; + auto err1 = parser.Embedded("id").Embedded("test").GetUInt64("test2", &id); + + ASSERT_THAT(err1, Eq(nullptr)); + ASSERT_THAT(id, Eq(2)); +} + + +TEST(ParseString, ErrorOnWrongEmbeddedKey) { + std::string json = R"({"id1":{"test":2}})"; + + JsonParser parser{json, false}; + + uint64_t id; + auto err = parser.Embedded("id").GetUInt64("test", &id); + + ASSERT_THAT(err, Ne(nullptr)); + ASSERT_THAT(err->Explain(), ::testing::HasSubstr("cannot find")); +} + +TEST(ParseString, ErrorOnWrongEmbeddedSubKey) { + std::string json = R"({"id1":{"test1":2}})"; + + JsonParser parser{json, false}; + + uint64_t id; + auto err = parser.Embedded("id").GetUInt64("test", &id); + + ASSERT_THAT(err, Ne(nullptr)); + ASSERT_THAT(err->Explain(), ::testing::HasSubstr("cannot find")); +} + + +TEST(ParseString, ErrorOnWrongKey) { + std::string json = R"({"_id":"2"})"; + + JsonParser parser{json, false}; + + uint64_t id; + auto err = parser.GetUInt64("_id1", &id); + + ASSERT_THAT(err, Ne(nullptr)); + ASSERT_THAT(err->Explain(), ::testing::HasSubstr("cannot find")); +} TEST(ParseString, ErrorOnWrongType) { std::string json = R"({"_id":"2"})"; @@ -75,6 +137,46 @@ TEST(ParseString, ErrorOnWrongDocument) { } + +TEST(ParseString, IntArrayConvertToJson) { + std::string json = R"({"array":[1,2,3]})"; + + JsonParser parser{json, false}; + + std::vector<uint64_t> vec; + auto err = parser.GetArrayUInt64("array", &vec); + + ASSERT_THAT(err, Eq(nullptr)); + ASSERT_THAT(vec, ElementsAre(1, 2, 3)); +} + + +TEST(ParseString, IntArrayErrorConvertToJson) { + std::string json = R"({"array":[1,2,"3"]})"; + + JsonParser parser{json, false}; + + std::vector<uint64_t> vec; + auto err = parser.GetArrayUInt64("array", &vec); + + ASSERT_THAT(err, Ne(nullptr)); + ASSERT_THAT(err->Explain(), HasSubstr("type")); +} + + +TEST(ParseString, StringArrayConvertToJson) { + std::string json = R"({"array":["s1","s2","s3"]})"; + + JsonParser parser{json, false}; + + std::vector<std::string> vec; + auto err = parser.GetArrayString("array", &vec); + + ASSERT_THAT(err, Eq(nullptr)); + ASSERT_THAT(vec, ElementsAre("s1", "s2", "s3")); +} + + class ParseFileTests : public Test { public: RapidJson parser{"filename", true}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9cd78d4856eea4c5cfb467e47eeb361c54c19663..0fb7141827c5997da8294a401a2bb2a3721a238e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,8 @@ find_package(Threads) add_subdirectory(common/cpp) add_subdirectory(system_io) +add_subdirectory(json_parser) + if(BUILD_MONGODB_CLIENTLIB) add_subdirectory(mongo_db) endif() diff --git a/tests/json_parser/CMakeLists.txt b/tests/json_parser/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..97a6482ba7df9c128c135a58c05f823d661385ad --- /dev/null +++ b/tests/json_parser/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(parse_config_file) diff --git a/tests/json_parser/parse_config_file/CMakeLists.txt b/tests/json_parser/parse_config_file/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..51aff5e133cfb1f40e45fb3febfba181aa3ff28d --- /dev/null +++ b/tests/json_parser/parse_config_file/CMakeLists.txt @@ -0,0 +1,19 @@ +set(TARGET_NAME parse_config_file) +set(SOURCE_FILES parse_config_file.cpp) + + +################################ +# Executable and link +################################ +add_executable(${TARGET_NAME} ${SOURCE_FILES}) +target_link_libraries(${TARGET_NAME} test_common hidra2-worker) +target_include_directories(${TARGET_NAME} PUBLIC ${HIDRA2_CXX_COMMON_INCLUDE_DIR}) + +################################ +# Testing +################################ + +add_integration_test(${TARGET_NAME} parseOK "${CMAKE_CURRENT_SOURCE_DIR}/config.json OK") +add_integration_test(${TARGET_NAME} parseFails "${CMAKE_CURRENT_SOURCE_DIR}/config_bad.json FAIL") + + diff --git a/tests/json_parser/parse_config_file/config.json b/tests/json_parser/parse_config_file/config.json new file mode 100644 index 0000000000000000000000000000000000000000..06f8798f2fd07e3558e274f32fef4234e9dd46b3 --- /dev/null +++ b/tests/json_parser/parse_config_file/config.json @@ -0,0 +1,14 @@ +{ + "server":"some_string", + + "port": 10, + "//": "This is a way to make comments", + "some_array":[1,2,3], + "//": "Another comment", + "some_string_array":["s1","s2","s3"], + "embedded_stuff": + { + "some_string":"embedded_string", + "some_int": 20 + } +} \ No newline at end of file diff --git a/tests/json_parser/parse_config_file/config_bad.json b/tests/json_parser/parse_config_file/config_bad.json new file mode 100644 index 0000000000000000000000000000000000000000..b960fb0ce8985f07ccadeb5180aade059fd89cca --- /dev/null +++ b/tests/json_parser/parse_config_file/config_bad.json @@ -0,0 +1,11 @@ +{ + "server":"some_string", + "port": 10, + "some_array":1, + "some_string_array":["s1","s2","s3"], + "embedded_stuff": + { + "some_string":"embedded_string", + "some_int": 20 + } +} \ No newline at end of file diff --git a/tests/json_parser/parse_config_file/parse_config_file.cpp b/tests/json_parser/parse_config_file/parse_config_file.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c185240553dc07c5786fcb8565ce38b3b0dc57f7 --- /dev/null +++ b/tests/json_parser/parse_config_file/parse_config_file.cpp @@ -0,0 +1,77 @@ +#include "json_parser/json_parser.h" + +#include <iostream> +#include <string> +#include <vector> + +#include "testing.h" + +using hidra2::M_AssertEq; + + +using namespace hidra2; + +struct Settings { + uint64_t port; + std::string server; + std::vector<uint64_t> intarray; + std::vector<std::string> stringarray; + std::string embedded_string; + uint64_t some_int; +}; + +void AssertSettings(const Settings& settings) { + M_AssertEq(10, settings.port); + + M_AssertEq(3, settings.intarray.size()); + M_AssertEq(1, settings.intarray[0]); + M_AssertEq(2, settings.intarray[1]); + M_AssertEq(3, settings.intarray[2]); + + M_AssertEq(3, settings.stringarray.size()); + M_AssertEq("s1", settings.stringarray[0]); + M_AssertEq("s2", settings.stringarray[1]); + M_AssertEq("s3", settings.stringarray[2]); + + M_AssertEq("some_string", settings.server); + M_AssertEq("embedded_string", settings.embedded_string); + M_AssertEq(20, settings.some_int); +} + +Settings Parse(const std::string& fname, Error* err) { + hidra2::JsonParser parser(fname, true); + + Settings settings; + + (*err = parser.GetUInt64("port", &settings.port)) || + (*err = parser.GetArrayString("some_string_array", &settings.stringarray)) || + (*err = parser.GetArrayUInt64("some_array", &settings.intarray)) || + (*err = parser.Embedded("embedded_stuff").GetString("some_string", &settings.embedded_string)) || + (*err = parser.Embedded("embedded_stuff").GetUInt64("some_int", &settings.some_int)) || + (*err = parser.GetString("server", &settings.server) + ); + + return settings; +} + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cout << "Wrong number of arguments" << std::endl; + exit(EXIT_FAILURE); + } + + auto fname = std::string{argv[1]}; + auto res = std::string{argv[2]}; + + Error err; + auto settings = Parse(fname, &err); + + if (err) { + return ! (res == "FAIL"); + } else { + AssertSettings(settings); + return ! (res == "OK"); + } +} + +