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")
|
"MIT" -> url("https://git.garrity.co/garrity-software/gs-predicate/LICENSE")
|
||||||
)
|
)
|
||||||
|
|
||||||
val noPublishSettings = Seq(
|
|
||||||
publish := {}
|
|
||||||
)
|
|
||||||
|
|
||||||
val sharedSettings = Seq(
|
val sharedSettings = Seq(
|
||||||
scalaVersion := scala3,
|
scalaVersion := scala3,
|
||||||
version := semVerSelected.value,
|
version := semVerSelected.value,
|
||||||
|
|
@ -32,13 +28,13 @@ val Deps = new {
|
||||||
|
|
||||||
val Circe = new {
|
val Circe = new {
|
||||||
val Core: ModuleID = "io.circe" %% "circe-core" % "0.14.15"
|
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 Optics: ModuleID = "io.circe" %% "circe-optics" % "0.15.1"
|
||||||
|
val Parser: ModuleID = "io.circe" %% "circe-parser" % "0.14.15"
|
||||||
}
|
}
|
||||||
|
|
||||||
val Gs = new {
|
val Gs = new {
|
||||||
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.3.3"
|
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"
|
val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.1.1"
|
||||||
|
|
@ -53,86 +49,16 @@ lazy val testSettings = Seq(
|
||||||
|
|
||||||
lazy val `gs-predicate` = project
|
lazy val `gs-predicate` = project
|
||||||
.in(file("."))
|
.in(file("."))
|
||||||
.aggregate(
|
.settings(sharedSettings)
|
||||||
`test-support`,
|
.settings(testSettings)
|
||||||
api,
|
|
||||||
keyValue,
|
|
||||||
json
|
|
||||||
)
|
|
||||||
.settings(noPublishSettings)
|
|
||||||
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
|
.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(
|
.settings(
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
Deps.Cats.Core,
|
Deps.Cats.Core,
|
||||||
Deps.Cats.Effect,
|
Deps.Cats.Effect,
|
||||||
Deps.Circe.Core,
|
Deps.Circe.Core,
|
||||||
Deps.Circe.Parser,
|
Deps.Circe.Generic,
|
||||||
Deps.Circe.Optics,
|
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
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import gs.uuid.v0.UUID
|
|
||||||
|
|
||||||
/** A _Predicate_ is some function that accepts any input and emits some
|
/** A _Predicate_ is some function that accepts any input and emits some
|
||||||
* [[Predicate.Result]] (whether the predicate matched).
|
* [[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
|
/** @return
|
||||||
* The unique identifier of this Predicate.
|
* The serializable predicate type.
|
||||||
*/
|
*/
|
||||||
def id: UUID
|
def predicateType: String
|
||||||
|
|
||||||
/** Evaluate this predicate against the given input.
|
/** Evaluate this predicate against the given input.
|
||||||
*
|
*
|
||||||
* @param input
|
|
||||||
* The input to evaluate this predicate against.
|
|
||||||
* @return
|
* @return
|
||||||
* Some [[Predicate.Result]] that describes whether the input matched the
|
* Some [[Predicate.Result]] that describes whether the input matched the
|
||||||
* predicate.
|
* predicate.
|
||||||
*/
|
*/
|
||||||
def eval(input: A): F[Predicate.Result]
|
def eval(): F[Predicate.Result]
|
||||||
|
|
||||||
/** Predicate equality is based on the _unique identifier_. Two predicates
|
end Predicate
|
||||||
* 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()}"
|
|
||||||
|
|
||||||
object 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] = False[F]
|
||||||
|
|
||||||
def alwaysFalse[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
|
||||||
|
|
||||||
/** The result of evaluating a [[Predicate]] is a Boolean value where:
|
/** The result of evaluating a [[Predicate]] is a Boolean value where:
|
||||||
*
|
*
|
||||||
|
|
@ -132,3 +107,5 @@ object Predicate:
|
||||||
def isMiss: Boolean = !result
|
def isMiss: Boolean = !result
|
||||||
|
|
||||||
end 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.
|
/** Interface for anything that can fetch values for stored keys.
|
||||||
*/
|
*/
|
||||||
trait KeyValueProvider[F[_], -K, V]:
|
trait KeyValueProvider[F[_]]:
|
||||||
/** Determine if some key exists.
|
/** Determine if some key exists.
|
||||||
*
|
*
|
||||||
* @param key
|
* @param key
|
||||||
|
|
@ -12,7 +12,7 @@ trait KeyValueProvider[F[_], -K, V]:
|
||||||
* @return
|
* @return
|
||||||
* True if the key exists, false otherwise.
|
* 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.
|
/** Get the value associated with some key.
|
||||||
*
|
*
|
||||||
|
|
@ -21,24 +21,29 @@ trait KeyValueProvider[F[_], -K, V]:
|
||||||
* @return
|
* @return
|
||||||
* The value stored for the key, or `None` if no such value exists.
|
* 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:
|
object KeyValueProvider:
|
||||||
|
|
||||||
|
def apply[F[_]](
|
||||||
|
using
|
||||||
|
kvp: KeyValueProvider[F]
|
||||||
|
): KeyValueProvider[F] = kvp
|
||||||
|
|
||||||
/** @return
|
/** @return
|
||||||
* New instance of the no-op [[KeyValueProvider]] implementation.
|
* 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.
|
/** 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
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def exists(key: Any): F[Boolean] = Applicative[F].pure(false)
|
override def exists(key: String): F[Boolean] = Applicative[F].pure(false)
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @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
|
end KeyValueProvider
|
||||||
|
|
@ -9,9 +9,9 @@ import cats.syntax.all.*
|
||||||
* @param map
|
* @param map
|
||||||
* The underlying map.
|
* The underlying map.
|
||||||
*/
|
*/
|
||||||
final class MemoryMapStringProvider[F[_]: Sync](
|
final class MemoryMapKeyValueProvider[F[_]: Sync](
|
||||||
private val map: MapRef[F, String, Option[String]]
|
private val map: MapRef[F, String, Option[String]]
|
||||||
) extends KeyValueStringProvider[F]:
|
) extends KeyValueProvider[F]:
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @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
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
import gs.uuid.v0.UUID
|
|
||||||
import support.IOSuite
|
import support.IOSuite
|
||||||
|
|
||||||
class AndTests extends IOSuite:
|
class AndTests extends IOSuite:
|
||||||
|
|
||||||
iotest("should return true if all are true") {
|
iotest("should return true if all are true") {
|
||||||
val id = UUID.v7()
|
|
||||||
val and = And(True[IO], True[IO], True[IO])
|
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
|
for
|
||||||
result <- and.eval(())
|
result <- and.eval()
|
||||||
result2 <- and2.eval(())
|
result2 <- and2.eval()
|
||||||
yield
|
yield
|
||||||
assertEquals(result.unwrap(), true)
|
assertEquals(result.unwrap(), true)
|
||||||
assertEquals(result2.unwrap(), true)
|
assertEquals(result2.unwrap(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return false if any are false") {
|
iotest("should return false if any are false") {
|
||||||
val id = UUID.v7()
|
|
||||||
val and = And(True[IO], False[IO], True[IO])
|
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
|
for
|
||||||
result <- and.eval(())
|
result <- and.eval()
|
||||||
result2 <- and2.eval(())
|
result2 <- and2.eval()
|
||||||
yield
|
yield
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
|
|
@ -35,12 +32,12 @@ class AndTests extends IOSuite:
|
||||||
iotest("should return false for an empty list") {
|
iotest("should return false for an empty list") {
|
||||||
val and = And.empty[IO]
|
val and = And.empty[IO]
|
||||||
val and2 = And[IO, Any]()
|
val and2 = And[IO, Any]()
|
||||||
val and3 = And[IO, Any](UUID.v7())
|
val and3 = And[IO, Any]()
|
||||||
|
|
||||||
for
|
for
|
||||||
result <- and.eval(())
|
result <- and.eval()
|
||||||
result2 <- and2.eval(())
|
result2 <- and2.eval()
|
||||||
result3 <- and3.eval(())
|
result3 <- and3.eval()
|
||||||
yield
|
yield
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
|
|
@ -50,11 +47,11 @@ class AndTests extends IOSuite:
|
||||||
iotest("should return the underlying predicate for a singular entry") {
|
iotest("should return the underlying predicate for a singular entry") {
|
||||||
val p = True[IO]
|
val p = True[IO]
|
||||||
val and = And(p)
|
val and = And(p)
|
||||||
val and2 = And(UUID.v7(), p)
|
val and2 = And(p)
|
||||||
|
|
||||||
for
|
for
|
||||||
result <- and.eval(())
|
result <- and.eval()
|
||||||
result2 <- and2.eval(())
|
result2 <- and2.eval()
|
||||||
yield
|
yield
|
||||||
assertEquals(result.unwrap(), true)
|
assertEquals(result.unwrap(), true)
|
||||||
assertEquals(result2.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
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
import gs.uuid.v0.UUID
|
|
||||||
import support.IOSuite
|
import support.IOSuite
|
||||||
|
|
||||||
class OrTests extends IOSuite:
|
class OrTests extends IOSuite:
|
||||||
|
|
||||||
iotest("should return true if any are true") {
|
iotest("should return true if any are true") {
|
||||||
val id = UUID.v7()
|
|
||||||
val or = Or(False[IO], True[IO], False[IO])
|
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
|
for
|
||||||
result <- or.eval(())
|
result <- or.eval()
|
||||||
result2 <- or2.eval(())
|
result2 <- or2.eval()
|
||||||
yield
|
yield
|
||||||
assertEquals(result.unwrap(), true)
|
assertEquals(result.unwrap(), true)
|
||||||
assertEquals(result2.unwrap(), true)
|
assertEquals(result2.unwrap(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return false if all are false") {
|
iotest("should return false if all are false") {
|
||||||
val id = UUID.v7()
|
|
||||||
val or = Or(False[IO], False[IO], False[IO])
|
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
|
for
|
||||||
result <- or.eval(())
|
result <- or.eval()
|
||||||
result2 <- or2.eval(())
|
result2 <- or2.eval()
|
||||||
yield
|
yield
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
|
|
@ -35,12 +32,12 @@ class OrTests extends IOSuite:
|
||||||
iotest("should return false for an empty list") {
|
iotest("should return false for an empty list") {
|
||||||
val or = Or.empty[IO]
|
val or = Or.empty[IO]
|
||||||
val or2 = Or[IO, Any]()
|
val or2 = Or[IO, Any]()
|
||||||
val or3 = Or[IO, Any](UUID.v7())
|
val or3 = Or[IO, Any]()
|
||||||
|
|
||||||
for
|
for
|
||||||
result <- or.eval(())
|
result <- or.eval()
|
||||||
result2 <- or2.eval(())
|
result2 <- or2.eval()
|
||||||
result3 <- or3.eval(())
|
result3 <- or3.eval()
|
||||||
yield
|
yield
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
|
|
@ -50,11 +47,11 @@ class OrTests extends IOSuite:
|
||||||
iotest("should return the underlying predicate for a singular entry") {
|
iotest("should return the underlying predicate for a singular entry") {
|
||||||
val p = True[IO]
|
val p = True[IO]
|
||||||
val or = Or(p)
|
val or = Or(p)
|
||||||
val or2 = Or(UUID.v7(), p)
|
val or2 = Or(p)
|
||||||
|
|
||||||
for
|
for
|
||||||
result <- or.eval(())
|
result <- or.eval()
|
||||||
result2 <- or2.eval(())
|
result2 <- or2.eval()
|
||||||
yield
|
yield
|
||||||
assertEquals(result.unwrap(), true)
|
assertEquals(result.unwrap(), true)
|
||||||
assertEquals(result2.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
|
import KeyExistsTests.Data
|
||||||
|
|
||||||
iotest("should find a key that exists within some provider") {
|
iotest("should find a key that exists within some provider") {
|
||||||
val p = KeyExists[IO, String, String](Data.ExistingKey)
|
KeyExistsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||||
for
|
given KeyValueProvider[IO] = provider
|
||||||
provider <- KeyExistsTests.newProvider(Data.KeyValues)
|
val p = KeyExists[IO](Data.ExistingKey)
|
||||||
result <- p.eval(provider)
|
for result <- p.eval()
|
||||||
yield
|
yield
|
||||||
// TODO: assertPredicateMatched(result)
|
// TODO: assertPredicateMatched(result)
|
||||||
assertEquals(result, Predicate.Result.matched())
|
assertEquals(result, Predicate.Result.matched())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
iotest("should not find a key that does not exist within some provider") {
|
iotest("should not find a key that does not exist within some provider") {
|
||||||
val p = KeyExists[IO, String, String](Data.NotExistingKey)
|
KeyExistsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||||
for
|
given KeyValueProvider[IO] = provider
|
||||||
provider <- KeyExistsTests.newProvider(Data.KeyValues)
|
val p = KeyExists[IO](Data.NotExistingKey)
|
||||||
result <- p.eval(provider)
|
for result <- p.eval()
|
||||||
yield assertEquals(result, Predicate.Result.missed())
|
yield assertEquals(result, Predicate.Result.missed())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object KeyExistsTests:
|
object KeyExistsTests:
|
||||||
|
|
||||||
|
|
@ -40,8 +42,8 @@ object KeyExistsTests:
|
||||||
|
|
||||||
end Data
|
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)
|
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||||
yield new MemoryMapStringProvider(map)
|
yield new MemoryMapKeyValueProvider(map)
|
||||||
|
|
||||||
end KeyExistsTests
|
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
|
import ValueEqualsTests.Data
|
||||||
|
|
||||||
iotest("should find an exact match against some value") {
|
iotest("should find an exact match against some value") {
|
||||||
|
ValueEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||||
|
given KeyValueProvider[IO] = provider
|
||||||
val p =
|
val p =
|
||||||
ValueEquals[IO, String, String](Data.ExistingKey, Data.ExistingValue)
|
ValueEquals[IO](Data.ExistingKey, Data.ExistingValue)
|
||||||
for
|
for result <- p.eval()
|
||||||
provider <- ValueEqualsTests.newProvider(Data.KeyValues)
|
|
||||||
result <- p.eval(provider)
|
|
||||||
yield assertEquals(result, Predicate.Result.matched())
|
yield assertEquals(result, Predicate.Result.matched())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
iotest("should not find a value if it is not associated to a key") {
|
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 =
|
val p =
|
||||||
ValueEquals[IO, String, String](Data.ExistingKey, Data.NotExistingValue)
|
ValueEquals[IO](Data.ExistingKey, Data.NotExistingValue)
|
||||||
for
|
for result <- p.eval()
|
||||||
provider <- ValueEqualsTests.newProvider(Data.KeyValues)
|
|
||||||
result <- p.eval(provider)
|
|
||||||
yield assertEquals(result, Predicate.Result.missed())
|
yield assertEquals(result, Predicate.Result.missed())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
iotest("should not find a key that does not exist within some provider") {
|
iotest("should not find a key that does not exist within some provider") {
|
||||||
val p = ValueEquals[IO, String, String](Data.NotExistingKey, "")
|
ValueEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
||||||
for
|
given KeyValueProvider[IO] = provider
|
||||||
provider <- ValueEqualsTests.newProvider(Data.KeyValues)
|
val p = ValueEquals[IO](Data.NotExistingKey, "")
|
||||||
result <- p.eval(provider)
|
for result <- p.eval()
|
||||||
yield assertEquals(result, Predicate.Result.missed())
|
yield assertEquals(result, Predicate.Result.missed())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object ValueEqualsTests:
|
object ValueEqualsTests:
|
||||||
|
|
||||||
|
|
@ -49,8 +52,8 @@ object ValueEqualsTests:
|
||||||
|
|
||||||
end Data
|
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)
|
for map <- MapRef.ofSingleImmutableMap[IO, String, String](data)
|
||||||
yield new MemoryMapStringProvider(map)
|
yield new MemoryMapKeyValueProvider(map)
|
||||||
|
|
||||||
end ValueEqualsTests
|
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