Created
February 11, 2018 13:16
-
-
Save aaabramov/3fa29477662b44bd1617d937535255dd to your computer and use it in GitHub Desktop.
Attempt to create Anti Fraud DSL based on rules
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package example.dsl | |
import scala.language.implicitConversions | |
case class Card(number: String, firstName: String, lastName: String) | |
case class User(username: String, email: String) | |
case class AntiFraudInput(card: Card, user: User, amount: Double) | |
object AntiFraudInput { | |
type InputField[T] = AntiFraudInput => T | |
} | |
sealed trait FieldOp[T] { | |
def matches(source: T, arg: T): Boolean | |
} | |
object FieldOpSyntax { | |
def equalTo[T]: ===[T] = new ===[T]() | |
def greaterThan[T: Ordering]: >[T] = >[T]() | |
def lessThan[T: Ordering]: <[T] = <[T]() | |
def greaterThanOrEqualTo[T: Ordering]: >=[T] = >=[T]() | |
def lessThanOrEqualTo[T: Ordering]: >=[T] = >=[T]() | |
def contains[T: SelfTypedContainer]: Contains[T] = Contains[T]() | |
} | |
case class ContainerWithValue[A, E](holder: A, container: Container[A, E]) { | |
def contains(elem: E): Boolean = container.contains(holder)(elem) | |
} | |
trait Container[A, E] { | |
def checkThat(value: A): ContainerWithValue[A, E] = ContainerWithValue(value, this) | |
def contains(elem: A): E => Boolean | |
} | |
trait SelfTypedContainer[T] extends Container[T, T] { | |
override def checkThat(value: T): ContainerWithValue[T, T] = ContainerWithValue(value, this) | |
override def contains(elem: T): T => Boolean | |
} | |
object SelfTypedContainers { | |
implicit object StringTypedContainer extends SelfTypedContainer[String] { | |
override def contains(elem: String): String => Boolean = _.contains(elem) | |
} | |
} | |
case class Contains[T: SelfTypedContainer]() extends FieldOp[T] { | |
def matches(source: T, arg: T): Boolean = implicitly[SelfTypedContainer[T]] checkThat source contains arg | |
} | |
case class StringContainer(s: String) extends Container[String, String] { | |
override def contains(elem: String): String => Boolean = _.contains(elem) | |
} | |
object CommonContainers { | |
implicit def stringContainer(s: String): StringContainer = StringContainer(s) | |
} | |
case class >[T: Ordering]() extends FieldOp[T] { | |
def matches(source: T, arg: T): Boolean = implicitly[Ordering[T]].gt(source, arg) | |
} | |
case class >=[T: Ordering]() extends FieldOp[T] { | |
def matches(source: T, arg: T): Boolean = implicitly[Ordering[T]].gteq(source, arg) | |
} | |
case class <[T: Ordering]() extends FieldOp[T] { | |
def matches(source: T, arg: T): Boolean = implicitly[Ordering[T]].lt(source, arg) | |
} | |
case class <=[T: Ordering]() extends FieldOp[T] { | |
def matches(source: T, arg: T): Boolean = implicitly[Ordering[T]].lteq(source, arg) | |
} | |
case class ===[T]() extends FieldOp[T] { | |
def matches(source: T, arg: T): Boolean = source == arg | |
} | |
case class Rule[T]( | |
field: AntiFraudInput.InputField[T], | |
op: FieldOp[T], | |
arg: T, | |
score: Double | |
) { | |
def score(af: AntiFraudInput): Double = if (op.matches(field(af), arg)) score else 0 | |
} | |
object DslTest extends App { | |
implicit val afInput = AntiFraudInput( | |
Card("4242 4242 4242 4242", "TEST", "USER"), | |
User("af_test", "test@gmail.com"), 200 | |
) | |
import FieldOpSyntax._ | |
import SelfTypedContainers._ | |
val rules = Seq( | |
Rule(_.amount, greaterThanOrEqualTo, 100.0, 15), | |
Rule(_.user.email, equalTo, "test@gmail.com", 30), | |
Rule(_.user.username, contains, "test", 30) | |
) | |
val res = rules.foldLeft(0.0)(_ + _.score(afInput)) | |
println(res) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment