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

io.atlassian.aws.dynamodb.Decoder.scala Maven / Gradle / Ivy

The newest version!
package io.atlassian.aws
package dynamodb

import java.util.UUID

import argonaut._, Argonaut._
import org.joda.time.{ DateTimeZone, DateTime, Instant }

import com.amazonaws.services.dynamodbv2.model.AttributeValue
import scodec.bits.ByteVector

import scalaz.Free.Trampoline
import scalaz.{ Traverse, Foldable, Functor, Tag, @@ }
import scalaz.syntax.id._

/**
 * Represents a function that tries to convert an AttributeValue into a
 * Scala value (typically that represents a field in an object).
 */
case class Decoder[A] private[Decoder] (run: Value => Attempt[A])(private[dynamodb] val dynamoType: Underlying.Type) {
  def decode(o: Value): Attempt[A] =
    run(o)

  def map[B](f: A => B): Decoder[B] =
    Decoder { run(_).map(f) }(dynamoType)

  def collect[B](f: A => Option[B]): Decoder[B] =
    Decoder {
      run(_).flatMap { a => f(a).fold(Attempt.fail[B](s"'$a' is an invalid value"))(Attempt.ok) }
    }(dynamoType)

  def collect[B](f: PartialFunction[A, B]): Decoder[B] =
    collect(f.lift)

  def mapAttempt[B](f: A => Attempt[B]): Decoder[B] =
    Decoder { run(_) flatMap f }(dynamoType)

  def or[AA >: A](f: => Decoder[AA]): Decoder[AA] =
    Decoder { v =>
      run(v).fold({ _ => f.decode(v) }, { a => Attempt.ok(a) })
    }(dynamoType)
}

/**
 * Contains the implicit decoders for different types.
 * Custom decoders are derived from this base set.
 */
object Decoder {
  import DynamoString.syntax._

  def apply[A: Decoder] =
    implicitly[Decoder[A]]

  private[Decoder] def decoder[A](typ: Underlying.Type)(f: Value => Attempt[A]): Decoder[A] =
    Decoder { f }(typ)

  def mandatoryField[A](typ: Underlying.Type)(label: String)(f: AttributeValue => A): Decoder[A] =
    decoder(typ) {
      case None     => Attempt.fail(s"No $label value present")
      case Some(av) => Attempt.safe(f(av))
    }

  // instances

  implicit val LongDecode: Decoder[Long] =
    mandatoryField(Underlying.NumberType)("Long") { _.getN.toLong }

  implicit val IntDecode: Decoder[Int] =
    mandatoryField(Underlying.NumberType)("Int") { _.getN.toInt }

  implicit val DateTimeDecode: Decoder[DateTime] =
    mandatoryField(Underlying.StringType)("DateTime") { _.getN.toLong |> { i => new DateTime(i, DateTimeZone.UTC) } }

  implicit val InstantDecode: Decoder[Instant] =
    Decoder[DateTime].map { _.toInstant }

  implicit val DynamoStringDecode: Decoder[DynamoString] =
    decoder(Underlying.StringType) {
      case None => Attempt.fail("No string value present")
      case Some(a) => a.getS |> { s =>
        if (s == null)
          Attempt.fail("No string value present")
        else
          Attempt.ok(DynamoString.apply(s))
      }
    }

  implicit val StringDecode: Decoder[String] =
    DynamoStringDecode.mapAttempt { _.asString.fold(Attempt.fail[String]("No string value present")) { Attempt.ok } }

  implicit val UUIDDecode: Decoder[UUID] =
    Decoder[String].mapAttempt { s => Attempt.safe(UUID.fromString(s)) }

  implicit def TaggedTypeDecode[A: Decoder, B]: Decoder[A @@ B] =
    Decoder[A].map(Tag.apply)

  implicit def OptionDecode[A](implicit d: Decoder[A]): Decoder[Option[A]] =
    decoder(d.dynamoType) {
      case s @ Some(_) => d.decode(s).toOption |> Attempt.ok
      case None        => Attempt.ok(None)
    }

  implicit val NonEmptyBytesDecode: Decoder[NonEmptyBytes] =
    decoder(Underlying.BinaryType) {
      case None => Attempt.fail("No value present")
      case Some(a) => a.getB |> { bytes =>
        if (bytes == null) Attempt.fail("No value present")
        else
          NonEmptyBytes.unapply(ByteVector(bytes)).fold(Attempt.fail[NonEmptyBytes]("No value present")) { Attempt.ok }
      }
    }

  implicit object DecodeAttributeValueFunctor extends Functor[Decoder] {
    def map[A, B](m: Decoder[A])(f: A => B) = m map f
  }

  private def decodeJson: Decoder[Json] =
    decoder(Underlying.StringType) {
      case None => Attempt.fail("No value present")
      case Some(a) =>
        import scala.collection.JavaConverters._
        import scalaz.syntax.traverse._, scalaz.std.list._
        lazy val optBool: Option[Json] = Option(a.getBOOL).map { b => jBool(b.booleanValue) }
        lazy val optNull: Option[Json] = Option(a.getNULL).flatMap { b => if (b) Some(jNull) else None }
        lazy val optNum: Option[Json] = Option(a.getN).flatMap { jNumber }
        lazy val optString: Option[Json] = Option(a.getS).flatMap { s => DynamoString(s).asString }.map { jString }
        lazy val optArray: Option[Json] =
          Option(a.getL).flatMap {
            _.asScala.toList.traverseU { av =>
              decodeJson.decode(Some(av))
            }.map { lj => jArray(lj) }.toOption
          }

        lazy val optObj: Option[Json] = Option(a.getM).flatMap {
          _.asScala.toList.traverseU {
            case (f, av) =>
              decodeJson.decode(Some(av)).map { decoded => f -> decoded }
          }.map { lj => jObjectFields(lj: _*) }.toOption
        }
        optBool orElse optNull orElse optNum orElse optString orElse optArray orElse optObj match {
          case None    => Attempt.fail("Could not parse JSON")
          case Some(j) => Attempt.ok(j)
        }
    }

  private def decodeJsonDirectEncoding[A: DecodeJson]: Decoder[A] =
    decodeJson.mapAttempt { _.as[A].fold({ case (msg, h) => Attempt.fail(msg) }, { a => Attempt.ok(a) }) }

  private def decodeJsonStringEncoding[A: DecodeJson]: Decoder[A] =
    StringDecode.mapAttempt { _.decodeEither[A].fold(Attempt.fail, Attempt.safe(_)) }

  implicit def decodeJsonDecoder[A: DecodeJson]: Decoder[A] =
    decodeJsonDirectEncoding[A] or decodeJsonStringEncoding[A]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy