refactored entire library
This commit is contained in:
parent
05e619fae7
commit
71d5d72d35
57 changed files with 1571 additions and 2484 deletions
|
|
@ -21,11 +21,6 @@ val sharedSettings = Seq(
|
||||||
)
|
)
|
||||||
|
|
||||||
val Deps = new {
|
val Deps = new {
|
||||||
val Cats = new {
|
|
||||||
val Core: ModuleID = "org.typelevel" %% "cats-core" % "2.13.0"
|
|
||||||
val Effect: ModuleID = "org.typelevel" %% "cats-effect" % "3.6.3"
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Generic: ModuleID = "io.circe" %% "circe-generic" % "0.14.15"
|
val Generic: ModuleID = "io.circe" %% "circe-generic" % "0.14.15"
|
||||||
|
|
@ -54,8 +49,6 @@ lazy val `gs-predicate` = project
|
||||||
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
|
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
|
||||||
.settings(
|
.settings(
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
Deps.Cats.Core,
|
|
||||||
Deps.Cats.Effect,
|
|
||||||
Deps.Circe.Core,
|
Deps.Circe.Core,
|
||||||
Deps.Circe.Generic,
|
Deps.Circe.Generic,
|
||||||
Deps.Circe.Optics,
|
Deps.Circe.Optics,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import gs.predicate.v0.serde.json.JsonKeys
|
import gs.predicate.v0.serde.json.JsonKeys
|
||||||
import io.circe.Decoder
|
import io.circe.Decoder
|
||||||
import io.circe.DecodingFailure
|
import io.circe.DecodingFailure
|
||||||
|
|
@ -15,9 +13,9 @@ import io.circe.syntax._
|
||||||
* @param ps
|
* @param ps
|
||||||
* The predicates to evaluate.
|
* The predicates to evaluate.
|
||||||
*/
|
*/
|
||||||
final class And[F[_]: Applicative](
|
final class And(
|
||||||
val ps: List[Predicate[F]]
|
val ps: List[Predicate]
|
||||||
) extends Predicate[F]:
|
) extends Predicate:
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
|
|
@ -25,46 +23,46 @@ final class And[F[_]: Applicative](
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def eval(): F[Predicate.Result] =
|
override def eval(input: Any): Predicate.Result =
|
||||||
ps.map(_.eval()).sequence.map(_.allMatch())
|
ps.map(p => p.eval(input)).allMatch()
|
||||||
|
|
||||||
object And:
|
object And:
|
||||||
|
|
||||||
final val PredicateType: String = "and"
|
final val PredicateType: String = "and"
|
||||||
|
|
||||||
def empty[F[_]: Applicative]: Predicate[F] = False[F]
|
def empty: Predicate = False
|
||||||
|
|
||||||
def apply[F[_]: Applicative](ps: Predicate[F]*): Predicate[F] =
|
def apply(ps: Predicate*): Predicate =
|
||||||
ps.toList match
|
ps.toList match
|
||||||
case Nil => False[F]
|
case Nil => False
|
||||||
case p :: Nil => p
|
case p :: Nil => p
|
||||||
case list => new And(list)
|
case list => new And(list)
|
||||||
|
|
||||||
given andEncoder[F[_]](
|
given andEncoder[F[_]](
|
||||||
using
|
using
|
||||||
Encoder[Predicate[F]]
|
Encoder[Predicate]
|
||||||
): Encoder[And[F]] =
|
): Encoder[And] =
|
||||||
Encoder.instance[And[F]] { p =>
|
Encoder.instance[And] { p =>
|
||||||
Json.obj(
|
Json.obj(
|
||||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||||
(JsonKeys.predicates, p.ps.asJson)
|
(JsonKeys.predicates, p.ps.asJson)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def consumeCursor[F[_]: Applicative](
|
private def consumeCursor(
|
||||||
cursor: HCursor
|
cursor: HCursor
|
||||||
)(
|
)(
|
||||||
using
|
using
|
||||||
Decoder[Predicate[F]]
|
Decoder[Predicate]
|
||||||
): Either[DecodingFailure, And[F]] =
|
): Either[DecodingFailure, And] =
|
||||||
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
|
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate]]
|
||||||
yield new And(ps)
|
yield new And(ps)
|
||||||
|
|
||||||
given andDecoder[F[_]: Applicative](
|
given andDecoder(
|
||||||
using
|
using
|
||||||
Decoder[Predicate[F]]
|
Decoder[Predicate]
|
||||||
): Decoder[And[F]] =
|
): Decoder[And] =
|
||||||
Decoder.instance[And[F]] { cursor =>
|
Decoder.instance[And] { cursor =>
|
||||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||||
case PredicateType => consumeCursor(cursor)
|
case PredicateType => consumeCursor(cursor)
|
||||||
case candidate =>
|
case candidate =>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import gs.predicate.v0.serde.json.JsonKeys
|
import gs.predicate.v0.serde.json.JsonKeys
|
||||||
import io.circe.Decoder
|
import io.circe.Decoder
|
||||||
import io.circe.DecodingFailure
|
import io.circe.DecodingFailure
|
||||||
|
|
@ -9,7 +8,7 @@ import io.circe.Json
|
||||||
|
|
||||||
/** Always returns a miss.
|
/** Always returns a miss.
|
||||||
*/
|
*/
|
||||||
final class False[F[_]: Applicative] extends Predicate[F]:
|
object False extends Predicate:
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
|
|
@ -17,26 +16,20 @@ final class False[F[_]: Applicative] extends Predicate[F]:
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def eval(): F[Predicate.Result] =
|
override def eval(input: Any): Predicate.Result =
|
||||||
Applicative[F].pure(Predicate.Result.missed())
|
Predicate.Result.missed()
|
||||||
|
|
||||||
end False
|
|
||||||
|
|
||||||
object False:
|
|
||||||
|
|
||||||
final val PredicateType: String = "false"
|
final val PredicateType: String = "false"
|
||||||
|
|
||||||
def apply[F[_]: Applicative]: False[F] = new False[F]
|
given falseEncoder: Encoder[False.type] =
|
||||||
|
Encoder.instance[False.type] { p =>
|
||||||
given falseEncoder[F[_]]: Encoder[False[F]] =
|
|
||||||
Encoder.instance[False[F]] { p =>
|
|
||||||
Json.obj((JsonKeys.predicateType, Json.fromString(p.predicateType)))
|
Json.obj((JsonKeys.predicateType, Json.fromString(p.predicateType)))
|
||||||
}
|
}
|
||||||
|
|
||||||
given falseDecoder[F[_]: Applicative]: Decoder[False[F]] =
|
given falseDecoder: Decoder[False.type] =
|
||||||
Decoder.instance[False[F]] { cursor =>
|
Decoder.instance[False.type] { cursor =>
|
||||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||||
case PredicateType => Right(False[F])
|
case PredicateType => Right(False)
|
||||||
case candidate =>
|
case candidate =>
|
||||||
Left(
|
Left(
|
||||||
DecodingFailure(
|
DecodingFailure(
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,16 @@ object Messages:
|
||||||
): String =
|
): String =
|
||||||
s"Received predicate type '$candidate' but expected '$expected'."
|
s"Received predicate type '$candidate' but expected '$expected'."
|
||||||
|
|
||||||
|
def unrecognizedStringComparison(
|
||||||
|
candidate: String
|
||||||
|
): String = s"Unrecognized string comparison: '$candidate'"
|
||||||
|
|
||||||
|
def invalidStringComparison(
|
||||||
|
candidate: String,
|
||||||
|
expected: String
|
||||||
|
): String =
|
||||||
|
s"Received string comparison name '$candidate' but expected '$expected'."
|
||||||
|
|
||||||
def unrecognizedJsonComparison(
|
def unrecognizedJsonComparison(
|
||||||
candidate: String
|
candidate: String
|
||||||
): String = s"Unrecognized JSON comparison: '$candidate'"
|
): String = s"Unrecognized JSON comparison: '$candidate'"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import gs.predicate.v0.serde.json.JsonKeys
|
import gs.predicate.v0.serde.json.JsonKeys
|
||||||
import io.circe.Decoder
|
import io.circe.Decoder
|
||||||
import io.circe.DecodingFailure
|
import io.circe.DecodingFailure
|
||||||
|
|
@ -14,9 +12,9 @@ import io.circe.syntax.*
|
||||||
* @param ps
|
* @param ps
|
||||||
* The predicates to evaluate.
|
* The predicates to evaluate.
|
||||||
*/
|
*/
|
||||||
final class Or[F[_]: Applicative](
|
final class Or(
|
||||||
val ps: List[Predicate[F]]
|
val ps: List[Predicate]
|
||||||
) extends Predicate[F]:
|
) extends Predicate:
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
|
|
@ -24,40 +22,40 @@ final class Or[F[_]: Applicative](
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def eval(): F[Predicate.Result] =
|
override def eval(input: Any): Predicate.Result =
|
||||||
ps.map(_.eval()).sequence.map(_.anyMatch())
|
ps.map(p => p.eval(input)).anyMatch()
|
||||||
|
|
||||||
object Or:
|
object Or:
|
||||||
|
|
||||||
final val PredicateType: String = "or"
|
final val PredicateType: String = "or"
|
||||||
|
|
||||||
def empty[F[_]: Applicative]: Predicate[F] = False[F]
|
def empty: Predicate = False
|
||||||
|
|
||||||
def apply[F[_]: Applicative](ps: Predicate[F]*): Predicate[F] =
|
def apply(ps: Predicate*): Predicate =
|
||||||
ps.toList match
|
ps.toList match
|
||||||
case Nil => False[F]
|
case Nil => False
|
||||||
case p :: Nil => p
|
case p :: Nil => p
|
||||||
case list => new Or(list)
|
case list => new Or(list)
|
||||||
|
|
||||||
given orEncoder[F[_]](
|
given orEncoder[F[_]](
|
||||||
using
|
using
|
||||||
Encoder[Predicate[F]]
|
Encoder[Predicate]
|
||||||
): Encoder[Or[F]] =
|
): Encoder[Or] =
|
||||||
Encoder.instance[Or[F]] { p =>
|
Encoder.instance[Or] { p =>
|
||||||
Json.obj(
|
Json.obj(
|
||||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||||
(JsonKeys.predicates, p.ps.asJson)
|
(JsonKeys.predicates, p.ps.asJson)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
given orDecoder[F[_]: Applicative](
|
given orDecoder(
|
||||||
using
|
using
|
||||||
Decoder[Predicate[F]]
|
Decoder[Predicate]
|
||||||
): Decoder[Or[F]] =
|
): Decoder[Or] =
|
||||||
Decoder.instance[Or[F]] { cursor =>
|
Decoder.instance[Or] { cursor =>
|
||||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||||
case PredicateType =>
|
case PredicateType =>
|
||||||
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
|
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate]]
|
||||||
yield new Or(ps)
|
yield new Or(ps)
|
||||||
case candidate =>
|
case candidate =>
|
||||||
Left(
|
Left(
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
|
|
||||||
/** 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 extracted from context to see if the predicate
|
* Predicates evaluate input extracted from context to see if the predicate
|
||||||
* matches that input.
|
* matches that input.
|
||||||
*/
|
*/
|
||||||
trait Predicate[F[_]]:
|
trait Predicate:
|
||||||
/** @return
|
/** @return
|
||||||
* The serializable predicate type.
|
* The serializable predicate type.
|
||||||
*/
|
*/
|
||||||
|
|
@ -20,15 +18,15 @@ trait Predicate[F[_]]:
|
||||||
* Some [[Predicate.Result]] that describes whether the input matched the
|
* Some [[Predicate.Result]] that describes whether the input matched the
|
||||||
* predicate.
|
* predicate.
|
||||||
*/
|
*/
|
||||||
def eval(): F[Predicate.Result]
|
def eval(input: Any): Predicate.Result
|
||||||
|
|
||||||
end Predicate
|
end Predicate
|
||||||
|
|
||||||
object Predicate:
|
object Predicate:
|
||||||
|
|
||||||
def alwaysTrue[F[_]: Applicative]: Predicate[F] = True[F]
|
def alwaysTrue: Predicate = True
|
||||||
|
|
||||||
def alwaysFalse[F[_]: Applicative]: Predicate[F] = False[F]
|
def alwaysFalse: Predicate = False
|
||||||
|
|
||||||
/** The result of evaluating a [[Predicate]] is a Boolean value where:
|
/** The result of evaluating a [[Predicate]] is a Boolean value where:
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import gs.predicate.v0.serde.json.JsonKeys
|
import gs.predicate.v0.serde.json.JsonKeys
|
||||||
import io.circe.Decoder
|
import io.circe.Decoder
|
||||||
import io.circe.DecodingFailure
|
import io.circe.DecodingFailure
|
||||||
|
|
@ -9,7 +8,7 @@ import io.circe.Json
|
||||||
|
|
||||||
/** Always returns a match.
|
/** Always returns a match.
|
||||||
*/
|
*/
|
||||||
final class True[F[_]: Applicative] extends Predicate[F]:
|
object True extends Predicate:
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
|
|
@ -17,26 +16,20 @@ final class True[F[_]: Applicative] extends Predicate[F]:
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def eval(): F[Predicate.Result] =
|
override def eval(input: Any): Predicate.Result =
|
||||||
Applicative[F].pure(Predicate.Result.matched())
|
Predicate.Result.matched()
|
||||||
|
|
||||||
end True
|
|
||||||
|
|
||||||
object True:
|
|
||||||
|
|
||||||
final val PredicateType: String = "true"
|
final val PredicateType: String = "true"
|
||||||
|
|
||||||
def apply[F[_]: Applicative]: True[F] = new True[F]
|
given trueEncoder: Encoder[True.type] =
|
||||||
|
Encoder.instance[True.type] { p =>
|
||||||
given trueEncoder[F[_]]: Encoder[True[F]] =
|
|
||||||
Encoder.instance[True[F]] { p =>
|
|
||||||
Json.obj((JsonKeys.predicateType, Json.fromString(p.predicateType)))
|
Json.obj((JsonKeys.predicateType, Json.fromString(p.predicateType)))
|
||||||
}
|
}
|
||||||
|
|
||||||
given trueDecoder[F[_]: Applicative]: Decoder[True[F]] =
|
given trueDecoder: Decoder[True.type] =
|
||||||
Decoder.instance[True[F]] { cursor =>
|
Decoder.instance[True.type] { cursor =>
|
||||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||||
case PredicateType => Right(True[F])
|
case PredicateType => Right(True)
|
||||||
case candidate =>
|
case candidate =>
|
||||||
Left(
|
Left(
|
||||||
DecodingFailure(
|
DecodingFailure(
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,79 @@ abstract class JsonComparison(val name: String):
|
||||||
|
|
||||||
object JsonComparison:
|
object JsonComparison:
|
||||||
|
|
||||||
|
object True extends JsonComparison("true"):
|
||||||
|
final val Name: String = "true"
|
||||||
|
|
||||||
|
def compare(input: Json): Boolean = true
|
||||||
|
|
||||||
|
given Encoder[True.type] = Encoder.instance[True.type] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[True.type] = Decoder.instance[True.type] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name => Right(True)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object False extends JsonComparison("false"):
|
||||||
|
final val Name: String = "false"
|
||||||
|
|
||||||
|
def compare(input: Json): Boolean = false
|
||||||
|
|
||||||
|
given Encoder[False.type] = Encoder.instance[False.type] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[False.type] = Decoder.instance[False.type] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name => Right(False)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class And(cs: List[JsonComparison]) extends JsonComparison(And.Name):
|
||||||
|
|
||||||
|
def compare(input: Json): Boolean = cs.map(_.compare(input)).foldLeft(true) {
|
||||||
|
(
|
||||||
|
acc,
|
||||||
|
item
|
||||||
|
) => acc && item
|
||||||
|
}
|
||||||
|
|
||||||
|
object And:
|
||||||
|
final val Name: String = "and"
|
||||||
|
|
||||||
|
case class Or(cs: List[JsonComparison]) extends JsonComparison(Or.Name):
|
||||||
|
|
||||||
|
def compare(input: Json): Boolean =
|
||||||
|
cs.map(_.compare(input)).foldLeft(false) {
|
||||||
|
(
|
||||||
|
acc,
|
||||||
|
item
|
||||||
|
) => acc || item
|
||||||
|
}
|
||||||
|
|
||||||
|
object Or:
|
||||||
|
final val Name: String = "or"
|
||||||
|
|
||||||
case class Eq(target: Json) extends JsonComparison(Eq.Name):
|
case class Eq(target: Json) extends JsonComparison(Eq.Name):
|
||||||
def compare(input: Json): Boolean = target.equals(input)
|
def compare(input: Json): Boolean = target.equals(input)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package gs.predicate.v0.json
|
||||||
|
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
|
import gs.predicate.v0.api.Predicate
|
||||||
|
import gs.predicate.v0.json.query.JsonQuery
|
||||||
|
import gs.predicate.v0.serde.json.JsonKeys
|
||||||
|
import gs.predicate.v0.serde.json.jsonComparisonDecoder
|
||||||
|
import gs.predicate.v0.serde.json.jsonComparisonEncoder
|
||||||
|
import io.circe.Decoder
|
||||||
|
import io.circe.DecodingFailure
|
||||||
|
import io.circe.Encoder
|
||||||
|
import io.circe.Json
|
||||||
|
import io.circe.syntax.*
|
||||||
|
|
||||||
|
/** Predicate that matches if the given JSON input matches the comparison
|
||||||
|
* function when the given query is applied.
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* The [[JsonQuery]] that must be satisfied.
|
||||||
|
* @param comparison
|
||||||
|
* The JSON comparison that must match the values identified by the query.
|
||||||
|
*/
|
||||||
|
final class JsonComparisonPredicate(
|
||||||
|
val query: JsonQuery,
|
||||||
|
val comparison: JsonComparison
|
||||||
|
) extends JsonPredicate:
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
final override val predicateType: String =
|
||||||
|
JsonComparisonPredicate.PredicateType
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def evalJson(input: Json): Predicate.Result =
|
||||||
|
Predicate.Result(query.eval(input, comparison.compare))
|
||||||
|
|
||||||
|
object JsonComparisonPredicate:
|
||||||
|
|
||||||
|
final val PredicateType: String = "json-query-comparison"
|
||||||
|
|
||||||
|
def apply(
|
||||||
|
query: JsonQuery,
|
||||||
|
comparison: JsonComparison
|
||||||
|
): JsonComparisonPredicate = new JsonComparisonPredicate(query, comparison)
|
||||||
|
|
||||||
|
given jsonQueryComparisonEncoder: Encoder[JsonComparisonPredicate] =
|
||||||
|
Encoder.instance[JsonComparisonPredicate] { p =>
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||||
|
(JsonKeys.query, Json.fromString(p.query.toString())),
|
||||||
|
(JsonKeys.comparison, p.comparison.asJson)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given jsonQueryComparisonDecoder: Decoder[JsonComparisonPredicate] =
|
||||||
|
Decoder.instance[JsonComparisonPredicate] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||||
|
case PredicateType =>
|
||||||
|
for
|
||||||
|
query <- cursor.downField(JsonKeys.query).as[JsonQuery]
|
||||||
|
comparison <- cursor
|
||||||
|
.downField(JsonKeys.comparison)
|
||||||
|
.as[JsonComparison]
|
||||||
|
yield new JsonComparisonPredicate(query, comparison)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidPredicateType(candidate, PredicateType),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end JsonComparisonPredicate
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -3,5 +3,11 @@ package gs.predicate.v0.json
|
||||||
import gs.predicate.v0.api.Predicate
|
import gs.predicate.v0.api.Predicate
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
|
|
||||||
abstract class JsonPredicate[F[_]: JsonProvider] extends Predicate[F]:
|
abstract class JsonPredicate extends Predicate:
|
||||||
def getJson(key: String): F[Option[Json]] = JsonProvider[F].get(key)
|
|
||||||
|
override def eval(input: Any): Predicate.Result =
|
||||||
|
input match
|
||||||
|
case json: Json => evalJson(json)
|
||||||
|
case _ => Predicate.Result.missed()
|
||||||
|
|
||||||
|
def evalJson(input: Json): Predicate.Result
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import gs.predicate.v0.api.Messages
|
|
||||||
import gs.predicate.v0.api.Predicate
|
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
|
||||||
import gs.predicate.v0.serde.json.JsonKeys
|
|
||||||
import gs.predicate.v0.serde.json.jsonComparisonDecoder
|
|
||||||
import gs.predicate.v0.serde.json.jsonComparisonEncoder
|
|
||||||
import io.circe.Decoder
|
|
||||||
import io.circe.DecodingFailure
|
|
||||||
import io.circe.Encoder
|
|
||||||
import io.circe.Json
|
|
||||||
import io.circe.syntax.*
|
|
||||||
|
|
||||||
/** 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 comparison function.
|
|
||||||
*
|
|
||||||
* This is the most general JSON predicate.
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
* The name of the JSON value that must satisfy the given query.
|
|
||||||
* @param query
|
|
||||||
* The [[JsonQuery]] that must be satisfied.
|
|
||||||
* @param comparison
|
|
||||||
* The JSON comparison that must match the query.
|
|
||||||
*/
|
|
||||||
final class JsonQueryComparison[F[_]: Applicative: JsonProvider](
|
|
||||||
val key: String,
|
|
||||||
val query: JsonQuery,
|
|
||||||
val comparison: JsonComparison
|
|
||||||
) extends Predicate[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
final override val predicateType: String = JsonQueryComparison.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, comparison.compare))
|
|
||||||
}
|
|
||||||
|
|
||||||
object JsonQueryComparison:
|
|
||||||
|
|
||||||
final val PredicateType: String = "json-query-comparison"
|
|
||||||
|
|
||||||
def apply[F[_]: Applicative: JsonProvider](
|
|
||||||
key: String,
|
|
||||||
query: JsonQuery,
|
|
||||||
comparison: JsonComparison
|
|
||||||
): JsonQueryComparison[F] =
|
|
||||||
new JsonQueryComparison[F](key, query, comparison)
|
|
||||||
|
|
||||||
given jsonQueryComparisonEncoder[F[_]]: Encoder[JsonQueryComparison[F]] =
|
|
||||||
Encoder.instance[JsonQueryComparison[F]] { p =>
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(p.key)),
|
|
||||||
(JsonKeys.query, Json.fromString(p.query.toString())),
|
|
||||||
(JsonKeys.comparison, p.comparison.asJson)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
given jsonQueryComparisonDecoder[F[_]: Applicative: JsonProvider]
|
|
||||||
: Decoder[JsonQueryComparison[F]] =
|
|
||||||
Decoder.instance[JsonQueryComparison[F]] { cursor =>
|
|
||||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
|
||||||
case PredicateType =>
|
|
||||||
for
|
|
||||||
key <- cursor.downField(JsonKeys.key).as[String]
|
|
||||||
query <- cursor.downField(JsonKeys.query).as[JsonQuery]
|
|
||||||
comparison <- cursor
|
|
||||||
.downField(JsonKeys.comparison)
|
|
||||||
.as[JsonComparison]
|
|
||||||
yield new JsonQueryComparison(key, query, comparison)
|
|
||||||
case candidate =>
|
|
||||||
Left(
|
|
||||||
DecodingFailure(
|
|
||||||
Messages.invalidPredicateType(candidate, PredicateType),
|
|
||||||
Nil
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end JsonQueryComparison
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import gs.predicate.v0.api.Messages
|
|
||||||
import gs.predicate.v0.api.Predicate
|
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
|
||||||
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, 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)
|
|
||||||
|
|
||||||
given jsonQueryEqualsEncoder[F[_]]: Encoder[JsonQueryEquals[F]] =
|
|
||||||
Encoder.instance[JsonQueryEquals[F]] { p =>
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(p.key)),
|
|
||||||
(JsonKeys.query, Json.fromString(p.query.toString())),
|
|
||||||
(JsonKeys.value, p.value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
given jsonQueryEqualsDecoder[F[_]: Applicative: JsonProvider]
|
|
||||||
: Decoder[JsonQueryEquals[F]] =
|
|
||||||
Decoder.instance[JsonQueryEquals[F]] { cursor =>
|
|
||||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
|
||||||
case PredicateType =>
|
|
||||||
for
|
|
||||||
key <- cursor.downField(JsonKeys.key).as[String]
|
|
||||||
query <- cursor.downField(JsonKeys.query).as[JsonQuery]
|
|
||||||
value <- cursor.downField(JsonKeys.value).as[Json]
|
|
||||||
yield new JsonQueryEquals(key, query, value)
|
|
||||||
case candidate =>
|
|
||||||
Left(
|
|
||||||
DecodingFailure(
|
|
||||||
Messages.invalidPredicateType(candidate, PredicateType),
|
|
||||||
Nil
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end JsonQueryEquals
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import gs.predicate.v0.api.Messages
|
|
||||||
import gs.predicate.v0.api.Predicate
|
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
|
||||||
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, and that blob contains the given query, and the result of the
|
|
||||||
* query matches one of the given values.
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
* The name of the JSON value that must satisfy the given query.
|
|
||||||
* @param query
|
|
||||||
* The [[JsonQuery]] that must be satisfied.
|
|
||||||
* @param values
|
|
||||||
* The set of JSON values, such that one value should match the input.
|
|
||||||
*/
|
|
||||||
final class JsonQueryIn[F[_]: Applicative: JsonProvider](
|
|
||||||
val key: String,
|
|
||||||
val query: JsonQuery,
|
|
||||||
val values: Set[Json]
|
|
||||||
) extends Predicate[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
final override val predicateType: String = JsonQueryIn.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, values.contains))
|
|
||||||
}
|
|
||||||
|
|
||||||
object JsonQueryIn:
|
|
||||||
|
|
||||||
final val PredicateType: String = "json-query-in"
|
|
||||||
|
|
||||||
def apply[F[_]: Applicative: JsonProvider](
|
|
||||||
key: String,
|
|
||||||
query: JsonQuery,
|
|
||||||
values: Set[Json]
|
|
||||||
): JsonQueryIn[F] =
|
|
||||||
new JsonQueryIn[F](key, query, values)
|
|
||||||
|
|
||||||
given jsonQueryInEncoder[F[_]]: Encoder[JsonQueryIn[F]] =
|
|
||||||
Encoder.instance[JsonQueryIn[F]] { p =>
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(p.key)),
|
|
||||||
(JsonKeys.query, Json.fromString(p.query.toString())),
|
|
||||||
(JsonKeys.values, Json.arr(p.values.toSeq*))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
given jsonQueryInDecoder[F[_]: Applicative: JsonProvider]
|
|
||||||
: Decoder[JsonQueryIn[F]] =
|
|
||||||
Decoder.instance[JsonQueryIn[F]] { cursor =>
|
|
||||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
|
||||||
case PredicateType =>
|
|
||||||
for
|
|
||||||
key <- cursor.downField(JsonKeys.key).as[String]
|
|
||||||
query <- cursor.downField(JsonKeys.query).as[JsonQuery]
|
|
||||||
values <- cursor.downField(JsonKeys.value).as[List[Json]]
|
|
||||||
yield new JsonQueryIn(key, query, values.toSet)
|
|
||||||
case candidate =>
|
|
||||||
Left(
|
|
||||||
DecodingFailure(
|
|
||||||
Messages.invalidPredicateType(candidate, PredicateType),
|
|
||||||
Nil
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end JsonQueryIn
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import gs.predicate.v0.api.Messages
|
|
||||||
import gs.predicate.v0.api.Predicate
|
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
|
||||||
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, 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 JsonQueryNotEquals[F[_]: Applicative: JsonProvider](
|
|
||||||
val key: String,
|
|
||||||
val query: JsonQuery,
|
|
||||||
val value: Json
|
|
||||||
) extends Predicate[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
final override val predicateType: String = JsonQueryNotEquals.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, v => !v.equals(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
object JsonQueryNotEquals:
|
|
||||||
|
|
||||||
final val PredicateType: String = "json-query-not-equals"
|
|
||||||
|
|
||||||
def apply[F[_]: Applicative: JsonProvider](
|
|
||||||
key: String,
|
|
||||||
query: JsonQuery,
|
|
||||||
value: Json
|
|
||||||
): JsonQueryNotEquals[F] =
|
|
||||||
new JsonQueryNotEquals[F](key, query, value)
|
|
||||||
|
|
||||||
given jsonQueryNotEqualsEncoder[F[_]]: Encoder[JsonQueryNotEquals[F]] =
|
|
||||||
Encoder.instance[JsonQueryNotEquals[F]] { p =>
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(p.key)),
|
|
||||||
(JsonKeys.query, Json.fromString(p.query.toString())),
|
|
||||||
(JsonKeys.value, p.value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
given jsonQueryNotEqualsDecoder[F[_]: Applicative: JsonProvider]
|
|
||||||
: Decoder[JsonQueryNotEquals[F]] =
|
|
||||||
Decoder.instance[JsonQueryNotEquals[F]] { cursor =>
|
|
||||||
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
|
||||||
case PredicateType =>
|
|
||||||
for
|
|
||||||
key <- cursor.downField(JsonKeys.key).as[String]
|
|
||||||
query <- cursor.downField(JsonKeys.query).as[JsonQuery]
|
|
||||||
value <- cursor.downField(JsonKeys.value).as[Json]
|
|
||||||
yield new JsonQueryNotEquals(key, query, value)
|
|
||||||
case candidate =>
|
|
||||||
Left(
|
|
||||||
DecodingFailure(
|
|
||||||
Messages.invalidPredicateType(candidate, PredicateType),
|
|
||||||
Nil
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end JsonQueryNotEquals
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
import cats.effect.std.MapRef
|
|
||||||
import io.circe.Json
|
|
||||||
|
|
||||||
/** Provides keys and JSON values, given some in-memory map.
|
|
||||||
*
|
|
||||||
* @param map
|
|
||||||
* The underlying map.
|
|
||||||
*/
|
|
||||||
final class MemoryMapJsonProvider[F[_]](
|
|
||||||
private val map: MapRef[F, String, Option[Json]]
|
|
||||||
) extends JsonProvider[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def get(key: String): F[Option[Json]] =
|
|
||||||
map.apply(key).get
|
|
||||||
|
|
||||||
end MemoryMapJsonProvider
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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)
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package gs.predicate.v0.kv
|
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
|
|
||||||
/** Interface for anything that can fetch values for stored keys.
|
|
||||||
*/
|
|
||||||
trait KeyValueProvider[F[_]]:
|
|
||||||
/** Determine if some key exists.
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
* The key to check.
|
|
||||||
* @return
|
|
||||||
* True if the key exists, false otherwise.
|
|
||||||
*/
|
|
||||||
def exists(key: String): F[Boolean]
|
|
||||||
|
|
||||||
/** Get the 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[String]]
|
|
||||||
|
|
||||||
object KeyValueProvider:
|
|
||||||
|
|
||||||
def apply[F[_]](
|
|
||||||
using
|
|
||||||
kvp: KeyValueProvider[F]
|
|
||||||
): KeyValueProvider[F] = kvp
|
|
||||||
|
|
||||||
/** @return
|
|
||||||
* New instance of the no-op [[KeyValueProvider]] implementation.
|
|
||||||
*/
|
|
||||||
def noop[F[_]: Applicative]: KeyValueProvider[F] = new Noop[F]
|
|
||||||
|
|
||||||
/** No-op implementation that never contains data.
|
|
||||||
*/
|
|
||||||
final class Noop[F[_]: Applicative] extends KeyValueProvider[F]:
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def exists(key: String): F[Boolean] = Applicative[F].pure(false)
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def get(key: String): F[Option[String]] = Applicative[F].pure(None)
|
|
||||||
|
|
||||||
end KeyValueProvider
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package gs.predicate.v0.kv
|
|
||||||
|
|
||||||
import cats.effect.Sync
|
|
||||||
import cats.effect.std.MapRef
|
|
||||||
import cats.syntax.all.*
|
|
||||||
|
|
||||||
/** Provides keys and values, given some in-memory map.
|
|
||||||
*
|
|
||||||
* @param map
|
|
||||||
* The underlying map.
|
|
||||||
*/
|
|
||||||
final class MemoryMapKeyValueProvider[F[_]: Sync](
|
|
||||||
private val map: MapRef[F, String, Option[String]]
|
|
||||||
) extends KeyValueProvider[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def exists(key: String): F[Boolean] =
|
|
||||||
map.apply(key).get.map(_.isDefined)
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def get(key: String): F[Option[String]] =
|
|
||||||
map.apply(key).get
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -12,10 +12,6 @@ object JsonKeys:
|
||||||
*/
|
*/
|
||||||
val predicates: String = "predicates"
|
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.
|
/** Used to represent any value.
|
||||||
*/
|
*/
|
||||||
val value: String = "value"
|
val value: String = "value"
|
||||||
|
|
@ -44,4 +40,8 @@ object JsonKeys:
|
||||||
*/
|
*/
|
||||||
val comparison: String = "comparison"
|
val comparison: String = "comparison"
|
||||||
|
|
||||||
|
/** Used to collect comparisons for composites such as AND and OR.
|
||||||
|
*/
|
||||||
|
val comparisons: String = "comparisons"
|
||||||
|
|
||||||
end JsonKeys
|
end JsonKeys
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package gs.predicate.v0.serde.json
|
package gs.predicate.v0.serde.json
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import gs.predicate.v0.api.And
|
import gs.predicate.v0.api.And
|
||||||
import gs.predicate.v0.api.False
|
import gs.predicate.v0.api.False
|
||||||
import gs.predicate.v0.api.Messages
|
import gs.predicate.v0.api.Messages
|
||||||
|
|
@ -8,15 +7,9 @@ import gs.predicate.v0.api.Or
|
||||||
import gs.predicate.v0.api.Predicate
|
import gs.predicate.v0.api.Predicate
|
||||||
import gs.predicate.v0.api.True
|
import gs.predicate.v0.api.True
|
||||||
import gs.predicate.v0.json.JsonComparison
|
import gs.predicate.v0.json.JsonComparison
|
||||||
import gs.predicate.v0.json.JsonProvider
|
import gs.predicate.v0.json.JsonComparisonPredicate
|
||||||
import gs.predicate.v0.json.JsonQueryComparison
|
import gs.predicate.v0.string.StringComparison
|
||||||
import gs.predicate.v0.kv.KeyExists
|
import gs.predicate.v0.string.StringComparisonPredicate
|
||||||
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.ValueNotEquals
|
|
||||||
import gs.predicate.v0.kv.ValueStartsWith
|
|
||||||
import io.circe.*
|
import io.circe.*
|
||||||
import io.circe.syntax.*
|
import io.circe.syntax.*
|
||||||
|
|
||||||
|
|
@ -27,12 +20,24 @@ import io.circe.syntax.*
|
||||||
* @return
|
* @return
|
||||||
* The JSON blob representing the [[And]].
|
* The JSON blob representing the [[And]].
|
||||||
*/
|
*/
|
||||||
def encodeAnd[F[_]](and: And[F]): Json =
|
def encodeAnd(and: And): Json =
|
||||||
Json.obj(
|
Json.obj(
|
||||||
(JsonKeys.predicateType, Json.fromString(And.PredicateType)),
|
(JsonKeys.predicateType, Json.fromString(And.PredicateType)),
|
||||||
(JsonKeys.predicates, and.ps.asJson)
|
(JsonKeys.predicates, and.ps.asJson)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def encodeJsonComparisonAnd(and: JsonComparison.And): Json =
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.And.Name)),
|
||||||
|
(JsonKeys.comparisons, and.cs.asJson)
|
||||||
|
)
|
||||||
|
|
||||||
|
def encodeStringComparisonAnd(and: StringComparison.And): Json =
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(StringComparison.And.Name)),
|
||||||
|
(JsonKeys.comparisons, and.cs.asJson)
|
||||||
|
)
|
||||||
|
|
||||||
/** Given some [[Or]] predicate, encode it as a JSON blob.
|
/** Given some [[Or]] predicate, encode it as a JSON blob.
|
||||||
*
|
*
|
||||||
* @param and
|
* @param and
|
||||||
|
|
@ -40,27 +45,36 @@ def encodeAnd[F[_]](and: And[F]): Json =
|
||||||
* @return
|
* @return
|
||||||
* The JSON blob representing the [[Or]].
|
* The JSON blob representing the [[Or]].
|
||||||
*/
|
*/
|
||||||
def encodeOr[F[_]](and: Or[F]): Json =
|
def encodeOr(and: Or): Json =
|
||||||
Json.obj(
|
Json.obj(
|
||||||
(JsonKeys.predicateType, Json.fromString(Or.PredicateType)),
|
(JsonKeys.predicateType, Json.fromString(Or.PredicateType)),
|
||||||
(JsonKeys.predicates, and.ps.asJson)
|
(JsonKeys.predicates, and.ps.asJson)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def encodeJsonComparisonOr(or: JsonComparison.Or): Json =
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.Or.Name)),
|
||||||
|
(JsonKeys.comparisons, or.cs.asJson)
|
||||||
|
)
|
||||||
|
|
||||||
|
def encodeStringComparisonOr(or: StringComparison.Or): Json =
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(StringComparison.Or.Name)),
|
||||||
|
(JsonKeys.comparisons, or.cs.asJson)
|
||||||
|
)
|
||||||
|
|
||||||
/** @return
|
/** @return
|
||||||
* Generic encoder for any [[Predicate]].
|
* Generic encoder for any [[Predicate]].
|
||||||
*/
|
*/
|
||||||
given predicateEncoder[F[_]]: Encoder[Predicate[F]] =
|
given predicateEncoder: Encoder[Predicate] =
|
||||||
Encoder.instance {
|
Encoder.instance {
|
||||||
case p: True[F] => Encoder[True[F]].apply(p)
|
case p: True.type => Encoder[True.type].apply(p)
|
||||||
case p: False[F] => Encoder[False[F]].apply(p)
|
case p: False.type => Encoder[False.type].apply(p)
|
||||||
case p: And[F] => encodeAnd[F](p)
|
case p: And => encodeAnd(p)
|
||||||
case p: Or[F] => encodeOr[F](p)
|
case p: Or => encodeOr(p)
|
||||||
case p: KeyExists[F] => Encoder[KeyExists[F]].apply(p)
|
case p: StringComparisonPredicate =>
|
||||||
case p: ValueEquals[F] => Encoder[ValueEquals[F]].apply(p)
|
Encoder[StringComparisonPredicate].apply(p)
|
||||||
case p: ValueContains[F] => Encoder[ValueContains[F]].apply(p)
|
case p: JsonComparisonPredicate => Encoder[JsonComparisonPredicate].apply(p)
|
||||||
case p: ValueStartsWith[F] => Encoder[ValueStartsWith[F]].apply(p)
|
|
||||||
case p: ValueEndsWith[F] => Encoder[ValueEndsWith[F]].apply(p)
|
|
||||||
case p: JsonQueryComparison[F] => Encoder[JsonQueryComparison[F]].apply(p)
|
|
||||||
case p =>
|
case p =>
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
s"Unsupported predicate type: '${p.predicateType}'"
|
s"Unsupported predicate type: '${p.predicateType}'"
|
||||||
|
|
@ -69,6 +83,12 @@ given predicateEncoder[F[_]]: Encoder[Predicate[F]] =
|
||||||
|
|
||||||
given jsonComparisonEncoder: Encoder[JsonComparison] =
|
given jsonComparisonEncoder: Encoder[JsonComparison] =
|
||||||
Encoder.instance[JsonComparison] {
|
Encoder.instance[JsonComparison] {
|
||||||
|
case jc: JsonComparison.And => encodeJsonComparisonAnd(jc)
|
||||||
|
case jc: JsonComparison.Or => encodeJsonComparisonOr(jc)
|
||||||
|
case jc: JsonComparison.True.type =>
|
||||||
|
Encoder[JsonComparison.True.type].apply(jc)
|
||||||
|
case jc: JsonComparison.False.type =>
|
||||||
|
Encoder[JsonComparison.False.type].apply(jc)
|
||||||
case jc: JsonComparison.Eq => Encoder[JsonComparison.Eq].apply(jc)
|
case jc: JsonComparison.Eq => Encoder[JsonComparison.Eq].apply(jc)
|
||||||
case jc: JsonComparison.Neq => Encoder[JsonComparison.Neq].apply(jc)
|
case jc: JsonComparison.Neq => Encoder[JsonComparison.Neq].apply(jc)
|
||||||
case jc: JsonComparison.StringContains =>
|
case jc: JsonComparison.StringContains =>
|
||||||
|
|
@ -104,6 +124,50 @@ given jsonComparisonEncoder: Encoder[JsonComparison] =
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
given stringComparisonEncoder: Encoder[StringComparison] =
|
||||||
|
Encoder.instance[StringComparison] {
|
||||||
|
case jc: StringComparison.And => encodeStringComparisonAnd(jc)
|
||||||
|
case jc: StringComparison.Or => encodeStringComparisonOr(jc)
|
||||||
|
case jc: StringComparison.True.type =>
|
||||||
|
Encoder[StringComparison.True.type].apply(jc)
|
||||||
|
case jc: StringComparison.False.type =>
|
||||||
|
Encoder[StringComparison.False.type].apply(jc)
|
||||||
|
case jc: StringComparison.Eq => Encoder[StringComparison.Eq].apply(jc)
|
||||||
|
case jc: StringComparison.Neq => Encoder[StringComparison.Neq].apply(jc)
|
||||||
|
case jc: StringComparison.StringContains =>
|
||||||
|
Encoder[StringComparison.StringContains].apply(jc)
|
||||||
|
case jc: StringComparison.StringPrefix =>
|
||||||
|
Encoder[StringComparison.StringPrefix].apply(jc)
|
||||||
|
case jc: StringComparison.StringSuffix =>
|
||||||
|
Encoder[StringComparison.StringSuffix].apply(jc)
|
||||||
|
case jc: StringComparison.IntLessThan =>
|
||||||
|
Encoder[StringComparison.IntLessThan].apply(jc)
|
||||||
|
case jc: StringComparison.IntLessThanOrEqualTo =>
|
||||||
|
Encoder[StringComparison.IntLessThanOrEqualTo].apply(jc)
|
||||||
|
case jc: StringComparison.IntGreaterThan =>
|
||||||
|
Encoder[StringComparison.IntGreaterThan].apply(jc)
|
||||||
|
case jc: StringComparison.IntGreaterThanOrEqualTo =>
|
||||||
|
Encoder[StringComparison.IntGreaterThanOrEqualTo].apply(jc)
|
||||||
|
case jc: StringComparison.IntBetweenInclusive =>
|
||||||
|
Encoder[StringComparison.IntBetweenInclusive].apply(jc)
|
||||||
|
case jc: StringComparison.IntBetweenExclusive =>
|
||||||
|
Encoder[StringComparison.IntBetweenExclusive].apply(jc)
|
||||||
|
case jc: StringComparison.DateEq =>
|
||||||
|
Encoder[StringComparison.DateEq].apply(jc)
|
||||||
|
case jc: StringComparison.DateBefore =>
|
||||||
|
Encoder[StringComparison.DateBefore].apply(jc)
|
||||||
|
case jc: StringComparison.DateAfter =>
|
||||||
|
Encoder[StringComparison.DateAfter].apply(jc)
|
||||||
|
case jc: StringComparison.DateBetweenInclusive =>
|
||||||
|
Encoder[StringComparison.DateBetweenInclusive].apply(jc)
|
||||||
|
case jc: StringComparison.DateBetweenExclusive =>
|
||||||
|
Encoder[StringComparison.DateBetweenExclusive].apply(jc)
|
||||||
|
case jc =>
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
s"Unsupported string comparison: '${jc.name}'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** Given some JSON cursor, decode a logical [[And]] predicate.
|
/** Given some JSON cursor, decode a logical [[And]] predicate.
|
||||||
*
|
*
|
||||||
* @param cursor
|
* @param cursor
|
||||||
|
|
@ -111,12 +175,24 @@ given jsonComparisonEncoder: Encoder[JsonComparison] =
|
||||||
* @return
|
* @return
|
||||||
* The [[And]] predicate or a decoding failure.
|
* The [[And]] predicate or a decoding failure.
|
||||||
*/
|
*/
|
||||||
def decodeAnd[F[_]: Applicative: KeyValueProvider: JsonProvider](
|
def decodeAnd(
|
||||||
cursor: HCursor
|
cursor: HCursor
|
||||||
): Either[DecodingFailure, And[F]] =
|
): Either[DecodingFailure, And] =
|
||||||
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
|
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate]]
|
||||||
yield new And(ps)
|
yield new And(ps)
|
||||||
|
|
||||||
|
def decodeJsonComparisonAnd(
|
||||||
|
cursor: HCursor
|
||||||
|
): Either[DecodingFailure, JsonComparison.And] =
|
||||||
|
for cs <- cursor.downField(JsonKeys.comparisons).as[List[JsonComparison]]
|
||||||
|
yield new JsonComparison.And(cs)
|
||||||
|
|
||||||
|
def decodeStringComparisonAnd(
|
||||||
|
cursor: HCursor
|
||||||
|
): Either[DecodingFailure, StringComparison.And] =
|
||||||
|
for cs <- cursor.downField(JsonKeys.comparisons).as[List[StringComparison]]
|
||||||
|
yield new StringComparison.And(cs)
|
||||||
|
|
||||||
/** Given some JSON cursor, decode a logical [[Or]] predicate.
|
/** Given some JSON cursor, decode a logical [[Or]] predicate.
|
||||||
*
|
*
|
||||||
* @param cursor
|
* @param cursor
|
||||||
|
|
@ -124,11 +200,22 @@ def decodeAnd[F[_]: Applicative: KeyValueProvider: JsonProvider](
|
||||||
* @return
|
* @return
|
||||||
* The [[Or]] predicate or a decoding failure.
|
* The [[Or]] predicate or a decoding failure.
|
||||||
*/
|
*/
|
||||||
def decodeOr[F[_]: Applicative: KeyValueProvider: JsonProvider](cursor: HCursor)
|
def decodeOr(cursor: HCursor): Either[DecodingFailure, Or] =
|
||||||
: Either[DecodingFailure, Or[F]] =
|
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate]]
|
||||||
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
|
|
||||||
yield new Or(ps)
|
yield new Or(ps)
|
||||||
|
|
||||||
|
def decodeJsonComparisonOr(
|
||||||
|
cursor: HCursor
|
||||||
|
): Either[DecodingFailure, JsonComparison.Or] =
|
||||||
|
for cs <- cursor.downField(JsonKeys.comparisons).as[List[JsonComparison]]
|
||||||
|
yield new JsonComparison.Or(cs)
|
||||||
|
|
||||||
|
def decodeStringComparisonOr(
|
||||||
|
cursor: HCursor
|
||||||
|
): Either[DecodingFailure, StringComparison.Or] =
|
||||||
|
for cs <- cursor.downField(JsonKeys.comparisons).as[List[StringComparison]]
|
||||||
|
yield new StringComparison.Or(cs)
|
||||||
|
|
||||||
/** Given some JSON cursor, decode any known [[Predicate]].
|
/** Given some JSON cursor, decode any known [[Predicate]].
|
||||||
*
|
*
|
||||||
* @param cursor
|
* @param cursor
|
||||||
|
|
@ -136,28 +223,20 @@ def decodeOr[F[_]: Applicative: KeyValueProvider: JsonProvider](cursor: HCursor)
|
||||||
* @return
|
* @return
|
||||||
* The decoded [[Predicate]] or some decoding failure.
|
* The decoded [[Predicate]] or some decoding failure.
|
||||||
*/
|
*/
|
||||||
def decodePredicate[F[_]: Applicative: KeyValueProvider: JsonProvider](
|
def decodePredicate(
|
||||||
cursor: HCursor
|
cursor: HCursor
|
||||||
): Either[DecodingFailure, Predicate[F]] =
|
): Either[DecodingFailure, Predicate] =
|
||||||
for
|
for
|
||||||
predicateType <- cursor.downField(JsonKeys.predicateType).as[String]
|
predicateType <- cursor.downField(JsonKeys.predicateType).as[String]
|
||||||
predicate <- predicateType match
|
predicate <- predicateType match
|
||||||
case True.PredicateType => Right(True[F])
|
case True.PredicateType => Right(True)
|
||||||
case False.PredicateType => Right(False[F])
|
case False.PredicateType => Right(False)
|
||||||
case And.PredicateType => decodeAnd[F](cursor)
|
case And.PredicateType => decodeAnd(cursor)
|
||||||
case Or.PredicateType => decodeOr[F](cursor)
|
case Or.PredicateType => decodeOr(cursor)
|
||||||
case KeyExists.PredicateType => Decoder[KeyExists[F]].apply(cursor)
|
case StringComparisonPredicate.PredicateType =>
|
||||||
case ValueEquals.PredicateType => Decoder[ValueEquals[F]].apply(cursor)
|
Decoder[StringComparisonPredicate].apply(cursor)
|
||||||
case ValueNotEquals.PredicateType =>
|
case JsonComparisonPredicate.PredicateType =>
|
||||||
Decoder[ValueNotEquals[F]].apply(cursor)
|
Decoder[JsonComparisonPredicate].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 JsonQueryComparison.PredicateType =>
|
|
||||||
Decoder[JsonQueryComparison[F]].apply(cursor)
|
|
||||||
case _ =>
|
case _ =>
|
||||||
Left(
|
Left(
|
||||||
DecodingFailure(
|
DecodingFailure(
|
||||||
|
|
@ -170,9 +249,8 @@ def decodePredicate[F[_]: Applicative: KeyValueProvider: JsonProvider](
|
||||||
/** @return
|
/** @return
|
||||||
* Generic decoder for any [[Predicate]].
|
* Generic decoder for any [[Predicate]].
|
||||||
*/
|
*/
|
||||||
given predicateDecoder[F[_]: Applicative: KeyValueProvider: JsonProvider]
|
given predicateDecoder: Decoder[Predicate] =
|
||||||
: Decoder[Predicate[F]] =
|
Decoder.instance[Predicate](cursor => decodePredicate(cursor))
|
||||||
Decoder.instance[Predicate[F]](cursor => decodePredicate[F](cursor))
|
|
||||||
|
|
||||||
/** Given some JSON cursor, decode any known JSON comparison.
|
/** Given some JSON cursor, decode any known JSON comparison.
|
||||||
*
|
*
|
||||||
|
|
@ -186,6 +264,12 @@ def decodeJsonComparison(cursor: HCursor)
|
||||||
for
|
for
|
||||||
name <- cursor.downField(JsonKeys.name).as[String]
|
name <- cursor.downField(JsonKeys.name).as[String]
|
||||||
comparison <- name match
|
comparison <- name match
|
||||||
|
case JsonComparison.And.Name => decodeJsonComparisonAnd(cursor)
|
||||||
|
case JsonComparison.Or.Name => decodeJsonComparisonOr(cursor)
|
||||||
|
case JsonComparison.True.Name =>
|
||||||
|
Decoder[JsonComparison.True.type].apply(cursor)
|
||||||
|
case JsonComparison.False.Name =>
|
||||||
|
Decoder[JsonComparison.False.type].apply(cursor)
|
||||||
case JsonComparison.Eq.Name => Decoder[JsonComparison.Eq].apply(cursor)
|
case JsonComparison.Eq.Name => Decoder[JsonComparison.Eq].apply(cursor)
|
||||||
case JsonComparison.Neq.Name => Decoder[JsonComparison.Neq].apply(cursor)
|
case JsonComparison.Neq.Name => Decoder[JsonComparison.Neq].apply(cursor)
|
||||||
case JsonComparison.StringContains.Name =>
|
case JsonComparison.StringContains.Name =>
|
||||||
|
|
@ -226,3 +310,57 @@ def decodeJsonComparison(cursor: HCursor)
|
||||||
*/
|
*/
|
||||||
given jsonComparisonDecoder: Decoder[JsonComparison] =
|
given jsonComparisonDecoder: Decoder[JsonComparison] =
|
||||||
Decoder.instance[JsonComparison](decodeJsonComparison)
|
Decoder.instance[JsonComparison](decodeJsonComparison)
|
||||||
|
|
||||||
|
def decodeStringComparison(cursor: HCursor)
|
||||||
|
: Either[DecodingFailure, StringComparison] =
|
||||||
|
for
|
||||||
|
name <- cursor.downField(JsonKeys.name).as[String]
|
||||||
|
comparison <- name match
|
||||||
|
case StringComparison.And.Name => decodeStringComparisonAnd(cursor)
|
||||||
|
case StringComparison.Or.Name => decodeStringComparisonOr(cursor)
|
||||||
|
case StringComparison.True.Name =>
|
||||||
|
Decoder[StringComparison.True.type].apply(cursor)
|
||||||
|
case StringComparison.False.Name =>
|
||||||
|
Decoder[StringComparison.False.type].apply(cursor)
|
||||||
|
case StringComparison.Eq.Name =>
|
||||||
|
Decoder[StringComparison.Eq].apply(cursor)
|
||||||
|
case StringComparison.Neq.Name =>
|
||||||
|
Decoder[StringComparison.Neq].apply(cursor)
|
||||||
|
case StringComparison.StringContains.Name =>
|
||||||
|
Decoder[StringComparison.StringContains].apply(cursor)
|
||||||
|
case StringComparison.StringPrefix.Name =>
|
||||||
|
Decoder[StringComparison.StringPrefix].apply(cursor)
|
||||||
|
case StringComparison.StringSuffix.Name =>
|
||||||
|
Decoder[StringComparison.StringSuffix].apply(cursor)
|
||||||
|
case StringComparison.IntLessThan.Name =>
|
||||||
|
Decoder[StringComparison.IntLessThan].apply(cursor)
|
||||||
|
case StringComparison.IntLessThanOrEqualTo.Name =>
|
||||||
|
Decoder[StringComparison.IntLessThanOrEqualTo].apply(cursor)
|
||||||
|
case StringComparison.IntGreaterThan.Name =>
|
||||||
|
Decoder[StringComparison.IntGreaterThan].apply(cursor)
|
||||||
|
case StringComparison.IntGreaterThanOrEqualTo.Name =>
|
||||||
|
Decoder[StringComparison.IntGreaterThanOrEqualTo].apply(cursor)
|
||||||
|
case StringComparison.IntBetweenInclusive.Name =>
|
||||||
|
Decoder[StringComparison.IntBetweenInclusive].apply(cursor)
|
||||||
|
case StringComparison.IntBetweenExclusive.Name =>
|
||||||
|
Decoder[StringComparison.IntBetweenExclusive].apply(cursor)
|
||||||
|
case StringComparison.DateEq.Name =>
|
||||||
|
Decoder[StringComparison.DateEq].apply(cursor)
|
||||||
|
case StringComparison.DateBefore.Name =>
|
||||||
|
Decoder[StringComparison.DateBefore].apply(cursor)
|
||||||
|
case StringComparison.DateAfter.Name =>
|
||||||
|
Decoder[StringComparison.DateAfter].apply(cursor)
|
||||||
|
case StringComparison.DateBetweenInclusive.Name =>
|
||||||
|
Decoder[StringComparison.DateBetweenInclusive].apply(cursor)
|
||||||
|
case StringComparison.DateBetweenExclusive.Name =>
|
||||||
|
Decoder[StringComparison.DateBetweenExclusive].apply(cursor)
|
||||||
|
case _ =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(Messages.unrecognizedStringComparison(name), Nil)
|
||||||
|
)
|
||||||
|
yield comparison
|
||||||
|
|
||||||
|
/** Generic decoder for any [[StringComparison]].
|
||||||
|
*/
|
||||||
|
given stringComparisonDecoder: Decoder[StringComparison] =
|
||||||
|
Decoder.instance[StringComparison](decodeStringComparison)
|
||||||
|
|
|
||||||
642
src/main/scala/gs/predicate/v0/string/StringComparison.scala
Normal file
642
src/main/scala/gs/predicate/v0/string/StringComparison.scala
Normal file
|
|
@ -0,0 +1,642 @@
|
||||||
|
package gs.predicate.v0.string
|
||||||
|
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
|
import gs.predicate.v0.serde.json.JsonKeys
|
||||||
|
import io.circe.Decoder
|
||||||
|
import io.circe.DecodingFailure
|
||||||
|
import io.circe.Encoder
|
||||||
|
import io.circe.Json
|
||||||
|
import java.time.LocalDate
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
/** Serializable comparisons against strings.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of the comparison - used for serialization.
|
||||||
|
*/
|
||||||
|
abstract class StringComparison(val name: String):
|
||||||
|
def compare(input: String): Boolean
|
||||||
|
|
||||||
|
object StringComparison:
|
||||||
|
|
||||||
|
object True extends StringComparison("true"):
|
||||||
|
final val Name: String = "true"
|
||||||
|
|
||||||
|
def compare(input: String): Boolean = true
|
||||||
|
|
||||||
|
given Encoder[True.type] = Encoder.instance[True.type] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[True.type] = Decoder.instance[True.type] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name => Right(True)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object False extends StringComparison("false"):
|
||||||
|
final val Name: String = "false"
|
||||||
|
|
||||||
|
def compare(input: String): Boolean = false
|
||||||
|
|
||||||
|
given Encoder[False.type] = Encoder.instance[False.type] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[False.type] = Decoder.instance[False.type] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name => Right(False)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class And(cs: List[StringComparison]) extends StringComparison(And.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
cs.map(_.compare(input)).foldLeft(true) {
|
||||||
|
(
|
||||||
|
acc,
|
||||||
|
item
|
||||||
|
) => acc && item
|
||||||
|
}
|
||||||
|
|
||||||
|
object And:
|
||||||
|
final val Name: String = "and"
|
||||||
|
|
||||||
|
case class Or(cs: List[StringComparison]) extends StringComparison(Or.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
cs.map(_.compare(input)).foldLeft(false) {
|
||||||
|
(
|
||||||
|
acc,
|
||||||
|
item
|
||||||
|
) => acc || item
|
||||||
|
}
|
||||||
|
|
||||||
|
object Or:
|
||||||
|
final val Name: String = "or"
|
||||||
|
|
||||||
|
case class Eq(target: String) extends StringComparison(Eq.Name):
|
||||||
|
def compare(input: String): Boolean = target.equals(input)
|
||||||
|
|
||||||
|
object Eq:
|
||||||
|
final val Name: String = "="
|
||||||
|
|
||||||
|
given Encoder[Eq] = Encoder.instance[Eq] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromString(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[Eq] = Decoder.instance[Eq] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[String]
|
||||||
|
yield Eq(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Neq(target: String) extends StringComparison(Neq.Name):
|
||||||
|
def compare(input: String): Boolean = !target.equals(input)
|
||||||
|
|
||||||
|
object Neq:
|
||||||
|
final val Name: String = "!="
|
||||||
|
|
||||||
|
given Encoder[Neq] = Encoder.instance[Neq] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromString(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[Neq] = Decoder.instance[Neq] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[String]
|
||||||
|
yield Neq(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class StringContains(target: String)
|
||||||
|
extends StringComparison(StringContains.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
input.contains(target)
|
||||||
|
|
||||||
|
object StringContains:
|
||||||
|
final val Name: String = "contains"
|
||||||
|
|
||||||
|
given Encoder[StringContains] = Encoder.instance[StringContains] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromString(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[StringContains] = Decoder.instance[StringContains] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[String]
|
||||||
|
yield StringContains(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class StringPrefix(target: String)
|
||||||
|
extends StringComparison(StringPrefix.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
input.startsWith(target)
|
||||||
|
|
||||||
|
object StringPrefix:
|
||||||
|
final val Name: String = "prefix"
|
||||||
|
|
||||||
|
given Encoder[StringPrefix] = Encoder.instance[StringPrefix] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromString(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[StringPrefix] = Decoder.instance[StringPrefix] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[String]
|
||||||
|
yield StringPrefix(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class StringSuffix(target: String)
|
||||||
|
extends StringComparison(StringSuffix.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
input.endsWith(target)
|
||||||
|
|
||||||
|
object StringSuffix:
|
||||||
|
final val Name: String = "suffix"
|
||||||
|
|
||||||
|
given Encoder[StringSuffix] = Encoder.instance[StringSuffix] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromString(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[StringSuffix] = Decoder.instance[StringSuffix] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[String]
|
||||||
|
yield StringSuffix(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IntLessThan(target: Int)
|
||||||
|
extends StringComparison(IntLessThan.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asInt(input).exists(_ < target)
|
||||||
|
|
||||||
|
object IntLessThan:
|
||||||
|
final val Name: String = "<"
|
||||||
|
|
||||||
|
given Encoder[IntLessThan] = Encoder.instance[IntLessThan] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromInt(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[IntLessThan] = Decoder.instance[IntLessThan] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[Int]
|
||||||
|
yield IntLessThan(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IntLessThanOrEqualTo(target: Int)
|
||||||
|
extends StringComparison(IntLessThanOrEqualTo.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asInt(input).exists(_ <= target)
|
||||||
|
|
||||||
|
object IntLessThanOrEqualTo:
|
||||||
|
final val Name: String = "<="
|
||||||
|
|
||||||
|
given Encoder[IntLessThanOrEqualTo] =
|
||||||
|
Encoder.instance[IntLessThanOrEqualTo] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromInt(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[IntLessThanOrEqualTo] =
|
||||||
|
Decoder.instance[IntLessThanOrEqualTo] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[Int]
|
||||||
|
yield IntLessThanOrEqualTo(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IntGreaterThan(target: Int)
|
||||||
|
extends StringComparison(IntGreaterThan.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asInt(input).exists(_ > target)
|
||||||
|
|
||||||
|
object IntGreaterThan:
|
||||||
|
final val Name: String = ">"
|
||||||
|
|
||||||
|
given Encoder[IntGreaterThan] = Encoder.instance[IntGreaterThan] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromInt(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[IntGreaterThan] = Decoder.instance[IntGreaterThan] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[Int]
|
||||||
|
yield IntGreaterThan(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IntGreaterThanOrEqualTo(target: Int)
|
||||||
|
extends StringComparison(IntGreaterThanOrEqualTo.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asInt(input).exists(_ >= target)
|
||||||
|
|
||||||
|
object IntGreaterThanOrEqualTo:
|
||||||
|
final val Name: String = ">="
|
||||||
|
|
||||||
|
given Encoder[IntGreaterThanOrEqualTo] =
|
||||||
|
Encoder.instance[IntGreaterThanOrEqualTo] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromInt(jc.target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[IntGreaterThanOrEqualTo] =
|
||||||
|
Decoder.instance[IntGreaterThanOrEqualTo] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[Int]
|
||||||
|
yield IntGreaterThanOrEqualTo(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IntBetweenInclusive(
|
||||||
|
lower: Int,
|
||||||
|
upper: Int
|
||||||
|
) extends StringComparison(IntBetweenInclusive.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asInt(input).exists(value => value >= lower && value <= upper)
|
||||||
|
|
||||||
|
object IntBetweenInclusive:
|
||||||
|
final val Name: String = "[]"
|
||||||
|
|
||||||
|
given Encoder[IntBetweenInclusive] =
|
||||||
|
Encoder.instance[IntBetweenInclusive] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.lower -> Json.fromInt(jc.lower),
|
||||||
|
JsonKeys.upper -> Json.fromInt(jc.upper)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[IntBetweenInclusive] =
|
||||||
|
Decoder.instance[IntBetweenInclusive] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for
|
||||||
|
lower <- cursor.downField(JsonKeys.lower).as[Int]
|
||||||
|
upper <- cursor.downField(JsonKeys.upper).as[Int]
|
||||||
|
yield IntBetweenInclusive(lower, upper)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IntBetweenExclusive(
|
||||||
|
lower: Int,
|
||||||
|
upper: Int
|
||||||
|
) extends StringComparison(IntBetweenExclusive.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asInt(input).exists(value => value > lower && value < upper)
|
||||||
|
|
||||||
|
object IntBetweenExclusive:
|
||||||
|
final val Name: String = "()"
|
||||||
|
|
||||||
|
given Encoder[IntBetweenExclusive] =
|
||||||
|
Encoder.instance[IntBetweenExclusive] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.lower -> Json.fromInt(jc.lower),
|
||||||
|
JsonKeys.upper -> Json.fromInt(jc.upper)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[IntBetweenExclusive] =
|
||||||
|
Decoder.instance[IntBetweenExclusive] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for
|
||||||
|
lower <- cursor.downField(JsonKeys.lower).as[Int]
|
||||||
|
upper <- cursor.downField(JsonKeys.upper).as[Int]
|
||||||
|
yield IntBetweenExclusive(lower, upper)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DateEq(target: LocalDate) extends StringComparison(DateEq.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asDate(input).exists(_.isEqual(target))
|
||||||
|
|
||||||
|
object DateEq:
|
||||||
|
final val Name: String = "date="
|
||||||
|
|
||||||
|
given Encoder[DateEq] = Encoder.instance[DateEq] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromString(jc.target.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[DateEq] = Decoder.instance[DateEq] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[LocalDate]
|
||||||
|
yield DateEq(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DateBefore(target: LocalDate)
|
||||||
|
extends StringComparison(DateBefore.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asDate(input).exists(_.isBefore(target))
|
||||||
|
|
||||||
|
object DateBefore:
|
||||||
|
final val Name: String = "date<"
|
||||||
|
|
||||||
|
given Encoder[DateBefore] = Encoder.instance[DateBefore] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromString(jc.target.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[DateBefore] = Decoder.instance[DateBefore] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[LocalDate]
|
||||||
|
yield DateBefore(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DateAfter(target: LocalDate)
|
||||||
|
extends StringComparison(DateAfter.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asDate(input).exists(_.isAfter(target))
|
||||||
|
|
||||||
|
object DateAfter:
|
||||||
|
final val Name: String = "date>"
|
||||||
|
|
||||||
|
given Encoder[DateAfter] = Encoder.instance[DateAfter] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.value -> Json.fromString(jc.target.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[DateAfter] = Decoder.instance[DateAfter] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[LocalDate]
|
||||||
|
yield DateAfter(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DateBetweenInclusive(
|
||||||
|
lower: LocalDate,
|
||||||
|
upper: LocalDate
|
||||||
|
) extends StringComparison(DateBetweenInclusive.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asDate(input)
|
||||||
|
.exists(value =>
|
||||||
|
dateLessThanOrEqualTo(value, upper) && dateGreaterThanOrEqualTo(
|
||||||
|
value,
|
||||||
|
lower
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
object DateBetweenInclusive:
|
||||||
|
final val Name: String = "date[]"
|
||||||
|
|
||||||
|
given Encoder[DateBetweenInclusive] =
|
||||||
|
Encoder.instance[DateBetweenInclusive] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.lower -> Json.fromString(jc.lower.toString()),
|
||||||
|
JsonKeys.upper -> Json.fromString(jc.upper.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[DateBetweenInclusive] =
|
||||||
|
Decoder.instance[DateBetweenInclusive] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for
|
||||||
|
lower <- cursor.downField(JsonKeys.lower).as[LocalDate]
|
||||||
|
upper <- cursor.downField(JsonKeys.upper).as[LocalDate]
|
||||||
|
yield DateBetweenInclusive(lower, upper)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class DateBetweenExclusive(
|
||||||
|
lower: LocalDate,
|
||||||
|
upper: LocalDate
|
||||||
|
) extends StringComparison(DateBetweenExclusive.Name):
|
||||||
|
|
||||||
|
def compare(input: String): Boolean =
|
||||||
|
asDate(input)
|
||||||
|
.exists(value => value.isAfter(lower) && value.isBefore(upper))
|
||||||
|
|
||||||
|
object DateBetweenExclusive:
|
||||||
|
final val Name: String = "date()"
|
||||||
|
|
||||||
|
given Encoder[DateBetweenExclusive] =
|
||||||
|
Encoder.instance[DateBetweenExclusive] { jc =>
|
||||||
|
Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(Name),
|
||||||
|
JsonKeys.lower -> Json.fromString(jc.lower.toString()),
|
||||||
|
JsonKeys.upper -> Json.fromString(jc.upper.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given Decoder[DateBetweenExclusive] =
|
||||||
|
Decoder.instance[DateBetweenExclusive] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for
|
||||||
|
lower <- cursor.downField(JsonKeys.lower).as[LocalDate]
|
||||||
|
upper <- cursor.downField(JsonKeys.upper).as[LocalDate]
|
||||||
|
yield DateBetweenExclusive(lower, upper)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidStringComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def asInt(input: String): Option[Int] =
|
||||||
|
input.toIntOption
|
||||||
|
|
||||||
|
private def asDate(input: String): Option[LocalDate] =
|
||||||
|
Try(LocalDate.parse(input)).toOption
|
||||||
|
|
||||||
|
private def dateLessThanOrEqualTo(
|
||||||
|
input: LocalDate,
|
||||||
|
target: LocalDate
|
||||||
|
): Boolean =
|
||||||
|
input.isBefore(target) || input.isEqual(target)
|
||||||
|
|
||||||
|
private def dateGreaterThanOrEqualTo(
|
||||||
|
input: LocalDate,
|
||||||
|
target: LocalDate
|
||||||
|
): Boolean =
|
||||||
|
input.isAfter(target) || input.isEqual(target)
|
||||||
|
|
||||||
|
end StringComparison
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package gs.predicate.v0.string
|
||||||
|
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
|
import gs.predicate.v0.api.Predicate
|
||||||
|
import gs.predicate.v0.serde.json.JsonKeys
|
||||||
|
import gs.predicate.v0.serde.json.stringComparisonDecoder
|
||||||
|
import gs.predicate.v0.serde.json.stringComparisonEncoder
|
||||||
|
import io.circe.Decoder
|
||||||
|
import io.circe.DecodingFailure
|
||||||
|
import io.circe.Encoder
|
||||||
|
import io.circe.Json
|
||||||
|
import io.circe.syntax.*
|
||||||
|
|
||||||
|
/** Predicate that matches if the given string input matches the comparison
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* @param comparison
|
||||||
|
* The string comparison that must match the input value.
|
||||||
|
*/
|
||||||
|
final class StringComparisonPredicate(
|
||||||
|
val comparison: StringComparison
|
||||||
|
) extends StringPredicate:
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
final override val predicateType: String =
|
||||||
|
StringComparisonPredicate.PredicateType
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def evalString(input: String): Predicate.Result =
|
||||||
|
Predicate.Result(comparison.compare(input))
|
||||||
|
|
||||||
|
object StringComparisonPredicate:
|
||||||
|
|
||||||
|
final val PredicateType: String = "string-comparison"
|
||||||
|
|
||||||
|
def apply(
|
||||||
|
comparison: StringComparison
|
||||||
|
): StringComparisonPredicate = new StringComparisonPredicate(comparison)
|
||||||
|
|
||||||
|
given jsonQueryComparisonEncoder: Encoder[StringComparisonPredicate] =
|
||||||
|
Encoder.instance[StringComparisonPredicate] { p =>
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.predicateType, Json.fromString(PredicateType)),
|
||||||
|
(JsonKeys.comparison, p.comparison.asJson)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given jsonQueryComparisonDecoder: Decoder[StringComparisonPredicate] =
|
||||||
|
Decoder.instance[StringComparisonPredicate] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.predicateType).as[String].flatMap {
|
||||||
|
case PredicateType =>
|
||||||
|
for comparison <- cursor
|
||||||
|
.downField(JsonKeys.comparison)
|
||||||
|
.as[StringComparison]
|
||||||
|
yield new StringComparisonPredicate(comparison)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidPredicateType(candidate, PredicateType),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end StringComparisonPredicate
|
||||||
12
src/main/scala/gs/predicate/v0/string/StringPredicate.scala
Normal file
12
src/main/scala/gs/predicate/v0/string/StringPredicate.scala
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package gs.predicate.v0.string
|
||||||
|
|
||||||
|
import gs.predicate.v0.api.Predicate
|
||||||
|
|
||||||
|
abstract class StringPredicate extends Predicate:
|
||||||
|
|
||||||
|
override def eval(input: Any): Predicate.Result =
|
||||||
|
input match
|
||||||
|
case str: String => evalString(str)
|
||||||
|
case _ => Predicate.Result.missed()
|
||||||
|
|
||||||
|
def evalString(input: String): Predicate.Result
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package support
|
|
||||||
|
|
||||||
import cats.effect.IO
|
|
||||||
import cats.effect.unsafe.IORuntime
|
|
||||||
import munit.FunSuite
|
|
||||||
import munit.Location
|
|
||||||
|
|
||||||
abstract class IOSuite extends FunSuite:
|
|
||||||
implicit val runtime: IORuntime = IORuntime.global
|
|
||||||
|
|
||||||
def iotest(
|
|
||||||
name: String
|
|
||||||
)(
|
|
||||||
body: => IO[Any]
|
|
||||||
)(
|
|
||||||
implicit
|
|
||||||
loc: Location
|
|
||||||
): Unit =
|
|
||||||
test(name)(body.unsafeRunSync())
|
|
||||||
|
|
@ -1,58 +1,47 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.effect.IO
|
class AndTests extends munit.FunSuite:
|
||||||
import support.IOSuite
|
|
||||||
|
|
||||||
class AndTests extends IOSuite:
|
test("should return true if all are true") {
|
||||||
|
val and = And(True, True, True)
|
||||||
|
val and2 = And(True, True, True)
|
||||||
|
|
||||||
iotest("should return true if all are true") {
|
val result = and.eval(())
|
||||||
val and = And(True[IO], True[IO], True[IO])
|
val result2 = and2.eval(())
|
||||||
val and2 = And(True[IO], True[IO], True[IO])
|
|
||||||
|
|
||||||
for
|
|
||||||
result <- and.eval()
|
|
||||||
result2 <- and2.eval()
|
|
||||||
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") {
|
test("should return false if any are false") {
|
||||||
val and = And(True[IO], False[IO], True[IO])
|
val and = And(True, False, True)
|
||||||
val and2 = And(True[IO], False[IO], True[IO])
|
val and2 = And(True, False, True)
|
||||||
|
|
||||||
for
|
val result = and.eval(())
|
||||||
result <- and.eval()
|
val result2 = and2.eval(())
|
||||||
result2 <- and2.eval()
|
|
||||||
yield
|
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return false for an empty list") {
|
test("should return false for an empty list") {
|
||||||
val and = And.empty[IO]
|
val and = And.empty
|
||||||
val and2 = And[IO]()
|
val and2 = And()
|
||||||
val and3 = And[IO]()
|
val and3 = And()
|
||||||
|
|
||||||
for
|
val result = and.eval(())
|
||||||
result <- and.eval()
|
val result2 = and2.eval(())
|
||||||
result2 <- and2.eval()
|
val result3 = and3.eval(())
|
||||||
result3 <- and3.eval()
|
|
||||||
yield
|
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
assertEquals(result3.unwrap(), false)
|
assertEquals(result3.unwrap(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return the underlying predicate for a singular entry") {
|
test("should return the underlying predicate for a singular entry") {
|
||||||
val p = True[IO]
|
val p = True
|
||||||
val and = And(p)
|
val and = And(p)
|
||||||
val and2 = And(p)
|
val and2 = And(p)
|
||||||
|
|
||||||
for
|
val result = and.eval(())
|
||||||
result <- and.eval()
|
val result2 = and2.eval(())
|
||||||
result2 <- and2.eval()
|
|
||||||
yield
|
|
||||||
assertEquals(result.unwrap(), true)
|
assertEquals(result.unwrap(), true)
|
||||||
assertEquals(result2.unwrap(), true)
|
assertEquals(result2.unwrap(), true)
|
||||||
assertEquals(p, and)
|
assertEquals(p, and)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.effect.IO
|
class FalseTests extends munit.FunSuite:
|
||||||
import support.IOSuite
|
|
||||||
|
|
||||||
class FalseTests extends IOSuite:
|
test("should return false") {
|
||||||
|
val predicate = False
|
||||||
iotest("should return false") {
|
val result = predicate.eval(())
|
||||||
val predicate = False[IO]
|
assertEquals(result.unwrap(), false)
|
||||||
|
|
||||||
for result <- predicate.eval()
|
|
||||||
yield assertEquals(result.unwrap(), false)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,47 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.effect.IO
|
class OrTests extends munit.FunSuite:
|
||||||
import support.IOSuite
|
|
||||||
|
|
||||||
class OrTests extends IOSuite:
|
test("should return true if any are true") {
|
||||||
|
val or = Or(False, True, False)
|
||||||
|
val or2 = Or(False, True, False)
|
||||||
|
|
||||||
iotest("should return true if any are true") {
|
val result = or.eval(())
|
||||||
val or = Or(False[IO], True[IO], False[IO])
|
val result2 = or2.eval(())
|
||||||
val or2 = Or(False[IO], True[IO], False[IO])
|
|
||||||
|
|
||||||
for
|
|
||||||
result <- or.eval()
|
|
||||||
result2 <- or2.eval()
|
|
||||||
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") {
|
test("should return false if all are false") {
|
||||||
val or = Or(False[IO], False[IO], False[IO])
|
val or = Or(False, False, False)
|
||||||
val or2 = Or(False[IO], False[IO], False[IO])
|
val or2 = Or(False, False, False)
|
||||||
|
|
||||||
for
|
val result = or.eval(())
|
||||||
result <- or.eval()
|
val result2 = or2.eval(())
|
||||||
result2 <- or2.eval()
|
|
||||||
yield
|
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return false for an empty list") {
|
test("should return false for an empty list") {
|
||||||
val or = Or.empty[IO]
|
val or = Or.empty
|
||||||
val or2 = Or[IO]()
|
val or2 = Or()
|
||||||
val or3 = Or[IO]()
|
val or3 = Or()
|
||||||
|
|
||||||
for
|
val result = or.eval(())
|
||||||
result <- or.eval()
|
val result2 = or2.eval(())
|
||||||
result2 <- or2.eval()
|
val result3 = or3.eval(())
|
||||||
result3 <- or3.eval()
|
|
||||||
yield
|
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
assertEquals(result3.unwrap(), false)
|
assertEquals(result3.unwrap(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return the underlying predicate for a singular entry") {
|
test("should return the underlying predicate for a singular entry") {
|
||||||
val p = True[IO]
|
val p = True
|
||||||
val or = Or(p)
|
val or = Or(p)
|
||||||
val or2 = Or(p)
|
val or2 = Or(p)
|
||||||
|
|
||||||
for
|
val result = or.eval(())
|
||||||
result <- or.eval()
|
val result2 = or2.eval(())
|
||||||
result2 <- or2.eval()
|
|
||||||
yield
|
|
||||||
assertEquals(result.unwrap(), true)
|
assertEquals(result.unwrap(), true)
|
||||||
assertEquals(result2.unwrap(), true)
|
assertEquals(result2.unwrap(), true)
|
||||||
assertEquals(p, or)
|
assertEquals(p, or)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.effect.IO
|
class TrueTests extends munit.FunSuite:
|
||||||
import support.IOSuite
|
|
||||||
|
|
||||||
class TrueTests extends IOSuite:
|
test("should return true") {
|
||||||
|
val predicate = True
|
||||||
iotest("should return true") {
|
val result = predicate.eval(())
|
||||||
val predicate = True[IO]
|
assertEquals(result.unwrap(), true)
|
||||||
|
|
||||||
for result <- predicate.eval()
|
|
||||||
yield assertEquals(result.unwrap(), true)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package gs.predicate.v0.json
|
||||||
|
|
||||||
|
import gs.datagen.v0.Gen
|
||||||
|
import gs.datagen.v0.generators.Size
|
||||||
|
import gs.predicate.v0.api.Predicate
|
||||||
|
import gs.predicate.v0.json.query.JsonQuery
|
||||||
|
import io.circe.Json
|
||||||
|
|
||||||
|
class JsonComparisonPredicateTests extends munit.FunSuite:
|
||||||
|
|
||||||
|
import JsonComparisonPredicateTests.Data
|
||||||
|
|
||||||
|
test("should find an exact match against some string value") {
|
||||||
|
val key = Data.keyGen.gen()
|
||||||
|
val value = Data.strValGen.gen()
|
||||||
|
val query = key
|
||||||
|
val blob = Json.obj(
|
||||||
|
key -> value
|
||||||
|
)
|
||||||
|
val jc = JsonComparison.Eq(value)
|
||||||
|
|
||||||
|
val p = JsonComparisonPredicate(JsonQuery.compile(query), jc)
|
||||||
|
val result = p.eval(blob)
|
||||||
|
assertEquals(result, Predicate.Result.matched())
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should fail to match against some non-equal string value") {
|
||||||
|
val key = Data.keyGen.gen()
|
||||||
|
val value = Data.strValGen.gen()
|
||||||
|
val searchValue = Data.strValGen.gen()
|
||||||
|
val query = key
|
||||||
|
val blob = Json.obj(
|
||||||
|
key -> value
|
||||||
|
)
|
||||||
|
val jc = JsonComparison.Eq(searchValue)
|
||||||
|
|
||||||
|
val p = JsonComparisonPredicate(JsonQuery.compile(query), jc)
|
||||||
|
val result = p.eval(blob)
|
||||||
|
assertEquals(result, Predicate.Result.missed())
|
||||||
|
}
|
||||||
|
|
||||||
|
object JsonComparisonPredicateTests:
|
||||||
|
|
||||||
|
object Data:
|
||||||
|
|
||||||
|
val keyGen: Gen[String] = Gen.string.alphaNumeric(Size.between(4, 16))
|
||||||
|
|
||||||
|
val strValGen: Gen[Json] =
|
||||||
|
Gen.string.uppercaseAlpha(Size.fixed(8)).map(Json.fromString)
|
||||||
|
|
||||||
|
end Data
|
||||||
|
|
||||||
|
end JsonComparisonPredicateTests
|
||||||
|
|
@ -10,6 +10,41 @@ import munit.FunSuite
|
||||||
class JsonComparisonTests extends FunSuite:
|
class JsonComparisonTests extends FunSuite:
|
||||||
import JsonComparisonTests.*
|
import JsonComparisonTests.*
|
||||||
|
|
||||||
|
test("should support True") {
|
||||||
|
val jc = JsonComparison.True
|
||||||
|
assert(jc.compare(Json.obj()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support False") {
|
||||||
|
val jc = JsonComparison.False
|
||||||
|
assert(!jc.compare(Json.obj()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support And") {
|
||||||
|
val jc1 = JsonComparison.And(
|
||||||
|
List(JsonComparison.True, JsonComparison.True, JsonComparison.False)
|
||||||
|
)
|
||||||
|
val jc2 = JsonComparison.And(List(JsonComparison.True, JsonComparison.True))
|
||||||
|
val jc3 = JsonComparison.And(List())
|
||||||
|
val json = Json.obj()
|
||||||
|
assert(!jc1.compare(json))
|
||||||
|
assert(jc2.compare(json))
|
||||||
|
assert(jc3.compare(json))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support Or") {
|
||||||
|
val jc1 = JsonComparison.Or(
|
||||||
|
List(JsonComparison.True, JsonComparison.True, JsonComparison.False)
|
||||||
|
)
|
||||||
|
val jc2 =
|
||||||
|
JsonComparison.Or(List(JsonComparison.False, JsonComparison.False))
|
||||||
|
val jc3 = JsonComparison.Or(List())
|
||||||
|
val json = Json.obj()
|
||||||
|
assert(jc1.compare(json))
|
||||||
|
assert(!jc2.compare(json))
|
||||||
|
assert(!jc3.compare(json))
|
||||||
|
}
|
||||||
|
|
||||||
test("should support Eq") {
|
test("should support Eq") {
|
||||||
val key = keyGen.gen()
|
val key = keyGen.gen()
|
||||||
val value = jsonStringGen.gen()
|
val value = jsonStringGen.gen()
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
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.json.query.JsonQuery
|
|
||||||
import io.circe.Json
|
|
||||||
import support.IOSuite
|
|
||||||
|
|
||||||
class JsonQueryComparisonTests extends IOSuite:
|
|
||||||
|
|
||||||
import JsonQueryComparisonTests.Data
|
|
||||||
import JsonQueryComparisonTests.newProvider
|
|
||||||
|
|
||||||
iotest("should find an exact match against some string value") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.strValGen.gen()
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
val jc = JsonComparison.Eq(value)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryComparison[IO](key, JsonQuery.compile(query), jc)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.matched()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should fail to match against some non-equal string value") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.strValGen.gen()
|
|
||||||
val searchValue = Data.strValGen.gen()
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
val jc = JsonComparison.Eq(searchValue)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryComparison[IO](key, JsonQuery.compile(query), jc)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.missed()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object JsonQueryComparisonTests:
|
|
||||||
|
|
||||||
object Data:
|
|
||||||
|
|
||||||
val keyGen: Gen[String] = Gen.string.alphaNumeric(Size.between(4, 16))
|
|
||||||
|
|
||||||
val strValGen: Gen[Json] =
|
|
||||||
Gen.string.uppercaseAlpha(Size.fixed(8)).map(Json.fromString)
|
|
||||||
|
|
||||||
end Data
|
|
||||||
|
|
||||||
def newProvider(data: Map[String, Json]): IO[JsonProvider[IO]] =
|
|
||||||
for map <- MapRef.ofSingleImmutableMap[IO, String, Json](data)
|
|
||||||
yield new MemoryMapJsonProvider(map)
|
|
||||||
|
|
||||||
end JsonQueryComparisonTests
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
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.json.query.JsonQuery
|
|
||||||
import io.circe.Json
|
|
||||||
import support.IOSuite
|
|
||||||
|
|
||||||
class JsonQueryEqualsTests extends IOSuite:
|
|
||||||
|
|
||||||
import JsonQueryEqualsTests.Data
|
|
||||||
import JsonQueryEqualsTests.newProvider
|
|
||||||
|
|
||||||
iotest("should find an exact match against some string value") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.strValGen.gen()
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryEquals[IO](key, JsonQuery.compile(query), value)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.matched()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should fail to match against some non-equal string value") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.strValGen.gen()
|
|
||||||
val searchValue = Data.strValGen.gen()
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryEquals[IO](key, JsonQuery.compile(query), searchValue)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.missed()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should find an exact match against some integer value") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.intValGen.gen()
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryEquals[IO](key, JsonQuery.compile(query), value)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.matched()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should fail to match against some non-equal integer value") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.intValGen.gen()
|
|
||||||
val searchValue = Data.intValGen.gen()
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryEquals[IO](key, JsonQuery.compile(query), searchValue)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.missed()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should find an exact match against some boolean value") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.boolValGen.gen()
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryEquals[IO](key, JsonQuery.compile(query), value)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.matched()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should fail to match against some non-equal boolean value") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Json.fromBoolean(true)
|
|
||||||
val searchValue = Json.fromBoolean(false)
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryEquals[IO](key, JsonQuery.compile(query), searchValue)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.missed()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object JsonQueryEqualsTests:
|
|
||||||
|
|
||||||
object Data:
|
|
||||||
|
|
||||||
val keyGen: Gen[String] = Gen.string.alphaNumeric(Size.between(4, 16))
|
|
||||||
|
|
||||||
val strValGen: Gen[Json] =
|
|
||||||
Gen.string.uppercaseAlpha(Size.fixed(8)).map(Json.fromString)
|
|
||||||
|
|
||||||
val intValGen: Gen[Json] = Gen.integer.inRange(0, 1000).map(Json.fromInt)
|
|
||||||
val boolValGen: Gen[Json] = Gen.boolean().map(Json.fromBoolean)
|
|
||||||
|
|
||||||
end Data
|
|
||||||
|
|
||||||
def newProvider(data: Map[String, Json]): IO[JsonProvider[IO]] =
|
|
||||||
for map <- MapRef.ofSingleImmutableMap[IO, String, Json](data)
|
|
||||||
yield new MemoryMapJsonProvider(map)
|
|
||||||
|
|
||||||
end JsonQueryEqualsTests
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
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.json.query.JsonQuery
|
|
||||||
import io.circe.Json
|
|
||||||
import support.IOSuite
|
|
||||||
|
|
||||||
class JsonQueryExistsTests extends IOSuite:
|
|
||||||
|
|
||||||
import JsonQueryExistsTests.Data
|
|
||||||
import JsonQueryExistsTests.newProvider
|
|
||||||
|
|
||||||
iotest("should find a match for an existing key") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.strValGen.gen()
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryExists[IO](key, JsonQuery.compile(query))
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.matched()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should fail to match against some non-existing key") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.strValGen.gen()
|
|
||||||
val query = "somethingelse"
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryExists[IO](key, JsonQuery.compile(query))
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.missed()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object JsonQueryExistsTests:
|
|
||||||
|
|
||||||
object Data:
|
|
||||||
|
|
||||||
val keyGen: Gen[String] = Gen.string.alphaNumeric(Size.between(4, 16))
|
|
||||||
|
|
||||||
val strValGen: Gen[Json] =
|
|
||||||
Gen.string.uppercaseAlpha(Size.fixed(8)).map(Json.fromString)
|
|
||||||
|
|
||||||
end Data
|
|
||||||
|
|
||||||
def newProvider(data: Map[String, Json]): IO[JsonProvider[IO]] =
|
|
||||||
for map <- MapRef.ofSingleImmutableMap[IO, String, Json](data)
|
|
||||||
yield new MemoryMapJsonProvider(map)
|
|
||||||
|
|
||||||
end JsonQueryExistsTests
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
package gs.predicate.v0.json
|
|
||||||
|
|
||||||
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.json.query.JsonQuery
|
|
||||||
import io.circe.Json
|
|
||||||
import support.IOSuite
|
|
||||||
|
|
||||||
class JsonQueryInTests extends IOSuite:
|
|
||||||
|
|
||||||
import JsonQueryInTests.Data
|
|
||||||
import JsonQueryInTests.newProvider
|
|
||||||
|
|
||||||
iotest("should find an exact match against some set of values") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.strValGen.gen()
|
|
||||||
val values = Set(value, Data.strValGen.gen(), Data.intValGen.gen())
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryIn[IO](key, JsonQuery.compile(query), values)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.matched()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should fail to match anything within some set of values") {
|
|
||||||
val key = Data.keyGen.gen()
|
|
||||||
val value = Data.strValGen.gen()
|
|
||||||
val searchValues = Set(Data.strValGen.gen())
|
|
||||||
val query = key
|
|
||||||
val blob = Json.obj(
|
|
||||||
key -> value
|
|
||||||
)
|
|
||||||
|
|
||||||
newProvider(Map(key -> blob)).flatMap { provider =>
|
|
||||||
given JsonProvider[IO] = provider
|
|
||||||
val p = JsonQueryIn[IO](key, JsonQuery.compile(query), searchValues)
|
|
||||||
p.eval().map(result => assertEquals(result, Predicate.Result.missed()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end JsonQueryInTests
|
|
||||||
|
|
||||||
object JsonQueryInTests:
|
|
||||||
|
|
||||||
object Data:
|
|
||||||
|
|
||||||
val keyGen: Gen[String] = Gen.string.alphaNumeric(Size.between(4, 16))
|
|
||||||
|
|
||||||
val strValGen: Gen[Json] =
|
|
||||||
Gen.string.uppercaseAlpha(Size.fixed(8)).map(Json.fromString)
|
|
||||||
|
|
||||||
val intValGen: Gen[Json] = Gen.integer.inRange(0, 1000).map(Json.fromInt)
|
|
||||||
|
|
||||||
end Data
|
|
||||||
|
|
||||||
def newProvider(data: Map[String, Json]): IO[JsonProvider[IO]] =
|
|
||||||
for map <- MapRef.ofSingleImmutableMap[IO, String, Json](data)
|
|
||||||
yield new MemoryMapJsonProvider(map)
|
|
||||||
|
|
||||||
end JsonQueryInTests
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package gs.predicate.v0.json.query
|
package gs.predicate.v0.json.query
|
||||||
|
|
||||||
import cats.Eq
|
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
|
|
||||||
class CompiledQueryEvalTests extends munit.FunSuite:
|
class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
|
|
@ -9,7 +8,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle a single, top-level key (matching case)") {
|
test("should handle a single, top-level key (matching case)") {
|
||||||
val query = compile("key")
|
val query = compile("key")
|
||||||
val expectedValue = Json.fromString("value")
|
val expectedValue = Json.fromString("value")
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, true)
|
assertEquals(result, true)
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +16,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle a single, top-level key (non-matching case)") {
|
test("should handle a single, top-level key (non-matching case)") {
|
||||||
val query = compile("missing")
|
val query = compile("missing")
|
||||||
val expectedValue = Json.fromString("value")
|
val expectedValue = Json.fromString("value")
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, false)
|
assertEquals(result, false)
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +26,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
) {
|
) {
|
||||||
val query = compile("foo.bar[any].baz[all].x")
|
val query = compile("foo.bar[any].baz[all].x")
|
||||||
val expectedValue = Json.fromInt(10)
|
val expectedValue = Json.fromInt(10)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, true)
|
assertEquals(result, true)
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +36,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
) {
|
) {
|
||||||
val query = compile("foo.bar[any].baz[all].z")
|
val query = compile("foo.bar[any].baz[all].z")
|
||||||
val expectedValue = Json.fromInt(0)
|
val expectedValue = Json.fromInt(0)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, false)
|
assertEquals(result, false)
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +46,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
) {
|
) {
|
||||||
val query = compile("foo.bar[all].baz[any].y")
|
val query = compile("foo.bar[all].baz[any].y")
|
||||||
val expectedValue = Json.fromString("a")
|
val expectedValue = Json.fromString("a")
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, true)
|
assertEquals(result, true)
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +56,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
) {
|
) {
|
||||||
val query = compile("foo.bar[all].baz[any].z")
|
val query = compile("foo.bar[all].baz[any].z")
|
||||||
val expectedValue = Json.fromInt(0)
|
val expectedValue = Json.fromInt(0)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, false)
|
assertEquals(result, false)
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +64,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle nested index (matching case)") {
|
test("should handle nested index (matching case)") {
|
||||||
val query = compile("foo.bar[0].baz[2].z")
|
val query = compile("foo.bar[0].baz[2].z")
|
||||||
val expectedValue = Json.fromInt(2)
|
val expectedValue = Json.fromInt(2)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, true)
|
assertEquals(result, true)
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +72,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle nested index (non-matching case)") {
|
test("should handle nested index (non-matching case)") {
|
||||||
val query = compile("foo.bar[0].baz[1].z")
|
val query = compile("foo.bar[0].baz[1].z")
|
||||||
val expectedValue = Json.fromInt(2)
|
val expectedValue = Json.fromInt(2)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, false)
|
assertEquals(result, false)
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +80,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle index after all (matching case)") {
|
test("should handle index after all (matching case)") {
|
||||||
val query = compile("foo.bar[all].baz[0].x")
|
val query = compile("foo.bar[all].baz[0].x")
|
||||||
val expectedValue = Json.fromInt(10)
|
val expectedValue = Json.fromInt(10)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, true)
|
assertEquals(result, true)
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +88,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle index after all (non-matching case)") {
|
test("should handle index after all (non-matching case)") {
|
||||||
val query = compile("foo.bar[all].baz[0].z")
|
val query = compile("foo.bar[all].baz[0].z")
|
||||||
val expectedValue = Json.fromInt(0)
|
val expectedValue = Json.fromInt(0)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, false)
|
assertEquals(result, false)
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +96,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle [any] raw array values (matching case)") {
|
test("should handle [any] raw array values (matching case)") {
|
||||||
val query = compile("rawValuesAny[any]")
|
val query = compile("rawValuesAny[any]")
|
||||||
val expectedValue = Json.fromInt(1)
|
val expectedValue = Json.fromInt(1)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, true)
|
assertEquals(result, true)
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +104,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle [any] raw array values (non-matching case)") {
|
test("should handle [any] raw array values (non-matching case)") {
|
||||||
val query = compile("rawValuesAny[any]")
|
val query = compile("rawValuesAny[any]")
|
||||||
val expectedValue = Json.fromInt(6)
|
val expectedValue = Json.fromInt(6)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, false)
|
assertEquals(result, false)
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +112,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle [all] raw array values (matching case)") {
|
test("should handle [all] raw array values (matching case)") {
|
||||||
val query = compile("rawValuesAll[all]")
|
val query = compile("rawValuesAll[all]")
|
||||||
val expectedValue = Json.fromInt(1)
|
val expectedValue = Json.fromInt(1)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, true)
|
assertEquals(result, true)
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +120,7 @@ class CompiledQueryEvalTests extends munit.FunSuite:
|
||||||
test("should handle [all] raw array values (non-matching case)") {
|
test("should handle [all] raw array values (non-matching case)") {
|
||||||
val query = compile("rawValuesAll[all]")
|
val query = compile("rawValuesAll[all]")
|
||||||
val expectedValue = Json.fromInt(6)
|
val expectedValue = Json.fromInt(6)
|
||||||
val p = (json: Json) => Eq[Json].eqv(json, expectedValue)
|
val p = (json: Json) => json.equals(expectedValue)
|
||||||
val result = query.eval(Data.parsedJson, p)
|
val result = query.eval(Data.parsedJson, p)
|
||||||
assertEquals(result, false)
|
assertEquals(result, false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +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 KeyExistsTests extends IOSuite:
|
|
||||||
|
|
||||||
import KeyExistsTests.Data
|
|
||||||
|
|
||||||
iotest("should find a key that exists within some provider") {
|
|
||||||
KeyExistsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
|
||||||
given KeyValueProvider[IO] = provider
|
|
||||||
val p = KeyExists[IO](Data.ExistingKey)
|
|
||||||
for result <- p.eval()
|
|
||||||
yield
|
|
||||||
// TODO: assertPredicateMatched(result)
|
|
||||||
assertEquals(result, Predicate.Result.matched())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should not find a key that does not exist within some provider") {
|
|
||||||
KeyExistsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
|
||||||
given KeyValueProvider[IO] = provider
|
|
||||||
val p = KeyExists[IO](Data.NotExistingKey)
|
|
||||||
for result <- p.eval()
|
|
||||||
yield assertEquals(result, Predicate.Result.missed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object KeyExistsTests:
|
|
||||||
|
|
||||||
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 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 KeyExistsTests
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package gs.predicate.v0.kv
|
|
||||||
|
|
||||||
import cats.Id
|
|
||||||
|
|
||||||
class KeyValueProviderTests extends munit.FunSuite:
|
|
||||||
|
|
||||||
test("should provide a no-op implementation") {
|
|
||||||
val kvp = KeyValueProvider.noop[Id]
|
|
||||||
assertEquals(kvp.exists("something"), false)
|
|
||||||
assertEquals(kvp.get("something"), None)
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +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") {
|
|
||||||
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
|
|
||||||
|
|
@ -1,100 +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 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
|
|
||||||
|
|
@ -1,101 +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 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
|
|
||||||
|
|
@ -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 ValueEqualsTests extends IOSuite:
|
|
||||||
|
|
||||||
import ValueEqualsTests.Data
|
|
||||||
|
|
||||||
iotest("should find an exact match against some value") {
|
|
||||||
ValueEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
|
||||||
given KeyValueProvider[IO] = provider
|
|
||||||
val p =
|
|
||||||
ValueEquals[IO](Data.ExistingKey, Data.ExistingValue)
|
|
||||||
for result <- p.eval()
|
|
||||||
yield assertEquals(result, Predicate.Result.matched())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should not find a value if it is not associated to a key") {
|
|
||||||
ValueEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
|
||||||
given KeyValueProvider[IO] = provider
|
|
||||||
val p =
|
|
||||||
ValueEquals[IO](Data.ExistingKey, Data.NotExistingValue)
|
|
||||||
for result <- p.eval()
|
|
||||||
yield assertEquals(result, Predicate.Result.missed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should not find a key that does not exist within some provider") {
|
|
||||||
ValueEqualsTests.newProvider(Data.KeyValues).flatMap { provider =>
|
|
||||||
given KeyValueProvider[IO] = provider
|
|
||||||
val p = ValueEquals[IO](Data.NotExistingKey, "")
|
|
||||||
for result <- p.eval()
|
|
||||||
yield assertEquals(result, Predicate.Result.missed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ValueEqualsTests:
|
|
||||||
|
|
||||||
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 ValueEqualsTests
|
|
||||||
|
|
@ -1,66 +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 ValueInTests extends IOSuite:
|
|
||||||
|
|
||||||
import ValueInTests.Data
|
|
||||||
|
|
||||||
iotest("should find an exact match against some value") {
|
|
||||||
ValueInTests.newProvider(Data.KeyValues).flatMap { provider =>
|
|
||||||
given KeyValueProvider[IO] = provider
|
|
||||||
val p =
|
|
||||||
ValueIn[IO](
|
|
||||||
Data.ExistingKey,
|
|
||||||
Set(
|
|
||||||
Data.ExistingValue,
|
|
||||||
"",
|
|
||||||
Gen.string.alphaNumeric(Size.Fixed(8)).gen()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for result <- p.eval()
|
|
||||||
yield assertEquals(result, Predicate.Result.matched())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should not find a value if it is not associated to a key") {
|
|
||||||
ValueInTests.newProvider(Data.KeyValues).flatMap { provider =>
|
|
||||||
given KeyValueProvider[IO] = provider
|
|
||||||
val p =
|
|
||||||
ValueIn[IO](Data.ExistingKey, Set(Data.NotExistingValue))
|
|
||||||
for result <- p.eval()
|
|
||||||
yield assertEquals(result, Predicate.Result.missed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should not find a key that does not exist within some provider") {
|
|
||||||
ValueInTests.newProvider(Data.KeyValues).flatMap { provider =>
|
|
||||||
given KeyValueProvider[IO] = provider
|
|
||||||
val p = ValueIn[IO](Data.NotExistingKey, Set(""))
|
|
||||||
for result <- p.eval()
|
|
||||||
yield assertEquals(result, Predicate.Result.missed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ValueInTests:
|
|
||||||
|
|
||||||
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 ValueInTests
|
|
||||||
|
|
@ -1,62 +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") {
|
|
||||||
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
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
package gs.predicate.v0.serde.json
|
package gs.predicate.v0.serde.json
|
||||||
|
|
||||||
import cats.effect.IO
|
|
||||||
import gs.predicate.v0.api.And
|
import gs.predicate.v0.api.And
|
||||||
import gs.predicate.v0.api.False
|
import gs.predicate.v0.api.False
|
||||||
import gs.predicate.v0.api.Messages
|
import gs.predicate.v0.api.Messages
|
||||||
import gs.predicate.v0.api.Or
|
import gs.predicate.v0.api.Or
|
||||||
import gs.predicate.v0.api.Predicate
|
import gs.predicate.v0.api.Predicate
|
||||||
import gs.predicate.v0.api.True
|
import gs.predicate.v0.api.True
|
||||||
import gs.predicate.v0.json.JsonProvider
|
|
||||||
import gs.predicate.v0.kv.KeyValueProvider
|
|
||||||
import io.circe.Decoder
|
import io.circe.Decoder
|
||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
|
|
@ -17,15 +14,12 @@ import munit.FunSuite
|
||||||
class ApiCodecTests extends FunSuite:
|
class ApiCodecTests extends FunSuite:
|
||||||
|
|
||||||
test("should fail to decode an unknown predicate") {
|
test("should fail to decode an unknown predicate") {
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
|
||||||
|
|
||||||
val candidate = "unrecognized"
|
val candidate = "unrecognized"
|
||||||
val json: Json = Json.obj(
|
val json: Json = Json.obj(
|
||||||
JsonKeys.predicateType -> Json.fromString(candidate)
|
JsonKeys.predicateType -> Json.fromString(candidate)
|
||||||
)
|
)
|
||||||
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(json)
|
val decoded = Decoder[Predicate].decodeJson(json)
|
||||||
assertEquals(decoded.isLeft, true)
|
assertEquals(decoded.isLeft, true)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
decoded.left.toOption.map(_.message),
|
decoded.left.toOption.map(_.message),
|
||||||
|
|
@ -34,9 +28,9 @@ class ApiCodecTests extends FunSuite:
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should serialize and deserialize a True predicate") {
|
test("should serialize and deserialize a True predicate") {
|
||||||
val p = True[IO]
|
val p = True
|
||||||
val encoded = Encoder[True[IO]].apply(p)
|
val encoded = Encoder[True.type].apply(p)
|
||||||
val decoded = Decoder[True[IO]].decodeJson(encoded)
|
val decoded = Decoder[True.type].decodeJson(encoded)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
encoded,
|
encoded,
|
||||||
Json.obj(
|
Json.obj(
|
||||||
|
|
@ -47,9 +41,9 @@ class ApiCodecTests extends FunSuite:
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should serialize and deserialize a False predicate") {
|
test("should serialize and deserialize a False predicate") {
|
||||||
val p = False[IO]
|
val p = False
|
||||||
val encoded = Encoder[False[IO]].apply(p)
|
val encoded = Encoder[False.type].apply(p)
|
||||||
val decoded = Decoder[False[IO]].decodeJson(encoded)
|
val decoded = Decoder[False.type].decodeJson(encoded)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
encoded,
|
encoded,
|
||||||
Json.obj(
|
Json.obj(
|
||||||
|
|
@ -60,16 +54,13 @@ class ApiCodecTests extends FunSuite:
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should serialize and deserialize an And predicate") {
|
test("should serialize and deserialize an And predicate") {
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
val t = True
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
val f = False
|
||||||
|
val trueJson = Encoder[True.type].apply(t)
|
||||||
val t = True[IO]
|
val falseJson = Encoder[False.type].apply(f)
|
||||||
val f = False[IO]
|
val p = And(t, t, f)
|
||||||
val trueJson = Encoder[True[IO]].apply(t)
|
val encoded = Encoder[And].apply(p.asInstanceOf[And])
|
||||||
val falseJson = Encoder[False[IO]].apply(f)
|
val decoded = Decoder[Predicate].decodeJson(encoded)
|
||||||
val p = And[IO](t, t, f)
|
|
||||||
val encoded = Encoder[And[IO]].apply(p.asInstanceOf[And[IO]])
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
encoded,
|
encoded,
|
||||||
Json.obj(
|
Json.obj(
|
||||||
|
|
@ -81,16 +72,13 @@ class ApiCodecTests extends FunSuite:
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should serialize and deserialize an Or predicate") {
|
test("should serialize and deserialize an Or predicate") {
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
val t = True
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
val f = False
|
||||||
|
val trueJson = Encoder[True.type].apply(t)
|
||||||
val t = True[IO]
|
val falseJson = Encoder[False.type].apply(f)
|
||||||
val f = False[IO]
|
val p = Or(t, t, f)
|
||||||
val trueJson = Encoder[True[IO]].apply(t)
|
val encoded = Encoder[Or].apply(p.asInstanceOf[Or])
|
||||||
val falseJson = Encoder[False[IO]].apply(f)
|
val decoded = Decoder[Predicate].decodeJson(encoded)
|
||||||
val p = Or[IO](t, t, f)
|
|
||||||
val encoded = Encoder[Or[IO]].apply(p.asInstanceOf[Or[IO]])
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
encoded,
|
encoded,
|
||||||
Json.obj(
|
Json.obj(
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
package gs.predicate.v0.serde.json
|
package gs.predicate.v0.serde.json
|
||||||
|
|
||||||
import cats.effect.IO
|
|
||||||
import gs.predicate.v0.api.Predicate
|
import gs.predicate.v0.api.Predicate
|
||||||
import gs.predicate.v0.json.JsonComparison
|
import gs.predicate.v0.json.JsonComparison
|
||||||
import gs.predicate.v0.json.JsonProvider
|
import gs.predicate.v0.json.JsonComparisonPredicate
|
||||||
import gs.predicate.v0.json.JsonQueryComparison
|
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
import gs.predicate.v0.json.query.JsonQuery
|
||||||
import gs.predicate.v0.kv.KeyValueProvider
|
|
||||||
import io.circe.Decoder
|
import io.circe.Decoder
|
||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
|
|
@ -15,36 +12,34 @@ import munit.FunSuite
|
||||||
|
|
||||||
class JsonCodecTests extends FunSuite:
|
class JsonCodecTests extends FunSuite:
|
||||||
|
|
||||||
test("should serialize and deserialize a predicate: JsonComparison") {
|
test(
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
"should serialize and deserialize a predicate: JsonComparisonPredicate"
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
) {
|
||||||
|
|
||||||
val key = "x"
|
val key = "x"
|
||||||
val value = Json.fromString("y")
|
val value = Json.fromString("y")
|
||||||
val query = JsonQuery.compile(key)
|
val query = JsonQuery.compile(key)
|
||||||
val comparison = JsonComparison.Eq(value)
|
val comparison = JsonComparison.Eq(value)
|
||||||
|
|
||||||
val p = JsonQueryComparison[IO](key, query, comparison)
|
val p = JsonComparisonPredicate(query, comparison)
|
||||||
val encoded = Encoder[JsonQueryComparison[IO]].apply(p)
|
val encoded = Encoder[JsonComparisonPredicate].apply(p)
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
val decoded = Decoder[Predicate].decodeJson(encoded)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
encoded,
|
encoded,
|
||||||
Json.obj(
|
Json.obj(
|
||||||
(
|
(
|
||||||
JsonKeys.predicateType,
|
JsonKeys.predicateType,
|
||||||
Json.fromString(JsonQueryComparison.PredicateType)
|
Json.fromString(JsonComparisonPredicate.PredicateType)
|
||||||
),
|
),
|
||||||
(JsonKeys.key, Json.fromString(key)),
|
|
||||||
(JsonKeys.query, Json.fromString(query.raw)),
|
(JsonKeys.query, Json.fromString(query.raw)),
|
||||||
(JsonKeys.comparison, comparison.asJson)
|
(JsonKeys.comparison, comparison.asJson)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
decoded.map {
|
decoded.map {
|
||||||
case d: JsonQueryComparison[?] =>
|
case d: JsonComparisonPredicate =>
|
||||||
(p.key, p.query.raw, p.comparison.asJson)
|
(p.query.raw, p.comparison.asJson)
|
||||||
case _ => fail("Decoded an unexpected predicate.")
|
case _ => fail("Decoded an unexpected predicate.")
|
||||||
},
|
},
|
||||||
Right((key, query.raw, comparison.asJson))
|
Right((query.raw, comparison.asJson))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,183 +0,0 @@
|
||||||
package gs.predicate.v0.serde.json
|
|
||||||
|
|
||||||
import cats.effect.IO
|
|
||||||
import gs.datagen.v0.Gen
|
|
||||||
import gs.datagen.v0.generators.Size
|
|
||||||
import gs.predicate.v0.api.Predicate
|
|
||||||
import gs.predicate.v0.json.JsonProvider
|
|
||||||
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.ValueNotEquals
|
|
||||||
import gs.predicate.v0.kv.ValueStartsWith
|
|
||||||
import io.circe.Decoder
|
|
||||||
import io.circe.Encoder
|
|
||||||
import io.circe.Json
|
|
||||||
import munit.FunSuite
|
|
||||||
|
|
||||||
class KeyValueCodecTests extends FunSuite:
|
|
||||||
|
|
||||||
test("should serialize and deserialize a KeyExists predicate") {
|
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
|
||||||
|
|
||||||
val key = Gen.string.alphaNumeric(Size.fixed(8)).gen()
|
|
||||||
val p = KeyExists.apply[IO](key)
|
|
||||||
val encoded = Encoder[KeyExists[IO]].apply(p)
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
|
||||||
assertEquals(
|
|
||||||
encoded,
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(KeyExists.PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(key))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(decoded.isRight, true)
|
|
||||||
assertEquals(
|
|
||||||
decoded.map {
|
|
||||||
case parsed: KeyExists[?] => parsed.key
|
|
||||||
case _ => ""
|
|
||||||
},
|
|
||||||
Right(key)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("should serialize and deserialize a ValueEquals predicate") {
|
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
|
||||||
|
|
||||||
val key = Gen.string.alphaNumeric(Size.fixed(8)).gen()
|
|
||||||
val value = Gen.string.alphaNumeric(Size.between(8, 16)).gen()
|
|
||||||
val p = ValueEquals.apply[IO](key, value)
|
|
||||||
val encoded = Encoder[ValueEquals[IO]].apply(p)
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
|
||||||
assertEquals(
|
|
||||||
encoded,
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(ValueEquals.PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(key)),
|
|
||||||
(JsonKeys.value, Json.fromString(value))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(decoded.isRight, true)
|
|
||||||
assertEquals(
|
|
||||||
decoded.map {
|
|
||||||
case parsed: ValueEquals[?] => parsed.key -> parsed.value
|
|
||||||
case _ => ""
|
|
||||||
},
|
|
||||||
Right(key -> value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("should serialize and deserialize a ValueNotEquals predicate") {
|
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
|
||||||
|
|
||||||
val key = Gen.string.alphaNumeric(Size.fixed(8)).gen()
|
|
||||||
val value = Gen.string.alphaNumeric(Size.between(8, 16)).gen()
|
|
||||||
val p = ValueNotEquals.apply[IO](key, value)
|
|
||||||
val encoded = Encoder[ValueNotEquals[IO]].apply(p)
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
|
||||||
assertEquals(
|
|
||||||
encoded,
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(ValueNotEquals.PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(key)),
|
|
||||||
(JsonKeys.value, Json.fromString(value))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(decoded.isRight, true)
|
|
||||||
assertEquals(
|
|
||||||
decoded.map {
|
|
||||||
case parsed: ValueNotEquals[?] => parsed.key -> parsed.value
|
|
||||||
case _ => ""
|
|
||||||
},
|
|
||||||
Right(key -> value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("should serialize and deserialize a ValueContains predicate") {
|
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
|
||||||
|
|
||||||
val key = Gen.string.alphaNumeric(Size.fixed(8)).gen()
|
|
||||||
val value = Gen.string.alphaNumeric(Size.between(8, 16)).gen()
|
|
||||||
val p = ValueContains.apply[IO](key, value)
|
|
||||||
val encoded = Encoder[ValueContains[IO]].apply(p)
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
|
||||||
assertEquals(
|
|
||||||
encoded,
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(ValueContains.PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(key)),
|
|
||||||
(JsonKeys.value, Json.fromString(value))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(decoded.isRight, true)
|
|
||||||
assertEquals(
|
|
||||||
decoded.map {
|
|
||||||
case parsed: ValueContains[?] => parsed.key -> parsed.containedValue
|
|
||||||
case _ => ""
|
|
||||||
},
|
|
||||||
Right(key -> value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("should serialize and deserialize a ValueStartsWith predicate") {
|
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
|
||||||
|
|
||||||
val key = Gen.string.alphaNumeric(Size.fixed(8)).gen()
|
|
||||||
val value = Gen.string.alphaNumeric(Size.between(8, 16)).gen()
|
|
||||||
val p = ValueStartsWith.apply[IO](key, value)
|
|
||||||
val encoded = Encoder[ValueStartsWith[IO]].apply(p)
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
|
||||||
assertEquals(
|
|
||||||
encoded,
|
|
||||||
Json.obj(
|
|
||||||
(
|
|
||||||
JsonKeys.predicateType,
|
|
||||||
Json.fromString(ValueStartsWith.PredicateType)
|
|
||||||
),
|
|
||||||
(JsonKeys.key, Json.fromString(key)),
|
|
||||||
(JsonKeys.value, Json.fromString(value))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(decoded.isRight, true)
|
|
||||||
assertEquals(
|
|
||||||
decoded.map {
|
|
||||||
case parsed: ValueStartsWith[?] => parsed.key -> parsed.prefix
|
|
||||||
case _ => ""
|
|
||||||
},
|
|
||||||
Right(key -> value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("should serialize and deserialize a ValueEndsWith predicate") {
|
|
||||||
given JsonProvider[IO] = JsonProvider.noop[IO]
|
|
||||||
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
|
||||||
|
|
||||||
val key = Gen.string.alphaNumeric(Size.fixed(8)).gen()
|
|
||||||
val value = Gen.string.alphaNumeric(Size.between(8, 16)).gen()
|
|
||||||
val p = ValueEndsWith.apply[IO](key, value)
|
|
||||||
val encoded = Encoder[ValueEndsWith[IO]].apply(p)
|
|
||||||
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
|
||||||
assertEquals(
|
|
||||||
encoded,
|
|
||||||
Json.obj(
|
|
||||||
(JsonKeys.predicateType, Json.fromString(ValueEndsWith.PredicateType)),
|
|
||||||
(JsonKeys.key, Json.fromString(key)),
|
|
||||||
(JsonKeys.value, Json.fromString(value))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(decoded.isRight, true)
|
|
||||||
assertEquals(
|
|
||||||
decoded.map {
|
|
||||||
case parsed: ValueEndsWith[?] => parsed.key -> parsed.suffix
|
|
||||||
case _ => ""
|
|
||||||
},
|
|
||||||
Right(key -> value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
package gs.predicate.v0.string
|
||||||
|
|
||||||
|
import gs.datagen.v0.Gen
|
||||||
|
import gs.datagen.v0.generators.MinMax
|
||||||
|
import gs.datagen.v0.generators.Size
|
||||||
|
import java.time.LocalDate
|
||||||
|
import munit.FunSuite
|
||||||
|
|
||||||
|
class StringComparisonTests extends FunSuite:
|
||||||
|
import StringComparisonTests.*
|
||||||
|
|
||||||
|
test("should support True") {
|
||||||
|
val jc = StringComparison.True
|
||||||
|
assert(jc.compare(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support False") {
|
||||||
|
val jc = StringComparison.False
|
||||||
|
assert(!jc.compare(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support And") {
|
||||||
|
val jc1 = StringComparison.And(
|
||||||
|
List(StringComparison.True, StringComparison.True, StringComparison.False)
|
||||||
|
)
|
||||||
|
val jc2 =
|
||||||
|
StringComparison.And(List(StringComparison.True, StringComparison.True))
|
||||||
|
val jc3 = StringComparison.And(List())
|
||||||
|
val str = ""
|
||||||
|
assert(!jc1.compare(str))
|
||||||
|
assert(jc2.compare(str))
|
||||||
|
assert(jc3.compare(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support Or") {
|
||||||
|
val jc1 = StringComparison.Or(
|
||||||
|
List(StringComparison.True, StringComparison.True, StringComparison.False)
|
||||||
|
)
|
||||||
|
val jc2 =
|
||||||
|
StringComparison.Or(List(StringComparison.False, StringComparison.False))
|
||||||
|
val jc3 = StringComparison.Or(List())
|
||||||
|
val str = ""
|
||||||
|
assert(jc1.compare(str))
|
||||||
|
assert(!jc2.compare(str))
|
||||||
|
assert(!jc3.compare(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support Eq") {
|
||||||
|
val value = strGen.gen()
|
||||||
|
val jc = StringComparison.Eq(value)
|
||||||
|
assert(jc.compare(value))
|
||||||
|
assert(!jc.compare(value + value))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support Neq") {
|
||||||
|
val value = strGen.gen()
|
||||||
|
val jc = StringComparison.Neq(value)
|
||||||
|
assert(!jc.compare(value))
|
||||||
|
assert(jc.compare(value + value))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support StringContains") {
|
||||||
|
val str = strGen.gen()
|
||||||
|
val substr = str.substring(3, 7)
|
||||||
|
val jc1 = StringComparison.StringContains(substr)
|
||||||
|
val jc2 = StringComparison.StringContains(str)
|
||||||
|
val jc3 = StringComparison.StringContains(longStrGen.gen())
|
||||||
|
assert(jc1.compare(str))
|
||||||
|
assert(jc2.compare(str))
|
||||||
|
assert(!jc3.compare(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support StringPrefix") {
|
||||||
|
val str = strGen.gen()
|
||||||
|
val substr = str.take(4)
|
||||||
|
val jc1 = StringComparison.StringPrefix(substr)
|
||||||
|
val jc2 = StringComparison.StringPrefix(str)
|
||||||
|
val jc3 = StringComparison.StringPrefix(longStrGen.gen())
|
||||||
|
assert(jc1.compare(str))
|
||||||
|
assert(jc2.compare(str))
|
||||||
|
assert(!jc3.compare(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support StringSuffix") {
|
||||||
|
val str = strGen.gen()
|
||||||
|
val substr = str.takeRight(4)
|
||||||
|
val jc1 = StringComparison.StringSuffix(substr)
|
||||||
|
val jc2 = StringComparison.StringSuffix(str)
|
||||||
|
val jc3 = StringComparison.StringSuffix(longStrGen.gen())
|
||||||
|
assert(jc1.compare(str))
|
||||||
|
assert(jc2.compare(str))
|
||||||
|
assert(!jc3.compare(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support IntLessThan") {
|
||||||
|
val int = intGen.gen()
|
||||||
|
val target = 101
|
||||||
|
val jc1 = StringComparison.IntLessThan(target)
|
||||||
|
val jc3 = StringComparison.IntLessThan(-1)
|
||||||
|
assert(jc1.compare(int.toString()))
|
||||||
|
assert(!jc3.compare(int.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support IntLessThanOrEqualTo") {
|
||||||
|
val int = intGen.gen()
|
||||||
|
val target = 101
|
||||||
|
val jc1 = StringComparison.IntLessThanOrEqualTo(target)
|
||||||
|
val jc2 = StringComparison.IntLessThanOrEqualTo(int)
|
||||||
|
val jc3 = StringComparison.IntLessThan(-1)
|
||||||
|
assert(jc1.compare(int.toString()))
|
||||||
|
assert(jc2.compare(int.toString()))
|
||||||
|
assert(!jc3.compare(int.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support IntGreaterThan") {
|
||||||
|
val int = intGen.gen()
|
||||||
|
val target = -1
|
||||||
|
val jc1 = StringComparison.IntGreaterThan(target)
|
||||||
|
val jc3 = StringComparison.IntGreaterThan(101)
|
||||||
|
assert(jc1.compare(int.toString()))
|
||||||
|
assert(!jc3.compare(int.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support IntGreaterThanOrEqualTo") {
|
||||||
|
val int = intGen.gen()
|
||||||
|
val target = -1
|
||||||
|
val jc1 = StringComparison.IntGreaterThanOrEqualTo(target)
|
||||||
|
val jc2 = StringComparison.IntGreaterThanOrEqualTo(int)
|
||||||
|
val jc3 = StringComparison.IntGreaterThan(101)
|
||||||
|
assert(jc1.compare(int.toString()))
|
||||||
|
assert(jc2.compare(int.toString()))
|
||||||
|
assert(!jc3.compare(int.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support IntBetweenInclusive") {
|
||||||
|
val int = intGen.gen()
|
||||||
|
val lower = 0
|
||||||
|
val upper = 100
|
||||||
|
val jc1 = StringComparison.IntBetweenInclusive(lower, upper)
|
||||||
|
val jc2 = StringComparison.IntBetweenInclusive(int, int)
|
||||||
|
val jc3 = StringComparison.IntBetweenInclusive(1000, 10000)
|
||||||
|
assert(jc1.compare(int.toString()))
|
||||||
|
assert(jc2.compare(int.toString()))
|
||||||
|
assert(!jc3.compare(int.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support IntBetweenExclusive") {
|
||||||
|
val int = intGen.gen()
|
||||||
|
val lower = -1
|
||||||
|
val upper = 101
|
||||||
|
val jc1 = StringComparison.IntBetweenExclusive(lower, upper)
|
||||||
|
val jc2 = StringComparison.IntBetweenExclusive(int, int)
|
||||||
|
val jc3 = StringComparison.IntBetweenExclusive(1000, 10000)
|
||||||
|
assert(jc1.compare(int.toString()))
|
||||||
|
assert(!jc2.compare(int.toString()))
|
||||||
|
assert(!jc3.compare(int.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support DateEq") {
|
||||||
|
val date = dateGen.gen()
|
||||||
|
val target = date
|
||||||
|
val jc1 = StringComparison.DateEq(target)
|
||||||
|
val jc2 = StringComparison.DateEq(target.plusDays(1L))
|
||||||
|
assert(jc1.compare(date.toString()))
|
||||||
|
assert(!jc2.compare(date.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support DateBefore") {
|
||||||
|
val date = dateGen.gen()
|
||||||
|
val target = date.plusDays(1L)
|
||||||
|
val jc1 = StringComparison.DateBefore(target)
|
||||||
|
val jc2 = StringComparison.DateBefore(date)
|
||||||
|
assert(jc1.compare(date.toString()))
|
||||||
|
assert(!jc2.compare(date.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support DateAfter") {
|
||||||
|
val date = dateGen.gen()
|
||||||
|
val target = date.minusDays(1L)
|
||||||
|
val jc1 = StringComparison.DateAfter(target)
|
||||||
|
val jc2 = StringComparison.DateAfter(date)
|
||||||
|
assert(jc1.compare(date.toString()))
|
||||||
|
assert(!jc2.compare(date.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support DateBetweenInclusive") {
|
||||||
|
val date = dateGen.gen()
|
||||||
|
val lower = date.minusDays(1L)
|
||||||
|
val upper = date.plusDays(1L)
|
||||||
|
val jc1 = StringComparison.DateBetweenInclusive(lower, upper)
|
||||||
|
val jc2 = StringComparison.DateBetweenInclusive(date, date)
|
||||||
|
val jc3 = StringComparison.DateBetweenInclusive(
|
||||||
|
date.plusDays(1L),
|
||||||
|
date.plusDays(1000L)
|
||||||
|
)
|
||||||
|
assert(jc1.compare(date.toString()))
|
||||||
|
assert(jc2.compare(date.toString()))
|
||||||
|
assert(!jc3.compare(date.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support DateBetweenExclusive") {
|
||||||
|
val date = dateGen.gen()
|
||||||
|
val lower = date.minusDays(1L)
|
||||||
|
val upper = date.plusDays(1L)
|
||||||
|
val jc1 = StringComparison.DateBetweenExclusive(lower, upper)
|
||||||
|
val jc2 = StringComparison.DateBetweenExclusive(date, date)
|
||||||
|
val jc3 = StringComparison.DateBetweenExclusive(
|
||||||
|
date.plusDays(1L),
|
||||||
|
date.plusDays(1000L)
|
||||||
|
)
|
||||||
|
assert(jc1.compare(date.toString()))
|
||||||
|
assert(!jc2.compare(date.toString()))
|
||||||
|
assert(!jc3.compare(date.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
object StringComparisonTests:
|
||||||
|
|
||||||
|
val keyGen: Gen[String] = Gen.string.alphaNumeric(Size.between(8, 16))
|
||||||
|
val strGen: Gen[String] = Gen.string.alphaNumeric(Size.between(8, 16))
|
||||||
|
val longStrGen: Gen[String] = Gen.string.alphaNumeric(Size.fixed(32))
|
||||||
|
val intGen: Gen[Int] = Gen.integer.inRange(0, 100)
|
||||||
|
val dateGen: Gen[LocalDate] = Gen.date.aroundToday(MinMax(1, 3))
|
||||||
|
|
||||||
|
end StringComparisonTests
|
||||||
Loading…
Add table
Reference in a new issue