Skip to content

Commit 3674dce

Browse files
author
Georgi Krastev
authored
Optimize decoders (#248)
1 parent 08a23e5 commit 3674dce

File tree

3 files changed

+155
-128
lines changed

3 files changed

+155
-128
lines changed
Lines changed: 113 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package scynamo
22

3-
import cats.data.{EitherNec, NonEmptyChain}
3+
import cats.data.{Chain, EitherNec, NonEmptyChain}
44
import cats.syntax.either._
5-
import cats.syntax.parallel._
65
import cats.{Monad, SemigroupK}
7-
import scynamo.StackFrame.Index
6+
import scynamo.StackFrame.{Index, MapKey}
87
import scynamo.generic.auto.AutoDerivationUnlocked
98
import scynamo.generic.{GenericScynamoDecoder, SemiautoDerivationDecoder}
9+
import scynamo.syntax.attributevalue._
1010
import shapeless.labelled.{field, FieldType}
1111
import shapeless.tag.@@
1212
import shapeless.{tag, Lazy}
@@ -17,24 +17,24 @@ import java.util.UUID
1717
import java.util.concurrent.TimeUnit
1818
import scala.annotation.tailrec
1919
import scala.collection.compat._
20+
import scala.collection.immutable.Seq
2021
import scala.concurrent.duration.{Duration, FiniteDuration}
21-
import scala.jdk.CollectionConverters._
2222
import scala.util.control.NonFatal
2323

2424
trait ScynamoDecoder[A] extends ScynamoDecoderFunctions { self =>
2525
def decode(attributeValue: AttributeValue): EitherNec[ScynamoDecodeError, A]
2626

2727
def map[B](f: A => B): ScynamoDecoder[B] =
28-
value => decode(value).map(f)
28+
ScynamoDecoder.instance(decode(_).map(f))
2929

3030
def flatMap[B](f: A => ScynamoDecoder[B]): ScynamoDecoder[B] =
31-
value => decode(value).flatMap(f(_).decode(value))
31+
ScynamoDecoder.instance(value => decode(value).flatMap(f(_).decode(value)))
3232

3333
def orElse[AA >: A](other: ScynamoDecoder[A]): ScynamoDecoder[AA] =
34-
value => decode(value).orElse(other.decode(value))
34+
ScynamoDecoder.instance(value => decode(value).orElse(other.decode(value)))
3535

3636
def transform[B](f: EitherNec[ScynamoDecodeError, A] => EitherNec[ScynamoDecodeError, B]): ScynamoDecoder[B] =
37-
value => f(decode(value))
37+
ScynamoDecoder.instance(value => f(decode(value)))
3838

3939
def defaultValue: Option[A] = None
4040

@@ -48,11 +48,14 @@ object ScynamoDecoder extends DefaultScynamoDecoderInstances {
4848
def apply[A](implicit instance: ScynamoDecoder[A]): ScynamoDecoder[A] = instance
4949

5050
def const[A](value: A): ScynamoDecoder[A] =
51-
_ => Right(value)
51+
instance(_ => Right(value))
52+
53+
// SAM syntax generates anonymous classes because of non-abstract methods like `defaultValue`.
54+
private[scynamo] def instance[A](f: AttributeValue => EitherNec[ScynamoDecodeError, A]): ScynamoDecoder[A] = f(_)
5255
}
5356

5457
trait DefaultScynamoDecoderInstances extends ScynamoDecoderFunctions with ScynamoIterableDecoder {
55-
import scynamo.syntax.attributevalue._
58+
private val rightNone = Right(None)
5659

5760
implicit val catsInstances: Monad[ScynamoDecoder] with SemigroupK[ScynamoDecoder] =
5861
new Monad[ScynamoDecoder] with SemigroupK[ScynamoDecoder] {
@@ -73,87 +76,97 @@ trait DefaultScynamoDecoderInstances extends ScynamoDecoderFunctions with Scynam
7376
case Left(errors) => Left(errors)
7477
}
7578

76-
go(a, _)
79+
ScynamoDecoder.instance(go(a, _))
7780
}
7881

7982
override def combineK[A](x: ScynamoDecoder[A], y: ScynamoDecoder[A]): ScynamoDecoder[A] =
8083
x.orElse(y)
8184
}
8285

83-
implicit val stringDecoder: ScynamoDecoder[String] = attributeValue => attributeValue.asEither(ScynamoType.String)
86+
implicit val stringDecoder: ScynamoDecoder[String] =
87+
ScynamoDecoder.instance(_.asEither(ScynamoType.String))
8488

8589
implicit val intDecoder: ScynamoDecoder[Int] =
86-
attributeValue => attributeValue.asEither(ScynamoType.Number).flatMap(s => convert(s, "Int")(_.toInt))
90+
ScynamoDecoder.instance(_.asEither(ScynamoType.Number).flatMap(convert(_, "Int")(_.toInt)))
8791

8892
implicit val longDecoder: ScynamoDecoder[Long] =
89-
attributeValue => attributeValue.asEither(ScynamoType.Number).flatMap(s => convert(s, "Long")(_.toLong))
93+
ScynamoDecoder.instance(_.asEither(ScynamoType.Number).flatMap(convert(_, "Long")(_.toLong)))
9094

9195
implicit val bigIntDecoder: ScynamoDecoder[BigInt] =
92-
attributeValue => attributeValue.asEither(ScynamoType.Number).flatMap(s => convert(s, "BigInt")(BigInt(_)))
96+
ScynamoDecoder.instance(_.asEither(ScynamoType.Number).flatMap(convert(_, "BigInt")(BigInt.apply)))
9397

9498
implicit val floatDecoder: ScynamoDecoder[Float] =
95-
attributeValue => attributeValue.asEither(ScynamoType.Number).flatMap(s => convert(s, "Float")(_.toFloat))
99+
ScynamoDecoder.instance(_.asEither(ScynamoType.Number).flatMap(convert(_, "Float")(_.toFloat)))
96100

97101
implicit val doubleDecoder: ScynamoDecoder[Double] =
98-
attributeValue => attributeValue.asEither(ScynamoType.Number).flatMap(s => convert(s, "Double")(_.toDouble))
102+
ScynamoDecoder.instance(_.asEither(ScynamoType.Number).flatMap(convert(_, "Double")(_.toDouble)))
99103

100104
implicit val bigDecimalDecoder: ScynamoDecoder[BigDecimal] =
101-
attributeValue => attributeValue.asEither(ScynamoType.Number).flatMap(s => convert(s, "BigDecimal")(BigDecimal(_)))
105+
ScynamoDecoder.instance(_.asEither(ScynamoType.Number).flatMap(convert(_, "BigDecimal")(BigDecimal.apply)))
102106

103-
implicit val booleanDecoder: ScynamoDecoder[Boolean] = attributeValue => attributeValue.asEither(ScynamoType.Bool)
107+
implicit val booleanDecoder: ScynamoDecoder[Boolean] =
108+
ScynamoDecoder.instance(_.asEither(ScynamoType.Bool))
104109

105110
implicit val instantDecoder: ScynamoDecoder[Instant] =
106-
attributeValue =>
111+
ScynamoDecoder.instance { attr =>
107112
for {
108-
nstring <- attributeValue.asEither(ScynamoType.Number)
109-
result <- convert(nstring, "Long")(_.toLong)
113+
number <- attr.asEither(ScynamoType.Number)
114+
result <- convert(number, "Long")(_.toLong)
110115
} yield Instant.ofEpochMilli(result)
116+
}
111117

112118
implicit val instantTtlDecoder: ScynamoDecoder[Instant @@ TimeToLive] =
113-
attributeValue =>
119+
ScynamoDecoder.instance { attr =>
114120
for {
115-
nstring <- attributeValue.asEither(ScynamoType.Number)
116-
result <- convert(nstring, "Long")(_.toLong)
121+
number <- attr.asEither(ScynamoType.Number)
122+
result <- convert(number, "Long")(_.toLong)
117123
} yield tag[TimeToLive][Instant](Instant.ofEpochSecond(result))
124+
}
118125

119-
implicit def seqDecoder[A: ScynamoDecoder]: ScynamoDecoder[scala.collection.immutable.Seq[A]] = iterableDecoder
120-
121-
implicit def listDecoder[A: ScynamoDecoder]: ScynamoDecoder[List[A]] = iterableDecoder
122-
126+
implicit def seqDecoder[A: ScynamoDecoder]: ScynamoDecoder[Seq[A]] = iterableDecoder
127+
implicit def listDecoder[A: ScynamoDecoder]: ScynamoDecoder[List[A]] = iterableDecoder
123128
implicit def vectorDecoder[A: ScynamoDecoder]: ScynamoDecoder[Vector[A]] = iterableDecoder
129+
implicit def setDecoder[A: ScynamoDecoder]: ScynamoDecoder[Set[A]] = iterableDecoder
124130

125-
implicit def setDecoder[A: ScynamoDecoder]: ScynamoDecoder[Set[A]] = iterableDecoder
126-
127-
implicit def optionDecoder[A: ScynamoDecoder]: ScynamoDecoder[Option[A]] =
131+
implicit def optionDecoder[A](implicit element: ScynamoDecoder[A]): ScynamoDecoder[Option[A]] =
128132
new ScynamoDecoder[Option[A]] {
133+
override val defaultValue: Option[Option[A]] = Some(None)
129134
override def decode(attributeValue: AttributeValue): EitherNec[ScynamoDecodeError, Option[A]] =
130-
if (attributeValue.nul()) Right(None) else ScynamoDecoder[A].decode(attributeValue).map(Some(_))
131-
132-
override def defaultValue: Option[Option[A]] = Some(None)
135+
if (attributeValue.nul) rightNone else element.decode(attributeValue).map(Some.apply)
133136
}
134137

135-
implicit val finiteDurationDecoder: ScynamoDecoder[FiniteDuration] = longDecoder.map(Duration.fromNanos)
136-
137-
implicit val durationDecoder: ScynamoDecoder[Duration] = longDecoder.map(n => Duration(n, TimeUnit.NANOSECONDS))
138-
139-
implicit val uuidDecoder: ScynamoDecoder[UUID] = attributeValue =>
140-
attributeValue.asEither(ScynamoType.String).flatMap(s => convert(s, "UUID")(UUID.fromString))
141-
142-
implicit def mapDecoder[A, B](implicit
143-
keyDecoder: ScynamoKeyDecoder[A],
144-
valueDecoder: ScynamoDecoder[B]
145-
): ScynamoDecoder[Map[A, B]] =
146-
attributeValue =>
147-
attributeValue.asEither(ScynamoType.Map).flatMap { javaMap =>
148-
javaMap.asScala.toVector.zipWithIndex
149-
.parTraverse { case ((key, value), i) =>
150-
(keyDecoder.decode(key), valueDecoder.decode(value)).parMapN(_ -> _).leftMap(_.map(_.push(Index(i))))
151-
}
152-
.map(_.toMap)
138+
implicit val finiteDurationDecoder: ScynamoDecoder[FiniteDuration] =
139+
longDecoder.map(Duration.fromNanos)
140+
141+
implicit val durationDecoder: ScynamoDecoder[Duration] =
142+
longDecoder.map(Duration(_, TimeUnit.NANOSECONDS))
143+
144+
implicit val uuidDecoder: ScynamoDecoder[UUID] =
145+
ScynamoDecoder.instance(_.asEither(ScynamoType.String).flatMap(convert(_, "UUID")(UUID.fromString)))
146+
147+
implicit def mapDecoder[A, B](implicit key: ScynamoKeyDecoder[A], value: ScynamoDecoder[B]): ScynamoDecoder[Map[A, B]] =
148+
ScynamoDecoder.instance(_.asEither(ScynamoType.Map).flatMap { attributes =>
149+
var allErrors = Chain.empty[ScynamoDecodeError]
150+
val allValues = Map.newBuilder[A, B]
151+
152+
attributes.forEach { (k, v) =>
153+
(key.decode(k), value.decode(v)) match {
154+
case (Right(k), Right(v)) =>
155+
allValues += k -> v
156+
case (Left(errors), Right(_)) =>
157+
allErrors ++= StackFrame.decoding(errors, MapKey(k)).toChain
158+
case (Right(_), Left(errors)) =>
159+
allErrors ++= StackFrame.decoding(errors, MapKey(k)).toChain
160+
case (Left(kErrors), Left(vErrors)) =>
161+
allErrors ++= StackFrame.decoding(kErrors ++ vErrors, MapKey(k)).toChain
162+
}
153163
}
154164

165+
NonEmptyChain.fromChain(allErrors).toLeft(allValues.result())
166+
})
167+
155168
implicit val attributeValueDecoder: ScynamoDecoder[AttributeValue] =
156-
attributeValue => Right(attributeValue)
169+
ScynamoDecoder.instance(Right.apply)
157170

158171
implicit def fieldDecoder[K, V](implicit V: Lazy[ScynamoDecoder[V]]): ScynamoDecoder[FieldType[K, V]] =
159172
new ScynamoDecoder[FieldType[K, V]] {
@@ -166,21 +179,24 @@ trait DefaultScynamoDecoderInstances extends ScynamoDecoderFunctions with Scynam
166179

167180
trait ScynamoIterableDecoder extends LowestPrioAutoDecoder {
168181
import scynamo.syntax.attributevalue._
169-
def iterableDecoder[A: ScynamoDecoder, C[_] <: Iterable[A], X](implicit factory: Factory[A, C[A]]): ScynamoDecoder[C[A]] =
170-
attributeValue =>
171-
attributeValue.asEither(ScynamoType.List).flatMap { theList =>
172-
val builder = factory.newBuilder
173-
var elems = Either.rightNec[ScynamoDecodeError, builder.type](builder)
174-
var i = 0
175-
176-
theList.forEach { elem =>
177-
val decoded = ScynamoDecoder[A].decode(elem).leftMap(_.map(_.push(Index(i))))
178-
elems = (elems, decoded).parMapN((builder, dec) => builder += dec)
179-
i += 1
182+
183+
def iterableDecoder[A, C[x] <: Iterable[x]](implicit element: ScynamoDecoder[A], factory: Factory[A, C[A]]): ScynamoDecoder[C[A]] =
184+
ScynamoDecoder.instance(_.asEither(ScynamoType.List).flatMap { attributes =>
185+
var allErrors = Chain.empty[ScynamoDecodeError]
186+
val allValues = factory.newBuilder
187+
var i = 0
188+
189+
while (i < attributes.size()) {
190+
element.decode(attributes.get(i)) match {
191+
case Right(value) => allValues += value
192+
case Left(errors) => allErrors ++= StackFrame.decoding(errors, Index(i)).toChain
180193
}
181194

182-
elems.map(_.result())
195+
i += 1
183196
}
197+
198+
NonEmptyChain.fromChain(allErrors).toLeft(allValues.result())
199+
})
184200
}
185201

186202
trait LowestPrioAutoDecoder {
@@ -209,17 +225,22 @@ trait ObjectScynamoDecoder[A] extends ScynamoDecoder[A] {
209225
attributeValue.asEither(ScynamoType.Map).flatMap(decodeMap)
210226

211227
override def map[B](f: A => B): ObjectScynamoDecoder[B] =
212-
attributes => decodeMap(attributes).map(f)
228+
ObjectScynamoDecoder.instance(decodeMap(_).map(f))
213229

214230
override def transform[B](f: EitherNec[ScynamoDecodeError, A] => EitherNec[ScynamoDecodeError, B]): ObjectScynamoDecoder[B] =
215-
attributes => f(decodeMap(attributes))
231+
ObjectScynamoDecoder.instance(attributes => f(decodeMap(attributes)))
216232
}
217233

218234
object ObjectScynamoDecoder extends ScynamoDecoderFunctions with SemiautoDerivationDecoder {
219235
def apply[A](implicit instance: ObjectScynamoDecoder[A]): ObjectScynamoDecoder[A] = instance
220236

237+
// SAM syntax generates anonymous classes because of non-abstract methods like `defaultValue`.
238+
private[scynamo] def instance[A](
239+
f: java.util.Map[String, AttributeValue] => EitherNec[ScynamoDecodeError, A]
240+
): ObjectScynamoDecoder[A] = f(_)
241+
221242
def const[A](value: A): ObjectScynamoDecoder[A] =
222-
_ => Right(value)
243+
instance(_ => Right(value))
223244

224245
implicit val catsInstances: Monad[ObjectScynamoDecoder] with SemigroupK[ObjectScynamoDecoder] =
225246
new Monad[ObjectScynamoDecoder] with SemigroupK[ObjectScynamoDecoder] {
@@ -230,7 +251,7 @@ object ObjectScynamoDecoder extends ScynamoDecoderFunctions with SemiautoDerivat
230251
ObjectScynamoDecoder.const(x)
231252

232253
override def flatMap[A, B](fa: ObjectScynamoDecoder[A])(f: A => ObjectScynamoDecoder[B]): ObjectScynamoDecoder[B] =
233-
attributes => fa.decodeMap(attributes).flatMap(f(_).decodeMap(attributes))
254+
instance(attributes => fa.decodeMap(attributes).flatMap(f(_).decodeMap(attributes)))
234255

235256
override def tailRecM[A, B](a: A)(f: A => ObjectScynamoDecoder[Either[A, B]]): ObjectScynamoDecoder[B] = {
236257
@tailrec def go(a: A, attributes: java.util.Map[String, AttributeValue]): EitherNec[ScynamoDecodeError, B] =
@@ -240,15 +261,27 @@ object ObjectScynamoDecoder extends ScynamoDecoderFunctions with SemiautoDerivat
240261
case Left(errors) => Left(errors)
241262
}
242263

243-
go(a, _)
264+
instance(go(a, _))
244265
}
245266

246267
override def combineK[A](x: ObjectScynamoDecoder[A], y: ObjectScynamoDecoder[A]): ObjectScynamoDecoder[A] =
247-
attributes => x.decodeMap(attributes).orElse(y.decodeMap(attributes))
268+
instance(attributes => x.decodeMap(attributes).orElse(y.decodeMap(attributes)))
248269
}
249270

250-
implicit def mapDecoder[A](implicit valueDecoder: ScynamoDecoder[A]): ObjectScynamoDecoder[Map[String, A]] =
251-
javaMap => javaMap.asScala.toVector.parTraverse { case (key, value) => valueDecoder.decode(value).map(key -> _) }.map(_.toMap)
271+
implicit def mapDecoder[A](implicit value: ScynamoDecoder[A]): ObjectScynamoDecoder[Map[String, A]] =
272+
instance { attributes =>
273+
var allErrors = Chain.empty[ScynamoDecodeError]
274+
val allValues = Map.newBuilder[String, A]
275+
276+
attributes.forEach { (k, v) =>
277+
value.decode(v) match {
278+
case Right(value) => allValues += k -> value
279+
case Left(errors) => allErrors ++= StackFrame.decoding(errors, MapKey(k)).toChain
280+
}
281+
}
282+
283+
NonEmptyChain.fromChain(allErrors).toLeft(allValues.result())
284+
}
252285
}
253286

254287
trait ScynamoKeyDecoder[A] {
@@ -258,11 +291,12 @@ trait ScynamoKeyDecoder[A] {
258291
object ScynamoKeyDecoder {
259292
def apply[A](implicit decoder: ScynamoKeyDecoder[A]): ScynamoKeyDecoder[A] = decoder
260293

261-
implicit val stringKeyDecoder: ScynamoKeyDecoder[String] = s => Right(s)
294+
// SAM syntax generates anonymous classes because of non-abstract methods like `defaultValue`.
295+
private[scynamo] def instance[A](f: String => EitherNec[ScynamoDecodeError, A]): ScynamoKeyDecoder[A] = f(_)
262296

263-
implicit val uuidKeyDecoder: ScynamoKeyDecoder[UUID] = s => {
264-
val result = Either.catchOnly[IllegalArgumentException](UUID.fromString(s))
297+
implicit val stringKeyDecoder: ScynamoKeyDecoder[String] =
298+
instance(Right.apply)
265299

266-
result.leftMap(e => NonEmptyChain.one(ScynamoDecodeError.conversionError(s"$s", "UUID", Some(e))))
267-
}
300+
implicit val uuidKeyDecoder: ScynamoKeyDecoder[UUID] =
301+
instance(ScynamoDecoder.convert(_, "UUID")(UUID.fromString))
268302
}

src/main/scala/scynamo/ScynamoError.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ object StackFrame {
3939
case class MapKey[A](value: A) extends StackFrame
4040
case class Custom(name: String) extends StackFrame
4141

42-
private[scynamo] def encoding[A](
43-
encoded: EitherNec[ScynamoEncodeError, A],
44-
frame: StackFrame
45-
): EitherNec[ScynamoEncodeError, A] =
42+
private[scynamo] def encoding[A](encoded: EitherNec[ScynamoEncodeError, A], frame: StackFrame): EitherNec[ScynamoEncodeError, A] =
4643
encoded.leftMap(encoding(_, frame))
4744

48-
private[scynamo] def encoding[A](
49-
errors: NonEmptyChain[ScynamoEncodeError],
50-
frame: StackFrame
51-
): NonEmptyChain[ScynamoEncodeError] =
45+
private[scynamo] def encoding[A](errors: NonEmptyChain[ScynamoEncodeError], frame: StackFrame): NonEmptyChain[ScynamoEncodeError] =
46+
errors.map(_.push(frame))
47+
48+
private[scynamo] def decoding[A](encoded: EitherNec[ScynamoDecodeError, A], frame: StackFrame): EitherNec[ScynamoDecodeError, A] =
49+
encoded.leftMap(decoding(_, frame))
50+
51+
private[scynamo] def decoding[A](errors: NonEmptyChain[ScynamoDecodeError], frame: StackFrame): NonEmptyChain[ScynamoDecodeError] =
5252
errors.map(_.push(frame))
5353
}
5454

0 commit comments

Comments
 (0)