Merge pull request #1 from rbkmoney/ft/create-fraudo-lang

Create lang fraudo BJ-332
This commit is contained in:
Kostya 2018-12-07 14:40:30 +03:00 committed by GitHub
commit 67e63848af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1214 additions and 1 deletions

73
.gitignore vendored Normal file
View File

@ -0,0 +1,73 @@
# Created by .ignore support plugin (hsz.mobi)
### Maven template
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# Gradle:
.idea/gradle.xml
.idea/libraries
# Mongo Explorer plugin:
.idea/mongoSettings.xml
## File-based project format:
*.iws
*.ipr
*.iml
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Java template
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
env.list

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "build_utils"]
path = build_utils
url = git@github.com:rbkmoney/build_utils.git
branch = master

13
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,13 @@
#!groovy
build('fraudo', 'docker-host') {
checkoutRepo()
loadBuildUtils()
def javaLibPipeline
runStage('load JavaLib pipeline') {
javaLibPipeline = load("build_utils/jenkins_lib/pipeJavaLib.groovy")
}
def buildImageTag = "4799280a02cb73761a3ba3641285aac8ec4ec482"
javaLibPipeline(buildImageTag)
}

View File

@ -1 +1,66 @@
# fraudo
![alt text](logo.jpg)
#### Syntax
![alt text](syntax.png)
##### OPERATIONS:
~~~~
* count("group_field", time_in_minutes)
* countSuccess("group_field", time_in_minutes)
* countError("group_field", time_in_minutes, "error_code")
* sum("group_field", time_in_minutes)
* sumSuccess("group_field", time_in_minutes)
* sumError(("group_field", time_in_minutes, "error_code")
* unique(("group_field", "by_field")
* in(("field", "first", "second", ...)
* inWhiteList("field")
* inBlackList("field")
* like("field", "regexp_in_java_style"[1])
* countryBy("field")
~~~~
1. [regexp_in_java_style](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html)
##### RESULTS:
~~~~
* accept
* 3ds
* decline
* notify
~~~~
##### EXAMPLES:
###### Simple:
~~~~
rule: 3 > 2 AND 1 = 1
-> accept;
~~~~
###### Black list check:
~~~~
rule: inBlackList("email")
-> notify;
~~~~
###### Counts check:
~~~~
rule: (count("ip", 1444) >= 10 OR countSuccess("email", 1444) > 5)
AND countError("fingerprint", 1444, "error_code") > 5
-> notify;
~~~~
###### Unique count emails for ip:
~~~~
rule: unique("email", "ip") < 4
-> decline;
~~~~
###### Check country by ip:
~~~~
rule: countryBy("ip") = "RU"
-> notify;
~~~~
###### Combined check:
~~~~
rule: 3 > 2 AND 1 > 1
-> decline;
rule: count("email", 10) <= 10 AND count("ip", 1444) = 10
-> 3ds;
~~~~

1
build_utils Submodule

@ -0,0 +1 @@
Subproject commit 9b664082ddc8ec8cdfbe7513d54e433d24198cc2

BIN
logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

79
pom.xml Normal file
View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.rbkmoney</groupId>
<artifactId>parent</artifactId>
<version>1.0.2</version>
</parent>
<artifactId>fraudo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<hamcrest.junit.version>2.0.0.0</hamcrest.junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.7.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!-- tests -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-junit</artifactId>
<version>${hamcrest.junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>4.7.1</version>
<configuration>
<arguments>
<argument>-package</argument>
<argument>com.rbkmoney.fraudo</argument>
</arguments>
<listener>false</listener>
<visitor>true</visitor>
</configuration>
<executions>
<execution>
<id>antlr</id>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,129 @@
grammar Fraudo;
parse
: fraud_rule* EOF
;
fraud_rule
: RULE_BLOCK expression RETURN result SCOL
;
expression
: LPAREN expression RPAREN #parenExpression
| NOT expression #notExpression
| left=expression op=comparator right=expression #comparatorExpression
| left=expression op=binary right=expression #binaryExpression
| bool #boolExpression
| count #countExpression
| count_success #countSuccessExpression
| count_error #countErrorExpression
| sum #sumExpression
| sum_success #sumSuccessExpression
| sum_error #sumErrorExpression
| unique #uniqueExpression
| in #inFunctionExpression
| in_white_list #inWhiteListExpression
| in_black_list #inBlackListExpression
| like #likeFunctionExpression
| country_by #countryByFunctionExpression
| IDENTIFIER #identifierExpression
| DECIMAL #decimalExpression
| STRING #stringExpression
;
comparator
: GT | GE | LT | LE | EQ
;
binary
: AND | OR
;
bool
: TRUE | FALSE
;
count
: 'count' LPAREN STRING DELIMETER DECIMAL RPAREN
;
count_success
: 'countSuccess' LPAREN STRING DELIMETER DECIMAL RPAREN
;
count_error
: 'countError' LPAREN STRING DELIMETER DECIMAL DELIMETER STRING RPAREN
;
sum
: 'sum' LPAREN STRING DELIMETER DECIMAL RPAREN
;
sum_success
: 'sumSuccess' LPAREN STRING DELIMETER DECIMAL RPAREN
;
sum_error
: 'sumError' LPAREN STRING DELIMETER DECIMAL DELIMETER STRING RPAREN
;
unique
: 'unique' LPAREN STRING DELIMETER STRING RPAREN
;
in
: 'in' LPAREN STRING DELIMETER string_list RPAREN
;
in_white_list
: 'inWhiteList' LPAREN STRING RPAREN
;
in_black_list
: 'inBlackList' LPAREN STRING RPAREN
;
like
: 'like' LPAREN STRING DELIMETER STRING RPAREN
;
country_by
: 'countryBy' LPAREN STRING RPAREN
;
result
: 'accept' | '3ds' | 'decline' | 'notify'
;
string_list
: STRING (',' STRING | WS)+
;
STRING
: '"' (~["\r\n] | '""')* '"'
;
DELIMETER : ',' ;
COMMENT
: '#' ~[\r\n]* -> skip
;
RETURN : '->' ;
RULE_BLOCK : 'rule:' ;
AND : 'AND' ;
OR : 'OR' ;
NOT : 'NOT';
TRUE : 'TRUE' ;
FALSE : 'FALSE' ;
GT : '>' ;
GE : '>=' ;
LT : '<' ;
LE : '<=' ;
EQ : '=' ;
LPAREN : '(' ;
RPAREN : ')' ;
DECIMAL : '-'? [0-9]+ ( '.' [0-9]+ )? ;
IDENTIFIER : [a-zA-Z_] [a-zA-Z_0-9]* ;
WS : [ \u000C\n]+ -> skip;
SCOL : ';';

View File

@ -0,0 +1,13 @@
package com.rbkmoney.fraudo.aggregator;
import com.rbkmoney.fraudo.constant.CheckedField;
public interface CountAggregator {
Integer count(CheckedField checkedField, String valueField, Long timeInMinutes);
Integer countSuccess(CheckedField checkedField, String valueField, Long timeInMinutes);
Integer countError(CheckedField checkedField, String valueField, Long timeInMinutes, String errorCode);
}

View File

@ -0,0 +1,13 @@
package com.rbkmoney.fraudo.aggregator;
import com.rbkmoney.fraudo.constant.CheckedField;
public interface SumAggregator {
Double sum(CheckedField checkedField, String email, Long timeInMinutes);
Double sumSuccess(CheckedField checkedField, String valueField, Long timeInMinutes);
Double sumError(CheckedField checkedField, String valueField, Long timeInMinutes, String errorCode);
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.fraudo.aggregator;
import com.rbkmoney.fraudo.constant.CheckedField;
public interface UniqueValueAggregator {
Integer countUniqueValue(CheckedField countField, CheckedField onField);
}

View File

@ -0,0 +1,35 @@
package com.rbkmoney.fraudo.constant;
import java.util.HashMap;
import java.util.Map;
public enum CheckedField {
EMAIL("email"),
IP("ip"),
FINGERPRINT("fingerprint"),
COUNTRY_BANK("country_bank"),
COUNTRY_IP("country_ip"),
BIN("bin"),
SHOP_ID("shop_ip"),
PARTY_ID("party_id"),
CARD_TOKEN("card_token");
private String value;
private static Map<String, CheckedField> valueMap = new HashMap<>();
static {
for (CheckedField value : CheckedField.values()) {
valueMap.put(value.value, value);
}
}
CheckedField(String value) {
this.value = value;
}
public static CheckedField getByValue(String value) {
return valueMap.get(value);
}
}

View File

@ -0,0 +1,30 @@
package com.rbkmoney.fraudo.constant;
import java.util.HashMap;
import java.util.Map;
public enum ResultStatus {
ACCEPT("accept"),
THREE_DS("3ds"),
DECLINE("decline"),
NOTIFY("notify");
private String value;
private static Map<String, ResultStatus> valueMap = new HashMap<>();
static {
for (ResultStatus value : ResultStatus.values()) {
valueMap.put(value.value, value);
}
}
ResultStatus(String value) {
this.value = value;
}
public static ResultStatus getByValue(String value) {
return valueMap.get(value);
}
}

View File

@ -0,0 +1,10 @@
package com.rbkmoney.fraudo.exception;
public class FieldUnsetException extends RuntimeException {
public static final String ERROR_MESSAGE = "Count target field is not set or bad format! (must be \"*\")";
public FieldUnsetException() {
super(ERROR_MESSAGE);
}
}

View File

@ -0,0 +1,11 @@
package com.rbkmoney.fraudo.exception;
public class NotImplementedOperatorException extends RuntimeException {
public static final String ERROR_MESSAGE = "not implemented: operator ";
public NotImplementedOperatorException(String operator) {
super(ERROR_MESSAGE + operator);
}
}

View File

@ -0,0 +1,11 @@
package com.rbkmoney.fraudo.exception;
public class UnknownResultException extends RuntimeException {
public static final String ERROR_MESSAGE = "Unknown result: ";
public UnknownResultException(String result) {
super(ERROR_MESSAGE + result);
}
}

View File

@ -0,0 +1,11 @@
package com.rbkmoney.fraudo.exception;
public class UnresolvableFieldException extends RuntimeException {
public static final String ERROR_MESSAGE = "Can't find this field: ";
public UnresolvableFieldException(String fieldName) {
super(ERROR_MESSAGE + fieldName);
}
}

View File

@ -0,0 +1,25 @@
package com.rbkmoney.fraudo.factory;
import com.rbkmoney.fraudo.FraudoVisitor;
import com.rbkmoney.fraudo.aggregator.CountAggregator;
import com.rbkmoney.fraudo.aggregator.SumAggregator;
import com.rbkmoney.fraudo.aggregator.UniqueValueAggregator;
import com.rbkmoney.fraudo.finder.InListFinder;
import com.rbkmoney.fraudo.model.FraudModel;
import com.rbkmoney.fraudo.resolver.CountryResolver;
import com.rbkmoney.fraudo.visitor.*;
public class FastFraudVisitorFactory implements FraudVisitorFactory {
@Override
public FraudoVisitor<Object> createVisitor(FraudModel model, CountAggregator countAggregator,
SumAggregator sumAggregator, UniqueValueAggregator uniqueValueAggregator,
CountryResolver countryResolver, InListFinder blackListFinder,
InListFinder whiteListFinder) {
CountVisitorImpl countVisitor = new CountVisitorImpl(model, countAggregator);
SumVisitorImpl sumVisitor = new SumVisitorImpl(model, sumAggregator);
ListVisitorImpl listVisitor = new ListVisitorImpl(model, blackListFinder, whiteListFinder);
CustomFuncVisitorImpl customFuncVisitor = new CustomFuncVisitorImpl(model, uniqueValueAggregator, countryResolver);
return new FastFraudVisitorImpl(countVisitor, sumVisitor, listVisitor, customFuncVisitor);
}
}

View File

@ -0,0 +1,18 @@
package com.rbkmoney.fraudo.factory;
import com.rbkmoney.fraudo.FraudoVisitor;
import com.rbkmoney.fraudo.aggregator.CountAggregator;
import com.rbkmoney.fraudo.aggregator.SumAggregator;
import com.rbkmoney.fraudo.aggregator.UniqueValueAggregator;
import com.rbkmoney.fraudo.finder.InListFinder;
import com.rbkmoney.fraudo.model.FraudModel;
import com.rbkmoney.fraudo.resolver.CountryResolver;
public interface FraudVisitorFactory {
FraudoVisitor<Object> createVisitor(FraudModel model, CountAggregator countAggregator,
SumAggregator sumAggregator, UniqueValueAggregator uniqueValueAggregator,
CountryResolver countryResolver, InListFinder blackListFinder,
InListFinder whiteListFinder);
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.fraudo.finder;
import com.rbkmoney.fraudo.constant.CheckedField;
public interface InListFinder {
Boolean findInList(CheckedField field, String value);
}

View File

@ -0,0 +1,15 @@
package com.rbkmoney.fraudo.model;
import lombok.Data;
@Data
public class FraudModel {
private String ip;
private String email;
private String bin;
private String fingerprint;
private String shopId;
private String partyId;
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.fraudo.resolver;
import com.rbkmoney.fraudo.constant.CheckedField;
public interface CountryResolver {
String resolveCountry(CheckedField checkedField, String value);
}

View File

@ -0,0 +1,24 @@
package com.rbkmoney.fraudo.resolver;
import com.rbkmoney.fraudo.constant.CheckedField;
import com.rbkmoney.fraudo.exception.UnresolvableFieldException;
import com.rbkmoney.fraudo.model.FraudModel;
public class FieldResolver {
public static String resolveString(String fieldName, FraudModel fraudModel) {
switch (CheckedField.getByValue(fieldName)) {
case BIN:
return fraudModel.getBin();
case IP:
return fraudModel.getIp();
case FINGERPRINT:
return fraudModel.getFingerprint();
case EMAIL:
return fraudModel.getEmail();
default:
throw new UnresolvableFieldException(fieldName);
}
}
}

View File

@ -0,0 +1,16 @@
package com.rbkmoney.fraudo.utils;
import com.rbkmoney.fraudo.exception.FieldUnsetException;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.Optional;
public class TextUtil {
public static String safeGetText(TerminalNode field) {
return Optional.ofNullable(field)
.orElseThrow(FieldUnsetException::new)
.getText().replace("\"", "");
}
}

View File

@ -0,0 +1,42 @@
package com.rbkmoney.fraudo.visitor;
import com.rbkmoney.fraudo.FraudoBaseVisitor;
import com.rbkmoney.fraudo.FraudoParser;
import com.rbkmoney.fraudo.aggregator.CountAggregator;
import com.rbkmoney.fraudo.constant.CheckedField;
import com.rbkmoney.fraudo.model.FraudModel;
import com.rbkmoney.fraudo.utils.TextUtil;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class CountVisitorImpl extends FraudoBaseVisitor<Object> {
private final FraudModel fraudModel;
private final CountAggregator countAggregator;
@Override
public Object visitCount(com.rbkmoney.fraudo.FraudoParser.CountContext ctx) {
String countTarget = TextUtil.safeGetText(ctx.STRING());
String time = TextUtil.safeGetText(ctx.DECIMAL());
return (double) countAggregator.count(CheckedField.getByValue(countTarget), fraudModel.getEmail(),
Long.valueOf(time));
}
@Override
public Object visitCount_success(FraudoParser.Count_successContext ctx) {
String countTarget = ctx.STRING().getText();
String time = TextUtil.safeGetText(ctx.DECIMAL());
return (double) countAggregator.countSuccess(CheckedField.getByValue(countTarget), fraudModel.getEmail(),
Long.valueOf(time));
}
@Override
public Object visitCount_error(FraudoParser.Count_errorContext ctx) {
String countTarget = TextUtil.safeGetText(ctx.STRING(0));
String time = TextUtil.safeGetText(ctx.DECIMAL());
String errorCode = TextUtil.safeGetText(ctx.STRING(1));
return (double) countAggregator.countError(CheckedField.getByValue(countTarget), fraudModel.getEmail(),
Long.valueOf(time), errorCode);
}
}

View File

@ -0,0 +1,55 @@
package com.rbkmoney.fraudo.visitor;
import com.rbkmoney.fraudo.FraudoBaseVisitor;
import com.rbkmoney.fraudo.FraudoParser;
import com.rbkmoney.fraudo.aggregator.UniqueValueAggregator;
import com.rbkmoney.fraudo.constant.CheckedField;
import com.rbkmoney.fraudo.model.FraudModel;
import com.rbkmoney.fraudo.resolver.CountryResolver;
import com.rbkmoney.fraudo.resolver.FieldResolver;
import com.rbkmoney.fraudo.utils.TextUtil;
import lombok.RequiredArgsConstructor;
import org.antlr.v4.runtime.tree.TerminalNode;
@RequiredArgsConstructor
public class CustomFuncVisitorImpl extends FraudoBaseVisitor<Object> {
private final FraudModel fraudModel;
private final UniqueValueAggregator uniqueValueAggregator;
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);
}
@Override
public Object visitIn(FraudoParser.InContext ctx) {
String field = TextUtil.safeGetText(ctx.STRING());
String fieldValue = FieldResolver.resolveString(field, fraudModel);
for (TerminalNode string : ctx.string_list().STRING()) {
if (fieldValue.equals(TextUtil.safeGetText(string))) {
return true;
}
}
return false;
}
@Override
public Object visitLike(FraudoParser.LikeContext ctx) {
String fieldName = TextUtil.safeGetText(ctx.STRING(0));
String fieldValue = FieldResolver.resolveString(fieldName, fraudModel);
String pattern = TextUtil.safeGetText(ctx.STRING(1));
return fieldValue.matches(pattern);
}
@Override
public Object visitUnique(FraudoParser.UniqueContext ctx) {
String field = TextUtil.safeGetText(ctx.STRING(0));
String fieldBy = TextUtil.safeGetText(ctx.STRING(1));
return (double) uniqueValueAggregator.countUniqueValue(CheckedField.getByValue(field), CheckedField.getByValue(fieldBy));
}
}

View File

@ -0,0 +1,164 @@
package com.rbkmoney.fraudo.visitor;
import com.rbkmoney.fraudo.FraudoBaseVisitor;
import com.rbkmoney.fraudo.FraudoParser;
import com.rbkmoney.fraudo.constant.ResultStatus;
import com.rbkmoney.fraudo.exception.NotImplementedOperatorException;
import com.rbkmoney.fraudo.exception.UnknownResultException;
import com.rbkmoney.fraudo.utils.TextUtil;
import lombok.RequiredArgsConstructor;
import java.util.Optional;
@RequiredArgsConstructor
public class FastFraudVisitorImpl extends FraudoBaseVisitor<Object> {
private final CountVisitorImpl countVisitor;
private final SumVisitorImpl sumVisitor;
private final ListVisitorImpl listVisitor;
private final CustomFuncVisitorImpl customFuncVisitor;
@Override
public Object visitFraud_rule(com.rbkmoney.fraudo.FraudoParser.Fraud_ruleContext ctx) {
if (asBoolean(ctx.expression())) {
return super.visit(ctx.result());
}
return ResultStatus.ACCEPT;
}
@Override
public Object visitParse(com.rbkmoney.fraudo.FraudoParser.ParseContext ctx) {
for (com.rbkmoney.fraudo.FraudoParser.Fraud_ruleContext fraud_ruleContext : ctx.fraud_rule()) {
if (asBoolean(fraud_ruleContext.expression())) {
String result = fraud_ruleContext.result().getText();
return Optional.ofNullable(ResultStatus.getByValue(result))
.orElseThrow(() -> new UnknownResultException(result));
}
}
return ResultStatus.ACCEPT;
}
@Override
public Object visitResult(com.rbkmoney.fraudo.FraudoParser.ResultContext ctx) {
return ctx.getText();
}
@Override
public Object visitDecimalExpression(com.rbkmoney.fraudo.FraudoParser.DecimalExpressionContext ctx) {
return Double.valueOf(ctx.DECIMAL().getText());
}
@Override
public Object visitStringExpression(FraudoParser.StringExpressionContext ctx) {
return TextUtil.safeGetText(ctx.STRING());
}
@Override
public Object visitNotExpression(com.rbkmoney.fraudo.FraudoParser.NotExpressionContext ctx) {
return !((Boolean) this.visit(ctx.expression()));
}
@Override
public Object visitParenExpression(com.rbkmoney.fraudo.FraudoParser.ParenExpressionContext ctx) {
return super.visit(ctx.expression());
}
@Override
public Object visitComparatorExpression(com.rbkmoney.fraudo.FraudoParser.ComparatorExpressionContext ctx) {
if (ctx.op.EQ() != null) {
return this.visit(ctx.left).equals(this.visit(ctx.right));
} else if (ctx.op.LE() != null) {
return asDouble(ctx.left) <= asDouble(ctx.right);
} else if (ctx.op.GE() != null) {
return asDouble(ctx.left) >= asDouble(ctx.right);
} else if (ctx.op.LT() != null) {
return asDouble(ctx.left) < asDouble(ctx.right);
} else if (ctx.op.GT() != null) {
return asDouble(ctx.left) > asDouble(ctx.right);
}
throw new NotImplementedOperatorException(ctx.op.getText());
}
@Override
public Object visitBinaryExpression(com.rbkmoney.fraudo.FraudoParser.BinaryExpressionContext ctx) {
if (ctx.op.AND() != null) {
return asBoolean(ctx.left) && asBoolean(ctx.right);
} else if (ctx.op.OR() != null) {
return asBoolean(ctx.left) || asBoolean(ctx.right);
}
throw new NotImplementedOperatorException(ctx.op.getText());
}
@Override
public Object visitBoolExpression(com.rbkmoney.fraudo.FraudoParser.BoolExpressionContext ctx) {
return Boolean.valueOf(ctx.getText());
}
@Override
public Object visitCount(com.rbkmoney.fraudo.FraudoParser.CountContext ctx) {
return countVisitor.visitCount(ctx);
}
@Override
public Object visitCount_success(FraudoParser.Count_successContext ctx) {
return countVisitor.visitCount_success(ctx);
}
@Override
public Object visitCount_error(FraudoParser.Count_errorContext ctx) {
return countVisitor.visitCount_error(ctx);
}
@Override
public Object visitSum(FraudoParser.SumContext ctx) {
return sumVisitor.visitSum(ctx);
}
@Override
public Object visitSum_success(FraudoParser.Sum_successContext ctx) {
return sumVisitor.visitSum_success(ctx);
}
@Override
public Object visitSum_error(FraudoParser.Sum_errorContext ctx) {
return sumVisitor.visitSum_error(ctx);
}
@Override
public Object visitCountry_by(FraudoParser.Country_byContext ctx) {
return customFuncVisitor.visitCountry_by(ctx);
}
@Override
public Object visitIn(FraudoParser.InContext ctx) {
return customFuncVisitor.visitIn(ctx);
}
@Override
public Object visitLike(FraudoParser.LikeContext ctx) {
return customFuncVisitor.visitLike(ctx);
}
@Override
public Object visitUnique(FraudoParser.UniqueContext ctx) {
return customFuncVisitor.visitUnique(ctx);
}
@Override
public Object visitIn_white_list(FraudoParser.In_white_listContext ctx) {
return listVisitor.visitIn_white_list(ctx);
}
@Override
public Object visitIn_black_list(FraudoParser.In_black_listContext ctx) {
return listVisitor.visitIn_black_list(ctx);
}
private boolean asBoolean(com.rbkmoney.fraudo.FraudoParser.ExpressionContext ctx) {
return (boolean) visit(ctx);
}
private double asDouble(com.rbkmoney.fraudo.FraudoParser.ExpressionContext ctx) {
return (double) visit(ctx);
}
}

View File

@ -0,0 +1,32 @@
package com.rbkmoney.fraudo.visitor;
import com.rbkmoney.fraudo.FraudoBaseVisitor;
import com.rbkmoney.fraudo.FraudoParser;
import com.rbkmoney.fraudo.constant.CheckedField;
import com.rbkmoney.fraudo.finder.InListFinder;
import com.rbkmoney.fraudo.model.FraudModel;
import com.rbkmoney.fraudo.resolver.FieldResolver;
import com.rbkmoney.fraudo.utils.TextUtil;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class ListVisitorImpl extends FraudoBaseVisitor<Object> {
private final FraudModel fraudModel;
private final InListFinder blackListFinder;
private final InListFinder whiteListFinder;
@Override
public Object visitIn_white_list(FraudoParser.In_white_listContext ctx) {
String fieldName = TextUtil.safeGetText(ctx.STRING());
String fieldValue = FieldResolver.resolveString(fieldName, fraudModel);
return whiteListFinder.findInList(CheckedField.getByValue(fieldName), fieldValue);
}
@Override
public Object visitIn_black_list(FraudoParser.In_black_listContext ctx) {
String fieldName = TextUtil.safeGetText(ctx.STRING());
String fieldValue = FieldResolver.resolveString(fieldName, fraudModel);
return blackListFinder.findInList(CheckedField.getByValue(fieldName), fieldValue);
}
}

View File

@ -0,0 +1,38 @@
package com.rbkmoney.fraudo.visitor;
import com.rbkmoney.fraudo.FraudoBaseVisitor;
import com.rbkmoney.fraudo.FraudoParser;
import com.rbkmoney.fraudo.aggregator.SumAggregator;
import com.rbkmoney.fraudo.constant.CheckedField;
import com.rbkmoney.fraudo.model.FraudModel;
import com.rbkmoney.fraudo.utils.TextUtil;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class SumVisitorImpl extends FraudoBaseVisitor<Object> {
private final FraudModel fraudModel;
private final SumAggregator sumAggregator;
@Override
public Object visitSum(FraudoParser.SumContext ctx) {
String countTarget = TextUtil.safeGetText(ctx.STRING());
String time = TextUtil.safeGetText(ctx.DECIMAL());
return sumAggregator.sum(CheckedField.getByValue(countTarget), fraudModel.getEmail(), Long.valueOf(time));
}
@Override
public Object visitSum_success(FraudoParser.Sum_successContext ctx) {
String countTarget = TextUtil.safeGetText(ctx.STRING());
return sumAggregator.sumSuccess(CheckedField.getByValue(countTarget), fraudModel.getEmail(), Long.valueOf(ctx.DECIMAL().getText()));
}
@Override
public Object visitSum_error(FraudoParser.Sum_errorContext ctx) {
String countTarget = TextUtil.safeGetText(ctx.STRING(0));
String errorCode = TextUtil.safeGetText(ctx.STRING(1));
return sumAggregator.sumError(CheckedField.getByValue(countTarget), fraudModel.getEmail(),
Long.valueOf(ctx.DECIMAL().getText()), errorCode);
}
}

View File

@ -0,0 +1,224 @@
package com.rbkmoney.fraudo;
import com.rbkmoney.fraudo.aggregator.CountAggregator;
import com.rbkmoney.fraudo.aggregator.SumAggregator;
import com.rbkmoney.fraudo.aggregator.UniqueValueAggregator;
import com.rbkmoney.fraudo.constant.ResultStatus;
import com.rbkmoney.fraudo.exception.UnknownResultException;
import com.rbkmoney.fraudo.factory.FastFraudVisitorFactory;
import com.rbkmoney.fraudo.finder.InListFinder;
import com.rbkmoney.fraudo.model.FraudModel;
import com.rbkmoney.fraudo.resolver.CountryResolver;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.io.InputStream;
import static org.mockito.Matchers.*;
public class FraudoTest {
public static final String TEST_GMAIL_RU = "test@gmail.ru";
@Mock
CountAggregator countAggregator;
@Mock
SumAggregator sumAggregator;
@Mock
UniqueValueAggregator uniqueValueAggregator;
@Mock
CountryResolver countryResolver;
@Mock
InListFinder whiteListFinder;
@Mock
InListFinder blackListFinder;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
@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(), anyString(), anyLong())).thenReturn(10);
Object result = new FastFraudVisitorFactory().createVisitor(new FraudModel(), countAggregator,
sumAggregator, uniqueValueAggregator, countryResolver, blackListFinder, whiteListFinder).visit(parser.parse());
Assert.assertEquals(ResultStatus.THREE_DS, result);
}
@Test
public void notifyTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/notify.frd");
Object result = parseAndVisit(resourceAsStream);
Assert.assertEquals(ResultStatus.NOTIFY, result);
}
@Test
public void declineTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/decline.frd");
Object result = parseAndVisit(resourceAsStream);
Assert.assertEquals(ResultStatus.DECLINE, result);
}
@Test
public void acceptTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/accept.frd");
Object result = parseAndVisit(resourceAsStream);
Assert.assertEquals(ResultStatus.ACCEPT, result);
}
@Test
public void ruleIsNotFireTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/rule_is_not_fire.frd");
Object result = parseAndVisit(resourceAsStream);
Assert.assertEquals(ResultStatus.ACCEPT, result);
}
@Test(expected = UnknownResultException.class)
public void notImplOperatorTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/unknownResult.frd");
parseAndVisit(resourceAsStream);
}
@Test
public void countTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/count.frd");
Mockito.when(countAggregator.count(anyObject(), anyString(), anyLong())).thenReturn(10);
Mockito.when(countAggregator.countError(anyObject(), anyString(), anyLong(), anyString())).thenReturn(6);
Mockito.when(countAggregator.countSuccess(anyObject(), anyString(), anyLong())).thenReturn(4);
com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream);
Object result = invokeParse(parseContext);
Assert.assertEquals(ResultStatus.NOTIFY, result);
Mockito.when(countAggregator.count(anyObject(), anyString(), anyLong())).thenReturn(9);
Mockito.when(countAggregator.countError(anyObject(), anyString(), anyLong(), anyString())).thenReturn(6);
Mockito.when(countAggregator.countSuccess(anyObject(), anyString(), anyLong())).thenReturn(6);
result = invokeParse(parseContext);
Assert.assertEquals(ResultStatus.NOTIFY, result);
}
@Test
public void sumTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/sum.frd");
Mockito.when(sumAggregator.sum(anyObject(), anyString(), anyLong())).thenReturn(10500.60);
Mockito.when(sumAggregator.sumError(anyObject(), anyString(), anyLong(), anyString())).thenReturn(524.0);
Mockito.when(sumAggregator.sumSuccess(anyObject(), anyString(), anyLong())).thenReturn(4.0);
com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream);
Object result = invokeParse(parseContext);
Assert.assertEquals(ResultStatus.NOTIFY, result);
Mockito.when(sumAggregator.sum(anyObject(), anyString(), anyLong())).thenReturn(90.0);
Mockito.when(sumAggregator.sumError(anyObject(), anyString(), anyLong(), anyString())).thenReturn(524.0);
Mockito.when(sumAggregator.sumSuccess(anyObject(), anyString(), anyLong())).thenReturn(501.0);
result = invokeParse(parseContext);
Assert.assertEquals(ResultStatus.NOTIFY, result);
}
@Test
public void inTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/in.frd");
com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream);
FraudModel model = new FraudModel();
model.setEmail(TEST_GMAIL_RU);
Object result = invoke(parseContext, model);
Assert.assertEquals(ResultStatus.NOTIFY, result);
}
@Test
public void likeTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/like.frd");
com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream);
FraudModel model = new FraudModel();
model.setEmail(TEST_GMAIL_RU);
Object result = invoke(parseContext, model);
Assert.assertEquals(ResultStatus.NOTIFY, result);
model.setEmail("teeeee");
result = invoke(parseContext, model);
Assert.assertEquals(ResultStatus.ACCEPT, result);
}
@Test
public void inNotTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/in_not.frd");
com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream);
FraudModel model = new FraudModel();
model.setEmail(TEST_GMAIL_RU);
Object result = invoke(parseContext, model);
Assert.assertEquals(ResultStatus.ACCEPT, result);
}
@Test
public void uniqCountTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/count_uniq.frd");
Mockito.when(uniqueValueAggregator.countUniqueValue(any(), any())).thenReturn(2);
com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream);
Object result = invokeParse(parseContext);
Assert.assertEquals(ResultStatus.DECLINE, result);
}
@Test
public void whiteBlackListTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/whitelist.frd");
com.rbkmoney.fraudo.FraudoParser.ParseContext parseContext = getParseContext(resourceAsStream);
Mockito.when(whiteListFinder.findInList(any(), anyString())).thenReturn(true);
Object result = invokeParse(parseContext);
Assert.assertEquals(ResultStatus.NOTIFY, result);
resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/blacklist.frd");
parseContext = getParseContext(resourceAsStream);
Mockito.when(blackListFinder.findInList(any(), anyString())).thenReturn(true);
result = invokeParse(parseContext);
Assert.assertEquals(ResultStatus.NOTIFY, result);
}
@Test
public void eqCountryTest() throws Exception {
InputStream resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/eq_country.frd");
Mockito.when(countryResolver.resolveCountry(any(), anyString())).thenReturn("RU");
Object result = parseAndVisit(resourceAsStream);
Assert.assertEquals(ResultStatus.NOTIFY, result);
Mockito.when(countryResolver.resolveCountry(any(), anyString())).thenReturn("US");
resourceAsStream = FraudoTest.class.getResourceAsStream("/rules/eq_country.frd");
result = parseAndVisit(resourceAsStream);
Assert.assertEquals(ResultStatus.ACCEPT, result);
}
private Object parseAndVisit(InputStream resourceAsStream) throws IOException {
com.rbkmoney.fraudo.FraudoParser.ParseContext parse = getParseContext(resourceAsStream);
return invokeParse(parse);
}
private Object invokeParse(com.rbkmoney.fraudo.FraudoParser.ParseContext parse) {
FraudModel model = new FraudModel();
return invoke(parse, model);
}
private Object invoke(com.rbkmoney.fraudo.FraudoParser.ParseContext parse, FraudModel model) {
return new FastFraudVisitorFactory().createVisitor(model, countAggregator,
sumAggregator, uniqueValueAggregator, countryResolver, blackListFinder, whiteListFinder).visit(parse);
}
private com.rbkmoney.fraudo.FraudoParser.ParseContext getParseContext(InputStream resourceAsStream) throws IOException {
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));
return parser.parse();
}
}

View File

@ -0,0 +1,2 @@
rule: 3 > 2 AND 1 = 1
-> accept;

View File

@ -0,0 +1,2 @@
rule: inBlackList("email")
-> notify;

View File

@ -0,0 +1,3 @@
rule: (count("ip", 1444) >= 10 OR countSuccess("email", 1444) > 5)
AND countError("fingerprint", 1444, "error_code") > 5
-> notify;

View File

@ -0,0 +1,2 @@
rule: unique("email", "ip") < 4
-> decline;

View File

@ -0,0 +1,2 @@
rule: 3 > 2 AND 1 = 1
-> decline;

View File

@ -0,0 +1,2 @@
rule: countryBy("ip") = "RU"
-> notify;

View File

@ -0,0 +1,2 @@
rule: in("email", "test@gmail", "test@gmail.ru")
-> notify;

View File

@ -0,0 +1,2 @@
rule: in("email", "testgmail", "test@g")
-> notify;

View File

@ -0,0 +1,2 @@
rule: like("email", "test.*")
-> notify;

View File

@ -0,0 +1,2 @@
rule: 3 > 2 AND 1 = 1
-> notify;

View File

@ -0,0 +1,2 @@
rule: 3 < 2
-> decline;

View File

@ -0,0 +1,3 @@
rule: (sum("ip", 1444) >= 10500.50 OR sumSuccess("email", 1444) > 500)
AND sumError("fingerprint", 1444, "error_code") > 523.12
-> notify;

View File

@ -0,0 +1,5 @@
rule: 3 > 2 AND 1 > 1
-> decline;
rule: count("email", 10) <= 10 AND count("ip", 1444) = 10
-> 3ds;

View File

@ -0,0 +1,2 @@
rule: 3 > 2
-> asd;

View File

@ -0,0 +1,2 @@
rule: inWhiteList("email")
-> notify;

BIN
syntax.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB