gs-predicate/src/main/scala/gs/predicate/v0/serde/json/codecs.scala

228 lines
8.9 KiB
Scala

package gs.predicate.v0.serde.json
import cats.Applicative
import gs.predicate.v0.api.And
import gs.predicate.v0.api.False
import gs.predicate.v0.api.Messages
import gs.predicate.v0.api.Or
import gs.predicate.v0.api.Predicate
import gs.predicate.v0.api.True
import gs.predicate.v0.json.JsonComparison
import gs.predicate.v0.json.JsonProvider
import gs.predicate.v0.json.JsonQueryComparison
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.*
import io.circe.syntax.*
/** Given some [[And]] predicate, encode it as a JSON blob.
*
* @param and
* The [[And]] predicate.
* @return
* The JSON blob representing the [[And]].
*/
def encodeAnd[F[_]](and: And[F]): Json =
Json.obj(
(JsonKeys.predicateType, Json.fromString(And.PredicateType)),
(JsonKeys.predicates, and.ps.asJson)
)
/** Given some [[Or]] predicate, encode it as a JSON blob.
*
* @param and
* The [[Or]] predicate.
* @return
* The JSON blob representing the [[Or]].
*/
def encodeOr[F[_]](and: Or[F]): Json =
Json.obj(
(JsonKeys.predicateType, Json.fromString(Or.PredicateType)),
(JsonKeys.predicates, and.ps.asJson)
)
/** @return
* Generic encoder for any [[Predicate]].
*/
given predicateEncoder[F[_]]: Encoder[Predicate[F]] =
Encoder.instance {
case p: True[F] => Encoder[True[F]].apply(p)
case p: False[F] => Encoder[False[F]].apply(p)
case p: And[F] => encodeAnd[F](p)
case p: Or[F] => encodeOr[F](p)
case p: KeyExists[F] => Encoder[KeyExists[F]].apply(p)
case p: ValueEquals[F] => Encoder[ValueEquals[F]].apply(p)
case p: ValueContains[F] => Encoder[ValueContains[F]].apply(p)
case p: ValueStartsWith[F] => Encoder[ValueStartsWith[F]].apply(p)
case p: ValueEndsWith[F] => Encoder[ValueEndsWith[F]].apply(p)
case p: JsonQueryComparison[F] => Encoder[JsonQueryComparison[F]].apply(p)
case p =>
throw new IllegalArgumentException(
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}'"
)
}
/** Given some JSON cursor, decode a logical [[And]] predicate.
*
* @param cursor
* The cursor that points to some JSON value.
* @return
* The [[And]] predicate or a decoding failure.
*/
def decodeAnd[F[_]: Applicative: KeyValueProvider: JsonProvider](
cursor: HCursor
): Either[DecodingFailure, And[F]] =
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
yield new And(ps)
/** Given some JSON cursor, decode a logical [[Or]] predicate.
*
* @param cursor
* The cursor that points to some JSON value.
* @return
* The [[Or]] predicate or a decoding failure.
*/
def decodeOr[F[_]: Applicative: KeyValueProvider: JsonProvider](cursor: HCursor)
: Either[DecodingFailure, Or[F]] =
for ps <- cursor.downField(JsonKeys.predicates).as[List[Predicate[F]]]
yield new Or(ps)
/** Given some JSON cursor, decode any known [[Predicate]].
*
* @param cursor
* The cursor that points to some JSON value.
* @return
* The decoded [[Predicate]] or some decoding failure.
*/
def decodePredicate[F[_]: Applicative: KeyValueProvider: JsonProvider](
cursor: HCursor
): Either[DecodingFailure, Predicate[F]] =
for
predicateType <- cursor.downField(JsonKeys.predicateType).as[String]
predicate <- predicateType match
case True.PredicateType => Right(True[F])
case False.PredicateType => Right(False[F])
case And.PredicateType => decodeAnd[F](cursor)
case Or.PredicateType => decodeOr[F](cursor)
case KeyExists.PredicateType => Decoder[KeyExists[F]].apply(cursor)
case ValueEquals.PredicateType => Decoder[ValueEquals[F]].apply(cursor)
case ValueNotEquals.PredicateType =>
Decoder[ValueNotEquals[F]].apply(cursor)
case ValueContains.PredicateType =>
Decoder[ValueContains[F]].apply(cursor)
case ValueStartsWith.PredicateType =>
Decoder[ValueStartsWith[F]].apply(cursor)
case ValueEndsWith.PredicateType =>
Decoder[ValueEndsWith[F]].apply(cursor)
case JsonQueryComparison.PredicateType =>
Decoder[JsonQueryComparison[F]].apply(cursor)
case _ =>
Left(
DecodingFailure(
Messages.unrecognizedPredicateType(predicateType),
Nil
)
)
yield predicate
/** @return
* Generic decoder for any [[Predicate]].
*/
given predicateDecoder[F[_]: Applicative: KeyValueProvider: JsonProvider]
: Decoder[Predicate[F]] =
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)