diff --git a/src/mtconnect/pipeline/json_mapper.cpp b/src/mtconnect/pipeline/json_mapper.cpp index 5d2d1662..8d94a83c 100644 --- a/src/mtconnect/pipeline/json_mapper.cpp +++ b/src/mtconnect/pipeline/json_mapper.cpp @@ -887,11 +887,10 @@ namespace mtconnect::pipeline { } else { - // Otherwise we have a problem, we cannot consume content - // if we don't know the data item since we cannot handle tables - // and data sets. - LOG(warning) << "Cannot map properties without data item"; - return false; + LOG(warning) << "Cannot map properties without data item, consuming erronous data"; + ErrorHandler handler; + if (!handler(reader, buff)) + return false; } m_props.clear(); m_dataItem.reset(); diff --git a/test_package/json_mapping_test.cpp b/test_package/json_mapping_test.cpp index db704acc..baf6fa7e 100644 --- a/test_package/json_mapping_test.cpp +++ b/test_package/json_mapping_test.cpp @@ -101,6 +101,15 @@ class JsonMappingTest : public testing::Test Properties ps(props); ErrorList errors; auto di = DataItem::make(ps, errors); + if (errors.size() > 0) + { + cerr << "Errors occurred during make data item" << endl; + for (auto &e : errors) + { + cerr << " " << e->getEntity() << ": " << e->what() << endl; + } + return nullptr; + } m_dataItems.emplace(di->getId(), di); dev->second->addDataItem(di, errors); @@ -1017,6 +1026,7 @@ TEST_F(JsonMappingTest, should_skip_erroneous_values) Properties props {{"VALUE", R"( { "timestamp": "2023-11-09T11:20:00Z", + "c": "BAD_ITEM", "a": { "r1": { "k1": 123.45 @@ -1047,5 +1057,168 @@ TEST_F(JsonMappingTest, should_skip_erroneous_values) ASSERT_EQ("MANUAL", obs->getValue()); } +/// @test if observation is incorrect, skip levels +TEST_F(JsonMappingTest, should_skip_erroneous_array) +{ + auto dev = makeDevice("Device", {{"id", "device"s}, {"name", "device"s}, {"uuid", "device"s}}); + makeDataItem("device", {{"id", "a"s}, {"type", "EXECUTION"s}, {"category", "EVENT"s}}); + makeDataItem("device", {{"id", "b"s}, {"type", "CONTROLLER_MODE"s}, {"category", "EVENT"s}}); + + Properties props {{"VALUE", R"( +{ + "timestamp": "2023-11-09T11:20:00Z", + "c": [1.0, 2.0, 3.0, 4.0, 5.0], + "a": { + "r1": { + "k1": 123.45 + }, + "r2": { + "k2": "ABCDEF", + "k3": 6789 + } + }, + "b": "MANUAL" +})"s}}; + + auto jmsg = std::make_shared("JsonMessage", props); + jmsg->m_device = dev; + + auto res = (*m_mapper)(std::move(jmsg)); + ASSERT_TRUE(res); + + auto value = res->getValue(); + ASSERT_TRUE(std::holds_alternative(value)); + auto list = get(value); + ASSERT_EQ(1, list.size()); + + auto obs = dynamic_pointer_cast(list.front()); + ASSERT_TRUE(obs); + ASSERT_EQ("ControllerMode", obs->getName()); + ASSERT_EQ("b", obs->getDataItem()->getId()); + ASSERT_EQ("MANUAL", obs->getValue()); +} + +/// @test if observation is incorrect, skip levels +TEST_F(JsonMappingTest, should_skip_erroneous_table) +{ + auto dev = makeDevice("Device", {{"id", "device"s}, {"name", "device"s}, {"uuid", "device"s}}); + makeDataItem("device", {{"id", "a"s}, {"type", "EXECUTION"s}, {"category", "EVENT"s}}); + makeDataItem("device", {{"id", "b"s}, {"type", "CONTROLLER_MODE"s}, {"category", "EVENT"s}}); + + Properties props {{"VALUE", R"( +{ + "timestamp": "2023-11-09T11:20:00Z", + "c": { + "r1": { + "k1": 123.45 + }, + "r2": { + "k2": "ABCDEF", + "k3": 6789 + } + }, + "a": { + "r1": { + "k1": 123.45 + }, + "r2": { + "k2": "ABCDEF", + "k3": 6789 + } + }, + "b": "MANUAL" +})"s}}; + + auto jmsg = std::make_shared("JsonMessage", props); + jmsg->m_device = dev; + + auto res = (*m_mapper)(std::move(jmsg)); + ASSERT_TRUE(res); + + auto value = res->getValue(); + ASSERT_TRUE(std::holds_alternative(value)); + auto list = get(value); + ASSERT_EQ(1, list.size()); + + auto obs = dynamic_pointer_cast(list.front()); + ASSERT_TRUE(obs); + ASSERT_EQ("ControllerMode", obs->getName()); + ASSERT_EQ("b", obs->getDataItem()->getId()); + ASSERT_EQ("MANUAL", obs->getValue()); +} + +// if the tag "blah" is not found in the device file, none of the other tags get processed +TEST_F(JsonMappingTest, should_ignore_Invalid_Tag_DataItem) +{ + auto dev = makeDevice("Device", {{"id", "device"s}, {"name", "device"s}, {"uuid", "device"s}}); + makeDataItem("device", {{"id", "a"s}, {"type", "EXECUTION"s}, {"category", "EVENT"s}}); + makeDataItem("device", {{"id", "c"s}, {"type", "CONTROLLER_MODE"s}, {"category", "EVENT"s}}); + + Properties props {{"VALUE", R"( +{ + "timestamp": "2023-11-09T11:20:00Z", +"c": "BLAH", + "a": { + "r1": { + "k1": 123.45 + }, + "r2": { + "k2": "ABCDEF", + "k3": 6789 + } + }, + "b": "MANUAL" +})"s}}; + + auto jmsg = std::make_shared("JsonMessage", props); + jmsg->m_device = dev; + + auto res = (*m_mapper)(std::move(jmsg)); + ASSERT_TRUE(res); + + auto value = res->getValue(); + ASSERT_TRUE(std::holds_alternative(value)); + auto list = get(value); + ASSERT_EQ(1, list.size()); + + auto obs = dynamic_pointer_cast(list.front()); + ASSERT_TRUE(obs); + ASSERT_EQ("ControllerMode", obs->getName()); + ASSERT_EQ("c", obs->getDataItem()->getId()); + ASSERT_EQ("BLAH", obs->getValue()); +} + +TEST_F(JsonMappingTest, should_ignore_Invalid_make_DataItem) +{ + auto dev = makeDevice("Device", {{"id", "device"s}, {"name", "device"s}, {"uuid", "device"s}}); + makeDataItem("device", {{"id", "a"s}, {"type", "EXECUTION"s}, {"category", "EVENT"s}}); + makeDataItem("device", {{"id", "b"s}, {"blah", "BLAH"s}, {"category", "EVENT"s}}); + + Properties props {{"VALUE", R"( +{ + "timestamp": "2023-11-09T11:20:00Z", + "c": "Blah", + "a": "ACTIVE", + "b": "MANUAL" +})"s}}; + + auto jmsg = std::make_shared("JsonMessage", props); + jmsg->m_device = dev; + + auto res = (*m_mapper)(std::move(jmsg)); + ASSERT_TRUE(res); + + auto value = res->getValue(); + ASSERT_TRUE(std::holds_alternative(value)); + auto list = get(value); + ASSERT_EQ(1, list.size()); + + auto obs = dynamic_pointer_cast(list.front()); + ASSERT_TRUE(obs); + ASSERT_EQ("Execution", obs->getName()); + ASSERT_EQ("a", obs->getDataItem()->getId()); + ASSERT_EQ("ACTIVE", obs->getValue()); +} + /// @test verify the json mapper can an asset in json TEST_F(JsonMappingTest, should_parse_json_asset) { GTEST_SKIP(); }