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)