
io.insource.framework.rule.Rule.kt Maven / Gradle / Ivy
@file:Suppress("unused")
package io.insource.framework.rule
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import kotlin.reflect.KClass
/**
* Represents a rule function to be evaluated for a result.
*
* @param description The description of the rule function
* @param ruleDefinition The definition of the rule
* @param T The return type of the rule function
*/
class Rule(val description: String, val ruleDefinition: RuleDefinition) {
/**
* Execute the rule as a function.
*
* @param arg The rule argument, which can be any type the rule expects to handle
*/
operator fun invoke(arg: Any): T = ruleDefinition(arg).let {
if (ruleDefinition.whenClause(it)) {
ruleDefinition.thenClause(it)
} else {
ruleDefinition.elseClause(it)
}
}
}
/**
* Represents a rule definition (e.g. an if-then expression).
*
* @param whenClause The if-part of a rule expression
* @param thenClause The then-part of a rule expression
*/
class RuleDefinition(
val givenClause: GivenExpression.() -> E,
val whenClause: (E) -> Boolean,
val thenClause: (E) -> T,
val elseClause: (E) -> T
) {
/**
* Execute the given clause of a rule definition to retrieve a strongly-typed
* rule argument.
*
* @param arg The rule argument, which can be any type the rule expects to handle
*/
internal operator fun invoke(arg: Any): E = GivenExpression(arg).givenClause()
/**
* Override the else-part of a rule expression with an action.
*
* @param elseClause The else-part of a rule expression
* @return A rule definition
*/
infix fun otherwiseDo(elseClause: (E) -> Unit): RuleDefinition = RuleDefinition(givenClause, whenClause, thenClause, { elseClause(it); this.elseClause(it) })
}
/**
* A transparent type representing a rule expression.
*/
class RuleExpression {
/**
* Provide the pre-condition of a rule expression.
*
* @param givenClause The pre-condition of a rule expression
* @return A when expression
*/
infix fun given(givenClause: GivenExpression.() -> E): WhenExpression = WhenExpression(givenClause)
}
/**
* A transparent type used to extend the capabilities of a rule expression.
*
* @param arg The rule argument, which can be any type the rule expects to handle
*/
class GivenExpression(private val arg: Any) {
companion object {
/**
* Accessor function to retrieve values from a Map. Null values throw a
* `NoSuchElementException`.
*/
val MapAccessStrategy = fun(key: String, arg: Any): Any = (arg as Map<*, *>).let {
if (it.containsKey(key)) it[key]!! else throw NoSuchElementException("Key $key not found")
}
/**
* Accessor function to retrieve values from a JSON object. Null values throw a
* `NoSuchElementException`.
*/
@Suppress("IMPLICIT_CAST_TO_ANY")
val JsonAccessStrategy = fun(key: String, arg: Any): Any = (arg as ObjectNode).let { obj ->
if (obj.has(key)) obj.get(key).let {
when {
it.isShort -> it.asInt().toShort()
it.isInt -> it.asInt()
it.isLong -> it.asLong()
it.isFloat -> it.asDouble().toFloat()
it.isDouble -> it.asDouble()
it.isBoolean -> it.asBoolean()
it.isTextual -> it.asText()
else -> it
}
}
else throw NoSuchElementException("Key $key not found")
}
/**
* Default accessor function for rule expressions.
*/
var DefaultAccessStrategy = JsonAccessStrategy
}
/**
* Apply the desired access strategy to this rule expression in order to
* access a nested field.
*
* @param accessStrategy An accessor function to retrieve values from nested
* objects.
*/
fun using(accessStrategy: (String, Any) -> Any): NestedGivenExpression = NestedGivenExpression(arg, accessStrategy)
/**
* Apply the `MapAccessStrategy` to this rule expression in order to access
* nested fields in a `Map`.
*/
fun usingMap() = using(MapAccessStrategy)
/**
* Apply the `JsonAccessStrategy` to this rule expression in order to access
* nested fields in a JSON object.
*/
fun usingJson() = using(JsonAccessStrategy)
/**
* Coerce the rule argument into a particular type.
*
* @param T The runtime type
* @return The argument, as the desired runtime type
*/
fun any(clazz: KClass): T = clazz.java.cast(arg)
/**
* Coerce the rule argument into a `Map`.
*
* @return The argument, as a `Map`
*/
fun anyMap(): Map<*, *> = arg as Map<*, *>
/**
* Coerce the rule argument into a `MutableMap`.
*
* @return The argument, as a `MutableMap`
*/
fun anyMutableMap(): MutableMap<*, *> = arg as MutableMap<*, *>
/**
* Coerce the rule argument into an `ObjectNode`.
*
* @return The argument, as an `ObjectNode`
*/
fun anyObject(): ObjectNode = arg as ObjectNode
/**
* Coerce the rule argument into an `ArrayNode`.
*
* @return The argument, as an `ArrayNode`
*/
fun anyArray(): ArrayNode = arg as ArrayNode
/**
* Coerce the argument into a `String`.
*
* @return The argument, as a `String`
*/
fun anyString(): String = arg.toString()
/**
* Coerce the argument into a `Short`.
*
* @return The argument, as a `Short`
*/
fun anyShort(): Short = when (arg) {
is Short -> arg
is Number -> arg.toShort()
else -> arg.toString().toShort()
}
/**
* Coerce the argument into an `Int`.
*
* @return The argument, as an `Int`
*/
fun anyInt(): Int = when (arg) {
is Int -> arg
is Number -> arg.toInt()
else -> arg.toString().toInt()
}
/**
* Coerce the argument into a `Long`.
*
* @return The argument, as a `Long`
*/
fun anyLong(): Long = when (arg) {
is Long -> arg
is Number -> arg.toLong()
else -> arg.toString().toLong()
}
/**
* Coerce the argument into a `Float`.
*
* @return The argument, as a `Float`
*/
fun anyFloat(): Float = when (arg) {
is Float -> arg
is Number -> arg.toFloat()
else -> arg.toString().toFloat()
}
/**
* Coerce the argument into a `Double`.
*
* @return The argument, as a `Double`
*/
fun anyDouble(): Double = when (arg) {
is Double -> arg
is Number -> arg.toDouble()
else -> arg.toString().toDouble()
}
/**
* Coerce the argument into a `Boolean`.
*
* @return The argument, as a `Boolean`
*/
fun anyBoolean(): Boolean = when (arg) {
is Boolean -> arg
else -> arg.toString().toLowerCase().let { str ->
when (str) {
"t", "true", "y", "yes", "1", "+" -> true
else -> false
}
}
}
/**
* Coerce a nested field into a `String`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `String`
*/
fun anyString(key: String): String = using(DefaultAccessStrategy).anyString(key)
/**
* Coerce a nested field into a `Short`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Short`
*/
fun anyShort(key: String): Short = using(DefaultAccessStrategy).anyShort(key)
/**
* Coerce a nested field into an `Int`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as an `Int`
*/
fun anyInt(key: String): Int = using(DefaultAccessStrategy).anyInt(key)
/**
* Coerce a nested field into a `Long`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Long`
*/
fun anyLong(key: String): Long = using(DefaultAccessStrategy).anyLong(key)
/**
* Coerce a nested field into a `Float`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Float`
*/
fun anyFloat(key: String): Float = using(DefaultAccessStrategy).anyFloat(key)
/**
* Coerce a nested field into a `Double`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Double`
*/
fun anyDouble(key: String): Double = using(DefaultAccessStrategy).anyDouble(key)
/**
* Coerce a nested field into a `Boolean`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Boolean`
*/
fun anyBoolean(key: String): Boolean = using(DefaultAccessStrategy).anyBoolean(key)
}
/**
* A specialized rule expression for nested values.
*
* @param arg The rule argument, which can be any type the rule expects to handle
* @param accessStrategy Accessor function to retrieve values from nested objects, e.g. an `ObjectNode` or `Map`
*/
class NestedGivenExpression(private val arg: Any, private var accessStrategy: (String, Any) -> Any) {
/**
* Coerce a nested field into a `Map`.
*
* @return The argument, as a `Map`
*/
fun anyMap(key: String): Map<*, *> = accessStrategy(key, arg) as Map<*, *>
/**
* Coerce a nested field into an `ObjectNode`.
*
* @return The argument, as an `ObjectNode`
*/
fun anyObject(key: String): ObjectNode = accessStrategy(key, arg) as ObjectNode
/**
* Coerce a nested field into an `ArrayNode`.
*
* @return The argument, as an `ArrayNode`
*/
fun anyArray(key: String): ArrayNode = accessStrategy(key, arg) as ArrayNode
/**
* Coerce a nested field into a `String`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `String`
*/
fun anyString(key: String): String = accessStrategy(key, arg).toString()
/**
* Coerce a nested field into a `Short`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Short`
*/
fun anyShort(key: String): Short = accessStrategy(key, arg).let {
when (it) {
is Short -> it
is Number -> it.toShort()
else -> it.toString().toShort()
}
}
/**
* Coerce a nested field into an `Int`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as an `Int`
*/
fun anyInt(key: String): Int = accessStrategy(key, arg).let {
when (it) {
is Int -> it
is Number -> it.toInt()
else -> it.toString().toInt()
}
}
/**
* Coerce a nested field into a `Long`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Long`
*/
fun anyLong(key: String): Long = accessStrategy(key, arg).let {
when (it) {
is Long -> it
is Number -> it.toLong()
else -> it.toString().toLong()
}
}
/**
* Coerce a nested field into a `Float`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Float`
*/
fun anyFloat(key: String): Float = accessStrategy(key, arg).let {
when (it) {
is Float -> it
is Number -> it.toFloat()
else -> it.toString().toFloat()
}
}
/**
* Coerce a nested field into a `Double`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Double`
*/
fun anyDouble(key: String): Double = accessStrategy(key, arg).let {
when (it) {
is Double -> it
is Number -> it.toDouble()
else -> it.toString().toDouble()
}
}
/**
* Coerce a nested field into a `Boolean`.
*
* @param key The key of a nested property to retrieve
* @return The argument, as a `Boolean`
*/
fun anyBoolean(key: String): Boolean = accessStrategy(key, arg).let {
when (it) {
is Boolean -> it
else -> it.toString().toLowerCase().let { str ->
when (str) {
"t", "true", "y", "yes", "1", "+" -> true
else -> false
}
}
}
}
}
/**
* A transparent type used to extend the capabilities of a rule expression.
*
* @param givenClause The pre-condition of a rule expression
*/
class WhenExpression(val givenClause: GivenExpression.() -> E) {
/**
* Provide the if-part of a rule expression.
*
* @param whenClause The if-part of a rule expression
* @return A then expression
*/
infix fun expect(whenClause: (E) -> Boolean): ThenExpression = ThenExpression(givenClause, whenClause)
/**
* Alias for `expect`.
*
* @param whenClause The if-part of a rule expression
* @return A then expression
*/
infix fun and(whenClause: (E) -> Boolean): ThenExpression = expect(whenClause)
/**
* Provide the if-part of a rule expression, as the argument supplied by the given.
*
* @param whenClause The if-part of a rule expression
* @return A then expression
*/
infix fun whose(whenClause: E.() -> Boolean): ThenExpression = ThenExpression(givenClause, { it.whenClause() })
/**
* Always return a value. This has the side effect of the rule always matching.
*
* @param thenClause Then then-part of a rule expression
* @return A rule definition
*/
infix fun alwaysReturn(thenClause: (E) -> T): RuleDefinition = expect { true } thenReturn(thenClause) otherwiseReturn(thenClause)
/**
* Always perform an action. This has the side effect of the rule always matching.
*
* @param thenClause Then then-part of a rule expression
* @return A rule definition
*/
infix fun alwaysDo(thenClause: (E) -> Unit): RuleDefinition = expect { true } thenDo(thenClause)
}
/**
* A transparent type used to extend the capabilities of a rule expression.
*
* @param givenClause The pre-condition of a rule expression
* @param whenClause The if-part of a rule expression
*/
class ThenExpression(val givenClause: GivenExpression.() -> E, val whenClause: (E) -> Boolean) {
/**
* Provide an additional if-part of a rule expression, AND'd to the first part.
*
* @param whenClause The if-part of a rule expression
* @return A then expression
*/
infix fun and(whenClause: (E) -> Boolean): ThenExpression = ThenExpression(givenClause, { this.whenClause(it) && whenClause(it) })
/**
* Provide an additional if-part of a rule expression, AND'd to the first part.
*
* @param whenClause The if-part of a rule expression
* @return A then expression
*/
infix fun or(whenClause: (E) -> Boolean): ThenExpression = ThenExpression(givenClause, { this.whenClause(it) || whenClause(it) })
/**
* Provide the then-part of a rule expression with a return value.
*
* @param thenClause Then then-part of a rule expression
* @return An otherwise expression
*/
infix fun thenReturn(thenClause: (E) -> T): OtherwiseExpression = OtherwiseExpression(givenClause, whenClause, thenClause)
/**
* Provide the then-part of a rule expression with an action.
*
* @param thenClause Then then-part of a rule expression
* @return A rule definition
*/
infix fun thenDo(thenClause: (E) -> Unit): RuleDefinition = RuleDefinition(givenClause, whenClause, { thenClause(it); true }, { false })
/**
* Alias for `thenReturn`.
*
* @param thenClause Then then-part of a rule expression
* @return An otherwise expression
*/
infix fun then(thenClause: (E) -> T): OtherwiseExpression = thenReturn(thenClause)
}
/**
* A transparent type used to extend the capabilities of a rule expression.
*
* @param givenClause The pre-condition of a rule expression
* @param whenClause The if-part of a rule expression
* @param thenClause The then-part of a rule expression
*/
class OtherwiseExpression(val givenClause: GivenExpression.() -> E, val whenClause: (E) -> Boolean, val thenClause: (E) -> T) {
/**
* Override the else-part of a rule expression with a return value.
*
* @param elseClause The else-part of a rule expression
* @return A rule definition
*/
infix fun otherwiseReturn(elseClause: (E) -> T): RuleDefinition = RuleDefinition(givenClause, whenClause, thenClause, elseClause)
/**
* Alias for `otherwiseReturn`.
*
* @param elseClause The else-part of a rule expression
* @return A rule definition
*/
infix fun otherwise(elseClause: (E) -> T): RuleDefinition = otherwiseReturn(elseClause)
}
/**
* Generate a rule function.
*
* @param description The description of the rule function
* @param letClause The definition of the rule, as a function that returns a rule definition
* @param T The return type of the rule function
* @return A rule function
*/
fun rule(description: String, letClause: RuleExpression.() -> RuleDefinition): Rule {
return Rule(description, RuleExpression().letClause())
}
/**
* Alias for arrayOf(...)
.
*
* @param rules
*/
fun rules(vararg rules: Rule): Array> = rules
/**
* Set the access strategy to retrieve values from a JSON object.
*/
fun useJsonAccessStrategy() {
GivenExpression.DefaultAccessStrategy = GivenExpression.JsonAccessStrategy
}
/**
* Set the access strategy to retrieve values from a Map.
*/
fun useMapAccessStrategy() {
GivenExpression.DefaultAccessStrategy = GivenExpression.MapAccessStrategy
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy