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

gql.goi.IDCodec.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2024 Valdemar Grange
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package gql.goi

import org.typelevel.twiddles.TwiddleSyntax
import cats.implicits._
import cats._
import cats.data._

trait IDCodec[A] { self =>
  def codecs: NonEmptyChain[String]

  def encode(a: A): NonEmptyChain[String]

  def decode(s: Array[String]): ValidatedNec[String, A]

  def opt: IDCodec[Option[A]] =
    IDCodec(
      self.codecs,
      s =>
        if (s.forall(_ == "null")) None.validNec
        else self.decode(s).map(Some(_)),
      {
        case Some(a) => self.encode(a)
        case None    => NonEmptyChain.one("null")
      }
    )

  def eimap[B](f: A => Either[String, B])(g: B => A): IDCodec[B] =
    IDCodec(
      self.codecs,
      s => self.decode(s).andThen(x => f(x).toValidatedNec),
      b => self.encode(g(b))
    )

  def product[B](that: IDCodec[B]): IDCodec[(A, B)] =
    IDCodec(
      self.codecs ++ that.codecs,
      s => {
        val (s1, s2) = s.splitAt(self.codecs.length.toInt)
        (self.decode(s1), that.decode(s2)).tupled
      },
      { a =>
        val (a1, b1) = a
        self.encode(a1) ++ that.encode(b1)
      }
    )
}

object IDCodec extends TwiddleSyntax[IDCodec] {
  def apply[A](
      codecs: NonEmptyChain[String],
      decode: Array[String] => ValidatedNec[String, A],
      encode: A => NonEmptyChain[String]
  ): IDCodec[A] = {
    val decode0 = decode(_)
    val encode0 = encode(_)
    val codecs0 = codecs
    new IDCodec[A] {
      override def codecs: NonEmptyChain[String] = codecs0
      override def encode(a: A): NonEmptyChain[String] = encode0(a)
      override def decode(s: Array[String]): ValidatedNec[String, A] = decode0(s)
    }
  }

  def make[A](decode: String => Either[String, A], encode: A => String, name: String): IDCodec[A] =
    IDCodec(
      NonEmptyChain.one(name),
      {
        case Array(s1) => decode(s1).toValidatedNec
        case s => s"Invalid input for codec $name, expected exactly one input but got ${s.size}: ${s.toSeq.mkString_(":")}".invalidNec
      },
      a => NonEmptyChain.one(encode(a))
    )

  implicit lazy val invariantSemigroupalForIdCodec: InvariantSemigroupal[IDCodec] = new InvariantSemigroupal[IDCodec] {
    override def imap[A, B](fa: IDCodec[A])(f: A => B)(g: B => A): IDCodec[B] =
      new IDCodec[B] {
        override def codecs: NonEmptyChain[String] = fa.codecs

        override def encode(a: B): NonEmptyChain[String] = fa.encode(g(a))

        override def decode(s: Array[String]): ValidatedNec[String, B] = fa.decode(s).map(f)
      }

    override def product[A, B](fa: IDCodec[A], fb: IDCodec[B]): IDCodec[(A, B)] = fa product fb
  }

  implicit val stringInstance: IDCodec[String] = IDCodec.make[String](Right(_), identity, "string")

  implicit val intInstance: IDCodec[Int] =
    IDCodec.make[Int](s => Either.catchNonFatal(s.toInt).leftMap(_ => s"Cannot parse '$s' as Int"), _.toString, "int")

  implicit val longInstance: IDCodec[Long] =
    IDCodec.make[Long](s => Either.catchNonFatal(s.toLong).leftMap(_ => s"Cannot parse '$s' as Long"), _.toString, "long")

  implicit val floatInstance: IDCodec[Float] =
    IDCodec
      .make[Float](s => Either.catchNonFatal(s.toFloat).leftMap(_ => s"Cannot parse '$s' as Float"), _.toString, "float")

  implicit val doubleInstance: IDCodec[Double] =
    IDCodec
      .make[Double](s => Either.catchNonFatal(s.toDouble).leftMap(_ => s"Cannot parse '$s' as Double"), _.toString, "double")

  implicit val booleanInstance: IDCodec[Boolean] =
    IDCodec.make[Boolean](s => Either.catchNonFatal(s.toBoolean).leftMap(_ => s"Cannot parse '$s' as Boolean"), _.toString, "boolean")

  implicit val uuidInstance: IDCodec[java.util.UUID] = IDCodec.make[java.util.UUID](
    s => Either.catchNonFatal(java.util.UUID.fromString(s)).leftMap(_ => s"Cannot parse '$s' as UUID"),
    _.toString,
    "uuid"
  )

  implicit val dateInstance: IDCodec[java.time.LocalDate] =
    IDCodec.make[java.time.LocalDate](
      s => Either.catchNonFatal(java.time.LocalDate.parse(s)).leftMap(_ => s"Cannot parse '$s' as LocalDate"),
      _.toString,
      "date"
    )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy