codec.Encoder.scala Maven / Gradle / Ivy
package meteor
package codec
import java.{util => ju}
import java.time.Instant
import cats._
import cats.implicits._
import software.amazon.awssdk.core.SdkBytes
import software.amazon.awssdk.services.dynamodb.model._
import scala.collection.immutable
import scala.jdk.CollectionConverters._
/** Provides an encoding function for a given type, write value of type A as a Java AttributeValue
*/
trait Encoder[A] {
/** Write a value of type A as a Java AttributeValue
* @param a value of type A
* @return a Java AttributeValue object
*/
def write(a: A): AttributeValue
}
object Encoder {
def apply[A](implicit dd: Encoder[A]): Encoder[A] = dd
/** Create a new instance of Encoder for type A. Helper methods and more examples can be found in
* [[meteor.syntax]].
*/
def instance[A](f: A => AttributeValue): Encoder[A] = (a: A) => f(a)
def const[A](key: String, value: String): Encoder[A] =
_ =>
AttributeValue
.builder()
.m(
Map(key -> Encoder[String].write(value)).asJava
)
.build()
def const[A](av: AttributeValue): Encoder[A] = _ => av
implicit val dynamoEncoderForAttributeValue: Encoder[AttributeValue] =
Encoder.instance(identity)
implicit def contravariantForDynamoDbDecoder: Contravariant[Encoder] =
new Contravariant[Encoder] {
def contramap[A, B](fa: Encoder[A])(f: B => A): Encoder[B] =
(b: B) => fa.write(f(b))
}
implicit def dynamoEncoderForEither[A: Encoder, B: Encoder]
: Encoder[Either[A, B]] =
Encoder.instance {
case Right(r) => Encoder[B].write(r)
case Left(l) => Encoder[A].write(l)
}
implicit val dynamoEncoderForNothing: Encoder[Nothing] =
Encoder.instance[Nothing] { _ =>
AttributeValue.builder().build()
}
implicit def dynamoEncoderForFoldableByteArray[F[_]: Foldable]
: Encoder[F[Array[Byte]]] =
Encoder.instance { byteArrays =>
val bs = byteArrays.foldLeft(List.empty[SdkBytes]) { (acc, elem) =>
acc :+ SdkBytes.fromByteArray(elem)
}
AttributeValue.builder().bs(bs: _*).build()
}
implicit val dynamoEncoderForSeqByteArray
: Encoder[immutable.Seq[Array[Byte]]] =
dynamoEncoderForFoldableByteArray[immutable.Seq]
implicit val dynamoEncoderForListByteArray: Encoder[List[Array[Byte]]] =
dynamoEncoderForFoldableByteArray[List]
/*
* It is intentional to not have a Decoder[Option[A]] instance
* because we don't have control over the data which have been written.
* We cannot distinguish between nul(true) and nonexistent key scenario to be read as None.
* Hence, instead of providing an Encoder for Option, the lib provides a syntax `getOpt`
* as an explicit way to treat both.
*/
implicit def dynamoEncoderForOption[A: Encoder]: Encoder[Option[A]] =
Encoder.instance { fa =>
fa.fold(AttributeValue.builder().nul(true).build())(Encoder[A].write)
}
implicit def dynamoEncoderForSeq[A: Encoder]: Encoder[immutable.Seq[A]] =
Encoder.instance { fa =>
AttributeValue.builder().l(fa.map(Encoder[A].write): _*).build()
}
implicit def dynamoEncoderForList[A: Encoder]: Encoder[List[A]] =
dynamoEncoderForFoldable[List, A]
implicit def dynamoEncoderForFoldable[G[_]: Foldable, A: Encoder]
: Encoder[G[A]] =
Encoder.instance { ga =>
val items = ga.foldLeft(List.empty[AttributeValue]) { (acc, item) =>
acc :+ Encoder[A].write(item)
}
AttributeValue.builder().l(items: _*).build()
}
implicit val dynamoEncoderForBoolean: Encoder[Boolean] =
Encoder.instance(bool => AttributeValue.builder().bool(bool).build())
implicit val dynamoEncoderForString: Encoder[String] =
Encoder.instance { str =>
AttributeValue.builder().s(str).build()
}
implicit val dynamoEncoderForUUID: Encoder[ju.UUID] =
Encoder[String].contramap(_.toString)
implicit val dynamoEncoderForLong: Encoder[Long] =
Encoder.instance(long => AttributeValue.builder().n(long.toString).build())
implicit val dynamoEncoderForFloat: Encoder[Float] =
Encoder.instance(float =>
AttributeValue.builder().n(float.toString).build()
)
implicit val dynamoEncoderForDouble: Encoder[Double] =
Encoder.instance(double =>
AttributeValue.builder().n(double.toString).build()
)
implicit val dynamoEncoderForBigDecimal: Encoder[BigDecimal] =
Encoder.instance(bd => AttributeValue.builder().n(bd.toString).build())
implicit val dynamoEncoderForBigInt: Encoder[BigInt] =
Encoder.instance(bi => AttributeValue.builder().n(bi.toString).build())
implicit val dynamoEncoderForShort: Encoder[Short] =
Encoder.instance(short =>
AttributeValue.builder().n(short.toString).build()
)
implicit val dynamoEncoderForByte: Encoder[Byte] =
Encoder.instance(byte => AttributeValue.builder().n(byte.toString).build())
implicit val dynamoEncoderForInt: Encoder[Int] =
Encoder.instance(int => AttributeValue.builder().n(int.toString).build())
implicit val dynamoEncoderForInstant: Encoder[Instant] =
dynamoEncoderForLong.contramap(instant => instant.toEpochMilli)
implicit def dynamoEncoderForMap[A: Encoder]: Encoder[Map[String, A]] =
Encoder.instance { mapOfA =>
val mapOfAttr =
mapOfA.map(kv => kv._1 -> Encoder[A].write(kv._2)).asJava
AttributeValue.builder().m(mapOfAttr).build()
}
implicit def dynamoEncoderForJavaUtilMap[A: Encoder]
: Encoder[ju.Map[String, A]] =
dynamoEncoderForMap[A].contramap[ju.Map[String, A]](_.asScala.toMap)
implicit val dynamoEncoderForByteArray: Encoder[Array[Byte]] =
Encoder.instance(bytes =>
AttributeValue.builder().b(SdkBytes.fromByteArray(bytes)).build()
)
}