--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/json-filter.h"
+
+namespace internal {
+
+std::vector<std::string> SplitString(std::string text, char delimiter) {
+ ScopeLogger();
+ std::vector<std::string> parts;
+ std::string word = "";
+ for (char c : text) {
+ if (c == delimiter) {
+ if (!word.empty()) {
+ parts.push_back(word);
+ word.clear();
+ }
+ } else {
+ word.push_back(c);
+ }
+ }
+ if (!word.empty()) {
+ parts.push_back(word);
+ }
+ return parts;
+}
+
+picojson::value GetAttribute(std::string attribute, picojson::value json) {
+ ScopeLogger();
+ for (auto node : SplitString(attribute, kAttributeSeparator)) {
+ if (!json.is<picojson::object>()) {
+ throw "filtered attribute not found";
+ }
+
+ auto obj = json.get<picojson::object>();
+ if (obj.find(node) == obj.end()) {
+ throw "filtered attribute not found";
+ }
+
+ json = obj.at(node);
+ }
+ return json;
+}
+
+} // namespace internal
+
+namespace common {
+
+JsonFilter::JsonFilter(picojson::value filter) : filter(filter) {
+ ScopeLogger();
+ operators = {{"$AND", &JsonFilter::AndOperator},
+ {"$OR", &JsonFilter::OrOperator},
+ {"$EXACTLY", &JsonFilter::EqualsOperator},
+ {"$CONTAINS", &JsonFilter::ContainsOperator}};
+}
+
+JsonFilter::~JsonFilter() {
+ ScopeLogger();
+}
+
+picojson::array JsonFilter::Filter(const picojson::array& records) const {
+ ScopeLogger();
+ picojson::array filtered;
+ for (const auto& record : records) {
+ if (IsMatch(record)) {
+ filtered.push_back(record);
+ }
+ }
+ return filtered;
+}
+
+bool JsonFilter::IsMatch(picojson::value record) const {
+ if (!filter.is<picojson::object>()) {
+ throw "filter is not a json object";
+ }
+ return EvaluateNode(filter.get<picojson::object>(), record);
+}
+
+bool JsonFilter::AndOperator(picojson::array args, picojson::value record) const {
+ ScopeLogger();
+ bool result = true;
+ for (const auto& value : args) {
+ if (value.is<picojson::object>()) {
+ result = result && EvaluateNode(value.get<picojson::object>(), record);
+ } else if (value.is<std::string>()) {
+ auto attribute = internal::GetAttribute(value.get<std::string>(), record);
+ result = result && attribute.evaluate_as_boolean();
+ } else {
+ result = result && value.evaluate_as_boolean();
+ }
+ }
+ return result;
+}
+
+bool JsonFilter::OrOperator(picojson::array args, picojson::value record) const {
+ ScopeLogger();
+ bool result = false;
+ for (const auto& value : args) {
+ if (value.is<picojson::object>()) {
+ result = result || EvaluateNode(value.get<picojson::object>(), record);
+ } else if (value.is<std::string>()) {
+ auto attribute = internal::GetAttribute(value.get<std::string>(), record);
+ result = result || attribute.evaluate_as_boolean();
+ } else {
+ result = result || value.evaluate_as_boolean();
+ }
+ }
+ return result;
+}
+
+bool JsonFilter::EqualsOperator(picojson::array args, picojson::value record) const {
+ ScopeLogger();
+ if (args.size() != 2) {
+ throw "equals operator takes exactly 2 arguments";
+ }
+
+ if (!args[0].is<std::string>()) {
+ throw "equals operator first argument must be a string (attribute path)";
+ }
+
+ auto attribute = internal::GetAttribute(args[0].get<std::string>(), record);
+ return args[1].serialize() == attribute.serialize();
+}
+
+bool JsonFilter::ContainsOperator(picojson::array args, picojson::value record) const {
+ ScopeLogger();
+ if (args.size() != 2) {
+ throw "contains operator takes exactly 2 arguments";
+ }
+
+ if (!args[0].is<std::string>() || !args[1].is<std::string>()) {
+ throw "contains operator arguments must be a string";
+ }
+
+ auto attribute = internal::GetAttribute(args[0].get<std::string>(), record);
+ if (!attribute.is<std::string>()) {
+ throw "attribute for contains operator must have a string value type";
+ }
+
+ return attribute.get<std::string>().find(args[1].get<std::string>()) != std::string::npos;
+}
+
+bool JsonFilter::IsOperator(std::string identifier) const {
+ ScopeLogger();
+ return operators.find(identifier) != operators.end();
+}
+
+bool JsonFilter::EvaluateOperator(std::string opType, picojson::value arguments,
+ picojson::value record) const {
+ ScopeLogger();
+ if (!arguments.is<picojson::array>()) {
+ throw "Operator requires an array of arguments";
+ }
+ return (this->*(operators.at(opType)))(arguments.get<picojson::array>(), record);
+}
+
+bool JsonFilter::EvaluateNode(picojson::object node, picojson::value record) const {
+ ScopeLogger();
+ bool result = true;
+ for (const auto attr : node) {
+ if (IsOperator(attr.first)) {
+ bool value = EvaluateOperator(attr.first, attr.second, record);
+ result = result && value;
+ }
+ }
+ return result;
+}
+
+} // namespace common
--- /dev/null
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "common/json-filter.h"
+#include "common/ut/json-filter.h"
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace {
+
+picojson::value jsonFromString(std::string json) {
+ picojson::value value;
+ picojson::parse(value, json);
+ return value;
+}
+
+} // namespace
+
+class SplitStringTest : public testing::Test {};
+
+TEST_F(SplitStringTest, BasicTest) {
+ std::string input = "attr1.attr2.attr3";
+ std::vector<std::string> expected = {"attr1", "attr2", "attr3"};
+ auto output = internal::SplitString(input, '.');
+ ASSERT_EQ(output.size(), expected.size());
+ ASSERT_EQ(output[0], expected[0]);
+ ASSERT_EQ(output[1], expected[1]);
+ ASSERT_EQ(output[2], expected[2]);
+}
+
+TEST_F(SplitStringTest, StartsWithDelimiter) {
+ std::string input = ".attr1.attr2";
+ std::vector<std::string> expected = {"attr1", "attr2"};
+ auto output = internal::SplitString(input, '.');
+ ASSERT_EQ(output.size(), expected.size());
+ ASSERT_EQ(output[0], expected[0]);
+ ASSERT_EQ(output[1], expected[1]);
+}
+
+TEST_F(SplitStringTest, EndsWithDelimiter) {
+ std::string input = "attr1.attr2.";
+ std::vector<std::string> expected = {"attr1", "attr2"};
+ auto output = internal::SplitString(input, '.');
+ ASSERT_EQ(output.size(), expected.size());
+ ASSERT_EQ(output[0], expected[0]);
+ ASSERT_EQ(output[1], expected[1]);
+}
+
+TEST_F(SplitStringTest, DelimitersOnly) {
+ std::string input = "...";
+ std::vector<std::string> expected = {};
+ auto output = internal::SplitString(input, '.');
+ ASSERT_EQ(output.size(), expected.size());
+}
+
+TEST_F(SplitStringTest, SingleAttributeOnly) {
+ std::string input = "attr";
+ std::vector<std::string> expected = {"attr"};
+ auto output = internal::SplitString(input, '.');
+ ASSERT_EQ(output.size(), expected.size());
+ ASSERT_EQ(output[0], expected[0]);
+}
+
+class GetAttributeTest : public testing::Test {};
+
+TEST_F(GetAttributeTest, BasicTest) {
+ std::string attribute = "attr1.attr2";
+ std::string json = "{\"attr1\": {\"attr2\": \"value\"}}";
+ picojson::value expected("value");
+ auto output = internal::GetAttribute(attribute, jsonFromString(json));
+ ASSERT_EQ(output.serialize(), expected.serialize());
+}
+
+TEST_F(GetAttributeTest, TopLevelAttribute) {
+ std::string attribute = "attr";
+ std::string json = "{\"attr\": \"value\"}";
+ picojson::value expected("value");
+ auto output = internal::GetAttribute(attribute, jsonFromString(json));
+ ASSERT_EQ(output.serialize(), expected.serialize());
+ ASSERT_EQ(output.get<std::string>(), expected.get<std::string>());
+}
+
+TEST_F(GetAttributeTest, AttributeNotFound) {
+ std::string attribute = "bad_attr";
+ std::string json = "{\"attr\": \"value\"}";
+ ASSERT_THROW(auto output = internal::GetAttribute(attribute, jsonFromString(json)), const char*);
+}
+
+TEST_F(GetAttributeTest, EmptyAttribute) {
+ std::string attribute = "";
+ std::string json = "{\"attr\": \"value\"}";
+ auto output = internal::GetAttribute(attribute, jsonFromString(json));
+ ASSERT_EQ(output.serialize(), jsonFromString(json).serialize());
+}
+
+class EqualsOperatorTest : public testing::Test {};
+
+TEST_F(EqualsOperatorTest, BasicPositiveTest) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value("attr1.attr2"),
+ jsonFromString("{\"name\": \"value\"}")};
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": {\"name\": \"value\"}}}");
+ bool output = jf.EqualsOperator(arguments, record);
+ ASSERT_TRUE(output);
+}
+
+TEST_F(EqualsOperatorTest, BasicNegativeTest) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value("attr1.attr2"),
+ jsonFromString("{\"name\": \"value\"}")};
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": {\"name\": \"different\"}}}");
+ bool output = jf.EqualsOperator(arguments, record);
+ ASSERT_FALSE(output);
+}
+
+TEST_F(EqualsOperatorTest, InvalidNumberOfArguments) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value("attr1.attr2")};
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": {\"name\": \"different\"}}}");
+ ASSERT_THROW(jf.EqualsOperator(arguments, record), const char*);
+}
+
+TEST_F(EqualsOperatorTest, FirstArgumentNotString) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value(false), jsonFromString("{}")};
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": {\"name\": \"different\"}}}");
+ ASSERT_THROW(jf.EqualsOperator(arguments, record), const char*);
+}
+
+class ContainsOperatorTest : public testing::Test {};
+
+TEST_F(ContainsOperatorTest, BasicPositiveTest) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value("attr1.attr2"), picojson::value("ello")};
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": \"Hello, world!\"}}");
+ bool output = jf.ContainsOperator(arguments, record);
+ ASSERT_TRUE(output);
+}
+
+TEST_F(ContainsOperatorTest, BasicNegativeTest) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value("attr1.attr2"), picojson::value("tizen")};
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": \"Hello, world!\"}}");
+ bool output = jf.ContainsOperator(arguments, record);
+ ASSERT_FALSE(output);
+}
+
+TEST_F(ContainsOperatorTest, InvalidNumberOfArguments) {
+ common::JsonFilter jf;
+ picojson::array arguments = {
+ picojson::value("attr1.attr2"),
+ };
+ auto record = jsonFromString("{}");
+ ASSERT_THROW(jf.ContainsOperator(arguments, record), const char*);
+}
+
+TEST_F(ContainsOperatorTest, InvalidArgumentsType) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value(123.5), picojson::value("substring")};
+ auto record = jsonFromString("{}");
+ ASSERT_THROW(jf.ContainsOperator(arguments, record), const char*);
+}
+
+TEST_F(ContainsOperatorTest, InvalidAttributeValueType) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value("attr1.attr2"), picojson::value("substring")};
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": 123.5}}");
+ ASSERT_THROW(jf.ContainsOperator(arguments, record), const char*);
+}
+
+class AndOperatorTest : public ::testing::Test {};
+
+TEST_F(AndOperatorTest, BasicPositiveTest) {
+ common::JsonFilter jf;
+ picojson::array arguments = {
+ picojson::value("attr1.attr2"), picojson::value("attr1.attr3"),
+ };
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": true, \"attr3\": true}}");
+ bool output = jf.AndOperator(arguments, record);
+ ASSERT_TRUE(output);
+}
+
+TEST_F(AndOperatorTest, BasicNegativeTest) {
+ common::JsonFilter jf;
+ picojson::array arguments = {
+ picojson::value("attr1.attr2"), picojson::value("attr1.attr3"),
+ };
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": true, \"attr3\": false}}");
+ bool output = jf.AndOperator(arguments, record);
+ ASSERT_FALSE(output);
+}
+
+TEST_F(AndOperatorTest, EvaluateWeirdArgumentsAsBooleans) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value(127.5), picojson::value(picojson::array())};
+ auto record = jsonFromString("{}");
+ bool output = jf.AndOperator(arguments, record);
+ ASSERT_TRUE(output);
+}
+
+class OrOperatorTest : public ::testing::Test {};
+
+TEST_F(OrOperatorTest, BasicPositiveTest) {
+ common::JsonFilter jf;
+ picojson::array arguments = {
+ picojson::value("attr1.attr2"), picojson::value("attr1.attr3"),
+ };
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": true, \"attr3\": false}}");
+ bool output = jf.OrOperator(arguments, record);
+ ASSERT_TRUE(output);
+}
+
+TEST_F(OrOperatorTest, BasicNegativeTest) {
+ common::JsonFilter jf;
+ picojson::array arguments = {
+ picojson::value("attr1.attr2"), picojson::value("attr1.attr3"),
+ };
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": false, \"attr3\": false}}");
+ bool output = jf.OrOperator(arguments, record);
+ ASSERT_FALSE(output);
+}
+
+TEST_F(OrOperatorTest, EvaluateWeirdArgumentsAsBooleans) {
+ common::JsonFilter jf;
+ picojson::array arguments = {picojson::value(127.5), picojson::value()};
+ auto record = jsonFromString("{}");
+ bool output = jf.OrOperator(arguments, record);
+ ASSERT_TRUE(output);
+}
+
+class EvaluateOperatorTest : public ::testing::Test {};
+
+TEST_F(EvaluateOperatorTest, BasicTest) {
+ common::JsonFilter jf;
+ auto arguments = jsonFromString("[\"attr1.attr2\", \"attr1.attr3\"]");
+ auto record = jsonFromString("{\"attr1\": {\"attr2\": true, \"attr3\": false}}");
+ EXPECT_FALSE(jf.EvaluateOperator("$AND", arguments, record));
+ EXPECT_TRUE(jf.EvaluateOperator("$OR", arguments, record));
+}
+
+TEST_F(EvaluateOperatorTest, ArgumentsNotAnArray) {
+ common::JsonFilter jf;
+ auto arguments = jsonFromString("{}");
+ auto record = jsonFromString("{}");
+ ASSERT_THROW(jf.EvaluateOperator("$AND", arguments, record), const char*);
+}
+
+class EvaluateNodeTest : public ::testing::Test {};
+
+TEST_F(EvaluateNodeTest, BasicPositiveTest) {
+ common::JsonFilter jf;
+ auto node = jsonFromString(
+ "{ "
+ " \"$AND\": [{ "
+ " \"$EXACTLY\": [ "
+ " \"attr1.attr2\","
+ " \"tizen\" "
+ " ], "
+ " \"$CONTAINS\": [ "
+ " \"attr1.attr3\","
+ " \"ello\" "
+ " ] "
+ " }] "
+ "} ");
+ auto record = jsonFromString(
+ "{ "
+ " \"attr1\": { "
+ " \"attr2\": \"tizen\", "
+ " \"attr3\": \"hello\" "
+ " } "
+ "} ");
+ ASSERT_TRUE(jf.EvaluateNode(node.get<picojson::object>(), record));
+}
+
+class IsMatchTest : public ::testing::Test {};
+
+TEST_F(IsMatchTest, BasicPositiveTest) {
+ common::JsonFilter jf(
+ jsonFromString("{ "
+ " \"$OR\": [{ "
+ " \"$EXACTLY\": [ "
+ " \"attr1.attr2\","
+ " \"tizen\" "
+ " ]}, "
+ " {\"$EXACTLY\": [ "
+ " \"attr1.attr2\","
+ " \"hello\" "
+ " ]} "
+ " }] "
+ "} "));
+ auto record1 = jsonFromString(
+ "{ "
+ " \"attr1\": { "
+ " \"attr2\": \"tizen\", "
+ " } "
+ "} ");
+ auto record2 = jsonFromString(
+ "{ "
+ " \"attr1\": { "
+ " \"attr2\": \"hello\" "
+ " } "
+ "} ");
+ auto record3 = jsonFromString(
+ "{ "
+ " \"attr1\": { "
+ " \"attr2\": \"wrong\" "
+ " } "
+ "} ");
+
+ EXPECT_TRUE(jf.IsMatch(record1));
+ EXPECT_TRUE(jf.IsMatch(record2));
+ EXPECT_FALSE(jf.IsMatch(record3));
+}