More tests, more serde, prep for refactor again
This commit is contained in:
parent
d0d2925ee4
commit
05e619fae7
15 changed files with 1106 additions and 13 deletions
|
|
@ -2,10 +2,24 @@ package gs.predicate.v0.api
|
||||||
|
|
||||||
object Messages:
|
object Messages:
|
||||||
|
|
||||||
|
def unrecognizedPredicateType(
|
||||||
|
candidate: String
|
||||||
|
): String = s"Unrecognized predicate type: '$candidate'"
|
||||||
|
|
||||||
def invalidPredicateType(
|
def invalidPredicateType(
|
||||||
candidate: String,
|
candidate: String,
|
||||||
expected: String
|
expected: String
|
||||||
): String =
|
): String =
|
||||||
s"Received predicate type '$candidate' but expected '$expected'."
|
s"Received predicate type '$candidate' but expected '$expected'."
|
||||||
|
|
||||||
|
def unrecognizedJsonComparison(
|
||||||
|
candidate: String
|
||||||
|
): String = s"Unrecognized JSON comparison: '$candidate'"
|
||||||
|
|
||||||
|
def invalidJsonComparison(
|
||||||
|
candidate: String,
|
||||||
|
expected: String
|
||||||
|
): String =
|
||||||
|
s"Received JSON comparison name '$candidate' but expected '$expected'."
|
||||||
|
|
||||||
end Messages
|
end Messages
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package gs.predicate.v0.json
|
package gs.predicate.v0.json
|
||||||
|
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
import gs.predicate.v0.serde.json.JsonKeys
|
import gs.predicate.v0.serde.json.JsonKeys
|
||||||
|
import io.circe.Decoder
|
||||||
|
import io.circe.DecodingFailure
|
||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
@ -29,6 +32,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
given Decoder[Eq] = Decoder.instance[Eq] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[Json]
|
||||||
|
yield Eq(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class Neq(target: Json) extends JsonComparison(Neq.Name):
|
case class Neq(target: Json) extends JsonComparison(Neq.Name):
|
||||||
def compare(input: Json): Boolean = !target.equals(input)
|
def compare(input: Json): Boolean = !target.equals(input)
|
||||||
|
|
||||||
|
|
@ -42,6 +60,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
given Decoder[Neq] = Decoder.instance[Neq] { cursor =>
|
||||||
|
cursor.downField(JsonKeys.name).as[String].flatMap {
|
||||||
|
case Name =>
|
||||||
|
for target <- cursor.downField(JsonKeys.value).as[Json]
|
||||||
|
yield Neq(target)
|
||||||
|
case candidate =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
Messages.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class StringContains(target: String)
|
case class StringContains(target: String)
|
||||||
extends JsonComparison(StringContains.Name):
|
extends JsonComparison(StringContains.Name):
|
||||||
|
|
||||||
|
|
@ -58,6 +91,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class StringPrefix(target: String)
|
case class StringPrefix(target: String)
|
||||||
extends JsonComparison(StringPrefix.Name):
|
extends JsonComparison(StringPrefix.Name):
|
||||||
|
|
||||||
|
|
@ -74,6 +122,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class StringSuffix(target: String)
|
case class StringSuffix(target: String)
|
||||||
extends JsonComparison(StringSuffix.Name):
|
extends JsonComparison(StringSuffix.Name):
|
||||||
|
|
||||||
|
|
@ -90,6 +153,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class IntLessThan(target: Int) extends JsonComparison(IntLessThan.Name):
|
case class IntLessThan(target: Int) extends JsonComparison(IntLessThan.Name):
|
||||||
|
|
||||||
def compare(input: Json): Boolean =
|
def compare(input: Json): Boolean =
|
||||||
|
|
@ -105,6 +183,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class IntLessThanOrEqualTo(target: Int)
|
case class IntLessThanOrEqualTo(target: Int)
|
||||||
extends JsonComparison(IntLessThanOrEqualTo.Name):
|
extends JsonComparison(IntLessThanOrEqualTo.Name):
|
||||||
|
|
||||||
|
|
@ -122,6 +215,22 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class IntGreaterThan(target: Int)
|
case class IntGreaterThan(target: Int)
|
||||||
extends JsonComparison(IntGreaterThan.Name):
|
extends JsonComparison(IntGreaterThan.Name):
|
||||||
|
|
||||||
|
|
@ -138,6 +247,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class IntGreaterThanOrEqualTo(target: Int)
|
case class IntGreaterThanOrEqualTo(target: Int)
|
||||||
extends JsonComparison(IntGreaterThanOrEqualTo.Name):
|
extends JsonComparison(IntGreaterThanOrEqualTo.Name):
|
||||||
|
|
||||||
|
|
@ -155,6 +279,22 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class IntBetweenInclusive(
|
case class IntBetweenInclusive(
|
||||||
lower: Int,
|
lower: Int,
|
||||||
upper: Int
|
upper: Int
|
||||||
|
|
@ -177,6 +317,24 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class IntBetweenExclusive(
|
case class IntBetweenExclusive(
|
||||||
lower: Int,
|
lower: Int,
|
||||||
upper: Int
|
upper: Int
|
||||||
|
|
@ -199,6 +357,24 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class DateEq(target: LocalDate) extends JsonComparison(DateEq.Name):
|
case class DateEq(target: LocalDate) extends JsonComparison(DateEq.Name):
|
||||||
|
|
||||||
def compare(input: Json): Boolean =
|
def compare(input: Json): Boolean =
|
||||||
|
|
@ -214,6 +390,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class DateBefore(target: LocalDate)
|
case class DateBefore(target: LocalDate)
|
||||||
extends JsonComparison(DateBefore.Name):
|
extends JsonComparison(DateBefore.Name):
|
||||||
|
|
||||||
|
|
@ -230,6 +421,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class DateAfter(target: LocalDate)
|
case class DateAfter(target: LocalDate)
|
||||||
extends JsonComparison(DateAfter.Name):
|
extends JsonComparison(DateAfter.Name):
|
||||||
|
|
||||||
|
|
@ -246,6 +452,21 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class DateBetweenInclusive(
|
case class DateBetweenInclusive(
|
||||||
lower: LocalDate,
|
lower: LocalDate,
|
||||||
upper: LocalDate
|
upper: LocalDate
|
||||||
|
|
@ -273,6 +494,24 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class DateBetweenExclusive(
|
case class DateBetweenExclusive(
|
||||||
lower: LocalDate,
|
lower: LocalDate,
|
||||||
upper: LocalDate
|
upper: LocalDate
|
||||||
|
|
@ -295,6 +534,24 @@ object JsonComparison:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.invalidJsonComparison(candidate, Name),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def asDate(input: String): Option[LocalDate] =
|
private def asDate(input: String): Option[LocalDate] =
|
||||||
Try(LocalDate.parse(input)).toOption
|
Try(LocalDate.parse(input)).toOption
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,17 @@ package gs.predicate.v0.json
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
import gs.predicate.v0.api.Predicate
|
import gs.predicate.v0.api.Predicate
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
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
|
/** 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
|
* given key, and that blob contains the given query, and the result of the
|
||||||
|
|
@ -47,4 +56,36 @@ object JsonQueryComparison:
|
||||||
): JsonQueryComparison[F] =
|
): JsonQueryComparison[F] =
|
||||||
new JsonQueryComparison[F](key, query, comparison)
|
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
|
end JsonQueryComparison
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@ package gs.predicate.v0.json
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
import gs.predicate.v0.api.Predicate
|
import gs.predicate.v0.api.Predicate
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
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
|
import io.circe.Json
|
||||||
|
|
||||||
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
||||||
|
|
@ -46,4 +51,34 @@ object JsonQueryEquals:
|
||||||
): JsonQueryEquals[F] =
|
): JsonQueryEquals[F] =
|
||||||
new JsonQueryEquals[F](key, query, value)
|
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
|
end JsonQueryEquals
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@ package gs.predicate.v0.json
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
import gs.predicate.v0.api.Predicate
|
import gs.predicate.v0.api.Predicate
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
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
|
import io.circe.Json
|
||||||
|
|
||||||
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
||||||
|
|
@ -46,4 +51,34 @@ object JsonQueryIn:
|
||||||
): JsonQueryIn[F] =
|
): JsonQueryIn[F] =
|
||||||
new JsonQueryIn[F](key, query, values)
|
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
|
end JsonQueryIn
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@ package gs.predicate.v0.json
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
import gs.predicate.v0.api.Predicate
|
import gs.predicate.v0.api.Predicate
|
||||||
import gs.predicate.v0.json.query.JsonQuery
|
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
|
import io.circe.Json
|
||||||
|
|
||||||
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
/** Predicate that matches if the JSON provider contains a JSON blob with the
|
||||||
|
|
@ -47,4 +52,34 @@ object JsonQueryNotEquals:
|
||||||
): JsonQueryNotEquals[F] =
|
): JsonQueryNotEquals[F] =
|
||||||
new JsonQueryNotEquals[F](key, query, value)
|
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
|
end JsonQueryNotEquals
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ import scala.util.matching.Regex
|
||||||
* Maximum allowed query depth.
|
* Maximum allowed query depth.
|
||||||
*/
|
*/
|
||||||
final class CompiledQuery private (
|
final class CompiledQuery private (
|
||||||
|
val raw: String,
|
||||||
private val parts: List[CompiledQuery.QueryPart],
|
private val parts: List[CompiledQuery.QueryPart],
|
||||||
private val reader: Json => List[Json],
|
private val reader: Json => List[Json],
|
||||||
private val maxQueryDepth: Int
|
private val maxQueryDepth: Int
|
||||||
|
|
@ -237,12 +238,14 @@ object CompiledQuery:
|
||||||
)
|
)
|
||||||
else if queryParts.exists(_.hasListTraversal) then
|
else if queryParts.exists(_.hasListTraversal) then
|
||||||
new CompiledQuery(
|
new CompiledQuery(
|
||||||
|
query,
|
||||||
queryParts,
|
queryParts,
|
||||||
readerWithTraversal(queryParts),
|
readerWithTraversal(queryParts),
|
||||||
maxQueryDepth
|
maxQueryDepth
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
new CompiledQuery(
|
new CompiledQuery(
|
||||||
|
query,
|
||||||
queryParts,
|
queryParts,
|
||||||
readerWithoutTraversal(queryParts),
|
readerWithoutTraversal(queryParts),
|
||||||
maxQueryDepth
|
maxQueryDepth
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import io.circe.Json
|
||||||
/** Implementation of [[JsonQuery]] that always refers to the input value as-is.
|
/** Implementation of [[JsonQuery]] that always refers to the input value as-is.
|
||||||
*/
|
*/
|
||||||
object EmptyQuery extends JsonQuery:
|
object EmptyQuery extends JsonQuery:
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
final val raw: String = ""
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,20 @@
|
||||||
package gs.predicate.v0.json.query
|
package gs.predicate.v0.json.query
|
||||||
|
|
||||||
|
import io.circe.Decoder
|
||||||
|
import io.circe.DecodingFailure
|
||||||
|
import io.circe.Encoder
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
|
import scala.util.Failure
|
||||||
|
import scala.util.Success
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
/** Predicate-integrated queries for JSON objects.
|
/** Predicate-integrated queries for JSON objects.
|
||||||
*/
|
*/
|
||||||
trait JsonQuery:
|
trait JsonQuery:
|
||||||
|
/** @return
|
||||||
|
* The raw query string.
|
||||||
|
*/
|
||||||
|
def raw: String
|
||||||
|
|
||||||
/** Query the given JSON value and evaluate the predicate as part of that
|
/** Query the given JSON value and evaluate the predicate as part of that
|
||||||
* query.
|
* query.
|
||||||
|
|
@ -48,4 +58,21 @@ object JsonQuery:
|
||||||
*/
|
*/
|
||||||
def compile(query: String): JsonQuery = CompiledQuery.compile(query)
|
def compile(query: String): JsonQuery = CompiledQuery.compile(query)
|
||||||
|
|
||||||
|
given Encoder[JsonQuery] =
|
||||||
|
Encoder.instance[JsonQuery](jq => Json.fromString(jq.raw))
|
||||||
|
|
||||||
|
given Decoder[JsonQuery] = Decoder.instance[JsonQuery] { cursor =>
|
||||||
|
cursor.as[String].flatMap { raw =>
|
||||||
|
Try(compile(raw)) match
|
||||||
|
case Success(query) => Right(query)
|
||||||
|
case Failure(exception) =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(
|
||||||
|
s"Failed to parse JsonQuery - could not compile the query: '${exception.getMessage()}'",
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
end JsonQuery
|
end JsonQuery
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,12 @@ object JsonKeys:
|
||||||
*/
|
*/
|
||||||
val upper: String = "upper"
|
val upper: String = "upper"
|
||||||
|
|
||||||
|
/** Represents JSON queries.
|
||||||
|
*/
|
||||||
|
val query: String = "query"
|
||||||
|
|
||||||
|
/** Represents JSON comparisons.
|
||||||
|
*/
|
||||||
|
val comparison: String = "comparison"
|
||||||
|
|
||||||
end JsonKeys
|
end JsonKeys
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@ package gs.predicate.v0.serde.json
|
||||||
import cats.Applicative
|
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.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.JsonComparison
|
||||||
import gs.predicate.v0.json.JsonProvider
|
import gs.predicate.v0.json.JsonProvider
|
||||||
|
import gs.predicate.v0.json.JsonQueryComparison
|
||||||
import gs.predicate.v0.kv.KeyExists
|
import gs.predicate.v0.kv.KeyExists
|
||||||
import gs.predicate.v0.kv.KeyValueProvider
|
import gs.predicate.v0.kv.KeyValueProvider
|
||||||
import gs.predicate.v0.kv.ValueContains
|
import gs.predicate.v0.kv.ValueContains
|
||||||
|
|
@ -48,18 +51,56 @@ def encodeOr[F[_]](and: Or[F]): Json =
|
||||||
*/
|
*/
|
||||||
given predicateEncoder[F[_]]: Encoder[Predicate[F]] =
|
given predicateEncoder[F[_]]: Encoder[Predicate[F]] =
|
||||||
Encoder.instance {
|
Encoder.instance {
|
||||||
case p: True[F] => Encoder[True[F]].apply(p)
|
case p: True[F] => Encoder[True[F]].apply(p)
|
||||||
case p: False[F] => Encoder[False[F]].apply(p)
|
case p: False[F] => Encoder[False[F]].apply(p)
|
||||||
case p: And[F] => encodeAnd[F](p)
|
case p: And[F] => encodeAnd[F](p)
|
||||||
case p: Or[F] => encodeOr[F](p)
|
case p: Or[F] => encodeOr[F](p)
|
||||||
case p: KeyExists[F] => Encoder[KeyExists[F]].apply(p)
|
case p: KeyExists[F] => Encoder[KeyExists[F]].apply(p)
|
||||||
case p: ValueEquals[F] => Encoder[ValueEquals[F]].apply(p)
|
case p: ValueEquals[F] => Encoder[ValueEquals[F]].apply(p)
|
||||||
case p: ValueContains[F] => Encoder[ValueContains[F]].apply(p)
|
case p: ValueContains[F] => Encoder[ValueContains[F]].apply(p)
|
||||||
case p: ValueStartsWith[F] => Encoder[ValueStartsWith[F]].apply(p)
|
case p: ValueStartsWith[F] => Encoder[ValueStartsWith[F]].apply(p)
|
||||||
case p: ValueEndsWith[F] => Encoder[ValueEndsWith[F]].apply(p)
|
case p: ValueEndsWith[F] => Encoder[ValueEndsWith[F]].apply(p)
|
||||||
case p =>
|
case p: JsonQueryComparison[F] => Encoder[JsonQueryComparison[F]].apply(p)
|
||||||
|
case p =>
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
s"Unsupported predicate type: ${p.predicateType}"
|
s"Unsupported predicate type: '${p.predicateType}'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
given jsonComparisonEncoder: Encoder[JsonComparison] =
|
||||||
|
Encoder.instance[JsonComparison] {
|
||||||
|
case jc: JsonComparison.Eq => Encoder[JsonComparison.Eq].apply(jc)
|
||||||
|
case jc: JsonComparison.Neq => Encoder[JsonComparison.Neq].apply(jc)
|
||||||
|
case jc: JsonComparison.StringContains =>
|
||||||
|
Encoder[JsonComparison.StringContains].apply(jc)
|
||||||
|
case jc: JsonComparison.StringPrefix =>
|
||||||
|
Encoder[JsonComparison.StringPrefix].apply(jc)
|
||||||
|
case jc: JsonComparison.StringSuffix =>
|
||||||
|
Encoder[JsonComparison.StringSuffix].apply(jc)
|
||||||
|
case jc: JsonComparison.IntLessThan =>
|
||||||
|
Encoder[JsonComparison.IntLessThan].apply(jc)
|
||||||
|
case jc: JsonComparison.IntLessThanOrEqualTo =>
|
||||||
|
Encoder[JsonComparison.IntLessThanOrEqualTo].apply(jc)
|
||||||
|
case jc: JsonComparison.IntGreaterThan =>
|
||||||
|
Encoder[JsonComparison.IntGreaterThan].apply(jc)
|
||||||
|
case jc: JsonComparison.IntGreaterThanOrEqualTo =>
|
||||||
|
Encoder[JsonComparison.IntGreaterThanOrEqualTo].apply(jc)
|
||||||
|
case jc: JsonComparison.IntBetweenInclusive =>
|
||||||
|
Encoder[JsonComparison.IntBetweenInclusive].apply(jc)
|
||||||
|
case jc: JsonComparison.IntBetweenExclusive =>
|
||||||
|
Encoder[JsonComparison.IntBetweenExclusive].apply(jc)
|
||||||
|
case jc: JsonComparison.DateEq => Encoder[JsonComparison.DateEq].apply(jc)
|
||||||
|
case jc: JsonComparison.DateBefore =>
|
||||||
|
Encoder[JsonComparison.DateBefore].apply(jc)
|
||||||
|
case jc: JsonComparison.DateAfter =>
|
||||||
|
Encoder[JsonComparison.DateAfter].apply(jc)
|
||||||
|
case jc: JsonComparison.DateBetweenInclusive =>
|
||||||
|
Encoder[JsonComparison.DateBetweenInclusive].apply(jc)
|
||||||
|
case jc: JsonComparison.DateBetweenExclusive =>
|
||||||
|
Encoder[JsonComparison.DateBetweenExclusive].apply(jc)
|
||||||
|
case jc =>
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
s"Unsupported JSON comparison: '${jc.name}'"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,9 +156,14 @@ def decodePredicate[F[_]: Applicative: KeyValueProvider: JsonProvider](
|
||||||
Decoder[ValueStartsWith[F]].apply(cursor)
|
Decoder[ValueStartsWith[F]].apply(cursor)
|
||||||
case ValueEndsWith.PredicateType =>
|
case ValueEndsWith.PredicateType =>
|
||||||
Decoder[ValueEndsWith[F]].apply(cursor)
|
Decoder[ValueEndsWith[F]].apply(cursor)
|
||||||
case predicateType =>
|
case JsonQueryComparison.PredicateType =>
|
||||||
|
Decoder[JsonQueryComparison[F]].apply(cursor)
|
||||||
|
case _ =>
|
||||||
Left(
|
Left(
|
||||||
DecodingFailure(s"Unrecognized predicate type: '$predicateType'", Nil)
|
DecodingFailure(
|
||||||
|
Messages.unrecognizedPredicateType(predicateType),
|
||||||
|
Nil
|
||||||
|
)
|
||||||
)
|
)
|
||||||
yield predicate
|
yield predicate
|
||||||
|
|
||||||
|
|
@ -127,3 +173,56 @@ def decodePredicate[F[_]: Applicative: KeyValueProvider: JsonProvider](
|
||||||
given predicateDecoder[F[_]: Applicative: KeyValueProvider: JsonProvider]
|
given predicateDecoder[F[_]: Applicative: KeyValueProvider: JsonProvider]
|
||||||
: Decoder[Predicate[F]] =
|
: Decoder[Predicate[F]] =
|
||||||
Decoder.instance[Predicate[F]](cursor => decodePredicate[F](cursor))
|
Decoder.instance[Predicate[F]](cursor => decodePredicate[F](cursor))
|
||||||
|
|
||||||
|
/** Given some JSON cursor, decode any known JSON comparison.
|
||||||
|
*
|
||||||
|
* @param cursor
|
||||||
|
* The cursor.
|
||||||
|
* @return
|
||||||
|
* The decoded [[JsonComparison]] or a decoding failure.
|
||||||
|
*/
|
||||||
|
def decodeJsonComparison(cursor: HCursor)
|
||||||
|
: Either[DecodingFailure, JsonComparison] =
|
||||||
|
for
|
||||||
|
name <- cursor.downField(JsonKeys.name).as[String]
|
||||||
|
comparison <- name match
|
||||||
|
case JsonComparison.Eq.Name => Decoder[JsonComparison.Eq].apply(cursor)
|
||||||
|
case JsonComparison.Neq.Name => Decoder[JsonComparison.Neq].apply(cursor)
|
||||||
|
case JsonComparison.StringContains.Name =>
|
||||||
|
Decoder[JsonComparison.StringContains].apply(cursor)
|
||||||
|
case JsonComparison.StringPrefix.Name =>
|
||||||
|
Decoder[JsonComparison.StringPrefix].apply(cursor)
|
||||||
|
case JsonComparison.StringSuffix.Name =>
|
||||||
|
Decoder[JsonComparison.StringSuffix].apply(cursor)
|
||||||
|
case JsonComparison.IntLessThan.Name =>
|
||||||
|
Decoder[JsonComparison.IntLessThan].apply(cursor)
|
||||||
|
case JsonComparison.IntLessThanOrEqualTo.Name =>
|
||||||
|
Decoder[JsonComparison.IntLessThanOrEqualTo].apply(cursor)
|
||||||
|
case JsonComparison.IntGreaterThan.Name =>
|
||||||
|
Decoder[JsonComparison.IntGreaterThan].apply(cursor)
|
||||||
|
case JsonComparison.IntGreaterThanOrEqualTo.Name =>
|
||||||
|
Decoder[JsonComparison.IntGreaterThanOrEqualTo].apply(cursor)
|
||||||
|
case JsonComparison.IntBetweenInclusive.Name =>
|
||||||
|
Decoder[JsonComparison.IntBetweenInclusive].apply(cursor)
|
||||||
|
case JsonComparison.IntBetweenExclusive.Name =>
|
||||||
|
Decoder[JsonComparison.IntBetweenExclusive].apply(cursor)
|
||||||
|
case JsonComparison.DateEq.Name =>
|
||||||
|
Decoder[JsonComparison.DateEq].apply(cursor)
|
||||||
|
case JsonComparison.DateBefore.Name =>
|
||||||
|
Decoder[JsonComparison.DateBefore].apply(cursor)
|
||||||
|
case JsonComparison.DateAfter.Name =>
|
||||||
|
Decoder[JsonComparison.DateAfter].apply(cursor)
|
||||||
|
case JsonComparison.DateBetweenInclusive.Name =>
|
||||||
|
Decoder[JsonComparison.DateBetweenInclusive].apply(cursor)
|
||||||
|
case JsonComparison.DateBetweenExclusive.Name =>
|
||||||
|
Decoder[JsonComparison.DateBetweenExclusive].apply(cursor)
|
||||||
|
case _ =>
|
||||||
|
Left(
|
||||||
|
DecodingFailure(Messages.unrecognizedJsonComparison(name), Nil)
|
||||||
|
)
|
||||||
|
yield comparison
|
||||||
|
|
||||||
|
/** Generic decoder for any [[JsonComparison]].
|
||||||
|
*/
|
||||||
|
given jsonComparisonDecoder: Decoder[JsonComparison] =
|
||||||
|
Decoder.instance[JsonComparison](decodeJsonComparison)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
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
|
||||||
|
|
@ -3,6 +3,7 @@ package gs.predicate.v0.serde.json
|
||||||
import cats.effect.IO
|
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.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
|
||||||
|
|
@ -15,6 +16,23 @@ import munit.FunSuite
|
||||||
|
|
||||||
class ApiCodecTests extends FunSuite:
|
class ApiCodecTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should fail to decode an unknown predicate") {
|
||||||
|
given JsonProvider[IO] = JsonProvider.noop[IO]
|
||||||
|
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
||||||
|
|
||||||
|
val candidate = "unrecognized"
|
||||||
|
val json: Json = Json.obj(
|
||||||
|
JsonKeys.predicateType -> Json.fromString(candidate)
|
||||||
|
)
|
||||||
|
|
||||||
|
val decoded = Decoder[Predicate[IO]].decodeJson(json)
|
||||||
|
assertEquals(decoded.isLeft, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.left.toOption.map(_.message),
|
||||||
|
Some(Messages.unrecognizedPredicateType(candidate))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
test("should serialize and deserialize a True predicate") {
|
test("should serialize and deserialize a True predicate") {
|
||||||
val p = True[IO]
|
val p = True[IO]
|
||||||
val encoded = Encoder[True[IO]].apply(p)
|
val encoded = Encoder[True[IO]].apply(p)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package gs.predicate.v0.serde.json
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import gs.predicate.v0.api.Predicate
|
||||||
|
import gs.predicate.v0.json.JsonComparison
|
||||||
|
import gs.predicate.v0.json.JsonProvider
|
||||||
|
import gs.predicate.v0.json.JsonQueryComparison
|
||||||
|
import gs.predicate.v0.json.query.JsonQuery
|
||||||
|
import gs.predicate.v0.kv.KeyValueProvider
|
||||||
|
import io.circe.Decoder
|
||||||
|
import io.circe.Encoder
|
||||||
|
import io.circe.Json
|
||||||
|
import io.circe.syntax.*
|
||||||
|
import munit.FunSuite
|
||||||
|
|
||||||
|
class JsonCodecTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should serialize and deserialize a predicate: JsonComparison") {
|
||||||
|
given JsonProvider[IO] = JsonProvider.noop[IO]
|
||||||
|
given KeyValueProvider[IO] = KeyValueProvider.noop[IO]
|
||||||
|
|
||||||
|
val key = "x"
|
||||||
|
val value = Json.fromString("y")
|
||||||
|
val query = JsonQuery.compile(key)
|
||||||
|
val comparison = JsonComparison.Eq(value)
|
||||||
|
|
||||||
|
val p = JsonQueryComparison[IO](key, query, comparison)
|
||||||
|
val encoded = Encoder[JsonQueryComparison[IO]].apply(p)
|
||||||
|
val decoded = Decoder[Predicate[IO]].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(
|
||||||
|
JsonKeys.predicateType,
|
||||||
|
Json.fromString(JsonQueryComparison.PredicateType)
|
||||||
|
),
|
||||||
|
(JsonKeys.key, Json.fromString(key)),
|
||||||
|
(JsonKeys.query, Json.fromString(query.raw)),
|
||||||
|
(JsonKeys.comparison, comparison.asJson)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonQueryComparison[?] =>
|
||||||
|
(p.key, p.query.raw, p.comparison.asJson)
|
||||||
|
case _ => fail("Decoded an unexpected predicate.")
|
||||||
|
},
|
||||||
|
Right((key, query.raw, comparison.asJson))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,403 @@
|
||||||
|
package gs.predicate.v0.serde.json
|
||||||
|
|
||||||
|
import gs.predicate.v0.api.Messages
|
||||||
|
import gs.predicate.v0.json.JsonComparison
|
||||||
|
import io.circe.Decoder
|
||||||
|
import io.circe.Json
|
||||||
|
import io.circe.syntax._
|
||||||
|
import java.time.LocalDate
|
||||||
|
import munit.FunSuite
|
||||||
|
|
||||||
|
class JsonComparisonCodecTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should fail to decode an unknown comparison") {
|
||||||
|
val candidate = "unrecognized"
|
||||||
|
val json: Json = Json.obj(
|
||||||
|
JsonKeys.name -> Json.fromString(candidate)
|
||||||
|
)
|
||||||
|
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(json)
|
||||||
|
assertEquals(decoded.isLeft, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.left.toOption.map(_.message),
|
||||||
|
Some(Messages.unrecognizedJsonComparison(candidate))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: Eq") {
|
||||||
|
val target: Json = Json.obj("x" -> Json.fromString("y"))
|
||||||
|
val jc = JsonComparison.Eq(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.Eq.Name)),
|
||||||
|
(JsonKeys.value, target)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.Eq => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: Neq") {
|
||||||
|
val target: Json = Json.obj("x" -> Json.fromString("y"))
|
||||||
|
val jc = JsonComparison.Neq(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.Neq.Name)),
|
||||||
|
(JsonKeys.value, target)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.Neq => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: StringContains") {
|
||||||
|
val target = "target"
|
||||||
|
val jc = JsonComparison.StringContains(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.StringContains.Name)),
|
||||||
|
(JsonKeys.value, Json.fromString(target))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.StringContains => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: StringPrefix") {
|
||||||
|
val target = "target"
|
||||||
|
val jc = JsonComparison.StringPrefix(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.StringPrefix.Name)),
|
||||||
|
(JsonKeys.value, Json.fromString(target))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.StringPrefix => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: StringSuffix") {
|
||||||
|
val target = "target"
|
||||||
|
val jc = JsonComparison.StringSuffix(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.StringSuffix.Name)),
|
||||||
|
(JsonKeys.value, Json.fromString(target))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.StringSuffix => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: IntLessThan") {
|
||||||
|
val target = 10
|
||||||
|
val jc = JsonComparison.IntLessThan(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.IntLessThan.Name)),
|
||||||
|
(JsonKeys.value, Json.fromInt(target))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.IntLessThan => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: IntLessThanOrEqualTo") {
|
||||||
|
val target = 10
|
||||||
|
val jc = JsonComparison.IntLessThanOrEqualTo(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(
|
||||||
|
JsonKeys.name,
|
||||||
|
Json.fromString(JsonComparison.IntLessThanOrEqualTo.Name)
|
||||||
|
),
|
||||||
|
(JsonKeys.value, Json.fromInt(target))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.IntLessThanOrEqualTo => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: IntGreaterThan") {
|
||||||
|
val target = 10
|
||||||
|
val jc = JsonComparison.IntGreaterThan(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.IntGreaterThan.Name)),
|
||||||
|
(JsonKeys.value, Json.fromInt(target))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.IntGreaterThan => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: IntGreaterThanOrEqualTo") {
|
||||||
|
val target = 10
|
||||||
|
val jc = JsonComparison.IntGreaterThanOrEqualTo(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(
|
||||||
|
JsonKeys.name,
|
||||||
|
Json.fromString(JsonComparison.IntGreaterThanOrEqualTo.Name)
|
||||||
|
),
|
||||||
|
(JsonKeys.value, Json.fromInt(target))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.IntGreaterThanOrEqualTo => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: IntBetweenInclusive") {
|
||||||
|
val lower = 1
|
||||||
|
val upper = 10
|
||||||
|
val jc = JsonComparison.IntBetweenInclusive(lower, upper)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(
|
||||||
|
JsonKeys.name,
|
||||||
|
Json.fromString(JsonComparison.IntBetweenInclusive.Name)
|
||||||
|
),
|
||||||
|
(JsonKeys.lower, Json.fromInt(lower)),
|
||||||
|
(JsonKeys.upper, Json.fromInt(upper))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.IntBetweenInclusive => d.lower -> d.upper
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(lower -> upper)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: IntBetweenExclusive") {
|
||||||
|
val lower = 1
|
||||||
|
val upper = 10
|
||||||
|
val jc = JsonComparison.IntBetweenExclusive(lower, upper)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(
|
||||||
|
JsonKeys.name,
|
||||||
|
Json.fromString(JsonComparison.IntBetweenExclusive.Name)
|
||||||
|
),
|
||||||
|
(JsonKeys.lower, Json.fromInt(lower)),
|
||||||
|
(JsonKeys.upper, Json.fromInt(upper))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.IntBetweenExclusive => d.lower -> d.upper
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(lower -> upper)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: DateEq") {
|
||||||
|
val target = LocalDate.now()
|
||||||
|
val jc = JsonComparison.DateEq(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.DateEq.Name)),
|
||||||
|
(JsonKeys.value, Json.fromString(target.toString()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.DateEq => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: DateBefore") {
|
||||||
|
val target = LocalDate.now()
|
||||||
|
val jc = JsonComparison.DateBefore(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.DateBefore.Name)),
|
||||||
|
(JsonKeys.value, Json.fromString(target.toString()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.DateBefore => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: DateAfter") {
|
||||||
|
val target = LocalDate.now()
|
||||||
|
val jc = JsonComparison.DateAfter(target)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(JsonKeys.name, Json.fromString(JsonComparison.DateAfter.Name)),
|
||||||
|
(JsonKeys.value, Json.fromString(target.toString()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.DateAfter => d.target
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(target)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: DateBetweenInclusive") {
|
||||||
|
val lower = LocalDate.now().minusDays(1L)
|
||||||
|
val upper = LocalDate.now().plusDays(1L)
|
||||||
|
val jc = JsonComparison.DateBetweenInclusive(lower, upper)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(
|
||||||
|
JsonKeys.name,
|
||||||
|
Json.fromString(JsonComparison.DateBetweenInclusive.Name)
|
||||||
|
),
|
||||||
|
(JsonKeys.lower, Json.fromString(lower.toString())),
|
||||||
|
(JsonKeys.upper, Json.fromString(upper.toString()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.DateBetweenInclusive => d.lower -> d.upper
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(lower -> upper)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should serialize and deserialize comparison: DateBetweenExclusive") {
|
||||||
|
val lower = LocalDate.now().minusDays(1L)
|
||||||
|
val upper = LocalDate.now().plusDays(1L)
|
||||||
|
val jc = JsonComparison.DateBetweenExclusive(lower, upper)
|
||||||
|
val encoded = jc.asJson
|
||||||
|
val decoded = Decoder[JsonComparison].decodeJson(encoded)
|
||||||
|
assertEquals(
|
||||||
|
encoded,
|
||||||
|
Json.obj(
|
||||||
|
(
|
||||||
|
JsonKeys.name,
|
||||||
|
Json.fromString(JsonComparison.DateBetweenExclusive.Name)
|
||||||
|
),
|
||||||
|
(JsonKeys.lower, Json.fromString(lower.toString())),
|
||||||
|
(JsonKeys.upper, Json.fromString(upper.toString()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(decoded.isRight, true)
|
||||||
|
assertEquals(
|
||||||
|
decoded.map {
|
||||||
|
case d: JsonComparison.DateBetweenExclusive => d.lower -> d.upper
|
||||||
|
case _ => fail("Parsed an unexpected JsonComparison.")
|
||||||
|
},
|
||||||
|
Right(lower -> upper)
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue