Skip to content

Commit

Permalink
feat: add support to CONTAINS operations (#20)
Browse files Browse the repository at this point in the history
This change adds a new operator `CONTAINS` that can be used to assert
data by checking part of its content.

When the `left` operand is a `string` it will compare if any part of the
given string contains the given `right` operand.

When the `left`operand is an `array` it will compare if any elements of
the given array contains the given `right` operand.
  • Loading branch information
rapatao authored Mar 23, 2024
1 parent bbaa9ab commit 1db40b0
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 40 deletions.
78 changes: 76 additions & 2 deletions JSON.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ To see more details, check its source: [here](src/test/kotlin/com/rapatao/projec
{
"left" : "field",
"operator" : "STARTS_WITH",
"right" : "\"fi\""
"right" : "\"value\""
}
```

Expand All @@ -130,7 +130,17 @@ To see more details, check its source: [here](src/test/kotlin/com/rapatao/projec
{
"left" : "field",
"operator" : "ENDS_WITH",
"right" : "\"ld\""
"right" : "\"value\""
}
```

* contains:

```json
{
"left" : "field",
"operator" : "CONTAINS",
"right" : "\"value\""
}
```

Expand Down Expand Up @@ -1747,3 +1757,67 @@ To see more details, check its source: [here](src/test/kotlin/com/rapatao/projec
}
```

```json
{
"left" : "item.name",
"operator" : "STARTS_WITH",
"right" : "\"product\""
}
```

```json
{
"left" : "item.name",
"operator" : "STARTS_WITH",
"right" : "\"name\""
}
```

```json
{
"left" : "item.name",
"operator" : "ENDS_WITH",
"right" : "\"name\""
}
```

```json
{
"left" : "item.name",
"operator" : "ENDS_WITH",
"right" : "\"product\""
}
```

```json
{
"left" : "item.name",
"operator" : "CONTAINS",
"right" : "\"duct\""
}
```

```json
{
"left" : "item.name",
"operator" : "CONTAINS",
"right" : "\"different value\""
}
```

```json
{
"left" : "item.tags",
"operator" : "CONTAINS",
"right" : "\"test\""
}
```

```json
{
"left" : "item.tags",
"operator" : "CONTAINS",
"right" : "\"different value\""
}
```

27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@ builder: `com.rapatao.projects.ruleset.engine.types.builder.ExpressionBuilder`

To create custom operations, just extends the interface `com.rapatao.projects.ruleset.engine.types.Expression`

| operator | description |
|-----------------------|---------------------------------------------------------|
| EQUALS | Represents the equals operator (`==`) |
| NOT_EQUALS | Represents the not equals operator (`!=`) |
| GREATER_THAN | Represents the greater than operator (`>`) |
| GREATER_OR_EQUAL_THAN | Represents the greater than or equal to operator (`>=`) |
| LESS_THAN | Represents the less than operator (`<`) |
| LESS_OR_EQUAL_THAN | Represents the less than or equal to operator (`<=`) |
| STARTS_WITH | Represents the `startsWith` operation |
| ENDS_WITH | Represents the `endsWith` operation |
| operator | description |
|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| EQUALS | Represents the equality operator (==), used to check if two values are equal. |
| NOT_EQUALS | Represents the inequality operator (!=), used to check if two values are not equal. |
| GREATER_THAN | Represents the greater than operator (>), used to compare if one value is greater than another. |
| GREATER_OR_EQUAL_THAN | Represents the greater than or equal to operator (>=), used to compare if one value is greater than or equal to another. |
| LESS_THAN | Represents the less than operator (<), used to compare if one value is less than another. |
| LESS_OR_EQUAL_THAN | Represents the less than or equal to operator (<=), used to compare if one value is less than or equal to another. |
| STARTS_WITH | Represents the operation to check if a string starts with a specified sequence of characters. |
| ENDS_WITH | Represents the operation to check if a string ends with a specified sequence of characters. |
| CONTAINS | Represents the operation to check if a string contains a specified sequence of characters or if an array contains a particular element. |

### Examples

Expand Down Expand Up @@ -74,9 +75,11 @@ To create custom operations, just extends the interface `com.rapatao.projects.ru

"field" lessOrEqualThan 10

"field" startsWith "\"fie\""
"field" startsWith "\"value\""

"field" endsWith "\"eld\""
"field" endsWith "\"value\""

"field" expContains "\"value\""
````

## Supported group operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,25 @@ internal object Parser {
Operator.NOT_EQUALS -> "!=".formatComparison(expression)
Operator.STARTS_WITH -> "startsWith".formatWithOperation(expression)
Operator.ENDS_WITH -> "endsWith".formatWithOperation(expression)
Operator.CONTAINS -> formatContainsOperation(expression)
else -> "==".formatComparison(expression)
}
}

private fun String.formatComparison(expression: Expression) = "(${expression.left}) $this (${expression.right})"
private fun String.formatComparison(expression: Expression) =
"(${expression.left}) $this (${expression.right})"

private fun String.formatWithOperation(expression: Expression) =
"${expression.left}.${this}(${expression.right})"

private fun formatContainsOperation(expression: Expression) =
"""
(function() {
if (Array.isArray(${expression.left})) {
return ${expression.left}.includes(${expression.right})
} else {
return ${expression.left}.indexOf(${expression.right}) !== -1
}
})()
""".trimIndent()
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,55 @@
package com.rapatao.projects.ruleset.engine.types

/**
* Enum class for different operators.
*
* This enum class represents various operators that can be used for comparison or evaluation operations.
* The available operators are:
* - EQUALS: Represents the equals operator (==).
* - NOT_EQUALS: Represents the not equals operator (!=).
* - GREATER_THAN: Represents the greater than operator (>).
* - GREATER_OR_EQUAL_THAN: Represents the greater than or equal to operator (>=).
* - LESS_THAN: Represents the less than operator (<).
* - LESS_OR_EQUAL_THAN: Represents the less than or equal to operator (<=).
* - STARTS_WITH: Represents the startsWith operation.
* - ENDS_WITH: Represents the endsWith operation.
* This enum class defines a set of operators that can be utilized in different contexts such as string comparison,
* numerical comparison, or array element verification.
*/
enum class Operator {
/**
* Represents the equality operator (==), used to check if two values are equal.
*/
EQUALS,

/**
* Represents the inequality operator (!=), used to check if two values are not equal.
*/
NOT_EQUALS,

/**
* Represents the greater than operator (>), used to compare if one value is greater than another.
*/
GREATER_THAN,

/**
* Represents the greater than or equal to operator (>=), used to compare if one value is greater than or equal to
* another.
*/
GREATER_OR_EQUAL_THAN,

/**
* Represents the less than operator (<), used to compare if one value is less than another.
*/
LESS_THAN,

/**
* Represents the less than or equal to operator (<=), used to compare if one value is less than or equal to
* another.
*/
LESS_OR_EQUAL_THAN,

/**
* Represents the operation to check if a string starts with a specified sequence of characters.
*/
STARTS_WITH,

/**
* Represents the operation to check if a string ends with a specified sequence of characters.
*/
ENDS_WITH,

/**
* Represents the operation to check if a string contains a specified sequence of characters or if an array contains
* a particular element.
*/
CONTAINS,
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.rapatao.projects.ruleset.engine.types.builder

import com.rapatao.projects.ruleset.engine.types.Expression
import com.rapatao.projects.ruleset.engine.types.Operator
import com.rapatao.projects.ruleset.engine.types.Operator.CONTAINS
import com.rapatao.projects.ruleset.engine.types.Operator.ENDS_WITH
import com.rapatao.projects.ruleset.engine.types.Operator.EQUALS
import com.rapatao.projects.ruleset.engine.types.Operator.GREATER_OR_EQUAL_THAN
Expand Down Expand Up @@ -56,6 +57,7 @@ object ExpressionBuilder {
*
* @param left The left-hand side of the expression.
*/
@Suppress("TooManyFunctions")
class Builder(private val left: Any?) {
/**
* Creates an expression that represents the "equals to" comparison between the left operand and the right
Expand Down Expand Up @@ -145,5 +147,17 @@ object ExpressionBuilder {
fun endsWith(right: Any?) = Expression(
left = left, operator = ENDS_WITH, right = right
)

/**
* Creates an expression to check if the left operand contains the right operand.
* If the left operand is a string, it checks if the string contains the value of the right operand.
* If the left operand is a list, it checks if the list contains the right operand.
*
* @param right The value to be checked for containment within the left operand. Can be of any type.
* @return An [Expression] object representing the containment check, with the operator set to CONTAINS.
*/
fun contains(right: Any?) = Expression(
left = left, operator = CONTAINS, right = right
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.rapatao.projects.ruleset.engine.types.builder.extensions

import com.rapatao.projects.ruleset.engine.types.Expression
import com.rapatao.projects.ruleset.engine.types.builder.ExpressionBuilder

/**
* Creates an expression to check if the current string contains the specified substring.
*
* @receiver The string in which to check for the presence of the substring.
* @param right The substring to search for within the current string.
* @return An [Expression] object representing the containment check, with the operator set to CONTAINS.
*/
infix fun String.expContains(right: String): Expression = ExpressionBuilder.left(this).contains(right)

/**
* Creates an expression to check if the current list contains the specified element.
*
* @receiver The list in which to check for the presence of the element.
* @param right The element to search for within the current list.
* @return An [Expression] object representing the containment check, with the operator set to CONTAINS.
*/
infix fun List<Any>.expContains(right: Any): Expression = ExpressionBuilder.left(this).contains(right)
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package com.rapatao.projects.ruleset

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.rapatao.projects.ruleset.engine.cases.ExpressionCases
import com.rapatao.projects.ruleset.engine.cases.MatcherCases
import com.rapatao.projects.ruleset.engine.cases.TestData
import com.rapatao.projects.ruleset.engine.types.Expression
import com.rapatao.projects.ruleset.engine.types.builder.MatcherBuilder.allMatch
import com.rapatao.projects.ruleset.engine.types.builder.MatcherBuilder.anyMatch
import com.rapatao.projects.ruleset.engine.types.builder.MatcherBuilder.noneMatch
import com.rapatao.projects.ruleset.engine.types.builder.extensions.endsWith
import com.rapatao.projects.ruleset.engine.types.builder.extensions.equalsTo
import com.rapatao.projects.ruleset.engine.types.builder.extensions.expContains
import com.rapatao.projects.ruleset.engine.types.builder.extensions.greaterOrEqualThan
import com.rapatao.projects.ruleset.engine.types.builder.extensions.greaterThan
import com.rapatao.projects.ruleset.engine.types.builder.extensions.isFalse
Expand Down Expand Up @@ -61,10 +61,13 @@ internal class SerializationExamplesBuilder {
"field" lessOrEqualThan 10,

"* startsWith:",
"field" startsWith "\"fi\"",
"field" startsWith "\"value\"",

"* endsWith:",
"field" endsWith "\"ld\"",
"field" endsWith "\"value\"",

"* contains:",
"field" expContains "\"value\"",

"* allMatch: ",
allMatch(
Expand Down Expand Up @@ -101,9 +104,9 @@ internal class SerializationExamplesBuilder {
),
)

private val casesFromTests =
ExpressionCases.cases().flatMap { it.get().toList() }.filterIsInstance<Expression>() +
MatcherCases.cases().flatMap { it.get().toList() }.filterIsInstance<Expression>()
private val casesFromTests = TestData.allCases()
.flatMap { it.get().toList() }
.filterIsInstance<Expression>()

@Test
fun print() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.rapatao.projects.ruleset.engine.cases

import com.rapatao.projects.ruleset.engine.types.builder.extensions.expContains
import org.junit.jupiter.params.provider.Arguments

object ContainsCases {
fun cases(): List<Arguments> = listOf(
Arguments.of(
"item.name" expContains "\"duct\"",
true,
),
Arguments.of(
"item.name" expContains "\"different value\"",
false,
),
Arguments.of(
"item.tags" expContains "\"test\"",
true,
),
Arguments.of(
"item.tags" expContains "\"different value\"",
false,
),
)
}
Loading

0 comments on commit 1db40b0

Please sign in to comment.