Skip to content

Commit

Permalink
Add Instant @@ TTL (#181)
Browse files Browse the repository at this point in the history
* Add Instant @@ TimeToLive

* Documentation

* Add AWS documentation link

* Bump version to 0.7.0

Co-authored-by: Jannik Arndt <[email protected]>
  • Loading branch information
saeltz and JannikArndt authored Mar 3, 2021
1 parent 8c4baf6 commit 2418da4
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 10 deletions.
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,5 @@ lazy val sonatypeSettings = {
}

lazy val mimaSettings = Seq(
mimaPreviousArtifacts := Set("io.moia" %% "scynamo" % "0.6.0")
)
mimaPreviousArtifacts := Set("io.moia" %% "scynamo" % "0.7.0")
)
10 changes: 10 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ val result2 = for {
} yield (encoded, decoded)
```

5. (Optional) You can use a tagged type to use `Instant` for DynamoDB's _TimeToLive_ which [is based on epoch seconds](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/time-to-live-ttl-before-you-start.html#time-to-live-ttl-before-you-start-formatting).
```scala mdoc
import scynamo._
import shapeless.tag.@@
import java.time.Instant

case class ExpriringUser(id: String, firstName: String, lastName: String, expiresAt: Instant @@ TimeToLive)
```
Be aware that you lose millisecond precision.

You can also look at the [minimal
example](#minimal-example-using-the-aws-sdk) below that uses the AWS
SDK (v2) with `scynamo`.
Expand Down
11 changes: 9 additions & 2 deletions src/main/scala/scynamo/ScynamoDecoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package scynamo
import java.time.Instant
import java.util.UUID
import java.util.concurrent.TimeUnit

import cats.data.{EitherNec, NonEmptyChain}
import cats.syntax.either._
import cats.syntax.parallel._
import cats.{Monad, SemigroupK}
import scynamo.StackFrame.Index
import scynamo.generic.auto.AutoDerivationUnlocked
import scynamo.generic.{GenericScynamoDecoder, SemiautoDerivationDecoder}
import shapeless.Lazy
import shapeless.{tag, Lazy}
import shapeless.tag.@@
import software.amazon.awssdk.services.dynamodb.model.AttributeValue

import scala.annotation.tailrec
Expand Down Expand Up @@ -124,6 +124,13 @@ trait DefaultScynamoDecoderInstances extends ScynamoDecoderFunctions with Scynam
result <- convert(nstring, "Long")(_.toLong)
} yield Instant.ofEpochMilli(result)

implicit val instantTtlDecoder: ScynamoDecoder[Instant @@ TimeToLive] =
attributeValue =>
for {
nstring <- attributeValue.asEither(ScynamoType.Number)
result <- convert(nstring, "Long")(_.toLong)
} yield tag[TimeToLive][Instant](Instant.ofEpochSecond(result))

implicit def seqDecoder[A: ScynamoDecoder]: ScynamoDecoder[scala.collection.immutable.Seq[A]] = iterableDecoder

implicit def listDecoder[A: ScynamoDecoder]: ScynamoDecoder[List[A]] = iterableDecoder
Expand Down
9 changes: 6 additions & 3 deletions src/main/scala/scynamo/ScynamoEncoder.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package scynamo

import java.time.Instant
import java.util.UUID

import cats.data.EitherNec
import cats.syntax.either._
import cats.syntax.parallel._
import scynamo.StackFrame.{Index, MapKey}
import scynamo.generic.auto.AutoDerivationUnlocked
import scynamo.generic.{GenericScynamoEncoder, SemiautoDerivationEncoder}
import shapeless._
import shapeless.tag.@@
import software.amazon.awssdk.services.dynamodb.model.AttributeValue

import java.time.Instant
import java.util.UUID
import scala.concurrent.duration.{Duration, FiniteDuration}
import scala.jdk.CollectionConverters._

Expand Down Expand Up @@ -46,6 +46,9 @@ trait DefaultScynamoEncoderInstances extends ScynamoIterableEncoder {

implicit val instantEncoder: ScynamoEncoder[Instant] = numberStringEncoder.contramap[Instant](_.toEpochMilli.toString)

implicit val instantTtlEncoder: ScynamoEncoder[Instant @@ TimeToLive] =
numberStringEncoder.contramap[Instant @@ TimeToLive](_.getEpochSecond.toString)

implicit val uuidEncoder: ScynamoEncoder[UUID] = stringEncoder.contramap[UUID](_.toString)

implicit def seqEncoder[A: ScynamoEncoder]: ScynamoEncoder[scala.collection.immutable.Seq[A]] =
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/scynamo/TimeToLive.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package scynamo

sealed trait TimeToLive
12 changes: 9 additions & 3 deletions src/test/scala/scynamo/ScynamoCodecProps.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package scynamo

import java.time.Instant
import java.util.UUID

import org.scalacheck.Prop.propBoolean
import org.scalacheck.{Gen, Prop, Properties}
import scynamo.ScynamoCodecProps.Shape
import scynamo.generic.semiauto._
import scynamo.wrapper.{ScynamoNumberSet, ScynamoStringSet}
import shapeless.tag

import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.UUID
import scala.concurrent.duration.Duration

class ScynamoCodecProps extends Properties("ScynamoCodec") {
Expand Down Expand Up @@ -46,6 +47,11 @@ class ScynamoCodecProps extends Properties("ScynamoCodec") {
decodeAfterEncodeIsIdentity(value)
}

propertyWithSeed("decode.encode === id (instant @@ ttl)", propertySeed) =
Prop.forAll(Gen.calendar.map(_.toInstant.truncatedTo(ChronoUnit.SECONDS)).map(tag[TimeToLive][Instant](_))) { value =>
decodeAfterEncodeIsIdentity(value)
}

propertyWithSeed("decode.encode === id (seq)", propertySeed) = Prop.forAll { value: scala.collection.immutable.Seq[Int] =>
decodeAfterEncodeIsIdentity(value)
}
Expand Down

0 comments on commit 2418da4

Please sign in to comment.