All Downloads are FREE. Search and download functionalities are using the official Maven repository.

me.wojnowski.googlecloud4s.firestore.codec.StandardCodecs.scala Maven / Gradle / Ivy

package me.wojnowski.googlecloud4s.firestore.codec

import cats.data.NonEmptyList
import me.wojnowski.googlecloud4s.firestore.Firestore.FirestoreDocument.Fields
import me.wojnowski.googlecloud4s.firestore.codec.FirestoreCodec.Error

import java.time.Instant
import cats.syntax.all._
import me.wojnowski.googlecloud4s.firestore.Reference
import me.wojnowski.googlecloud4s.firestore.Value

import scala.collection.Factory

trait StandardCodecs {

  implicit val valueCodec: FirestoreCodec[Value] = new FirestoreCodec[Value] {
    override def encode(value: Value): Value = value

    override def decode(value: Value): Either[Error, Value] = Right(value)
  }

  implicit val fieldsCodec: FirestoreCodec[Fields] = new FirestoreCodec[Fields] {
    override def encode(fields: Fields): Value = fields.toMapValue

    override def decode(value: Value): Either[Error, Fields] =
      value.narrowCollect {
        case Value.Map(map) => Fields(map)
      }

  }

  implicit val instantCodec: FirestoreCodec[Instant] = new FirestoreCodec[Instant] {
    override def encode(instant: Instant): Value = Value.Timestamp(instant)

    override def decode(value: Value): Either[Error, Instant] =
      value.narrowCollect[Instant] {
        case Value.Timestamp(instant) => instant
      }

  }

  implicit val booleanCodec: FirestoreCodec[Boolean] = new FirestoreCodec[Boolean] {
    override def encode(boolean: Boolean): Value = Value.Boolean(boolean)

    override def decode(value: Value): Either[Error, Boolean] =
      value.narrowCollect {
        case Value.Boolean(boolean) => boolean
      }

  }

  implicit val bytes: FirestoreCodec[Array[Byte]] = new FirestoreCodec[Array[Byte]] {
    override def encode(bytes: Array[Byte]): Value = Value.Bytes(bytes)

    override def decode(value: Value): Either[Error, Array[Byte]] =
      value.narrowCollect {
        case Value.Bytes(bytes) => bytes
      }

  }

  implicit val intCodec: FirestoreCodec[Int] = new FirestoreCodec[Int] {
    override def encode(int: Int): Value = Value.Integer(int)

    override def decode(value: Value): Either[Error, Int] =
      value.narrowCollect {
        case Value.Integer(int) => int
      }

  }

  implicit val doubleCodec: FirestoreCodec[Double] = new FirestoreCodec[Double] {
    override def encode(double: Double): Value = Value.Double(double)

    override def decode(value: Value): Either[Error, Double] =
      value.narrowCollect {
        case Value.Double(double) => double
      }

  }

  implicit val documentReference: FirestoreCodec[Reference.Document] = new FirestoreCodec[Reference.Document] {
    override def encode(reference: Reference.Document): Value = Value.Reference(reference)

    override def decode(value: Value): Either[Error, Reference.Document] =
      value.narrowCollect {
        case Value.Reference(reference) => reference
      }

  }

  implicit val stringCodec: FirestoreCodec[String] = new FirestoreCodec[String] {
    override def encode(string: String): Value = Value.String(string)

    override def decode(value: Value): Either[Error, String] =
      value.narrowCollect {
        case Value.String(string) => string
      }

  }

  implicit def optionCodec[A: FirestoreCodec]: FirestoreCodec[Option[A]] =
    new FirestoreCodec[Option[A]] {

      override def encode(maybeA: Option[A]): Value =
        maybeA match {
          case Some(value) => FirestoreCodec[A].encode(value)
          case None        => Value.Null
        }

      override def decode(value: Value): Either[Error, Option[A]] =
        value.narrowCollect {
          case Value.Null => none[A].asRight[Error]
          case value      => FirestoreCodec[A].decode(value).map(_.some)
        }.flatten

    }

  implicit def mapCodec[A: FirestoreCodec]: FirestoreCodec[Map[String, A]] =
    new FirestoreCodec[Map[String, A]] {
      override def encode(map: Map[String, A]): Value = Value.Map(map.fmap(FirestoreCodec[A].encode))

      override def decode(value: Value): Either[Error, Map[String, A]] =
        value.narrowCollect {
          case Value.Map(map) =>
            map
              .toList
              .traverse {
                case (key, value) =>
                  FirestoreCodec[A].decode(value).map(key -> _)
              }
              .map(_.toMap)
        }.flatten

    }

  implicit def iterableCodec[A, C[A] <: IterableOnce[A]](
    implicit aCodec: FirestoreCodec[A],
    factory: Factory[A, C[A]]
  ): FirestoreCodec[C[A]] =
    new FirestoreCodec[C[A]] {
      override def encode(collection: C[A]): Value = Value.Array(collection.iterator.map(FirestoreCodec[A].encode).toSeq)

      override def decode(value: Value): Either[Error, C[A]] =
        value.narrowCollect {
          case Value.Array(values) => values.toSeq.traverse(_.as[A]).map(factory.fromSpecific)
        }.flatten

    }

  implicit def nonEmptyListCodec[A: FirestoreCodec]: FirestoreCodec[NonEmptyList[A]] =
    new FirestoreCodec[NonEmptyList[A]] {
      override def encode(as: NonEmptyList[A]): Value = FirestoreCodec[List[A]].encode(as.toList)

      override def decode(value: Value): Either[FirestoreCodec.Error, NonEmptyList[A]] =
        FirestoreCodec[List[A]].decode(value).flatMap {
          case head :: tail => Right(NonEmptyList(head, tail))
          case _            => Left(FirestoreCodec.Error.GenericError("Expected a non-empty array"))
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy