Major refactor, lots more content, lots of cleanup.
This commit is contained in:
parent
910f14a3c6
commit
fae37780e9
61 changed files with 1624 additions and 1020 deletions
86
build.sbt
86
build.sbt
|
|
@ -13,10 +13,6 @@ ThisBuild / licenses := Seq(
|
|||
"MIT" -> url("https://git.garrity.co/garrity-software/gs-predicate/LICENSE")
|
||||
)
|
||||
|
||||
val noPublishSettings = Seq(
|
||||
publish := {}
|
||||
)
|
||||
|
||||
val sharedSettings = Seq(
|
||||
scalaVersion := scala3,
|
||||
version := semVerSelected.value,
|
||||
|
|
@ -32,13 +28,13 @@ val Deps = new {
|
|||
|
||||
val Circe = new {
|
||||
val Core: ModuleID = "io.circe" %% "circe-core" % "0.14.15"
|
||||
val Parser: ModuleID = "io.circe" %% "circe-parser" % "0.14.15"
|
||||
val Generic: ModuleID = "io.circe" %% "circe-generic" % "0.14.15"
|
||||
val Optics: ModuleID = "io.circe" %% "circe-optics" % "0.15.1"
|
||||
val Parser: ModuleID = "io.circe" %% "circe-parser" % "0.14.15"
|
||||
}
|
||||
|
||||
val Gs = new {
|
||||
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.3.3"
|
||||
val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.4.1"
|
||||
}
|
||||
|
||||
val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.1.1"
|
||||
|
|
@ -53,86 +49,16 @@ lazy val testSettings = Seq(
|
|||
|
||||
lazy val `gs-predicate` = project
|
||||
.in(file("."))
|
||||
.aggregate(
|
||||
`test-support`,
|
||||
api,
|
||||
keyValue,
|
||||
json
|
||||
)
|
||||
.settings(noPublishSettings)
|
||||
.settings(sharedSettings)
|
||||
.settings(testSettings)
|
||||
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
|
||||
|
||||
/** Internal project used for unit tests.
|
||||
*/
|
||||
lazy val `test-support` = project
|
||||
.in(file("modules/test-support"))
|
||||
.settings(sharedSettings)
|
||||
.settings(testSettings)
|
||||
.settings(noPublishSettings)
|
||||
.settings(
|
||||
name := s"${gsProjectName.value}-test-support"
|
||||
)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
Deps.Cats.Core,
|
||||
Deps.Cats.Effect
|
||||
)
|
||||
)
|
||||
|
||||
/** Core API - Defines fundamental predicates.
|
||||
*/
|
||||
lazy val api = project
|
||||
.in(file("modules/api"))
|
||||
.dependsOn(`test-support` % "test->test")
|
||||
.settings(sharedSettings)
|
||||
.settings(testSettings)
|
||||
.settings(
|
||||
name := s"${gsProjectName.value}-api-v${semVerMajor.value}"
|
||||
)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
Deps.Cats.Core,
|
||||
Deps.Gs.Uuid
|
||||
)
|
||||
)
|
||||
|
||||
/** Key-Value - Defines predicates that can match on string keys and values.
|
||||
*/
|
||||
lazy val keyValue = project
|
||||
.in(file("modules/kv"))
|
||||
.dependsOn(`test-support` % "test->test")
|
||||
.dependsOn(api)
|
||||
.settings(sharedSettings)
|
||||
.settings(testSettings)
|
||||
.settings(
|
||||
name := s"${gsProjectName.value}-kv-v${semVerMajor.value}"
|
||||
)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
Deps.Cats.Core,
|
||||
Deps.Cats.Effect,
|
||||
Deps.Gs.Uuid
|
||||
)
|
||||
)
|
||||
|
||||
/** JSON - Defines predicates that can match on JSON values.
|
||||
*/
|
||||
lazy val json = project
|
||||
.in(file("modules/json"))
|
||||
.dependsOn(`test-support` % "test->test")
|
||||
.dependsOn(api)
|
||||
.settings(sharedSettings)
|
||||
.settings(testSettings)
|
||||
.settings(
|
||||
name := s"${gsProjectName.value}-json-v${semVerMajor.value}"
|
||||
)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
Deps.Cats.Core,
|
||||
Deps.Cats.Effect,
|
||||
Deps.Circe.Core,
|
||||
Deps.Circe.Parser,
|
||||
Deps.Circe.Generic,
|
||||
Deps.Circe.Optics,
|
||||
Deps.Gs.Uuid
|
||||
Deps.Circe.Parser
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import cats.syntax.all.*
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Implements logical AND.
|
||||
*
|
||||
* @param ps
|
||||
* The predicates to evaluate.
|
||||
*/
|
||||
final class And[F[_]: Applicative, -A](
|
||||
val id: UUID,
|
||||
private val ps: List[Predicate[F, A]]
|
||||
) extends Predicate[F, A]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: A): F[Predicate.Result] =
|
||||
ps.map(_.eval(input)).sequence.map(_.allMatch())
|
||||
|
||||
object And:
|
||||
|
||||
def empty[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
||||
|
||||
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): Predicate[F, A] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new And(UUID.v7(), list)
|
||||
|
||||
def apply[F[_]: Applicative, A](
|
||||
id: UUID,
|
||||
ps: Predicate[F, A]*
|
||||
): Predicate[F, A] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new And(id, list)
|
||||
|
||||
end And
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Always returns a miss.
|
||||
*/
|
||||
final class False[F[_]: Applicative](
|
||||
val id: UUID
|
||||
) extends Predicate[F, Any]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: Any): F[Predicate.Result] =
|
||||
Applicative[F].pure(Predicate.Result.missed())
|
||||
|
||||
object False:
|
||||
|
||||
def apply[F[_]: Applicative]: False[F] = new False[F](UUID.v7())
|
||||
|
||||
def apply[F[_]: Applicative](id: UUID): False[F] = new False[F](id)
|
||||
|
||||
end False
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import cats.syntax.all.*
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Implements logical OR.
|
||||
*
|
||||
* @param ps
|
||||
* The predicates to evaluate.
|
||||
*/
|
||||
final class Or[F[_]: Applicative, -A](
|
||||
val id: UUID,
|
||||
private val ps: List[Predicate[F, A]]
|
||||
) extends Predicate[F, A]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: A): F[Predicate.Result] =
|
||||
ps.map(_.eval(input)).sequence.map(_.anyMatch())
|
||||
|
||||
object Or:
|
||||
|
||||
def empty[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
||||
|
||||
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): Predicate[F, A] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new Or(UUID.v7(), list)
|
||||
|
||||
def apply[F[_]: Applicative, A](
|
||||
id: UUID,
|
||||
ps: Predicate[F, A]*
|
||||
): Predicate[F, A] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new Or(id, list)
|
||||
|
||||
end Or
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Always returns a match.
|
||||
*/
|
||||
final class True[F[_]: Applicative](
|
||||
val id: UUID
|
||||
) extends Predicate[F, Any]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: Any): F[Predicate.Result] =
|
||||
Applicative[F].pure(Predicate.Result.matched())
|
||||
|
||||
object True:
|
||||
|
||||
def apply[F[_]: Applicative]: True[F] = new True[F](UUID.v7())
|
||||
|
||||
def apply[F[_]: Applicative](id: UUID): True[F] = new True[F](id)
|
||||
|
||||
end True
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.effect.IO
|
||||
import gs.uuid.v0.UUID
|
||||
import support.IOSuite
|
||||
|
||||
class FalseTests extends IOSuite:
|
||||
|
||||
iotest("should return false") {
|
||||
val predicate = False[IO]
|
||||
|
||||
for result <- predicate.eval(())
|
||||
yield assertEquals(result.unwrap(), false)
|
||||
}
|
||||
|
||||
iotest("should provide a unique identifier") {
|
||||
val id = UUID.v7()
|
||||
val predicate = False[IO](id)
|
||||
|
||||
for result <- predicate.eval(())
|
||||
yield
|
||||
assertEquals(result.unwrap(), false)
|
||||
assertEquals(predicate.id, id)
|
||||
assertEquals(predicate.hashCode(), id.hashCode())
|
||||
assertEquals(predicate.toString(), s"predicate:${id.withoutDashes()}")
|
||||
}
|
||||
|
||||
test("should support equality") {
|
||||
val predicate = False[IO]
|
||||
val p2 = False[IO](predicate.id)
|
||||
val p3 = False[IO]
|
||||
assertEquals(predicate, predicate)
|
||||
assertEquals(predicate, p2)
|
||||
assertNotEquals(predicate, p3)
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import munit.FunSuite
|
||||
|
||||
class PredicateTests extends FunSuite:
|
||||
|
||||
test("should not equal non-predicate objects") {
|
||||
val x = "other"
|
||||
val p = Predicate.alwaysTrue[cats.Id]
|
||||
assertEquals(p.equals(x), false)
|
||||
assertNotEquals(p, null)
|
||||
}
|
||||
|
||||
test("should provide a default toString implementation") {
|
||||
val p = Predicate.alwaysFalse[cats.Id]
|
||||
assertEquals(p.toString(), s"predicate:${p.id.withoutDashes()}")
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.effect.IO
|
||||
import gs.uuid.v0.UUID
|
||||
import support.IOSuite
|
||||
|
||||
class TrueTests extends IOSuite:
|
||||
|
||||
iotest("should return true") {
|
||||
val predicate = True[IO]
|
||||
|
||||
for result <- predicate.eval(())
|
||||
yield assertEquals(result.unwrap(), true)
|
||||
}
|
||||
|
||||
iotest("should provide a unique identifier") {
|
||||
val id = UUID.v7()
|
||||
val predicate = True[IO](id)
|
||||
|
||||
for result <- predicate.eval(())
|
||||
yield
|
||||
assertEquals(result.unwrap(), true)
|
||||
assertEquals(predicate.id, id)
|
||||
assertEquals(predicate.hashCode(), id.hashCode())
|
||||
assertEquals(predicate.toString(), s"predicate:${id.withoutDashes()}")
|
||||
}
|
||||
|
||||
test("should support equality") {
|
||||
val predicate = True[IO]
|
||||
val p2 = True[IO](predicate.id)
|
||||
val p3 = True[IO]
|
||||
assertEquals(predicate, predicate)
|
||||
assertEquals(predicate, p2)
|
||||
assertNotEquals(predicate, p3)
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package gs.predicate.v0.json
|
||||
|
||||
import cats.Applicative
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.uuid.v0.UUID
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if JSON blob is an object that contains the given
|
||||
* key.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
*/
|
||||
final class JsonKeyExists[F[_]: Applicative](
|
||||
val id: UUID,
|
||||
val queryString: String
|
||||
) extends Predicate[F, Json]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: Json): F[Predicate.Result] =
|
||||
Applicative[F].pure(Predicate.Result(false))
|
||||
|
||||
object JsonKeyExists:
|
||||
|
||||
def apply[F[_]: Applicative](key: String): JsonKeyExists[F] =
|
||||
new JsonKeyExists[F](UUID.v7(), key)
|
||||
|
||||
end JsonKeyExists
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
*/
|
||||
final class KeyExists[F[_]: Functor, K, V](
|
||||
val id: UUID,
|
||||
val key: K
|
||||
) extends Predicate[F, KeyValueProvider[F, K, V]]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: KeyValueProvider[F, K, V]): F[Predicate.Result] =
|
||||
input.exists(key).map(Predicate.Result.apply)
|
||||
|
||||
object KeyExists:
|
||||
|
||||
def apply[F[_]: Functor, K, V](key: K): KeyExists[F, K, V] =
|
||||
new KeyExists[F, K, V](UUID.v7(), key)
|
||||
|
||||
end KeyExists
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
/** Type alias for a [[KeyValueProvider]] that associates string values with
|
||||
* string keys.
|
||||
*/
|
||||
type KeyValueStringProvider[F[_]] = KeyValueProvider[F, String, String]
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Predicate that matches if some (string-valued) [[KeyValueProvider]] contains
|
||||
* the given key, and the string value associated with that key contains some
|
||||
* other string.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param containedValue
|
||||
* The substring that must be contained in the value.
|
||||
*/
|
||||
final class StringContains[F[_]: Functor, K](
|
||||
val id: UUID,
|
||||
val key: K,
|
||||
val containedValue: String
|
||||
) extends Predicate[F, KeyValueProvider[F, K, String]]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: KeyValueProvider[F, K, String])
|
||||
: F[Predicate.Result] =
|
||||
input.get(key).map {
|
||||
case Some(value) => Predicate.Result(value.contains(containedValue))
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object StringContains:
|
||||
|
||||
def apply[F[_]: Functor, K](
|
||||
key: K,
|
||||
containedValue: String
|
||||
): StringContains[F, K] =
|
||||
new StringContains[F, K](UUID.v7(), key, containedValue)
|
||||
|
||||
end StringContains
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Predicate that matches if some (string-valued) [[KeyValueProvider]] contains
|
||||
* the given key, and the string value associated with that key ends with some
|
||||
* other string.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param suffix
|
||||
* The substring that must be the suffix of the value.
|
||||
*/
|
||||
final class StringEndsWith[F[_]: Functor, K](
|
||||
val id: UUID,
|
||||
val key: K,
|
||||
val suffix: String
|
||||
) extends Predicate[F, KeyValueProvider[F, K, String]]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: KeyValueProvider[F, K, String])
|
||||
: F[Predicate.Result] =
|
||||
input.get(key).map {
|
||||
case Some(value) => Predicate.Result(value.endsWith(suffix))
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object StringEndsWith:
|
||||
|
||||
def apply[F[_]: Functor, K](
|
||||
key: K,
|
||||
suffix: String
|
||||
): StringEndsWith[F, K] =
|
||||
new StringEndsWith[F, K](UUID.v7(), key, suffix)
|
||||
|
||||
end StringEndsWith
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Predicate that matches if some (string-valued) [[KeyValueProvider]] contains
|
||||
* the given key, and the string value associated with that key starts with
|
||||
* some other string.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param prefix
|
||||
* The substring that must be the prefix of the value.
|
||||
*/
|
||||
final class StringStartsWith[F[_]: Functor, K](
|
||||
val id: UUID,
|
||||
val key: K,
|
||||
val prefix: String
|
||||
) extends Predicate[F, KeyValueProvider[F, K, String]]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: KeyValueProvider[F, K, String])
|
||||
: F[Predicate.Result] =
|
||||
input.get(key).map {
|
||||
case Some(value) => Predicate.Result(value.startsWith(prefix))
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object StringStartsWith:
|
||||
|
||||
def apply[F[_]: Functor, K](
|
||||
key: K,
|
||||
prefix: String
|
||||
): StringStartsWith[F, K] =
|
||||
new StringStartsWith[F, K](UUID.v7(), key, prefix)
|
||||
|
||||
end StringStartsWith
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key,
|
||||
* and the value associated with that key equals the given value.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param value
|
||||
* The value that must be associated with the key.
|
||||
*/
|
||||
final class ValueEquals[F[_]: Functor, K, V](
|
||||
val id: UUID,
|
||||
val key: K,
|
||||
val expectedValue: V
|
||||
)(
|
||||
using
|
||||
CanEqual[V, V]
|
||||
) extends Predicate[F, KeyValueProvider[F, K, V]]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: KeyValueProvider[F, K, V]): F[Predicate.Result] =
|
||||
input.get(key).map {
|
||||
case Some(value) => Predicate.Result(value == expectedValue)
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object ValueEquals:
|
||||
|
||||
def apply[F[_]: Functor, K, V](
|
||||
key: K,
|
||||
value: V
|
||||
)(
|
||||
using
|
||||
CanEqual[V, V]
|
||||
): ValueEquals[F, K, V] =
|
||||
new ValueEquals[F, K, V](UUID.v7(), key, value)
|
||||
|
||||
end ValueEquals
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key,
|
||||
* and the value associated with that key is not equal to the given value.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param value
|
||||
* The value that must not be associated with the key.
|
||||
*/
|
||||
final class ValueNotEquals[F[_]: Functor, K, V](
|
||||
val id: UUID,
|
||||
val key: K,
|
||||
val value: V
|
||||
)(
|
||||
using
|
||||
CanEqual[V, V]
|
||||
) extends Predicate[F, KeyValueProvider[F, K, V]]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: KeyValueProvider[F, K, V]): F[Predicate.Result] =
|
||||
input.get(key).map {
|
||||
case Some(v) => Predicate.Result(v != value)
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object ValueNotEquals:
|
||||
|
||||
def apply[F[_]: Functor, K, V](
|
||||
key: K,
|
||||
value: V
|
||||
)(
|
||||
using
|
||||
CanEqual[V, V]
|
||||
): ValueNotEquals[F, K, V] =
|
||||
new ValueNotEquals[F, K, V](UUID.v7(), key, value)
|
||||
|
||||
end ValueNotEquals
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.std.MapRef
|
||||
import gs.datagen.v0.Gen
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.kv.StringContainsTests.Data.Substring1
|
||||
import support.IOSuite
|
||||
|
||||
class StringContainsTests extends IOSuite:
|
||||
|
||||
import StringContainsTests.Data
|
||||
|
||||
iotest("should find a contained string in any position") {
|
||||
val p1 = StringContains[IO, String](Data.PassingKey, Data.Substring1)
|
||||
val p2 = StringContains[IO, String](Data.PassingKey, Data.Substring2)
|
||||
val p3 = StringContains[IO, String](Data.PassingKey, Data.Substring3)
|
||||
for
|
||||
provider <- StringContainsTests.newProvider(Data.KeyValues)
|
||||
r1 <- p1.eval(provider)
|
||||
r2 <- p2.eval(provider)
|
||||
r3 <- p3.eval(provider)
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
assertEquals(r3, Predicate.Result.matched())
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
val p = StringContains[IO, String](Data.NotExistingKey, "")
|
||||
for
|
||||
provider <- StringContainsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
|
||||
iotest("should match if an empty substring is provided") {
|
||||
val p1 = StringContains[IO, String](Data.EmptyStringKey, "")
|
||||
val p2 = StringContains[IO, String](Data.PassingKey, "")
|
||||
for
|
||||
provider <- StringContainsTests.newProvider(Data.KeyValues)
|
||||
r1 <- p1.eval(provider)
|
||||
r2 <- p2.eval(provider)
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
}
|
||||
|
||||
iotest("should not match if the target value is not contained in the input") {
|
||||
val p = StringContains[IO, String](Data.PassingKey, Substring1.reverse)
|
||||
for
|
||||
provider <- StringContainsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
|
||||
object StringContainsTests:
|
||||
|
||||
object Data:
|
||||
|
||||
val PassingKey: String = Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
||||
val PassingValue: String = "abcdefhij"
|
||||
val Substring1: String = "abc"
|
||||
val Substring2: String = "def"
|
||||
val Substring3: String = "hij"
|
||||
|
||||
val NotExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(6)).gen()
|
||||
val NotExistingValue: String = Gen.string.alphaNumeric(Size.Fixed(4)).gen()
|
||||
|
||||
val EmptyStringKey: String = "empty"
|
||||
|
||||
val KeyValues: Map[String, String] = Map(
|
||||
PassingKey -> PassingValue,
|
||||
EmptyStringKey -> ""
|
||||
)
|
||||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueStringProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapStringProvider(map)
|
||||
|
||||
end StringContainsTests
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.std.MapRef
|
||||
import gs.datagen.v0.Gen
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import support.IOSuite
|
||||
|
||||
class StringEndsWithTests extends IOSuite:
|
||||
|
||||
import StringEndsWithTests.Data
|
||||
|
||||
iotest("should find a string as a prefix of the input") {
|
||||
val p1 = StringEndsWith[IO, String](Data.PassingKey, Data.Substring1)
|
||||
val p2 = StringEndsWith[IO, String](Data.PassingKey, Data.Substring2)
|
||||
val p3 = StringEndsWith[IO, String](Data.PassingKey, Data.Substring3)
|
||||
for
|
||||
provider <- StringEndsWithTests.newProvider(Data.KeyValues)
|
||||
r1 <- p1.eval(provider)
|
||||
r2 <- p2.eval(provider)
|
||||
r3 <- p3.eval(provider)
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
assertEquals(r3, Predicate.Result.matched())
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
val p = StringEndsWith[IO, String](Data.NotExistingKey, "")
|
||||
for
|
||||
provider <- StringEndsWithTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
|
||||
iotest("should match if an empty substring is provided") {
|
||||
val p1 = StringEndsWith[IO, String](Data.EmptyStringKey, "")
|
||||
val p2 = StringEndsWith[IO, String](Data.PassingKey, "")
|
||||
for
|
||||
provider <- StringEndsWithTests.newProvider(Data.KeyValues)
|
||||
r1 <- p1.eval(provider)
|
||||
r2 <- p2.eval(provider)
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
}
|
||||
|
||||
iotest(
|
||||
"should not match if the target value is not the suffix of the input"
|
||||
) {
|
||||
val p1 = StringEndsWith[IO, String](Data.PassingKey, Data.Substring3 + "z")
|
||||
val p2 =
|
||||
StringEndsWith[IO, String](Data.PassingKey, Data.Substring3.reverse)
|
||||
val p3 =
|
||||
StringEndsWith[IO, String](Data.PassingKey, Data.Substring2.reverse)
|
||||
for
|
||||
provider <- StringEndsWithTests.newProvider(Data.KeyValues)
|
||||
r1 <- p1.eval(provider)
|
||||
r2 <- p2.eval(provider)
|
||||
r3 <- p3.eval(provider)
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.missed())
|
||||
assertEquals(r2, Predicate.Result.missed())
|
||||
assertEquals(r3, Predicate.Result.missed())
|
||||
}
|
||||
|
||||
object StringEndsWithTests:
|
||||
|
||||
object Data:
|
||||
|
||||
val PassingKey: String = Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
||||
val PassingValue: String = "abcdefghi"
|
||||
val Substring1: String = "i"
|
||||
val Substring2: String = "hi"
|
||||
val Substring3: String = "abcdefghi"
|
||||
|
||||
val NotExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(6)).gen()
|
||||
|
||||
val EmptyStringKey: String = "empty"
|
||||
|
||||
val KeyValues: Map[String, String] = Map(
|
||||
PassingKey -> PassingValue,
|
||||
EmptyStringKey -> ""
|
||||
)
|
||||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueStringProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapStringProvider(map)
|
||||
|
||||
end StringEndsWithTests
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.std.MapRef
|
||||
import gs.datagen.v0.Gen
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import support.IOSuite
|
||||
|
||||
class StringStartsWithTests extends IOSuite:
|
||||
|
||||
import StringStartsWithTests.Data
|
||||
|
||||
iotest("should find a string as a prefix of the input") {
|
||||
val p1 = StringStartsWith[IO, String](Data.PassingKey, Data.Substring1)
|
||||
val p2 = StringStartsWith[IO, String](Data.PassingKey, Data.Substring2)
|
||||
val p3 = StringStartsWith[IO, String](Data.PassingKey, Data.Substring3)
|
||||
for
|
||||
provider <- StringStartsWithTests.newProvider(Data.KeyValues)
|
||||
r1 <- p1.eval(provider)
|
||||
r2 <- p2.eval(provider)
|
||||
r3 <- p3.eval(provider)
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
assertEquals(r3, Predicate.Result.matched())
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
val p = StringStartsWith[IO, String](Data.NotExistingKey, "")
|
||||
for
|
||||
provider <- StringStartsWithTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
|
||||
iotest("should match if an empty substring is provided") {
|
||||
val p1 = StringStartsWith[IO, String](Data.EmptyStringKey, "")
|
||||
val p2 = StringStartsWith[IO, String](Data.PassingKey, "")
|
||||
for
|
||||
provider <- StringStartsWithTests.newProvider(Data.KeyValues)
|
||||
r1 <- p1.eval(provider)
|
||||
r2 <- p2.eval(provider)
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
}
|
||||
|
||||
iotest(
|
||||
"should not match if the target value is not the prefix of the input"
|
||||
) {
|
||||
val p1 =
|
||||
StringStartsWith[IO, String](Data.PassingKey, Data.Substring3 + "k")
|
||||
val p2 =
|
||||
StringStartsWith[IO, String](Data.PassingKey, Data.Substring3.reverse)
|
||||
val p3 =
|
||||
StringStartsWith[IO, String](Data.PassingKey, Data.Substring2.reverse)
|
||||
for
|
||||
provider <- StringStartsWithTests.newProvider(Data.KeyValues)
|
||||
r1 <- p1.eval(provider)
|
||||
r2 <- p2.eval(provider)
|
||||
r3 <- p3.eval(provider)
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.missed())
|
||||
assertEquals(r2, Predicate.Result.missed())
|
||||
assertEquals(r3, Predicate.Result.missed())
|
||||
}
|
||||
|
||||
object StringStartsWithTests:
|
||||
|
||||
object Data:
|
||||
|
||||
val PassingKey: String = Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
||||
val PassingValue: String = "abcdefghi"
|
||||
val Substring1: String = "a"
|
||||
val Substring2: String = "ab"
|
||||
val Substring3: String = "abcdefghi"
|
||||
|
||||
val NotExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(6)).gen()
|
||||
|
||||
val EmptyStringKey: String = "empty"
|
||||
|
||||
val KeyValues: Map[String, String] = Map(
|
||||
PassingKey -> PassingValue,
|
||||
EmptyStringKey -> ""
|
||||
)
|
||||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueStringProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapStringProvider(map)
|
||||
|
||||
end StringStartsWithTests
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.std.MapRef
|
||||
import gs.datagen.v0.Gen
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import support.IOSuite
|
||||
|
||||
class ValueNotEqualsTests extends IOSuite:
|
||||
|
||||
import ValueNotEqualsTests.Data
|
||||
|
||||
iotest("should NOT find an exact match against some value") {
|
||||
val p =
|
||||
ValueNotEquals[IO, String, String](Data.ExistingKey, Data.ExistingValue)
|
||||
for
|
||||
provider <- ValueNotEqualsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
|
||||
iotest("should match a value if that value is not equal to the target") {
|
||||
val p =
|
||||
ValueNotEquals[IO, String, String](
|
||||
Data.ExistingKey,
|
||||
Data.NotExistingValue
|
||||
)
|
||||
for
|
||||
provider <- ValueNotEqualsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
yield assertEquals(result, Predicate.Result.matched())
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
val p = ValueNotEquals[IO, String, String](Data.NotExistingKey, "")
|
||||
for
|
||||
provider <- ValueNotEqualsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
|
||||
object ValueNotEqualsTests:
|
||||
|
||||
object Data:
|
||||
|
||||
val ExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
||||
val NotExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(6)).gen()
|
||||
val ExistingValue: String = Gen.string.alphaNumeric(Size.Fixed(10)).gen()
|
||||
val NotExistingValue: String = Gen.string.alphaNumeric(Size.Fixed(4)).gen()
|
||||
val KeyValues: Map[String, String] = Map(ExistingKey -> ExistingValue)
|
||||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueStringProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapStringProvider(map)
|
||||
|
||||
end ValueNotEqualsTests
|
||||
80
src/main/scala/gs/predicate/v0/api/And.scala
Normal file
80
src/main/scala/gs/predicate/v0/api/And.scala
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.HCursor
|
||||
import io.circe.Json
|
||||
import io.circe.syntax._
|
||||
|
||||
/** Implements logical AND.
|
||||
*
|
||||
* @param ps
|
||||
* The predicates to evaluate.
|
||||
*/
|
||||
final class And[F[_]: Applicative](
|
||||
val ps: List[Predicate[F]]
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = And.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
ps.map(_.eval()).sequence.map(_.allMatch())
|
||||
|
||||
object And:
|
||||
|
||||
final val PredicateType: String = "and"
|
||||
|
||||
def empty[F[_]: Applicative]: Predicate[F] = False[F]
|
||||
|
||||
def apply[F[_]: Applicative, A](ps: Predicate[F]*): Predicate[F] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new And(list)
|
||||
|
||||
given andEncoder[F[_]](
|
||||
using
|
||||
Encoder[Predicate[F]]
|
||||
): Encoder[And[F]] =
|
||||
Encoder.instance[And[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.predicates, p.ps.asJson)
|
||||
)
|
||||
}
|
||||
|
||||
private def consumeCursor[F[_]: Applicative](
|
||||
cursor: HCursor
|
||||
)(
|
||||
using
|
||||
Decoder[Predicate[F]]
|
||||
): Either[DecodingFailure, And[F]] =
|
||||
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
|
||||
yield new And(ps)
|
||||
|
||||
given andDecoder[F[_]: Applicative](
|
||||
using
|
||||
Decoder[Predicate[F]]
|
||||
): Decoder[And[F]] =
|
||||
Decoder.instance[And[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType => consumeCursor(cursor)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end And
|
||||
50
src/main/scala/gs/predicate/v0/api/False.scala
Normal file
50
src/main/scala/gs/predicate/v0/api/False.scala
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Always returns a miss.
|
||||
*/
|
||||
final class False[F[_]: Applicative] extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = False.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
Applicative[F].pure(Predicate.Result.missed())
|
||||
|
||||
end False
|
||||
|
||||
object False:
|
||||
|
||||
final val PredicateType: String = "false"
|
||||
|
||||
def apply[F[_]: Applicative]: False[F] = new False[F]
|
||||
|
||||
given falseEncoder[F[_]]: Encoder[False[F]] =
|
||||
Encoder.instance[False[F]] { p =>
|
||||
Json.obj((JsonKeys.predicateType, Json.fromString(p.predicateType)))
|
||||
}
|
||||
|
||||
given falseDecoder[F[_]: Applicative]: Decoder[False[F]] =
|
||||
Decoder.instance[False[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType => Right(False[F])
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end False
|
||||
11
src/main/scala/gs/predicate/v0/api/Messages.scala
Normal file
11
src/main/scala/gs/predicate/v0/api/Messages.scala
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
object Messages:
|
||||
|
||||
def invalidPredicateType(
|
||||
candidate: String,
|
||||
expected: String
|
||||
): String =
|
||||
s"Received predicate type '$candidate' but expected '$expected'."
|
||||
|
||||
end Messages
|
||||
72
src/main/scala/gs/predicate/v0/api/Or.scala
Normal file
72
src/main/scala/gs/predicate/v0/api/Or.scala
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
import io.circe.syntax.*
|
||||
|
||||
/** Implements logical OR.
|
||||
*
|
||||
* @param ps
|
||||
* The predicates to evaluate.
|
||||
*/
|
||||
final class Or[F[_]: Applicative](
|
||||
val ps: List[Predicate[F]]
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = Or.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
ps.map(_.eval()).sequence.map(_.anyMatch())
|
||||
|
||||
object Or:
|
||||
|
||||
final val PredicateType: String = "or"
|
||||
|
||||
def empty[F[_]: Applicative]: Predicate[F] = False[F]
|
||||
|
||||
def apply[F[_]: Applicative, A](ps: Predicate[F]*): Predicate[F] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new Or(list)
|
||||
|
||||
given orEncoder[F[_]](
|
||||
using
|
||||
Encoder[Predicate[F]]
|
||||
): Encoder[Or[F]] =
|
||||
Encoder.instance[Or[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.predicates, p.ps.asJson)
|
||||
)
|
||||
}
|
||||
|
||||
given orDecoder[F[_]: Applicative](
|
||||
using
|
||||
Decoder[Predicate[F]]
|
||||
): Decoder[Or[F]] =
|
||||
Decoder.instance[Or[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
|
||||
yield new Or(ps)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end Or
|
||||
|
|
@ -1,59 +1,34 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/** A _Predicate_ is some function that accepts any input and emits some
|
||||
* [[Predicate.Result]] (whether the predicate matched).
|
||||
*
|
||||
* Predicates evaluate input to see if the predicate matches that input.
|
||||
* Predicates evaluate input extracted from context to see if the predicate
|
||||
* matches that input.
|
||||
*/
|
||||
trait Predicate[F[_], -A]:
|
||||
trait Predicate[F[_]]:
|
||||
/** @return
|
||||
* The unique identifier of this Predicate.
|
||||
* The serializable predicate type.
|
||||
*/
|
||||
def id: UUID
|
||||
def predicateType: String
|
||||
|
||||
/** Evaluate this predicate against the given input.
|
||||
*
|
||||
* @param input
|
||||
* The input to evaluate this predicate against.
|
||||
* @return
|
||||
* Some [[Predicate.Result]] that describes whether the input matched the
|
||||
* predicate.
|
||||
*/
|
||||
def eval(input: A): F[Predicate.Result]
|
||||
def eval(): F[Predicate.Result]
|
||||
|
||||
/** Predicate equality is based on the _unique identifier_. Two predicates
|
||||
* with the same ID are considered equal.
|
||||
*
|
||||
* @param that
|
||||
* The other object.
|
||||
* @return
|
||||
* True if the other object is a predicate with the same ID, false
|
||||
* otherwise.
|
||||
*/
|
||||
override def equals(that: Any): Boolean =
|
||||
that match
|
||||
case p: Predicate[?, ?] => p.id == id
|
||||
case _ => false
|
||||
|
||||
/** @return
|
||||
* The hash code of the unique identifier.
|
||||
*/
|
||||
override def hashCode(): Int = id.hashCode()
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def toString(): String = s"predicate:${id.withoutDashes()}"
|
||||
end Predicate
|
||||
|
||||
object Predicate:
|
||||
|
||||
given CanEqual[Predicate[?, ?], Predicate[?, ?]] = CanEqual.derived
|
||||
def alwaysTrue[F[_]: Applicative]: Predicate[F] = True[F]
|
||||
|
||||
def alwaysTrue[F[_]: Applicative]: Predicate[F, Any] = True[F]
|
||||
|
||||
def alwaysFalse[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
||||
def alwaysFalse[F[_]: Applicative]: Predicate[F] = False[F]
|
||||
|
||||
/** The result of evaluating a [[Predicate]] is a Boolean value where:
|
||||
*
|
||||
|
|
@ -132,3 +107,5 @@ object Predicate:
|
|||
def isMiss: Boolean = !result
|
||||
|
||||
end Result
|
||||
|
||||
end Predicate
|
||||
50
src/main/scala/gs/predicate/v0/api/True.scala
Normal file
50
src/main/scala/gs/predicate/v0/api/True.scala
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Always returns a match.
|
||||
*/
|
||||
final class True[F[_]: Applicative] extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = True.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
Applicative[F].pure(Predicate.Result.matched())
|
||||
|
||||
end True
|
||||
|
||||
object True:
|
||||
|
||||
final val PredicateType: String = "true"
|
||||
|
||||
def apply[F[_]: Applicative]: True[F] = new True[F]
|
||||
|
||||
given trueEncoder[F[_]]: Encoder[True[F]] =
|
||||
Encoder.instance[True[F]] { p =>
|
||||
Json.obj((JsonKeys.predicateType, Json.fromString(p.predicateType)))
|
||||
}
|
||||
|
||||
given trueDecoder[F[_]: Applicative]: Decoder[True[F]] =
|
||||
Decoder.instance[True[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType => Right(True[F])
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end True
|
||||
63
src/main/scala/gs/predicate/v0/json/JsonExists.scala
Normal file
63
src/main/scala/gs/predicate/v0/json/JsonExists.scala
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package gs.predicate.v0.json
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Messages
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
||||
* given key.
|
||||
*
|
||||
* @param key
|
||||
* The name of the JSON value that should exist.
|
||||
*/
|
||||
final class JsonExists[F[_]: Functor: JsonProvider](
|
||||
val key: String
|
||||
) extends JsonPredicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
final override val predicateType: String = JsonExists.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
getJson(key).map(_.isDefined).map(Predicate.Result.apply)
|
||||
|
||||
object JsonExists:
|
||||
|
||||
final val PredicateType: String = "json-exists"
|
||||
|
||||
def apply[F[_]: Functor: JsonProvider](key: String): JsonExists[F] =
|
||||
new JsonExists[F](key)
|
||||
|
||||
given jsonExistsEncoder[F[_]]: Encoder[JsonExists[F]] =
|
||||
Encoder.instance[JsonExists[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.key, Json.fromString(p.key))
|
||||
)
|
||||
}
|
||||
|
||||
given jsonExistsDecoder[F[_]: Functor: JsonProvider]: Decoder[JsonExists[F]] =
|
||||
Decoder.instance[JsonExists[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for key <- cursor.downField(JsonKeys.key).as[String]
|
||||
yield new JsonExists(key)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end JsonExists
|
||||
7
src/main/scala/gs/predicate/v0/json/JsonPredicate.scala
Normal file
7
src/main/scala/gs/predicate/v0/json/JsonPredicate.scala
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package gs.predicate.v0.json
|
||||
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import io.circe.Json
|
||||
|
||||
abstract class JsonPredicate[F[_]: JsonProvider] extends Predicate[F]:
|
||||
def getJson(key: String): F[Option[Json]] = JsonProvider[F].get(key)
|
||||
37
src/main/scala/gs/predicate/v0/json/JsonProvider.scala
Normal file
37
src/main/scala/gs/predicate/v0/json/JsonProvider.scala
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package gs.predicate.v0.json
|
||||
|
||||
import cats.Applicative
|
||||
import io.circe.Json
|
||||
|
||||
/** Interface for anything that can fetch values for stored keys.
|
||||
*/
|
||||
trait JsonProvider[F[_]]:
|
||||
/** Get the JSON value associated with some key.
|
||||
*
|
||||
* @param key
|
||||
* The key to fetch.
|
||||
* @return
|
||||
* The value stored for the key, or `None` if no such value exists.
|
||||
*/
|
||||
def get(key: String): F[Option[Json]]
|
||||
|
||||
object JsonProvider:
|
||||
|
||||
def apply[F[_]](
|
||||
using
|
||||
jp: JsonProvider[F]
|
||||
): JsonProvider[F] = jp
|
||||
|
||||
/** @return
|
||||
* New instance of the no-op [[JsonProvider]] implementation.
|
||||
*/
|
||||
def noop[F[_]: Applicative]: JsonProvider[F] = new Noop[F]
|
||||
|
||||
/** No-op implementation that never contains data.
|
||||
*/
|
||||
final class Noop[F[_]: Applicative] extends JsonProvider[F]:
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def get(key: String): F[Option[Json]] = Applicative[F].pure(None)
|
||||
|
||||
end JsonProvider
|
||||
49
src/main/scala/gs/predicate/v0/json/JsonQueryEquals.scala
Normal file
49
src/main/scala/gs/predicate/v0/json/JsonQueryEquals.scala
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package gs.predicate.v0.json
|
||||
|
||||
import cats.Applicative
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.json.query.JsonQuery
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
||||
* given key, and that blob contains the given query, and the result of the
|
||||
* query matches the given value.
|
||||
*
|
||||
* @param key
|
||||
* The name of the JSON value that must satisfy the given query.
|
||||
* @param query
|
||||
* The [[JsonQuery]] that must be satisfied.
|
||||
* @param value
|
||||
* The JSON value that must match the query.
|
||||
*/
|
||||
final class JsonQueryEquals[F[_]: Applicative: JsonProvider](
|
||||
val key: String,
|
||||
val query: JsonQuery,
|
||||
val value: Json
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
final override val predicateType: String = JsonQueryEquals.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
JsonProvider[F].get(key).map {
|
||||
case None => Predicate.Result.missed()
|
||||
case Some(json) => Predicate.Result(query.eval(json, _.equals(value)))
|
||||
}
|
||||
|
||||
object JsonQueryEquals:
|
||||
|
||||
final val PredicateType: String = "json-query-equals"
|
||||
|
||||
def apply[F[_]: Applicative: JsonProvider](
|
||||
key: String,
|
||||
query: JsonQuery,
|
||||
value: Json
|
||||
): JsonQueryEquals[F] =
|
||||
new JsonQueryEquals[F](key, query, value)
|
||||
|
||||
end JsonQueryEquals
|
||||
41
src/main/scala/gs/predicate/v0/json/JsonQueryExists.scala
Normal file
41
src/main/scala/gs/predicate/v0/json/JsonQueryExists.scala
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package gs.predicate.v0.json
|
||||
|
||||
import cats.Applicative
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.json.query.JsonQuery
|
||||
|
||||
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
||||
* given key, and that blob contains the given query.
|
||||
*
|
||||
* @param key
|
||||
* The name of the JSON value that must satisfy the given query.
|
||||
*/
|
||||
final class JsonQueryExists[F[_]: Applicative: JsonProvider](
|
||||
val key: String,
|
||||
val query: JsonQuery
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
final override val predicateType: String = JsonQueryExists.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
JsonProvider[F].get(key).map {
|
||||
case None => Predicate.Result.missed()
|
||||
case Some(json) => Predicate.Result(query.eval(json, _ => true))
|
||||
}
|
||||
|
||||
object JsonQueryExists:
|
||||
|
||||
final val PredicateType: String = "json-exists"
|
||||
|
||||
def apply[F[_]: Applicative: JsonProvider](
|
||||
key: String,
|
||||
query: JsonQuery
|
||||
): JsonQueryExists[F] =
|
||||
new JsonQueryExists[F](key, query)
|
||||
|
||||
end JsonQueryExists
|
||||
65
src/main/scala/gs/predicate/v0/kv/KeyExists.scala
Normal file
65
src/main/scala/gs/predicate/v0/kv/KeyExists.scala
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Messages
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
*/
|
||||
final class KeyExists[F[_]: Functor: KeyValueProvider](
|
||||
val key: String
|
||||
) extends KeyValuePredicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = KeyExists.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
KeyValueProvider[F].exists(key).map(Predicate.Result.apply)
|
||||
|
||||
object KeyExists:
|
||||
|
||||
final val PredicateType: String = "kv-key-exists"
|
||||
|
||||
def apply[F[_]: Functor: KeyValueProvider](key: String): KeyExists[F] =
|
||||
new KeyExists[F](key)
|
||||
|
||||
given keyExistsEncoder[F[_]]: Encoder[KeyExists[F]] =
|
||||
Encoder.instance[KeyExists[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.key, Json.fromString(p.key))
|
||||
)
|
||||
}
|
||||
|
||||
given keyExistsDecoder[F[_]: Functor: KeyValueProvider]
|
||||
: Decoder[KeyExists[F]] =
|
||||
Decoder.instance[KeyExists[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for key <- cursor.downField(JsonKeys.key).as[String]
|
||||
yield new KeyExists(key)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end KeyExists
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import gs.predicate.v0.api.Predicate
|
||||
|
||||
abstract class KeyValuePredicate[F[_]: KeyValueProvider] extends Predicate[F]:
|
||||
def keyExists(key: String): F[Boolean] = KeyValueProvider[F].exists(key)
|
||||
|
||||
def getValue(key: String): F[Option[String]] = KeyValueProvider[F].get(key)
|
||||
|
|
@ -4,7 +4,7 @@ import cats.Applicative
|
|||
|
||||
/** Interface for anything that can fetch values for stored keys.
|
||||
*/
|
||||
trait KeyValueProvider[F[_], -K, V]:
|
||||
trait KeyValueProvider[F[_]]:
|
||||
/** Determine if some key exists.
|
||||
*
|
||||
* @param key
|
||||
|
|
@ -12,7 +12,7 @@ trait KeyValueProvider[F[_], -K, V]:
|
|||
* @return
|
||||
* True if the key exists, false otherwise.
|
||||
*/
|
||||
def exists(key: K): F[Boolean]
|
||||
def exists(key: String): F[Boolean]
|
||||
|
||||
/** Get the value associated with some key.
|
||||
*
|
||||
|
|
@ -21,24 +21,29 @@ trait KeyValueProvider[F[_], -K, V]:
|
|||
* @return
|
||||
* The value stored for the key, or `None` if no such value exists.
|
||||
*/
|
||||
def get(key: K): F[Option[V]]
|
||||
def get(key: String): F[Option[String]]
|
||||
|
||||
object KeyValueProvider:
|
||||
|
||||
def apply[F[_]](
|
||||
using
|
||||
kvp: KeyValueProvider[F]
|
||||
): KeyValueProvider[F] = kvp
|
||||
|
||||
/** @return
|
||||
* New instance of the no-op [[KeyValueProvider]] implementation.
|
||||
*/
|
||||
def noop[F[_]: Applicative]: KeyValueProvider[F, Any, Any] = new Noop[F]
|
||||
def noop[F[_]: Applicative]: KeyValueProvider[F] = new Noop[F]
|
||||
|
||||
/** No-op implementation that never contains data.
|
||||
*/
|
||||
final class Noop[F[_]: Applicative] extends KeyValueProvider[F, Any, Any]:
|
||||
final class Noop[F[_]: Applicative] extends KeyValueProvider[F]:
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def exists(key: Any): F[Boolean] = Applicative[F].pure(false)
|
||||
override def exists(key: String): F[Boolean] = Applicative[F].pure(false)
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def get(key: Any): F[Option[Any]] = Applicative[F].pure(None)
|
||||
override def get(key: String): F[Option[String]] = Applicative[F].pure(None)
|
||||
|
||||
end KeyValueProvider
|
||||
|
|
@ -9,9 +9,9 @@ import cats.syntax.all.*
|
|||
* @param map
|
||||
* The underlying map.
|
||||
*/
|
||||
final class MemoryMapStringProvider[F[_]: Sync](
|
||||
final class MemoryMapKeyValueProvider[F[_]: Sync](
|
||||
private val map: MapRef[F, String, Option[String]]
|
||||
) extends KeyValueStringProvider[F]:
|
||||
) extends KeyValueProvider[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
76
src/main/scala/gs/predicate/v0/kv/ValueContains.scala
Normal file
76
src/main/scala/gs/predicate/v0/kv/ValueContains.scala
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Messages
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key,
|
||||
* and the string value associated with that key contains some other string.
|
||||
*
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param containedValue
|
||||
* The substring that must be contained in the value.
|
||||
*/
|
||||
final class ValueContains[F[_]: Functor: KeyValueProvider](
|
||||
val key: String,
|
||||
val containedValue: String
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = ValueContains.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
KeyValueProvider[F].get(key).map {
|
||||
case Some(value) => Predicate.Result(value.contains(containedValue))
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object ValueContains:
|
||||
|
||||
final val PredicateType: String = "kv-string-contains"
|
||||
|
||||
def apply[F[_]: Functor: KeyValueProvider](
|
||||
key: String,
|
||||
containedValue: String
|
||||
): ValueContains[F] =
|
||||
new ValueContains[F](key, containedValue)
|
||||
|
||||
given valueContainsEncoder[F[_]]: Encoder[ValueContains[F]] =
|
||||
Encoder.instance[ValueContains[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.key, Json.fromString(p.key)),
|
||||
(JsonKeys.value, Json.fromString(p.containedValue))
|
||||
)
|
||||
}
|
||||
|
||||
given valueContainsDecoder[F[_]: Functor: KeyValueProvider]
|
||||
: Decoder[ValueContains[F]] =
|
||||
Decoder.instance[ValueContains[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for
|
||||
key <- cursor.downField(JsonKeys.key).as[String]
|
||||
value <- cursor.downField(JsonKeys.value).as[String]
|
||||
yield new ValueContains(key, value)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end ValueContains
|
||||
79
src/main/scala/gs/predicate/v0/kv/ValueEndsWith.scala
Normal file
79
src/main/scala/gs/predicate/v0/kv/ValueEndsWith.scala
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Messages
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if some (string-valued) [[KeyValueProvider]] contains
|
||||
* the given key, and the string value associated with that key ends with some
|
||||
* other string.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param suffix
|
||||
* The substring that must be the suffix of the value.
|
||||
*/
|
||||
final class ValueEndsWith[F[_]: Functor: KeyValueProvider](
|
||||
val key: String,
|
||||
val suffix: String
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = ValueEndsWith.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
KeyValueProvider[F].get(key).map {
|
||||
case Some(value) => Predicate.Result(value.endsWith(suffix))
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object ValueEndsWith:
|
||||
|
||||
final val PredicateType: String = "kv-string-ends-with"
|
||||
|
||||
def apply[F[_]: Functor: KeyValueProvider](
|
||||
key: String,
|
||||
suffix: String
|
||||
): ValueEndsWith[F] =
|
||||
new ValueEndsWith[F](key, suffix)
|
||||
|
||||
given valueEndsWithEncoder[F[_]]: Encoder[ValueEndsWith[F]] =
|
||||
Encoder.instance[ValueEndsWith[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.key, Json.fromString(p.key)),
|
||||
(JsonKeys.value, Json.fromString(p.suffix))
|
||||
)
|
||||
}
|
||||
|
||||
given valueEndsWithDecoder[F[_]: Functor: KeyValueProvider]
|
||||
: Decoder[ValueEndsWith[F]] =
|
||||
Decoder.instance[ValueEndsWith[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for
|
||||
key <- cursor.downField(JsonKeys.key).as[String]
|
||||
value <- cursor.downField(JsonKeys.value).as[String]
|
||||
yield new ValueEndsWith(key, value)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end ValueEndsWith
|
||||
76
src/main/scala/gs/predicate/v0/kv/ValueEquals.scala
Normal file
76
src/main/scala/gs/predicate/v0/kv/ValueEquals.scala
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Messages
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key,
|
||||
* and the value associated with that key equals the given value.
|
||||
*
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param value
|
||||
* The value that must be associated with the key.
|
||||
*/
|
||||
final class ValueEquals[F[_]: Functor: KeyValueProvider](
|
||||
val key: String,
|
||||
val value: String
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = ValueEquals.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
KeyValueProvider[F].get(key).map {
|
||||
case Some(value) => Predicate.Result(this.value == value)
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object ValueEquals:
|
||||
|
||||
final val PredicateType: String = "kv-value-equals"
|
||||
|
||||
def apply[F[_]: Functor: KeyValueProvider](
|
||||
key: String,
|
||||
value: String
|
||||
): ValueEquals[F] =
|
||||
new ValueEquals[F](key, value)
|
||||
|
||||
given valueEqualsEncoder[F[_]]: Encoder[ValueEquals[F]] =
|
||||
Encoder.instance[ValueEquals[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.key, Json.fromString(p.key)),
|
||||
(JsonKeys.value, Json.fromString(p.value))
|
||||
)
|
||||
}
|
||||
|
||||
given valueEqualsDecoder[F[_]: Functor: KeyValueProvider]
|
||||
: Decoder[ValueEquals[F]] =
|
||||
Decoder.instance[ValueEquals[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for
|
||||
key <- cursor.downField(JsonKeys.key).as[String]
|
||||
value <- cursor.downField(JsonKeys.value).as[String]
|
||||
yield new ValueEquals(key, value)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end ValueEquals
|
||||
76
src/main/scala/gs/predicate/v0/kv/ValueIn.scala
Normal file
76
src/main/scala/gs/predicate/v0/kv/ValueIn.scala
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Messages
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key,
|
||||
* and the value associated with that key is contained in the set of given
|
||||
* values.
|
||||
*
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param values
|
||||
* The list of values, such that one value should match the input.
|
||||
*/
|
||||
final class ValueIn[F[_]: Functor: KeyValueProvider](
|
||||
val key: String,
|
||||
val values: Set[String]
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = ValueIn.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
KeyValueProvider[F].get(key).map {
|
||||
case Some(value) => Predicate.Result(values.contains(value))
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object ValueIn:
|
||||
|
||||
final val PredicateType: String = "kv-value-in"
|
||||
|
||||
def apply[F[_]: Functor: KeyValueProvider](
|
||||
key: String,
|
||||
values: Set[String]
|
||||
): ValueIn[F] =
|
||||
new ValueIn[F](key, values)
|
||||
|
||||
given valueInEncoder[F[_]]: Encoder[ValueIn[F]] =
|
||||
Encoder.instance[ValueIn[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.key, Json.fromString(p.key)),
|
||||
(JsonKeys.values, Json.fromValues(p.values.map(Json.fromString)))
|
||||
)
|
||||
}
|
||||
|
||||
given valueInDecoder[F[_]: Functor: KeyValueProvider]: Decoder[ValueIn[F]] =
|
||||
Decoder.instance[ValueIn[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for
|
||||
key <- cursor.downField(JsonKeys.key).as[String]
|
||||
values <- cursor.downField(JsonKeys.value).as[Set[String]]
|
||||
yield new ValueIn(key, values)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end ValueIn
|
||||
78
src/main/scala/gs/predicate/v0/kv/ValueNotEquals.scala
Normal file
78
src/main/scala/gs/predicate/v0/kv/ValueNotEquals.scala
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Messages
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key,
|
||||
* and the value associated with that key is not equal to the given value.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier of this [[Predicate]].
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param value
|
||||
* The value that must not be associated with the key.
|
||||
*/
|
||||
final class ValueNotEquals[F[_]: Functor: KeyValueProvider](
|
||||
val key: String,
|
||||
val value: String
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = ValueNotEquals.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
KeyValueProvider[F].get(key).map {
|
||||
case Some(v) => Predicate.Result(v != value)
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object ValueNotEquals:
|
||||
|
||||
final val PredicateType: String = "kv-value-not-equals"
|
||||
|
||||
def apply[F[_]: Functor: KeyValueProvider](
|
||||
key: String,
|
||||
value: String
|
||||
): ValueNotEquals[F] =
|
||||
new ValueNotEquals[F](key, value)
|
||||
|
||||
given valueNotEqualsEncoder[F[_]]: Encoder[ValueNotEquals[F]] =
|
||||
Encoder.instance[ValueNotEquals[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.key, Json.fromString(p.key)),
|
||||
(JsonKeys.value, Json.fromString(p.value))
|
||||
)
|
||||
}
|
||||
|
||||
given valueNotEqualsDecoder[F[_]: Functor: KeyValueProvider]
|
||||
: Decoder[ValueNotEquals[F]] =
|
||||
Decoder.instance[ValueNotEquals[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for
|
||||
key <- cursor.downField(JsonKeys.key).as[String]
|
||||
value <- cursor.downField(JsonKeys.value).as[String]
|
||||
yield new ValueNotEquals(key, value)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end ValueNotEquals
|
||||
76
src/main/scala/gs/predicate/v0/kv/ValueStartsWith.scala
Normal file
76
src/main/scala/gs/predicate/v0/kv/ValueStartsWith.scala
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Messages
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.serde.json.JsonKeys
|
||||
import io.circe.Decoder
|
||||
import io.circe.DecodingFailure
|
||||
import io.circe.Encoder
|
||||
import io.circe.Json
|
||||
|
||||
/** Predicate that matches if some [[KeyValueProvider]] contains the given key,
|
||||
* and the string value associated with that key starts with some other string.
|
||||
*
|
||||
* @param key
|
||||
* The key that should exist.
|
||||
* @param prefix
|
||||
* The substring that must be the prefix of the value.
|
||||
*/
|
||||
final class ValueStartsWith[F[_]: Functor: KeyValueProvider](
|
||||
val key: String,
|
||||
val prefix: String
|
||||
) extends Predicate[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def predicateType: String = ValueStartsWith.PredicateType
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(): F[Predicate.Result] =
|
||||
KeyValueProvider[F].get(key).map {
|
||||
case Some(value) => Predicate.Result(value.startsWith(prefix))
|
||||
case _ => Predicate.Result.missed()
|
||||
}
|
||||
|
||||
object ValueStartsWith:
|
||||
|
||||
final val PredicateType: String = "kv-string-starts-with"
|
||||
|
||||
def apply[F[_]: Functor: KeyValueProvider](
|
||||
key: String,
|
||||
prefix: String
|
||||
): ValueStartsWith[F] =
|
||||
new ValueStartsWith[F](key, prefix)
|
||||
|
||||
given valueStartsWithEncoder[F[_]]: Encoder[ValueStartsWith[F]] =
|
||||
Encoder.instance[ValueStartsWith[F]] { p =>
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||
(JsonKeys.key, Json.fromString(p.key)),
|
||||
(JsonKeys.value, Json.fromString(p.prefix))
|
||||
)
|
||||
}
|
||||
|
||||
given valueStartsWithDecoder[F[_]: Functor: KeyValueProvider]
|
||||
: Decoder[ValueStartsWith[F]] =
|
||||
Decoder.instance[ValueStartsWith[F]] { cursor =>
|
||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||
case PredicateType =>
|
||||
for
|
||||
key <- cursor.downField(JsonKeys.key).as[String]
|
||||
value <- cursor.downField(JsonKeys.value).as[String]
|
||||
yield new ValueStartsWith(key, value)
|
||||
case candidate =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
Messages.invalidPredicateType(candidate, PredicateType),
|
||||
Nil
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
end ValueStartsWith
|
||||
27
src/main/scala/gs/predicate/v0/serde/json/JsonKeys.scala
Normal file
27
src/main/scala/gs/predicate/v0/serde/json/JsonKeys.scala
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package gs.predicate.v0.serde.json
|
||||
|
||||
/** Standard keys for JSON serialization and deserialization.
|
||||
*/
|
||||
object JsonKeys:
|
||||
|
||||
/** Designates the type of predicate.
|
||||
*/
|
||||
val predicateType: String = "predicateType"
|
||||
|
||||
/** Used to collect contained predicates for composites such as AND and OR.
|
||||
*/
|
||||
val predicates: String = "predicates"
|
||||
|
||||
/** Used to represent some "key" (e.g. a key/value or JSON key).
|
||||
*/
|
||||
val key: String = "key"
|
||||
|
||||
/** Used to represent any value.
|
||||
*/
|
||||
val value: String = "value"
|
||||
|
||||
/** Used to represent any collection of values.
|
||||
*/
|
||||
val values: String = "values"
|
||||
|
||||
end JsonKeys
|
||||
127
src/main/scala/gs/predicate/v0/serde/json/codecs.scala
Normal file
127
src/main/scala/gs/predicate/v0/serde/json/codecs.scala
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package gs.predicate.v0.serde.json
|
||||
|
||||
import cats.Applicative
|
||||
import gs.predicate.v0.api.And
|
||||
import gs.predicate.v0.api.False
|
||||
import gs.predicate.v0.api.Or
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.api.True
|
||||
import gs.predicate.v0.kv.KeyExists
|
||||
import gs.predicate.v0.kv.KeyValueProvider
|
||||
import gs.predicate.v0.kv.ValueContains
|
||||
import gs.predicate.v0.kv.ValueEndsWith
|
||||
import gs.predicate.v0.kv.ValueEquals
|
||||
import gs.predicate.v0.kv.ValueStartsWith
|
||||
import io.circe.*
|
||||
import io.circe.syntax.*
|
||||
|
||||
/** Given some [[And]] predicate, encode it as a JSON blob.
|
||||
*
|
||||
* @param and
|
||||
* The [[And]] predicate.
|
||||
* @return
|
||||
* The JSON blob representing the [[And]].
|
||||
*/
|
||||
def encodeAnd[F[_]](and: And[F]): Json =
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(And.PredicateType)),
|
||||
(JsonKeys.predicates, and.ps.asJson)
|
||||
)
|
||||
|
||||
/** Given some [[Or]] predicate, encode it as a JSON blob.
|
||||
*
|
||||
* @param and
|
||||
* The [[Or]] predicate.
|
||||
* @return
|
||||
* The JSON blob representing the [[Or]].
|
||||
*/
|
||||
def encodeOr[F[_]](and: Or[F]): Json =
|
||||
Json.obj(
|
||||
(JsonKeys.predicateType, Json.fromString(Or.PredicateType)),
|
||||
(JsonKeys.predicates, and.ps.asJson)
|
||||
)
|
||||
|
||||
/** @return
|
||||
* Generic encoder for any [[Predicate]].
|
||||
*/
|
||||
given predicateEncoder[F[_]]: Encoder[Predicate[F]] =
|
||||
Encoder.instance {
|
||||
case p: True[F] => Encoder[True[F]].apply(p)
|
||||
case p: False[F] => Encoder[False[F]].apply(p)
|
||||
case p: And[F] => encodeAnd[F](p)
|
||||
case p: Or[F] => encodeOr[F](p)
|
||||
case p: KeyExists[F] => Encoder[KeyExists[F]].apply(p)
|
||||
case p: ValueEquals[F] => Encoder[ValueEquals[F]].apply(p)
|
||||
case p: ValueContains[F] => Encoder[ValueContains[F]].apply(p)
|
||||
case p: ValueStartsWith[F] => Encoder[ValueStartsWith[F]].apply(p)
|
||||
case p: ValueEndsWith[F] => Encoder[ValueEndsWith[F]].apply(p)
|
||||
case p =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Unsupported predicate type: ${p.predicateType}"
|
||||
)
|
||||
}
|
||||
|
||||
/** Given some JSON cursor, decode a logical [[And]] predicate.
|
||||
*
|
||||
* TODO: Recursive depth limitations.
|
||||
*
|
||||
* @param cursor
|
||||
* The cursor that points to some JSON value.
|
||||
* @return
|
||||
* The [[And]] predicate or a decoding failure.
|
||||
*/
|
||||
def decodeAnd[F[_]: Applicative: KeyValueProvider](cursor: HCursor)
|
||||
: Either[DecodingFailure, And[F]] =
|
||||
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
|
||||
yield new And(ps)
|
||||
|
||||
/** Given some JSON cursor, decode a logical [[Or]] predicate.
|
||||
*
|
||||
* TODO: Recursive depth limitations.
|
||||
*
|
||||
* @param cursor
|
||||
* The cursor that points to some JSON value.
|
||||
* @return
|
||||
* The [[Or]] predicate or a decoding failure.
|
||||
*/
|
||||
def decodeOr[F[_]: Applicative: KeyValueProvider](cursor: HCursor)
|
||||
: Either[DecodingFailure, Or[F]] =
|
||||
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
|
||||
yield new Or(ps)
|
||||
|
||||
/** Given some JSON cursor, decode any known [[Predicate]].
|
||||
*
|
||||
* @param cursor
|
||||
* The cursor that points to some JSON value.
|
||||
* @return
|
||||
* The decoded [[Predicate]] or some decoding failure.
|
||||
*/
|
||||
def decodePredicate[F[_]: Applicative: KeyValueProvider](cursor: HCursor)
|
||||
: Either[DecodingFailure, Predicate[F]] =
|
||||
for
|
||||
predicateType <- cursor.downField(JsonKeys.predicateType).as[String]
|
||||
predicate <- predicateType match
|
||||
case True.PredicateType => Right(True[F])
|
||||
case False.PredicateType => Right(False[F])
|
||||
case And.PredicateType => decodeAnd[F](cursor)
|
||||
case Or.PredicateType => decodeOr[F](cursor)
|
||||
case KeyExists.PredicateType => Decoder[KeyExists[F]].apply(cursor)
|
||||
case ValueEquals.PredicateType => Decoder[ValueEquals[F]].apply(cursor)
|
||||
case ValueContains.PredicateType =>
|
||||
Decoder[ValueContains[F]].apply(cursor)
|
||||
case ValueStartsWith.PredicateType =>
|
||||
Decoder[ValueStartsWith[F]].apply(cursor)
|
||||
case ValueEndsWith.PredicateType =>
|
||||
Decoder[ValueEndsWith[F]].apply(cursor)
|
||||
case predicateType =>
|
||||
Left(
|
||||
DecodingFailure(s"Unrecognized predicate type: '$predicateType'", Nil)
|
||||
)
|
||||
yield predicate
|
||||
|
||||
/** @return
|
||||
* Generic decoder for any [[Predicate]].
|
||||
*/
|
||||
given predicateDecoder[F[_]: Applicative: KeyValueProvider]
|
||||
: Decoder[Predicate[F]] =
|
||||
Decoder.instance[Predicate[F]](cursor => decodePredicate[F](cursor))
|
||||
|
|
@ -1,32 +1,29 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.effect.IO
|
||||
import gs.uuid.v0.UUID
|
||||
import support.IOSuite
|
||||
|
||||
class AndTests extends IOSuite:
|
||||
|
||||
iotest("should return true if all are true") {
|
||||
val id = UUID.v7()
|
||||
val and = And(True[IO], True[IO], True[IO])
|
||||
val and2 = And(id, True[IO], True[IO], True[IO])
|
||||
val and2 = And(True[IO], True[IO], True[IO])
|
||||
|
||||
for
|
||||
result <- and.eval(())
|
||||
result2 <- and2.eval(())
|
||||
result <- and.eval()
|
||||
result2 <- and2.eval()
|
||||
yield
|
||||
assertEquals(result.unwrap(), true)
|
||||
assertEquals(result2.unwrap(), true)
|
||||
}
|
||||
|
||||
iotest("should return false if any are false") {
|
||||
val id = UUID.v7()
|
||||
val and = And(True[IO], False[IO], True[IO])
|
||||
val and2 = And(id, True[IO], False[IO], True[IO])
|
||||
val and2 = And(True[IO], False[IO], True[IO])
|
||||
|
||||
for
|
||||
result <- and.eval(())
|
||||
result2 <- and2.eval(())
|
||||
result <- and.eval()
|
||||
result2 <- and2.eval()
|
||||
yield
|
||||
assertEquals(result.unwrap(), false)
|
||||
assertEquals(result2.unwrap(), false)
|
||||
|
|
@ -35,12 +32,12 @@ class AndTests extends IOSuite:
|
|||
iotest("should return false for an empty list") {
|
||||
val and = And.empty[IO]
|
||||
val and2 = And[IO, Any]()
|
||||
val and3 = And[IO, Any](UUID.v7())
|
||||
val and3 = And[IO, Any]()
|
||||
|
||||
for
|
||||
result <- and.eval(())
|
||||
result2 <- and2.eval(())
|
||||
result3 <- and3.eval(())
|
||||
result <- and.eval()
|
||||
result2 <- and2.eval()
|
||||
result3 <- and3.eval()
|
||||
yield
|
||||
assertEquals(result.unwrap(), false)
|
||||
assertEquals(result2.unwrap(), false)
|
||||
|
|
@ -50,11 +47,11 @@ class AndTests extends IOSuite:
|
|||
iotest("should return the underlying predicate for a singular entry") {
|
||||
val p = True[IO]
|
||||
val and = And(p)
|
||||
val and2 = And(UUID.v7(), p)
|
||||
val and2 = And(p)
|
||||
|
||||
for
|
||||
result <- and.eval(())
|
||||
result2 <- and2.eval(())
|
||||
result <- and.eval()
|
||||
result2 <- and2.eval()
|
||||
yield
|
||||
assertEquals(result.unwrap(), true)
|
||||
assertEquals(result2.unwrap(), true)
|
||||
13
src/test/scala/gs/predicate/v0/api/FalseTests.scala
Normal file
13
src/test/scala/gs/predicate/v0/api/FalseTests.scala
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.effect.IO
|
||||
import support.IOSuite
|
||||
|
||||
class FalseTests extends IOSuite:
|
||||
|
||||
iotest("should return false") {
|
||||
val predicate = False[IO]
|
||||
|
||||
for result <- predicate.eval()
|
||||
yield assertEquals(result.unwrap(), false)
|
||||
}
|
||||
|
|
@ -1,32 +1,29 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.effect.IO
|
||||
import gs.uuid.v0.UUID
|
||||
import support.IOSuite
|
||||
|
||||
class OrTests extends IOSuite:
|
||||
|
||||
iotest("should return true if any are true") {
|
||||
val id = UUID.v7()
|
||||
val or = Or(False[IO], True[IO], False[IO])
|
||||
val or2 = Or(id, False[IO], True[IO], False[IO])
|
||||
val or2 = Or(False[IO], True[IO], False[IO])
|
||||
|
||||
for
|
||||
result <- or.eval(())
|
||||
result2 <- or2.eval(())
|
||||
result <- or.eval()
|
||||
result2 <- or2.eval()
|
||||
yield
|
||||
assertEquals(result.unwrap(), true)
|
||||
assertEquals(result2.unwrap(), true)
|
||||
}
|
||||
|
||||
iotest("should return false if all are false") {
|
||||
val id = UUID.v7()
|
||||
val or = Or(False[IO], False[IO], False[IO])
|
||||
val or2 = Or(id, False[IO], False[IO], False[IO])
|
||||
val or2 = Or(False[IO], False[IO], False[IO])
|
||||
|
||||
for
|
||||
result <- or.eval(())
|
||||
result2 <- or2.eval(())
|
||||
result <- or.eval()
|
||||
result2 <- or2.eval()
|
||||
yield
|
||||
assertEquals(result.unwrap(), false)
|
||||
assertEquals(result2.unwrap(), false)
|
||||
|
|
@ -35,12 +32,12 @@ class OrTests extends IOSuite:
|
|||
iotest("should return false for an empty list") {
|
||||
val or = Or.empty[IO]
|
||||
val or2 = Or[IO, Any]()
|
||||
val or3 = Or[IO, Any](UUID.v7())
|
||||
val or3 = Or[IO, Any]()
|
||||
|
||||
for
|
||||
result <- or.eval(())
|
||||
result2 <- or2.eval(())
|
||||
result3 <- or3.eval(())
|
||||
result <- or.eval()
|
||||
result2 <- or2.eval()
|
||||
result3 <- or3.eval()
|
||||
yield
|
||||
assertEquals(result.unwrap(), false)
|
||||
assertEquals(result2.unwrap(), false)
|
||||
|
|
@ -50,11 +47,11 @@ class OrTests extends IOSuite:
|
|||
iotest("should return the underlying predicate for a singular entry") {
|
||||
val p = True[IO]
|
||||
val or = Or(p)
|
||||
val or2 = Or(UUID.v7(), p)
|
||||
val or2 = Or(p)
|
||||
|
||||
for
|
||||
result <- or.eval(())
|
||||
result2 <- or2.eval(())
|
||||
result <- or.eval()
|
||||
result2 <- or2.eval()
|
||||
yield
|
||||
assertEquals(result.unwrap(), true)
|
||||
assertEquals(result2.unwrap(), true)
|
||||
13
src/test/scala/gs/predicate/v0/api/TrueTests.scala
Normal file
13
src/test/scala/gs/predicate/v0/api/TrueTests.scala
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.effect.IO
|
||||
import support.IOSuite
|
||||
|
||||
class TrueTests extends IOSuite:
|
||||
|
||||
iotest("should return true") {
|
||||
val predicate = True[IO]
|
||||
|
||||
for result <- predicate.eval()
|
||||
yield assertEquals(result.unwrap(), true)
|
||||
}
|
||||
|
|
@ -12,22 +12,24 @@ class KeyExistsTests extends IOSuite:
|
|||
import KeyExistsTests.Data
|
||||
|
||||
iotest("should find a key that exists within some provider") {
|
||||
val p = KeyExists[IO, String, String](Data.ExistingKey)
|
||||
for
|
||||
provider <- KeyExistsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
KeyExistsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p = KeyExists[IO](Data.ExistingKey)
|
||||
for result <- p.eval()
|
||||
yield
|
||||
// TODO: assertPredicateMatched(result)
|
||||
assertEquals(result, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
val p = KeyExists[IO, String, String](Data.NotExistingKey)
|
||||
for
|
||||
provider <- KeyExistsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
KeyExistsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p = KeyExists[IO](Data.NotExistingKey)
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
object KeyExistsTests:
|
||||
|
||||
|
|
@ -40,8 +42,8 @@ object KeyExistsTests:
|
|||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueStringProvider[IO]] =
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapStringProvider(map)
|
||||
yield new MemoryMapKeyValueProvider(map)
|
||||
|
||||
end KeyExistsTests
|
||||
90
src/test/scala/gs/predicate/v0/kv/StringContainsTests.scala
Normal file
90
src/test/scala/gs/predicate/v0/kv/StringContainsTests.scala
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.std.MapRef
|
||||
import gs.datagen.v0.Gen
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import gs.predicate.v0.kv.StringContainsTests.Data.Substring1
|
||||
import support.IOSuite
|
||||
|
||||
class StringContainsTests extends IOSuite:
|
||||
|
||||
import StringContainsTests.Data
|
||||
|
||||
iotest("should find a contained string in any position") {
|
||||
StringContainsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p1 = ValueContains[IO](Data.PassingKey, Data.Substring1)
|
||||
val p2 = ValueContains[IO](Data.PassingKey, Data.Substring2)
|
||||
val p3 = ValueContains[IO](Data.PassingKey, Data.Substring3)
|
||||
for
|
||||
r1 <- p1.eval()
|
||||
r2 <- p2.eval()
|
||||
r3 <- p3.eval()
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
assertEquals(r3, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
StringContainsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p = ValueContains[IO](Data.NotExistingKey, "")
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should match if an empty substring is provided") {
|
||||
StringContainsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p1 = ValueContains[IO](Data.EmptyStringKey, "")
|
||||
val p2 = ValueContains[IO](Data.PassingKey, "")
|
||||
for
|
||||
r1 <- p1.eval()
|
||||
r2 <- p2.eval()
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should not match if the target value is not contained in the input") {
|
||||
StringContainsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p = ValueContains[IO](Data.PassingKey, Substring1.reverse)
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
object StringContainsTests:
|
||||
|
||||
object Data:
|
||||
|
||||
val PassingKey: String = Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
||||
val PassingValue: String = "abcdefhij"
|
||||
val Substring1: String = "abc"
|
||||
val Substring2: String = "def"
|
||||
val Substring3: String = "hij"
|
||||
|
||||
val NotExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(6)).gen()
|
||||
val NotExistingValue: String = Gen.string.alphaNumeric(Size.Fixed(4)).gen()
|
||||
|
||||
val EmptyStringKey: String = "empty"
|
||||
|
||||
val KeyValues: Map[String, String] = Map(
|
||||
PassingKey -> PassingValue,
|
||||
EmptyStringKey -> ""
|
||||
)
|
||||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapKeyValueProvider(map)
|
||||
|
||||
end StringContainsTests
|
||||
100
src/test/scala/gs/predicate/v0/kv/StringEndsWithTests.scala
Normal file
100
src/test/scala/gs/predicate/v0/kv/StringEndsWithTests.scala
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.std.MapRef
|
||||
import gs.datagen.v0.Gen
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import support.IOSuite
|
||||
|
||||
class ValueEndsWithTests extends IOSuite:
|
||||
|
||||
import ValueEndsWithTests.Data
|
||||
|
||||
iotest("should find a string as a prefix of the input") {
|
||||
ValueEndsWithTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p1 = ValueEndsWith[IO](Data.PassingKey, Data.Substring1)
|
||||
val p2 = ValueEndsWith[IO](Data.PassingKey, Data.Substring2)
|
||||
val p3 = ValueEndsWith[IO](Data.PassingKey, Data.Substring3)
|
||||
for
|
||||
r1 <- p1.eval()
|
||||
r2 <- p2.eval()
|
||||
r3 <- p3.eval()
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
assertEquals(r3, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
ValueEndsWithTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p = ValueEndsWith[IO](Data.NotExistingKey, "")
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should match if an empty substring is provided") {
|
||||
ValueEndsWithTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p1 = ValueEndsWith[IO](Data.EmptyStringKey, "")
|
||||
val p2 = ValueEndsWith[IO](Data.PassingKey, "")
|
||||
for
|
||||
r1 <- p1.eval()
|
||||
r2 <- p2.eval()
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest(
|
||||
"should not match if the target value is not the suffix of the input"
|
||||
) {
|
||||
ValueEndsWithTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p1 = ValueEndsWith[IO](Data.PassingKey, Data.Substring3 + "z")
|
||||
val p2 =
|
||||
ValueEndsWith[IO](Data.PassingKey, Data.Substring3.reverse)
|
||||
val p3 =
|
||||
ValueEndsWith[IO](Data.PassingKey, Data.Substring2.reverse)
|
||||
for
|
||||
r1 <- p1.eval()
|
||||
r2 <- p2.eval()
|
||||
r3 <- p3.eval()
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.missed())
|
||||
assertEquals(r2, Predicate.Result.missed())
|
||||
assertEquals(r3, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
object ValueEndsWithTests:
|
||||
|
||||
object Data:
|
||||
|
||||
val PassingKey: String = Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
||||
val PassingValue: String = "abcdefghi"
|
||||
val Substring1: String = "i"
|
||||
val Substring2: String = "hi"
|
||||
val Substring3: String = "abcdefghi"
|
||||
|
||||
val NotExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(6)).gen()
|
||||
|
||||
val EmptyStringKey: String = "empty"
|
||||
|
||||
val KeyValues: Map[String, String] = Map(
|
||||
PassingKey -> PassingValue,
|
||||
EmptyStringKey -> ""
|
||||
)
|
||||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapKeyValueProvider(map)
|
||||
|
||||
end ValueEndsWithTests
|
||||
101
src/test/scala/gs/predicate/v0/kv/StringStartsWithTests.scala
Normal file
101
src/test/scala/gs/predicate/v0/kv/StringStartsWithTests.scala
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.std.MapRef
|
||||
import gs.datagen.v0.Gen
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import support.IOSuite
|
||||
|
||||
class ValueStartsWithTests extends IOSuite:
|
||||
|
||||
import ValueStartsWithTests.Data
|
||||
|
||||
iotest("should find a string as a prefix of the input") {
|
||||
ValueStartsWithTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p1 = ValueStartsWith[IO](Data.PassingKey, Data.Substring1)
|
||||
val p2 = ValueStartsWith[IO](Data.PassingKey, Data.Substring2)
|
||||
val p3 = ValueStartsWith[IO](Data.PassingKey, Data.Substring3)
|
||||
for
|
||||
r1 <- p1.eval()
|
||||
r2 <- p2.eval()
|
||||
r3 <- p3.eval()
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
assertEquals(r3, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
ValueStartsWithTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p = ValueStartsWith[IO](Data.NotExistingKey, "")
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should match if an empty substring is provided") {
|
||||
ValueStartsWithTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p1 = ValueStartsWith[IO](Data.EmptyStringKey, "")
|
||||
val p2 = ValueStartsWith[IO](Data.PassingKey, "")
|
||||
for
|
||||
r1 <- p1.eval()
|
||||
r2 <- p2.eval()
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.matched())
|
||||
assertEquals(r2, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest(
|
||||
"should not match if the target value is not the prefix of the input"
|
||||
) {
|
||||
ValueStartsWithTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p1 =
|
||||
ValueStartsWith[IO](Data.PassingKey, Data.Substring3 + "k")
|
||||
val p2 =
|
||||
ValueStartsWith[IO](Data.PassingKey, Data.Substring3.reverse)
|
||||
val p3 =
|
||||
ValueStartsWith[IO](Data.PassingKey, Data.Substring2.reverse)
|
||||
for
|
||||
r1 <- p1.eval()
|
||||
r2 <- p2.eval()
|
||||
r3 <- p3.eval()
|
||||
yield
|
||||
assertEquals(r1, Predicate.Result.missed())
|
||||
assertEquals(r2, Predicate.Result.missed())
|
||||
assertEquals(r3, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
object ValueStartsWithTests:
|
||||
|
||||
object Data:
|
||||
|
||||
val PassingKey: String = Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
||||
val PassingValue: String = "abcdefghi"
|
||||
val Substring1: String = "a"
|
||||
val Substring2: String = "ab"
|
||||
val Substring3: String = "abcdefghi"
|
||||
|
||||
val NotExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(6)).gen()
|
||||
|
||||
val EmptyStringKey: String = "empty"
|
||||
|
||||
val KeyValues: Map[String, String] = Map(
|
||||
PassingKey -> PassingValue,
|
||||
EmptyStringKey -> ""
|
||||
)
|
||||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapKeyValueProvider(map)
|
||||
|
||||
end ValueStartsWithTests
|
||||
|
|
@ -12,30 +12,33 @@ class ValueEqualsTests extends IOSuite:
|
|||
import ValueEqualsTests.Data
|
||||
|
||||
iotest("should find an exact match against some value") {
|
||||
ValueEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p =
|
||||
ValueEquals[IO, String, String](Data.ExistingKey, Data.ExistingValue)
|
||||
for
|
||||
provider <- ValueEqualsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
ValueEquals[IO](Data.ExistingKey, Data.ExistingValue)
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should not find a value if it is not associated to a key") {
|
||||
ValueEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p =
|
||||
ValueEquals[IO, String, String](Data.ExistingKey, Data.NotExistingValue)
|
||||
for
|
||||
provider <- ValueEqualsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
ValueEquals[IO](Data.ExistingKey, Data.NotExistingValue)
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
val p = ValueEquals[IO, String, String](Data.NotExistingKey, "")
|
||||
for
|
||||
provider <- ValueEqualsTests.newProvider(Data.KeyValues)
|
||||
result <- p.eval(provider)
|
||||
ValueEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p = ValueEquals[IO](Data.NotExistingKey, "")
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
object ValueEqualsTests:
|
||||
|
||||
|
|
@ -49,8 +52,8 @@ object ValueEqualsTests:
|
|||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueStringProvider[IO]] =
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapStringProvider(map)
|
||||
yield new MemoryMapKeyValueProvider(map)
|
||||
|
||||
end ValueEqualsTests
|
||||
62
src/test/scala/gs/predicate/v0/kv/ValueNotEqualsTests.scala
Normal file
62
src/test/scala/gs/predicate/v0/kv/ValueNotEqualsTests.scala
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package gs.predicate.v0.kv
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.std.MapRef
|
||||
import gs.datagen.v0.Gen
|
||||
import gs.datagen.v0.generators.Size
|
||||
import gs.predicate.v0.api.Predicate
|
||||
import support.IOSuite
|
||||
|
||||
class ValueNotEqualsTests extends IOSuite:
|
||||
|
||||
import ValueNotEqualsTests.Data
|
||||
|
||||
iotest("should NOT find an exact match against some value") {
|
||||
ValueNotEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p =
|
||||
ValueNotEquals[IO](Data.ExistingKey, Data.ExistingValue)
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should match a value if that value is not equal to the target") {
|
||||
ValueNotEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p =
|
||||
ValueNotEquals[IO](
|
||||
Data.ExistingKey,
|
||||
Data.NotExistingValue
|
||||
)
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.matched())
|
||||
}
|
||||
}
|
||||
|
||||
iotest("should not find a key that does not exist within some provider") {
|
||||
ValueNotEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||
given KeyValueProvider[IO] = provider
|
||||
val p = ValueNotEquals[IO](Data.NotExistingKey, "")
|
||||
for result <- p.eval()
|
||||
yield assertEquals(result, Predicate.Result.missed())
|
||||
}
|
||||
}
|
||||
|
||||
object ValueNotEqualsTests:
|
||||
|
||||
object Data:
|
||||
|
||||
val ExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
||||
val NotExistingKey: String = Gen.string.alphaNumeric(Size.Fixed(6)).gen()
|
||||
val ExistingValue: String = Gen.string.alphaNumeric(Size.Fixed(10)).gen()
|
||||
val NotExistingValue: String = Gen.string.alphaNumeric(Size.Fixed(4)).gen()
|
||||
val KeyValues: Map[String, String] = Map(ExistingKey -> ExistingValue)
|
||||
|
||||
end Data
|
||||
|
||||
def newProvider(data: Map[String, String]): IO[KeyValueProvider[IO]] =
|
||||
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||
yield new MemoryMapKeyValueProvider(map)
|
||||
|
||||
end ValueNotEqualsTests
|
||||
Loading…
Add table
Reference in a new issue