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

ru.tinkoff.phobos.decoding.AttributeDecoder.scala Maven / Gradle / Ivy

The newest version!
package ru.tinkoff.phobos.decoding

import java.time._
import java.time.format.DateTimeFormatter
import java.util.{Base64, UUID}

/** Warning! This is an internal API which may change in future. Do not implement or use this trait directly unless you
  * know what you are doing.
  *
  * Use XmlDecoder for decoding XML documents.
  *
  * AttributeDecoder instance must exist for every type decoded from attribute. This typeclass is used for decoding case
  * class parameters with @attr annotation.
  *
  * To create new instance use .map or .emap method of existing instance.
  */
trait AttributeDecoder[A] { self =>
  def decodeAsAttribute(c: Cursor, localName: String, namespaceUri: Option[String]): Either[DecodingError, A]

  def map[B](f: A => B): AttributeDecoder[B] =
    new AttributeDecoder[B] {
      def decodeAsAttribute(c: Cursor, localName: String, namespaceUri: Option[String]): Either[DecodingError, B] =
        self.decodeAsAttribute(c, localName, namespaceUri).map(f)
    }

  def emap[B](f: (List[String], A) => Either[DecodingError, B]): AttributeDecoder[B] =
    new AttributeDecoder[B] {
      def decodeAsAttribute(c: Cursor, localName: String, namespaceUri: Option[String]): Either[DecodingError, B] =
        self.decodeAsAttribute(c, localName, namespaceUri) match {
          case Right(a)    => f(c.history, a)
          case Left(error) => Left(error)
        }
    }
}

object AttributeDecoder extends AttributeLiteralInstances {

  def apply[A](implicit instance: AttributeDecoder[A]) = instance

  /** Instances
    */
  implicit val stringDecoder: AttributeDecoder[String] =
    new AttributeDecoder[String] {
      def decodeAsAttribute(
          c: Cursor,
          localName: String,
          namespaceUri: Option[String],
      ): Either[DecodingError, String] = {
        val idx = c.getAttributeIndex(namespaceUri.orNull, localName)
        if (idx > -1) {
          Right(c.getAttributeValue(idx))
        } else {
          Left(c.error(s"Missing '$localName' attribute"))
        }
      }
    }

  implicit val unitDecoder: AttributeDecoder[Unit] = new AttributeDecoder[Unit] {
    def decodeAsAttribute(c: Cursor, localName: String, namespaceUri: Option[String]): Either[DecodingError, Unit] =
      Right(())
  }

  implicit val booleanDecoder: AttributeDecoder[Boolean] =
    stringDecoder.emap((history, string) =>
      string match {
        case "true" | "1"  => Right(true)
        case "false" | "0" => Right(false)
        case str           => Left(DecodingError(s"Value `$str` is not `true` or `false`", history, None))
      },
    )

  implicit val javaBooleanDecoder: AttributeDecoder[java.lang.Boolean] = booleanDecoder.map(_.booleanValue())

  implicit val charDecoder: AttributeDecoder[Char] =
    stringDecoder.emap((history, string) => {
      if (string.length != 1) {
        Left(DecodingError("Value too long for char", history, None))
      } else {
        Right(string.head)
      }
    })
  implicit val javaCharacterDecoder: AttributeDecoder[java.lang.Character] = charDecoder.map(_.charValue())
  implicit val floatDecoder: AttributeDecoder[Float]                       = stringDecoder.emap(wrapException(_.toFloat))
  implicit val javaFloatDecoder: AttributeDecoder[java.lang.Float]         = floatDecoder.map(_.floatValue())
  implicit val doubleDecoder: AttributeDecoder[Double]                     = stringDecoder.emap(wrapException(_.toDouble))
  implicit val javaDoubleDecoder: AttributeDecoder[java.lang.Double]       = doubleDecoder.map(_.doubleValue())
  implicit val byteDecoder: AttributeDecoder[Byte]                         = stringDecoder.emap(wrapException(_.toByte))
  implicit val javaByteDecoder: AttributeDecoder[java.lang.Byte]           = byteDecoder.map(_.byteValue())
  implicit val shortDecoder: AttributeDecoder[Short]                       = stringDecoder.emap(wrapException(_.toShort))
  implicit val javaShortDecoder: AttributeDecoder[java.lang.Short]         = shortDecoder.map(_.shortValue())
  implicit val intDecoder: AttributeDecoder[Int]                           = stringDecoder.emap(wrapException(_.toInt))
  implicit val javaIntegerDecoder: AttributeDecoder[java.lang.Integer]     = intDecoder.map(_.intValue())
  implicit val longDecoder: AttributeDecoder[Long]                         = stringDecoder.emap(wrapException(_.toLong))
  implicit val javaLongDecoder: AttributeDecoder[java.lang.Long]           = longDecoder.map(_.longValue())
  implicit val bigIntDecoder: AttributeDecoder[BigInt]                     = stringDecoder.emap(wrapException(BigInt.apply))

  implicit val javaBigIntegerDecoder: AttributeDecoder[java.math.BigInteger] =
    stringDecoder.emap(wrapException(str => new java.math.BigInteger(str)))

  implicit val bigDecimalDecoder: AttributeDecoder[BigDecimal]               = stringDecoder.map(BigDecimal.apply)
  implicit val javaBigDecimalDecoder: AttributeDecoder[java.math.BigDecimal] = bigDecimalDecoder.map(_.bigDecimal)
  implicit val UUIDDecoder: AttributeDecoder[UUID]                           = stringDecoder.emap(wrapException(UUID.fromString))

  implicit val base64Decoder: AttributeDecoder[Array[Byte]] =
    stringDecoder.emap(wrapException(Base64.getDecoder.decode))

  implicit def optionDecoder[A](implicit decoder: AttributeDecoder[A]): AttributeDecoder[Option[A]] =
    new AttributeDecoder[Option[A]] {
      def decodeAsAttribute(
          c: Cursor,
          localName: String,
          namespaceUri: Option[String],
      ): Either[DecodingError, Option[A]] = {
        val idx = c.getAttributeIndex(namespaceUri.orNull, localName)
        if (idx > -1) {
          decoder.decodeAsAttribute(c, localName, namespaceUri).map(Some.apply)
        } else {
          Right(None)
        }
      }
    }

  implicit def someDecoder[A](implicit e: AttributeDecoder[A]): AttributeDecoder[Some[A]] = e.map(Some.apply)

  implicit val noneDecoder: AttributeDecoder[None.type] =
    new AttributeDecoder[None.type] {
      def decodeAsAttribute(
          c: Cursor,
          localName: String,
          namespaceUri: Option[String],
      ): Either[DecodingError, None.type] = Right(None)
    }

  implicit val instantDecoder: AttributeDecoder[Instant] =
    stringDecoder.emap(wrapException(Instant.parse))

  def instantDecoderWithFormatter(formatter: DateTimeFormatter): AttributeDecoder[Instant] =
    stringDecoder.emap(wrapException(string => Instant.from(formatter.parse(string))))

  implicit val localDateTimeDecoder: AttributeDecoder[LocalDateTime] =
    stringDecoder.emap(wrapException(LocalDateTime.parse))

  def localDateTimeDecoderWithFormatter(formatter: DateTimeFormatter): AttributeDecoder[LocalDateTime] =
    stringDecoder.emap(wrapException(LocalDateTime.parse(_, formatter)))

  implicit val zonedDateTimeDecoder: AttributeDecoder[ZonedDateTime] =
    stringDecoder.emap(wrapException(ZonedDateTime.parse))

  def zonedDateTimeDecoderWithFormatter(formatter: DateTimeFormatter): AttributeDecoder[ZonedDateTime] =
    stringDecoder.emap(wrapException(ZonedDateTime.parse(_, formatter)))

  implicit val offsetDateTimeDecoder: AttributeDecoder[OffsetDateTime] =
    stringDecoder.emap(wrapException(OffsetDateTime.parse))

  def offsetDateTimeDecoderWithFormatter(formatter: DateTimeFormatter): AttributeDecoder[OffsetDateTime] =
    stringDecoder.emap(wrapException(OffsetDateTime.parse(_, formatter)))

  implicit val localDateDecoder: AttributeDecoder[LocalDate] =
    stringDecoder.emap(wrapException(LocalDate.parse))

  def localDateDecoderWithFormatter(formatter: DateTimeFormatter): AttributeDecoder[LocalDate] =
    stringDecoder.emap(wrapException(LocalDate.parse(_, formatter)))

  implicit val localTimeDecoder: AttributeDecoder[LocalTime] =
    stringDecoder.emap(wrapException(LocalTime.parse))

  def localTimeDecoderWithFormatter(formatter: DateTimeFormatter): AttributeDecoder[LocalTime] =
    stringDecoder.emap(wrapException(LocalTime.parse(_, formatter)))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy