Skip to content
This repository has been archived by the owner on Sep 27, 2021. It is now read-only.

Commit

Permalink
Feature: adjust Option[A] codecs. (#138)
Browse files Browse the repository at this point in the history
* Introduce decodeOptional, to pass into decoder optional attribute values.

* Fix build
  • Loading branch information
aliubymov-playq authored Aug 7, 2020
1 parent b22296c commit 4e3058c
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 11 deletions.
7 changes: 6 additions & 1 deletion d4s-circe/src/test/scala/d4s/codecs/DynamoCodecTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package d4s.codecs
import d4s.codecs.Fixtures._
import d4s.codecs.circe.{D4SCirceAttributeCodec, D4SCirceCodec}
import d4s.models.DynamoException.DecoderException
import io.circe.{Decoder, Encoder}
import io.circe.generic.extras.semiauto
import io.circe.{Decoder, Encoder}
import org.scalacheck.Prop
import org.scalatest.wordspec.AnyWordSpec
import org.scalatestplus.scalacheck.Checkers
Expand All @@ -13,6 +13,11 @@ import software.amazon.awssdk.core.SdkBytes
import scala.jdk.CollectionConverters._

final class DynamoCodecTest extends AnyWordSpec with Checkers {
"decode options from missed attribute" in {
val decoder = D4SDecoder.optionDecoder[Int]
assert(decoder.decodeOptional(None, "").exists(_.isEmpty))
}

"encode/decode TestCaseClass" in check {
Prop.forAllNoShrink {
testData: TestCaseClass =>
Expand Down
27 changes: 18 additions & 9 deletions d4s/src/main/scala/d4s/codecs/D4SDecoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import scala.util.control.NonFatal
trait D4SDecoder[T] {
def decode(attr: AttributeValue): Either[DecoderException, T]
def decodeObject(item: Map[String, AttributeValue]): Either[DecoderException, T]
def decodeOptional(attr: Option[AttributeValue], label: String): Either[DecoderException, T] = {
attr match {
case Some(value) => decode(value)
case None => Left(DecoderException(s"Cannot find parameter with name `$label` of type [${this.getClass.getSimpleName}].", None))
}
}

final def decodeObject(item: java.util.Map[String, AttributeValue]): Either[DecoderException, T] = decodeObject(item.asScala.toMap)

Expand Down Expand Up @@ -80,10 +86,8 @@ object D4SDecoder extends D4SDecoderScala213 {
item =>
ctx.constructMonadic {
p =>
item.get(p.label) match {
case Some(value) => p.typeclass.decode(value)
case None => Left(DecoderException(s"Cannot find parameter with name ${p.label}", None))
}
val label = p.label
p.typeclass.decodeOptional(item.get(label), label)
}
}

Expand All @@ -92,7 +96,7 @@ object D4SDecoder extends D4SDecoderScala213 {
if (item.m().isEmpty) {
ctx.subtypes
.find(_.typeName.short == item.s())
.toRight(DecoderException(s" Cannot decode item of type ${ctx.typeName.full} from string: ${item.s()}", None))
.toRight(DecoderException(s"Cannot decode item of type [${ctx.typeName.full}] from string: ${item.s()}", None))
.flatMap(_.typeclass.decode(item))
} else {
if (item.m().size != 1) {
Expand All @@ -101,7 +105,7 @@ object D4SDecoder extends D4SDecoderScala213 {
val (typeName, attrValue) = item.m().asScala.head
ctx.subtypes
.find(_.typeName.short == typeName)
.toRight(DecoderException(s"Cannot find a subtype $typeName for a sealed trait ${ctx.typeName.full}", None))
.toRight(DecoderException(s"Cannot find a subtype [$typeName] for a sealed trait [${ctx.typeName.full}]", None))
.flatMap(_.typeclass.decode(attrValue))
}
}
Expand Down Expand Up @@ -188,9 +192,14 @@ object D4SDecoder extends D4SDecoderScala213 {
}
}

implicit def optionDecoder[A](implicit T: D4SDecoder[A]): D4SDecoder[Option[A]] = attributeDecoder {
attr =>
if (attr.nul()) Right(None) else T.decode(attr).map(Some(_))
implicit def optionDecoder[A](implicit T: D4SDecoder[A]): D4SDecoder[Option[A]] = new D4SDecoder[Option[A]] {
override def decode(attr: AttributeValue): Either[DecoderException, Option[A]] = decodeOptional(Some(attr), "")
override def decodeObject(item: Map[String, AttributeValue]): Either[DecoderException, Option[A]] = decode(AttributeValue.builder().m(item.asJava).build())
override def decodeOptional(attr: Option[AttributeValue], label: String): Either[DecoderException, Option[A]] = attr match {
case None => Right(None)
case Some(attr) if attr.nul() => Right(None)
case Some(attr) => T.decode(attr).map(Some(_))
}
}

implicit def eitherDecoder[A: D4SDecoder, B: D4SDecoder]: D4SDecoder[Either[A, B]] = D4SDecoder.derived
Expand Down
2 changes: 1 addition & 1 deletion d4s/src/main/scala/d4s/models/DynamoException.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object DynamoException {
}
}

final case class DecoderException(override val message: String, maybeCause: Option[Throwable]) extends DynamoException(message, maybeCause.orNull) {
final case class DecoderException(override val message: String, maybeCause: Option[Throwable]) extends DynamoException(message, maybeCause.getOrElse(new RuntimeException(message))) {
def union(that: DecoderException): DecoderException = {
val errorLog = message + "\n" + that.getMessage
DecoderException(errorLog, maybeCause.orElse(that.maybeCause))
Expand Down

0 comments on commit 4e3058c

Please sign in to comment.