diff --git a/.gitignore b/.gitignore index ddbf952..9165e9d 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,5 @@ fabric.properties # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* env.list + +gen \ No newline at end of file diff --git a/README.md b/README.md index 6ea6327..d4b083a 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,19 @@ * inWhiteList("field") * inBlackList("field") * like("field", "regexp_in_java_style"[1]) -* countryBy("field") - this function can return result "unknown", you must remember it! +* amount() +* country() - this function can return result "unknown", you must remember it! ~~~~ + +##### group_field: + * email, + * ip, + * fingerprint, + * bin, + * shop_ip, + * party_id, + * card_token + 1. [regexp_in_java_style](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) ##### RESULTS: ~~~~ @@ -54,9 +65,14 @@ rule: unique("email", "ip") < 4 ~~~~ ###### Check country by ip: ~~~~ -rule: countryBy("ip") = "RU" +rule: country() = "RU" -> notify; ~~~~ +###### Check current amount: +~~~~ +rule: amount() < 100 +-> accept; +~~~~ ###### Combined check: ~~~~ rule: 3 > 2 AND 1 > 1 diff --git a/src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 b/src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 index 15eff2f..7641b7e 100644 --- a/src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 +++ b/src/main/antlr4/com.rbkmoney.fraudo/Fraudo.g4 @@ -5,7 +5,7 @@ parse ; fraud_rule - : RULE_BLOCK expression RETURN result SCOL + : RULE_BLOCK expression RETURN result (CATCH_ERROR catch_result)? SCOL ; expression @@ -25,7 +25,8 @@ expression | in_white_list #inWhiteListExpression | in_black_list #inBlackListExpression | like #likeFunctionExpression - | country_by #countryByFunctionExpression + | country #countryFunctionExpression + | amount #amountFunctionExpression | IDENTIFIER #identifierExpression | DECIMAL #decimalExpression | STRING #stringExpression @@ -43,6 +44,10 @@ bool : TRUE | FALSE ; +amount + : 'amount' LPAREN RPAREN + ; + count : 'count' LPAREN STRING DELIMETER DECIMAL RPAREN ; @@ -87,14 +92,18 @@ like : 'like' LPAREN STRING DELIMETER STRING RPAREN ; -country_by - : 'countryBy' LPAREN STRING RPAREN +country + : 'country' LPAREN RPAREN ; result : 'accept' | '3ds' | 'decline' | 'notify' ; +catch_result + : 'accept' | '3ds' | 'decline' | 'notify' + ; + string_list : STRING (',' STRING | WS)+ ; @@ -110,12 +119,13 @@ COMMENT ; RETURN : '->' ; +CATCH_ERROR: 'catch:' ; RULE_BLOCK : 'rule:' ; -AND : 'AND' ; -OR : 'OR' ; -NOT : 'NOT'; -TRUE : 'TRUE' ; -FALSE : 'FALSE' ; +AND : 'AND' | 'and'; +OR : 'OR' | 'or' ; +NOT : 'NOT' | 'not'; +TRUE : 'TRUE' | 'true'; +FALSE : 'FALSE' | 'false'; GT : '>' ; GE : '>=' ; LT : '<' ; diff --git a/src/main/java/com/rbkmoney/fraudo/resolver/CountryResolver.java b/src/main/java/com/rbkmoney/fraudo/resolver/CountryResolver.java index 47eb034..7300746 100644 --- a/src/main/java/com/rbkmoney/fraudo/resolver/CountryResolver.java +++ b/src/main/java/com/rbkmoney/fraudo/resolver/CountryResolver.java @@ -1,11 +1,9 @@ package com.rbkmoney.fraudo.resolver; -import com.rbkmoney.fraudo.constant.CheckedField; - public interface CountryResolver { String UNKNOWN_VALUE = "unknown"; - String resolveCountry(CheckedField checkedField, String value); + String resolveCountryByIp(String value); } diff --git a/src/main/java/com/rbkmoney/fraudo/visitor/CustomFuncVisitorImpl.java b/src/main/java/com/rbkmoney/fraudo/visitor/CustomFuncVisitorImpl.java index 693d3b8..c145418 100644 --- a/src/main/java/com/rbkmoney/fraudo/visitor/CustomFuncVisitorImpl.java +++ b/src/main/java/com/rbkmoney/fraudo/visitor/CustomFuncVisitorImpl.java @@ -19,10 +19,8 @@ public class CustomFuncVisitorImpl extends FraudoBaseVisitor { private final CountryResolver countryResolver; @Override - public Object visitCountry_by(FraudoParser.Country_byContext ctx) { - String fieldName = TextUtil.safeGetText(ctx.STRING()); - String fieldValue = FieldResolver.resolveString(fieldName, fraudModel); - return countryResolver.resolveCountry(CheckedField.getByValue(fieldName), fieldValue); + public Object visitCountry(FraudoParser.CountryContext ctx) { + return countryResolver.resolveCountryByIp(fraudModel.getIp()); } @Override @@ -53,4 +51,8 @@ public class CustomFuncVisitorImpl extends FraudoBaseVisitor { CheckedField.getByValue(fieldBy)); } + @Override + public Object visitAmount(FraudoParser.AmountContext ctx) { + return (double) fraudModel.getAmount(); + } } diff --git a/src/main/java/com/rbkmoney/fraudo/visitor/FastFraudVisitorImpl.java b/src/main/java/com/rbkmoney/fraudo/visitor/FastFraudVisitorImpl.java index 9ab6aa1..56f121f 100644 --- a/src/main/java/com/rbkmoney/fraudo/visitor/FastFraudVisitorImpl.java +++ b/src/main/java/com/rbkmoney/fraudo/visitor/FastFraudVisitorImpl.java @@ -22,8 +22,15 @@ public class FastFraudVisitorImpl extends FraudoBaseVisitor { @Override public Object visitFraud_rule(com.rbkmoney.fraudo.FraudoParser.Fraud_ruleContext ctx) { - if (asBoolean(ctx.expression())) { - return super.visit(ctx.result()); + try { + if (asBoolean(ctx.expression())) { + return ResultStatus.getByValue((String) super.visit(ctx.result())); + } + } catch (Exception e) { + if (ctx.catch_result() != null && ctx.catch_result().getText() != null) { + return ResultStatus.getByValue(ctx.catch_result().getText()); + } + return ResultStatus.THREE_DS; } return ResultStatus.NORMAL; } @@ -32,15 +39,13 @@ public class FastFraudVisitorImpl extends FraudoBaseVisitor { public Object visitParse(com.rbkmoney.fraudo.FraudoParser.ParseContext ctx) { List notifications = new ArrayList<>(); for (com.rbkmoney.fraudo.FraudoParser.Fraud_ruleContext fraud_ruleContext : ctx.fraud_rule()) { - if (asBoolean(fraud_ruleContext.expression())) { - String result = fraud_ruleContext.result().getText(); - if (result != null && ResultStatus.NOTIFY.equals(ResultStatus.getByValue(result))) { - notifications.add(String.valueOf(fraud_ruleContext.getRuleIndex())); - } else if (result != null && ResultStatus.getByValue(result) != null) { - return new ResultModel(ResultStatus.getByValue(result), notifications); - } else { - throw new UnknownResultException(result); - } + ResultStatus result = (ResultStatus) visitFraud_rule(fraud_ruleContext); + if (result != null && ResultStatus.NOTIFY.equals(result)) { + notifications.add(String.valueOf(fraud_ruleContext.getRuleIndex())); + } else if (result != null && !ResultStatus.NORMAL.equals(result)) { + return new ResultModel(result, notifications); + } else if (result == null) { + throw new UnknownResultException(fraud_ruleContext.getText()); } } return new ResultModel(ResultStatus.NORMAL, notifications); @@ -133,8 +138,8 @@ public class FastFraudVisitorImpl extends FraudoBaseVisitor { } @Override - public Object visitCountry_by(FraudoParser.Country_byContext ctx) { - return customFuncVisitor.visitCountry_by(ctx); + public Object visitCountry(FraudoParser.CountryContext ctx) { + return customFuncVisitor.visitCountry(ctx); } @Override @@ -162,6 +167,11 @@ public class FastFraudVisitorImpl extends FraudoBaseVisitor { return listVisitor.visitIn_black_list(ctx); } + @Override + public Object visitAmount(FraudoParser.AmountContext ctx) { + return customFuncVisitor.visitAmount(ctx); + } + private boolean asBoolean(com.rbkmoney.fraudo.FraudoParser.ExpressionContext ctx) { return (boolean) visit(ctx); } diff --git a/src/test/java/com/rbkmoney/fraudo/FraudoTest.java b/src/test/java/com/rbkmoney/fraudo/FraudoTest.java index 13c53b1..78336a6 100644 --- a/src/test/java/com/rbkmoney/fraudo/FraudoTest.java +++ b/src/test/java/com/rbkmoney/fraudo/FraudoTest.java @@ -49,12 +49,8 @@ public class FraudoTest { @Test public void threeDsTest() throws Exception { InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/three_ds.frd"); - com.rbkmoney.fraudo.FraudoLexer lexer = new com.rbkmoney.fraudo.FraudoLexer(new ANTLRInputStream(resourceAsStream)); - com.rbkmoney.fraudo.FraudoParser parser = new com.rbkmoney.fraudo.FraudoParser(new CommonTokenStream(lexer)); - Mockito.when(countAggregator.count(anyObject(), any(), anyLong())).thenReturn(10); - ResultModel result = (ResultModel) new FastFraudVisitorFactory().createVisitor(new FraudModel(), countAggregator, - sumAggregator, uniqueValueAggregator, countryResolver, blackListFinder, whiteListFinder).visit(parser.parse()); + ResultModel result = parseAndVisit(resourceAsStream); Assert.assertEquals(ResultStatus.THREE_DS, result.getResultStatus()); } @@ -141,6 +137,25 @@ public class FraudoTest { Assert.assertEquals(ResultStatus.ACCEPT, result.getResultStatus()); } + @Test + public void amountTest() throws Exception { + InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/amount.frd"); + com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream); + FraudModel model = new FraudModel(); + model.setAmount(56L); + ResultModel result = invoke(parseContext, model); + Assert.assertEquals(ResultStatus.ACCEPT, result.getResultStatus()); + } + + @Test + public void catchTest() throws Exception { + InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/catch.frd"); + Mockito.when(uniqueValueAggregator.countUniqueValue(any(), any(), any())).thenThrow(new UnknownResultException("as")); + com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream); + ResultModel result = invokeParse(parseContext); + Assert.assertEquals(ResultStatus.DECLINE, result.getResultStatus()); + } + @Test public void likeTest() throws Exception { InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/like.frd"); @@ -194,13 +209,13 @@ public class FraudoTest { public void eqCountryTest() throws Exception { InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/eq_country.frd"); - Mockito.when(countryResolver.resolveCountry(any(), anyString())).thenReturn("RU"); + Mockito.when(countryResolver.resolveCountryByIp( anyString())).thenReturn("RU"); ResultModel result = parseAndVisit(resourceAsStream); Assert.assertEquals(ResultStatus.NORMAL, result.getResultStatus()); Assert.assertEquals(1, result.getNotificationsRule().size()); - Mockito.when(countryResolver.resolveCountry(any(), anyString())).thenReturn("US"); + Mockito.when(countryResolver.resolveCountryByIp( anyString())).thenReturn("US"); resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/eq_country.frd"); result = parseAndVisit(resourceAsStream); Assert.assertEquals(ResultStatus.NORMAL, result.getResultStatus()); diff --git a/src/test/resources/rules/amount.frd b/src/test/resources/rules/amount.frd new file mode 100644 index 0000000..da7a528 --- /dev/null +++ b/src/test/resources/rules/amount.frd @@ -0,0 +1,2 @@ +rule: amount() < 100 +-> accept; \ No newline at end of file diff --git a/src/test/resources/rules/catch.frd b/src/test/resources/rules/catch.frd new file mode 100644 index 0000000..8b70f3d --- /dev/null +++ b/src/test/resources/rules/catch.frd @@ -0,0 +1,3 @@ +rule: unique("email", "ip") < 4 +-> accept +catch: decline; \ No newline at end of file diff --git a/src/test/resources/rules/eq_country.frd b/src/test/resources/rules/eq_country.frd index 73199fa..cdeebd4 100644 --- a/src/test/resources/rules/eq_country.frd +++ b/src/test/resources/rules/eq_country.frd @@ -1,2 +1,2 @@ -rule: countryBy("ip") = "RU" +rule: country() = "RU" -> notify; \ No newline at end of file diff --git a/src/test/resources/rules/sum.frd b/src/test/resources/rules/sum.frd index b1cd462..cefbc02 100644 --- a/src/test/resources/rules/sum.frd +++ b/src/test/resources/rules/sum.frd @@ -1,3 +1,3 @@ -rule: (sum("ip", 1444) >= 10500.50 OR sumSuccess("email", 1444) > 500) - AND sumError("fingerprint", 1444, "error_code") > 523.12 +rule: (sum("ip", 1444) >= 10500.50 or sumSuccess("email", 1444) > 500) + and sumError("fingerprint", 1444, "error_code") > 523.12 -> notify; \ No newline at end of file diff --git a/syntax.png b/syntax.png index a83a0a3..63fe62e 100644 Binary files a/syntax.png and b/syntax.png differ