Skip to content

Commit

Permalink
Add support for filter match with an array property set entry
Browse files Browse the repository at this point in the history
  • Loading branch information
pnoltes committed Jan 7, 2024
1 parent a8038ec commit 968cc8a
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 143 deletions.
82 changes: 37 additions & 45 deletions libs/utils/gtest/src/CxxFilterTestSuite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,55 +113,47 @@ TEST_F(CxxFilterTestSuite, HasMandatoryNegatedPresenceAttribute) {
}

TEST_F(CxxFilterTestSuite, FilterForLongAttribute) {
celix::Filter filter1{"(key1>=10)"};
EXPECT_TRUE(filter1.match({{"key1", "12"}}));
EXPECT_TRUE(filter1.match({{"key1", "11.1"}}));
EXPECT_TRUE(filter1.match({{"key1", "10"}}));
EXPECT_FALSE(filter1.match({{"key1", "2"}}));
EXPECT_FALSE(filter1.match({{"key1", "1"}}));
EXPECT_FALSE(filter1.match({{"key1", "0.1"}}));

celix::Filter filter2{"(key1>20)"};
EXPECT_TRUE(filter2.match({{"key1", "21"}}));
EXPECT_TRUE(filter2.match({{"key1", "20.1"}}));
EXPECT_FALSE(filter2.match({{"key1", "20"}}));
EXPECT_FALSE(filter2.match({{"key1", "3"}}));
EXPECT_FALSE(filter2.match({{"key1", "2"}}));
EXPECT_FALSE(filter2.match({{"key1", "0.1"}}));
celix::Properties props{};
props.set("key1", 1L);
props.set("key2", 20L);

EXPECT_EQ(celix::Properties::ValueType::Long, props.getType("key1"));
EXPECT_EQ(celix::Properties::ValueType::Long, props.getType("key2"));

celix::Filter filter1{"(key1>=1)"};
EXPECT_TRUE(filter1.match(props));
celix::Filter filter2{"(key2<=3)"};
EXPECT_FALSE(filter2.match(props));
celix::Filter filter3{"(key2>=1)"};
EXPECT_TRUE(filter3.match(props));
}

TEST_F(CxxFilterTestSuite, FilterForDoubleAttribute) {
celix::Filter filter1{"(key1>=10.5)"};
EXPECT_TRUE(filter1.match({{"key1", "12"}}));
EXPECT_TRUE(filter1.match({{"key1", "11.1"}}));
EXPECT_TRUE(filter1.match({{"key1", "10.5"}}));
EXPECT_FALSE(filter1.match({{"key1", "2"}}));
EXPECT_FALSE(filter1.match({{"key1", "1"}}));
EXPECT_FALSE(filter1.match({{"key1", "0.1"}}));

celix::Filter filter2{"(key1>20.5)"};
EXPECT_TRUE(filter2.match({{"key1", "21"}}));
EXPECT_TRUE(filter2.match({{"key1", "20.7"}}));
EXPECT_FALSE(filter2.match({{"key1", "20.5"}}));
EXPECT_FALSE(filter2.match({{"key1", "3"}}));
EXPECT_FALSE(filter2.match({{"key1", "2"}}));
EXPECT_FALSE(filter2.match({{"key1", "0.1"}}));
celix::Properties props{};
props.set("key1", 1.1);
props.set("key2", 20.2);

EXPECT_EQ(celix::Properties::ValueType::Double, props.getType("key1"));
EXPECT_EQ(celix::Properties::ValueType::Double, props.getType("key2"));

celix::Filter filter1{"(key1>=1.1)"};
EXPECT_TRUE(filter1.match(props));
celix::Filter filter2{"(key2<=3.3)"};
EXPECT_FALSE(filter2.match(props));
}

TEST_F(CxxFilterTestSuite, FilterForVersionAttribute) {
celix::Filter filter1{"(key1>=1.2.3.qualifier)"};
EXPECT_TRUE(filter1.match({{"key1", "1.2.3.qualifier"}}));
EXPECT_TRUE(filter1.match({{"key1", "2"}}));
EXPECT_TRUE(filter1.match({{"key1", "2.0.0"}}));
EXPECT_TRUE(filter1.match({{"key1", "1.2.4"}}));
EXPECT_FALSE(filter1.match({{"key1", "1.2.2"}}));
EXPECT_FALSE(filter1.match({{"key1", "1.0.0"}}));
EXPECT_FALSE(filter1.match({{"key1", "0.1"}}));

celix::Filter filter2{"(key1>2.3.4)"};
EXPECT_TRUE(filter2.match({{"key1", "3"}}));
EXPECT_TRUE(filter2.match({{"key1", "3.0.0"}}));
EXPECT_TRUE(filter2.match({{"key1", "2.3.5"}}));
EXPECT_FALSE(filter2.match({{"key1", "2.3.4"}}));
EXPECT_FALSE(filter2.match({{"key1", "0.0.3"}}));
celix::Properties props{};
props.set("key1", celix::Version{1, 2, 3});
props.set("key2", celix::Version{2, 0, 0});

EXPECT_EQ(celix::Properties::ValueType::Version, props.getType("key1"));
EXPECT_EQ(celix::Properties::ValueType::Version, props.getType("key2"));

celix::Filter filter1{"(key1>=1.2.3)"};
EXPECT_TRUE(filter1.match(props));
celix::Filter filter2{"(key2<=2.0.0)"};
EXPECT_TRUE(filter2.match(props));
celix::Filter filter3{"(key2<=1.2.3)"};
EXPECT_FALSE(filter3.match(props));
}
113 changes: 113 additions & 0 deletions libs/utils/gtest/src/FilterTestSuite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,119 @@ TEST_F(FilterTestSuite, InvalidEscapeTest) {
EXPECT_EQ(nullptr, celix_filter_create("(\\")); //escape without following char
}

TEST_F(FilterTestSuite, UnmatchedTypeMatchTest) {
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "str", "20");
celix_properties_setLong(props, "long", 20);

celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(str<3)");
EXPECT_TRUE(filter1 != nullptr); //note string is compared as string

celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(long<3)");
EXPECT_TRUE(filter2 != nullptr);
EXPECT_FALSE(celix_filter_match(filter2, props)); //note long is compared as long
}

TEST_F(FilterTestSuite, MatchArrayTypesTest) {
const char* strings[] = {"a", "b", "c"};
const long longs[] = {1, 2, 3};
const double doubles[] = {1.0, 2.0, 3.0};
const bool bools[] = {true, true, true};
celix_autoptr(celix_version_t) v1 = celix_version_createVersionFromString("1.0.0");
celix_autoptr(celix_version_t) v2 = celix_version_createVersionFromString("2.0.0");
celix_autoptr(celix_version_t) v3 = celix_version_createVersionFromString("3.0.0");
const celix_version_t* versions[] = {v1, v2, v3};

celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_setStrings(props, "strings", strings, 3);
celix_properties_setLongs(props, "longs", longs, 3);
celix_properties_setDoubles(props, "doubles", doubles, 3);
celix_properties_setBooleans(props, "bools", bools, 3);
celix_properties_setVersions(props, "versions", versions, 3);

// Check if match is true if any of the array elements match
celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(strings=a)");
EXPECT_TRUE(filter1 != nullptr);
EXPECT_TRUE(celix_filter_match(filter1, props));

celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(longs<2)");
EXPECT_TRUE(filter2 != nullptr);
EXPECT_TRUE(celix_filter_match(filter2, props));

celix_autoptr(celix_filter_t) filter3 = celix_filter_create("(doubles>2.9)");
EXPECT_TRUE(filter3 != nullptr);
EXPECT_TRUE(celix_filter_match(filter3, props));

celix_autoptr(celix_filter_t) filter4 = celix_filter_create("(bools=true)");
EXPECT_TRUE(filter4 != nullptr);
EXPECT_TRUE(celix_filter_match(filter4, props));

celix_autoptr(celix_filter_t) filter5 = celix_filter_create("(&(versions>=2.0.0)(versions<=2.0.0))");
EXPECT_TRUE(filter5 != nullptr);
EXPECT_TRUE(celix_filter_match(filter5, props));

// Check if match is false if none of the array elements match
celix_autoptr(celix_filter_t) filter6 = celix_filter_create("(strings=x)");
EXPECT_TRUE(filter6 != nullptr);
EXPECT_FALSE(celix_filter_match(filter6, props));

celix_autoptr(celix_filter_t) filter7 = celix_filter_create("(longs>3)");
EXPECT_TRUE(filter7 != nullptr);
EXPECT_FALSE(celix_filter_match(filter7, props));

celix_autoptr(celix_filter_t) filter8 = celix_filter_create("(doubles<0.9)");
EXPECT_TRUE(filter8 != nullptr);
EXPECT_FALSE(celix_filter_match(filter8, props));

celix_autoptr(celix_filter_t) filter9 = celix_filter_create("(bools=false)");
EXPECT_TRUE(filter9 != nullptr);
EXPECT_FALSE(celix_filter_match(filter9, props));

celix_autoptr(celix_filter_t) filter10 = celix_filter_create("(&(versions>=4.0.0)(versions<=4.0.0))");
EXPECT_TRUE(filter10 != nullptr);
EXPECT_FALSE(celix_filter_match(filter10, props));
}

TEST_F(FilterTestSuite, ApproxWithArrayAttributesTest) {
const char* strings[] = {"abcdef", "defghi", "ghijkl"};
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_setStrings(props, "strings", strings, 3);

celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(strings~=abc)");
EXPECT_TRUE(filter1 != nullptr);
EXPECT_TRUE(celix_filter_match(filter1, props));

celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(strings~=def)");
EXPECT_TRUE(filter2 != nullptr);
EXPECT_TRUE(celix_filter_match(filter2, props));

celix_autoptr(celix_filter_t) filter3 = celix_filter_create("(strings~=jkl)");
EXPECT_TRUE(filter3 != nullptr);
EXPECT_TRUE(celix_filter_match(filter3, props));

celix_autoptr(celix_filter_t) filter4 = celix_filter_create("(strings~=mno)");
EXPECT_TRUE(filter4 != nullptr);
EXPECT_FALSE(celix_filter_match(filter4, props));
}

TEST_F(FilterTestSuite, SubStringWithArrayAttributesTest) {
const char* strings[] = {"John Doe", "Jane Doe", "John Smith"};
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_setStrings(props, "strings", strings, 3);

celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(strings=John*)");
EXPECT_TRUE(filter1 != nullptr);
EXPECT_TRUE(celix_filter_match(filter1, props));

celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(strings=*Doe)");
EXPECT_TRUE(filter2 != nullptr);
EXPECT_TRUE(celix_filter_match(filter2, props));

// check if match is false if none of the array elements match with a substring
celix_autoptr(celix_filter_t) filter3 = celix_filter_create("(strings=*Johnson)");
EXPECT_TRUE(filter3 != nullptr);
EXPECT_FALSE(celix_filter_match(filter3, props));
}

#include "filter.h"
TEST_F(FilterTestSuite, DeprecatedApiTest) {
Expand Down
44 changes: 41 additions & 3 deletions libs/utils/include/celix_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @file celix_filter.h
* @brief Header file for the Celix Filter API.
*
* #Introduction
* An Apache Celix filter is a based on LDAP filters, for more information about LDAP filters see:
* - https://tools.ietf.org/html/rfc4515
* - https://tools.ietf.org/html/rfc1960
Expand Down Expand Up @@ -54,10 +55,47 @@
* Apache Celix filters can be used to match a set of Apache Celix properties and such Apache Celix filters should be
* used together with a set of properties.
*
* Internally attribute values will be parsed to a long, double, boolean and Apache Celix version if
* possible during creation. And these typed attribute values will be used in the to-be-matched property value,
* if the property value is of the same type.
* #Filter matching and property value types
* When matching a filter the attribute type of a filter node is used to determine the type of the property value and
* this type is used to compare the property value with the filter attribute value.
* If the property value is of a type that the filter attribute value can be parsed to, the filter attribute value the
* comparison is done with the matching type. If the property value is not of a type that the filter attribute value
* can be parsed to, the comparison is done with the string representation of the property value.
*
* Internally attribute values will be parsed to a long, double, boolean, Apache Celix version and array list of
* longs, doubles, booleans, Apache Celix versions and string if possible during creation.
* And these typed attribute values will be used in the to-be-matched property value.
*
* Example: The filter "(key>20)" and a property set with a long value 3 for key "key", will match and the same
* filter but with a property set which has a string value "3" for key "key", will not match.
*
* #Filter matching and property value arrays
* If a filter matches a property set entry which has an array value (either a long, boolean, double, string or version
* array) the filter match will check if the array contains a single entry which matches the filter attribute value using
* the aforementioned "Filter matching and property value types" rules.
*
* Filter matching with array is supported for the following operands: "=" (including substring), ">=", "<=", ">", "<"
* and "~=".
*
* Example: The filter "(key=3)" will match the property set entry with key "key" and a long array list value of
* [1,2,3].
*
* #Substring filter operand
* The substring filter operand is used to match a string value with a filter attribute value. The filter attribute
* value can contain a `*` to match any character sequence. The filter attribute value can also contain a `*` at the
* start or end of the string to match any character sequence at the start or end of the string.
*
* A substring filter operand uses the string representation of the property value to match with the filter attribute
* or if the property value is an string array the substring filter operand will match if any of the string array values.
*
* Example: The filter "(key=*Smith)" will match the property set entry with key "key" and a string value "John Smith".
*
* #Approx filter operand
* The approx filter operand is used to check if the filter attribute value is a substring of the property value.
* A approx filter operand uses the string representation of the property value to match with the filter attribute or
* if the property value is an string array the approx filter operand will match if any of the string array values.
*
* Example: The filter "(key~=Smith)" will match the property set entry with key "key" and a string value "John Smith".
*/

#ifndef CELIX_FILTER_H_
Expand Down
Loading

0 comments on commit 968cc8a

Please sign in to comment.