Skip to content

Commit

Permalink
Supporting lists of values for anything-but-prefix and anything-but-s…
Browse files Browse the repository at this point in the history
…uffix (#143)

* Adding support for lists of strings for anything-but-prefix and anything-but-suffix

* Testing for deletion

* Addressing Rishi's comments
  • Loading branch information
jonessha authored Feb 29, 2024
1 parent 3f24ee6 commit 3b90aa9
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 95 deletions.
42 changes: 39 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ actual backslash character. A backslash escaping any character other than asteri

Anything-but matching does what the name says: matches anything *except* what's provided in the rule.

Anything-but works with single string and numeric values or lists, which have to contain entirely strings or
entirely numerics. It also may be applied to a prefix match.
Anything-but works with single string and numeric values or lists, which have to contain entirely strings or entirely
numerics. It also may be applied to a prefix, suffix, or equals-ignore-case match of a string or a list of strings.

Single anything-but (string, then numeric):
```javascript
Expand Down Expand Up @@ -198,6 +198,15 @@ Anything-but prefix:
}
```

Anything-but prefix list (strings):
```javascript
{
"detail": {
"state": [ { "anything-but": { "prefix": [ "init", "error" ] } } ]
}
}
```

Anything-but suffix:
```javascript
{
Expand All @@ -207,6 +216,25 @@ Anything-but suffix:
}
```

Anything-but suffix list (strings):
```javascript
{
"detail": {
"instance-id": [ { "anything-but": { "suffix": [ "1234", "6789" ] } } ]
}
}
```

Anything-but-ignore-case:
```javascript
{
"detail": {
"state": [ { "anything-but": {"equals-ignore-case": "Stopped" } } ]
}
}

```

Anything-but-ignore-case list (strings):
```javascript
{
Expand Down Expand Up @@ -717,7 +745,15 @@ public static ValuePatterns suffixEqualsIgnoreCaseMatch(final String suffix);
public static ValuePatterns equalsIgnoreCaseMatch(final String value);
public static ValuePatterns wildcardMatch(final String value);
public static AnythingBut anythingButMatch(final String anythingBut);
public static AnythingBut anythingButPrefix(final String prefix);
public static AnythingBut anythingButMatch(final Set<String> anythingButs);
public static AnythingBut anythingButMatch(final double anythingBut);
public static AnythingBut anythingButNumberMatch(final Set<Double> anythingButs);
public static AnythingButValuesSet anythingButPrefix(final String prefix);
public static AnythingButValuesSet anythingButPrefix(final Set<String> anythingButs);
public static AnythingButValuesSet anythingButSuffix(final String suffix);
public static AnythingButValuesSet anythingButSuffix(final Set<String> anythingButs);
public static AnythingButValuesSet anythingButIgnoreCaseMatch(final String anythingBut);
public static AnythingButValuesSet anythingButIgnoreCaseMatch(final Set<String> anythingButs);
public static ValuePatterns numericEquals(final double val);
public static Range lessThan(final double val);
public static Range lessThanOrEqualTo(final double val);
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<groupId>software.amazon.event.ruler</groupId>
<artifactId>event-ruler</artifactId>
<name>Event Ruler</name>
<version>1.7.2</version>
<version>1.7.3</version>
<description>Event Ruler is a Java library that allows matching Rules to Events. An event is a list of fields,
which may be given as name/value pairs or as a JSON object. A rule associates event field names with lists of
possible values. There are two reasons to use Ruler: 1/ It's fast; the time it takes to match Events doesn't
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
import java.util.Set;

/**
* Represents denylist like rule: any value matches if it's *not* in the anything-but/ignore-case list.
* Represents denylist like rule: any value matches if it's *not* in the anything-but set.
* It supports lists whose members must be all strings.
* Matching is case-insensitive
* This can be used for anything-but-equals-ignore-case, anything-but-prefix, and anything-but-suffix.
*/
public class AnythingButEqualsIgnoreCase extends Patterns {
public class AnythingButValuesSet extends Patterns {

private final Set<String> values;

AnythingButEqualsIgnoreCase(final Set<String> values) {
super(MatchType.ANYTHING_BUT_IGNORE_CASE);
AnythingButValuesSet(final MatchType matchType, final Set<String> values) {
super(matchType);
this.values = Collections.unmodifiableSet(values);
}

Expand All @@ -34,7 +34,7 @@ public boolean equals(Object o) {
return false;
}

AnythingButEqualsIgnoreCase that = (AnythingButEqualsIgnoreCase) o;
AnythingButValuesSet that = (AnythingButValuesSet) o;

return (Objects.equals(values, that.values));
}
Expand All @@ -48,6 +48,6 @@ public int hashCode() {

@Override
public String toString() {
return "ABIC:"+ values + ", (" + super.toString() + ")";
return "ABVS:"+ values + ", (" + super.toString() + ")";
}
}
28 changes: 14 additions & 14 deletions src/main/software/amazon/event/ruler/ByteMachine.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,17 @@ void deletePattern(final Patterns pattern) {
deleteAnythingButPattern((AnythingBut) pattern);
break;
case ANYTHING_BUT_IGNORE_CASE:
assert pattern instanceof AnythingButEqualsIgnoreCase;
deleteAnythingButEqualsIgnoreCasePattern((AnythingButEqualsIgnoreCase) pattern);
case ANYTHING_BUT_PREFIX:
case ANYTHING_BUT_SUFFIX:
assert pattern instanceof AnythingButValuesSet;
deleteAnythingButValuesSetPattern((AnythingButValuesSet) pattern);
break;
case EXACT:
case NUMERIC_EQ:
case PREFIX:
case PREFIX_EQUALS_IGNORE_CASE:
case SUFFIX:
case SUFFIX_EQUALS_IGNORE_CASE:
case ANYTHING_BUT_PREFIX:
case ANYTHING_BUT_SUFFIX:
case EQUALS_IGNORE_CASE:
case WILDCARD:
assert pattern instanceof ValuePatterns;
Expand All @@ -181,7 +181,7 @@ private void deleteAnythingButPattern(AnythingBut pattern) {
deleteMatchStep(startState, 0, pattern, getParser().parse(pattern.type(), value)));
}

private void deleteAnythingButEqualsIgnoreCasePattern(AnythingButEqualsIgnoreCase pattern) {
private void deleteAnythingButValuesSetPattern(AnythingButValuesSet pattern) {
pattern.getValues().forEach(value ->
deleteMatchStep(startState, 0, pattern, getParser().parse(pattern.type(), value)));
}
Expand Down Expand Up @@ -687,11 +687,11 @@ NameState addPattern(final Patterns pattern, final NameState nameState) {
assert pattern instanceof AnythingBut;
return addAnythingButPattern((AnythingBut) pattern, nameState);
case ANYTHING_BUT_IGNORE_CASE:
assert pattern instanceof AnythingButEqualsIgnoreCase;
return addAnythingButEqualsIgnoreCasePattern((AnythingButEqualsIgnoreCase) pattern, nameState);

case ANYTHING_BUT_SUFFIX:
case ANYTHING_BUT_PREFIX:
assert pattern instanceof AnythingButValuesSet;
return addAnythingButValuesSetPattern((AnythingButValuesSet) pattern, nameState);

case EXACT:
case NUMERIC_EQ:
case PREFIX:
Expand Down Expand Up @@ -731,7 +731,7 @@ private NameState addAnythingButPattern(AnythingBut pattern, NameState nameState
return nameStateToBeReturned;
}

private NameState addAnythingButEqualsIgnoreCasePattern(AnythingButEqualsIgnoreCase pattern, NameState nameState) {
private NameState addAnythingButValuesSetPattern(AnythingButValuesSet pattern, NameState nameState) {

NameState nameStateToBeReturned = nameState;
NameState nameStateChecker = null;
Expand Down Expand Up @@ -937,16 +937,16 @@ NameState findPattern(final Patterns pattern) {
assert pattern instanceof AnythingBut;
return findAnythingButPattern((AnythingBut) pattern);
case ANYTHING_BUT_IGNORE_CASE:
assert pattern instanceof AnythingButEqualsIgnoreCase;
return findAnythingButEqualsIgnoreCasePattern((AnythingButEqualsIgnoreCase) pattern);
case ANYTHING_BUT_SUFFIX:
case ANYTHING_BUT_PREFIX:
assert pattern instanceof AnythingButValuesSet;
return findAnythingButValuesSetPattern((AnythingButValuesSet) pattern);
case EXACT:
case NUMERIC_EQ:
case PREFIX:
case PREFIX_EQUALS_IGNORE_CASE:
case SUFFIX:
case SUFFIX_EQUALS_IGNORE_CASE:
case ANYTHING_BUT_SUFFIX:
case ANYTHING_BUT_PREFIX:
case EQUALS_IGNORE_CASE:
case WILDCARD:
assert pattern instanceof ValuePatterns;
Expand Down Expand Up @@ -974,7 +974,7 @@ private NameState findAnythingButPattern(AnythingBut pattern) {
return null;
}

private NameState findAnythingButEqualsIgnoreCasePattern(AnythingButEqualsIgnoreCase pattern) {
private NameState findAnythingButValuesSetPattern(AnythingButValuesSet pattern) {

Set<NameState> nextNameStates = new HashSet<>(pattern.getValues().size());
for (String value : pattern.getValues()) {
Expand Down
132 changes: 81 additions & 51 deletions src/main/software/amazon/event/ruler/JsonRuleCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ private static Patterns processMatchExpression(final JsonParser parser) throws I
return range;
} else if (Constants.ANYTHING_BUT_MATCH.equals(matchTypeName)) {

boolean isIgnoreCase = false;
MatchType matchType = MatchType.ANYTHING_BUT;
JsonToken anythingButExpressionToken = parser.nextToken();
if (anythingButExpressionToken == JsonToken.START_OBJECT) {

Expand All @@ -417,36 +417,22 @@ private static Patterns processMatchExpression(final JsonParser parser) throws I
barf(parser, "Anything-But expression name not found");
}
final String anythingButObjectOp = parser.getCurrentName();
final boolean isPrefix = Constants.PREFIX_MATCH.equals(anythingButObjectOp);
final boolean isSuffix = Constants.SUFFIX_MATCH.equals(anythingButObjectOp);
isIgnoreCase = Constants.EQUALS_IGNORE_CASE.equals(anythingButObjectOp);
if(!isIgnoreCase) {
if (!isPrefix && !isSuffix) {
switch (anythingButObjectOp) {
case Constants.EQUALS_IGNORE_CASE:
matchType = MatchType.ANYTHING_BUT_IGNORE_CASE;
break;
case Constants.PREFIX_MATCH:
matchType = MatchType.ANYTHING_BUT_PREFIX;
break;
case Constants.SUFFIX_MATCH:
matchType = MatchType.ANYTHING_BUT_SUFFIX;
break;
default:
barf(parser, "Unsupported anything-but pattern: " + anythingButObjectOp);
}
final JsonToken anythingButParamType = parser.nextToken();
if (anythingButParamType != JsonToken.VALUE_STRING) {
barf(parser, "prefix/suffix match pattern must be a string");
}
final String text = parser.getText();
if (text.isEmpty()) {
barf(parser, "Null prefix/suffix not allowed");
}
if (parser.nextToken() != JsonToken.END_OBJECT) {
barf(parser, "Only one key allowed in match expression");
}
if (parser.nextToken() != JsonToken.END_OBJECT) {
barf(parser, "Only one key allowed in match expression");
}
if(isPrefix) {
return Patterns.anythingButPrefix('"' + text); // note no trailing quote
} else {
return Patterns.anythingButSuffix(text + '"'); // note no leading quote
}
} else {
// Step into anything-but's equals-ignore-case
anythingButExpressionToken = parser.nextToken();
}

// Step into anything-but's equals-ignore-case/prefix/suffix
anythingButExpressionToken = parser.nextToken();
}

if (anythingButExpressionToken != JsonToken.START_ARRAY &&
Expand All @@ -459,24 +445,24 @@ private static Patterns processMatchExpression(final JsonParser parser) throws I

Patterns anythingBut;
if (anythingButExpressionToken == JsonToken.START_ARRAY) {
if(isIgnoreCase) {
anythingBut = processAnythingButEqualsIgnoreCaseListMatchExpression(parser);
} else {
anythingBut = processAnythingButListMatchExpression(parser);
}
if (matchType == MatchType.ANYTHING_BUT) {
anythingBut = processAnythingButListMatchExpression(parser);
} else {
anythingBut = processAnythingButValuesSetMatchExpression(parser, matchType);
}
} else {
if(isIgnoreCase) {
anythingBut = processAnythingButEqualsIgnoreCaseMatchExpression(parser, anythingButExpressionToken);
} else {
anythingBut = processAnythingButMatchExpression(parser, anythingButExpressionToken);
}
if (matchType == MatchType.ANYTHING_BUT) {
anythingBut = processAnythingButMatchExpression(parser, anythingButExpressionToken);
} else {
anythingBut = processAnythingButValuesSetSingleValueMatchExpression(parser, anythingButExpressionToken, matchType);
}
}

if (parser.nextToken() != JsonToken.END_OBJECT) {
tooManyElements(parser);
}
// Complete the object closure for equals-ignore-case
if (isIgnoreCase && parser.nextToken() != JsonToken.END_OBJECT) {
// Complete the object closure when a set is present
if (matchType != MatchType.ANYTHING_BUT && parser.nextToken() != JsonToken.END_OBJECT) {
tooManyElements(parser);
}

Expand Down Expand Up @@ -608,25 +594,39 @@ private static Patterns processAnythingButListMatchExpression(JsonParser parser)
return AnythingBut.anythingButMatch(values, hasNumber);
}

private static Patterns processAnythingButEqualsIgnoreCaseListMatchExpression(JsonParser parser) throws JsonParseException {
private static Patterns processAnythingButValuesSetMatchExpression(JsonParser parser, MatchType matchType)
throws JsonParseException {
JsonToken token;
Set<String> values = new HashSet<>();
boolean hasNumber = false;
try {
while ((token = parser.nextToken()) != JsonToken.END_ARRAY) {
switch (token) {
case VALUE_STRING:
values.add('"' + parser.getText() + '"');
String text = parser.getText();
if (matchType != MatchType.ANYTHING_BUT_IGNORE_CASE && text.isEmpty()) {
barf(parser, "Null prefix/suffix not allowed");
}
values.add(generateValueBasedOnMatchType(text, matchType));
break;
default:
barf(parser, "Inside anything-but/equals-ignore-case list, number|start|null|boolean is not supported.");
if (matchType == MatchType.ANYTHING_BUT_IGNORE_CASE) {
barf(parser, "Inside anything-but/equals-ignore-case list, number|start|null|boolean is not supported.");
} else {
barf(parser, "prefix/suffix match pattern must be a string");
}
}
}
} catch (IllegalArgumentException | IOException e) {
barf(parser, e.getMessage());
}

return AnythingButEqualsIgnoreCase.anythingButIgnoreCaseMatch(values);
switch (matchType) {
case ANYTHING_BUT_IGNORE_CASE: return Patterns.anythingButIgnoreCaseMatch(values);
case ANYTHING_BUT_PREFIX: return Patterns.anythingButPrefix(values);
case ANYTHING_BUT_SUFFIX: return Patterns.anythingButSuffix(values);
// Not barfing as this is a code bug rather than bad JSON.
default: throw new IllegalArgumentException("processAnythingButValuesSetMatchExpression received invalid matchType of " + matchType);
}
}

private static Patterns processAnythingButMatchExpression(JsonParser parser,
Expand All @@ -648,17 +648,47 @@ private static Patterns processAnythingButMatchExpression(JsonParser parser,
return AnythingBut.anythingButMatch(values, hasNumber);
}

private static Patterns processAnythingButEqualsIgnoreCaseMatchExpression(JsonParser parser,
JsonToken anythingButExpressionToken) throws IOException {
private static Patterns processAnythingButValuesSetSingleValueMatchExpression(JsonParser parser,
JsonToken anythingButExpressionToken, MatchType matchType) throws IOException {
Set<String> values = new HashSet<>();
switch (anythingButExpressionToken) {
case VALUE_STRING:
values.add('"' + parser.getText() + '"');
String text = parser.getText();
if (matchType != MatchType.ANYTHING_BUT_IGNORE_CASE && text.isEmpty()) {
barf(parser, "Null prefix/suffix not allowed");
}
values.add(generateValueBasedOnMatchType(text, matchType));
break;
default:
barf(parser, "Inside anything-but/equals-ignore-case list, number|start|null|boolean is not supported.");
if (matchType == MatchType.ANYTHING_BUT_IGNORE_CASE) {
barf(parser, "Inside anything-but/equals-ignore-case list, number|start|null|boolean is not supported.");
} else {
barf(parser, "prefix/suffix match pattern must be a string");
}
}

switch (matchType) {
case ANYTHING_BUT_IGNORE_CASE: return Patterns.anythingButIgnoreCaseMatch(values);
case ANYTHING_BUT_PREFIX: return Patterns.anythingButPrefix(values);
case ANYTHING_BUT_SUFFIX: return Patterns.anythingButSuffix(values);
// Not barfing as this is a code bug rather than bad JSON.
default: throw new IllegalArgumentException("processAnythingButValuesSetSingleValueMatchExpression received invalid matchType of " + matchType);
}
}

private static String generateValueBasedOnMatchType(String text, MatchType matchType) {
switch (matchType) {
case PREFIX:
case PREFIX_EQUALS_IGNORE_CASE:
case ANYTHING_BUT_PREFIX:
return '"' + text;
case SUFFIX:
case SUFFIX_EQUALS_IGNORE_CASE:
case ANYTHING_BUT_SUFFIX:
return text + '"';
default:
return '"' + text + '"';
}
return AnythingButEqualsIgnoreCase.anythingButIgnoreCaseMatch(values);
}

private static Patterns processNumericMatchExpression(final JsonParser parser) throws IOException {
Expand Down
Loading

0 comments on commit 3b90aa9

Please sign in to comment.