Skip to content

Commit

Permalink
Validate conditions syntax (#850)
Browse files Browse the repository at this point in the history
Validate conditions syntax on device template loading and during package build
  • Loading branch information
KraPete authored Jan 14, 2025
1 parent 5be6de2 commit 8992c74
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 0 deletions.
6 changes: 6 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
wb-mqtt-serial (2.152.5) stable; urgency=medium

* Validate conditions syntax in device templates

-- Petr Krasnoshchekov <petr.krasnoshchekov@wirenboard.com> Tue, 14 Jan 2025 12:13:58 +0500

wb-mqtt-serial (2.152.4) stable; urgency=medium

* Disable events for `RGB Strip Hue` for WB-LED and WB-MRGBW-D
Expand Down
1 change: 1 addition & 0 deletions src/expression_evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ namespace Expressions
*
* @param str string containing expression to parse
* @return resulting AST
* @throw std::runtime_error on parsing error
*/
std::unique_ptr<TAstNode> Parse(const std::string& str);
};
Expand Down
44 changes: 44 additions & 0 deletions src/templates_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <filesystem>

#include "expression_evaluator.h"
#include "file_utils.h"
#include "json_common.h"
#include "log.h"
Expand Down Expand Up @@ -36,6 +37,48 @@ namespace
}
}
}

void ValidateCondition(const Json::Value& node, std::unordered_set<std::string>& validConditions)
{
if (node.isMember("condition")) {
auto condition = node["condition"].asString();
if (validConditions.find(condition) == validConditions.end()) {
Expressions::TParser parser;
parser.Parse(condition);
validConditions.insert(condition);
}
}
}

std::string GetNodeName(const Json::Value& node, const std::string& name)
{
const std::vector<std::string> keys = {"name", "title"};
for (const auto& key: keys) {
if (node.isMember(key)) {
return node[key].asString();
}
}
return name;
}

void ValidateConditions(Json::Value& deviceTemplate)
{
std::unordered_set<std::string> validConditions;
std::vector<std::string> sections = {"channels", "setup", "parameters"};
for (const auto& section: sections) {
if (deviceTemplate.isMember(section)) {
Json::Value& sectionNodes = deviceTemplate[section];
for (auto it = sectionNodes.begin(); it != sectionNodes.end(); ++it) {
try {
ValidateCondition(*it, validConditions);
} catch (const runtime_error& e) {
throw runtime_error("Failed to parse condition in " + section + "[" +
GetNodeName(*it, it.name()) + "]: " + e.what());
}
}
}
}
}
}

//=============================================================================
Expand Down Expand Up @@ -253,6 +296,7 @@ const Json::Value& TDeviceTemplate::GetTemplate()
if (!IsDeprecated()) {
try {
Validator->Validate(root);
ValidateConditions(root["device"]);
} catch (const std::runtime_error& e) {
throw std::runtime_error("File: " + GetFilePath() + " error: " + e.what());
}
Expand Down
16 changes: 16 additions & 0 deletions test/device-templates/config-invalid-channel-condition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"device_type": "invalid_channel_condition",
"device": {
"name": "invalid channel condition",
"id": "invalid channel condition",
"channels": [
{
"name": "Temperature",
"reg_type": "input",
"format": "s32",
"address": "0x0504",
"condition": "( in1 > 3 )"
}
]
}
}
30 changes: 30 additions & 0 deletions test/device-templates/config-invalid-param-condition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"device_type": "invalid_param_condition",
"device": {
"name": "invalid param condition",
"id": "invalid param condition",
"setup": [
{
"title": "Param",
"address": "0x0504",
"value": 1
}
],
"channels": [
{
"name": "Temperature",
"reg_type": "input",
"format": "s32",
"address": "0x0504"
}
],
"parameters": [
{
"id": "Test",
"title": "p1",
"address": 9992,
"condition": "( in1 > 3 )"
}
]
}
}
23 changes: 23 additions & 0 deletions test/device-templates/config-invalid-setup-condition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"device_type": "invalid_setup_condition",
"device": {
"name": "invalid setup condition",
"id": "invalid setup condition",
"setup": [
{
"title": "Param",
"address": "0x0504",
"value": 1,
"condition": "( in1 > 3 )"
}
],
"channels": [
{
"name": "Temperature",
"reg_type": "input",
"format": "s32",
"address": "0x0504"
}
]
}
}
32 changes: 32 additions & 0 deletions test/device_templates_file_extension_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,35 @@ TEST_F(TDeviceTemplatesTest, InvalidParameterName)
EXPECT_NO_THROW(templates.GetTemplate("parameters_object_invalid_name")->GetTemplate());
EXPECT_THROW(templates.GetTemplate("tpl1_parameters_object_invalid_name")->GetTemplate(), std::runtime_error);
}

TEST_F(TDeviceTemplatesTest, InvalidCondition)
{
auto commonDeviceSchema(
WBMQTT::JSON::Parse(TLoggedFixture::GetDataFilePath("../wb-mqtt-serial-confed-common.schema.json")));
Json::Value templatesSchema(
LoadConfigTemplatesSchema(TLoggedFixture::GetDataFilePath("../wb-mqtt-serial-device-template.schema.json"),
commonDeviceSchema));
TTemplateMap templates(templatesSchema);
templates.AddTemplatesDir(TLoggedFixture::GetDataFilePath("device-templates"), false);

const std::unordered_map<std::string, std::string> expectedErrors = {
{"invalid_channel_condition",
"File: test/device-templates/config-invalid-channel-condition.json error: Failed to parse condition in "
"channels[Temperature]: unexpected symbol ' ' at position 2"},
{"invalid_setup_condition",
"File: test/device-templates/config-invalid-setup-condition.json error: Failed to parse condition in "
"setup[Param]: unexpected symbol ' ' at position 2"},
{"invalid_param_condition",
"File: test/device-templates/config-invalid-param-condition.json error: Failed to parse condition in "
"parameters[p1]: unexpected symbol ' ' at position 2"},
};

for (const auto& [deviceType, expectedError]: expectedErrors) {
try {
templates.GetTemplate(deviceType)->GetTemplate();
ADD_FAILURE() << "Expect std::runtime_error";
} catch (const std::runtime_error& e) {
ASSERT_STREQ(expectedError.c_str(), e.what());
}
}
}

0 comments on commit 8992c74

Please sign in to comment.