Skip to content

Commit 2418da4

Browse files
saeltzJannikArndt
andauthored
Add Instant @@ TTL (#181)
* Add Instant @@ TimeToLive * Documentation * Add AWS documentation link * Bump version to 0.7.0 Co-authored-by: Jannik Arndt <jannik.arndt@moia.io>
1 parent 8c4baf6 commit 2418da4

File tree

6 files changed

+39
-10
lines changed

6 files changed

+39
-10
lines changed

build.sbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,5 @@ lazy val sonatypeSettings = {
109109
}
110110

111111
lazy val mimaSettings = Seq(
112-
mimaPreviousArtifacts := Set("io.moia" %% "scynamo" % "0.6.0")
113-
)
112+
mimaPreviousArtifacts := Set("io.moia" %% "scynamo" % "0.7.0")
113+
)

docs/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ val result2 = for {
4545
} yield (encoded, decoded)
4646
```
4747

48+
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).
49+
```scala mdoc
50+
import scynamo._
51+
import shapeless.tag.@@
52+
import java.time.Instant
53+
54+
case class ExpriringUser(id: String, firstName: String, lastName: String, expiresAt: Instant @@ TimeToLive)
55+
```
56+
Be aware that you lose millisecond precision.
57+
4858
You can also look at the [minimal
4959
example](#minimal-example-using-the-aws-sdk) below that uses the AWS
5060
SDK (v2) with `scynamo`.

src/main/scala/scynamo/ScynamoDecoder.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ package scynamo
33
import java.time.Instant
44
import java.util.UUID
55
import java.util.concurrent.TimeUnit
6-
76
import cats.data.{EitherNec, NonEmptyChain}
87
import cats.syntax.either._
98
import cats.syntax.parallel._
109
import cats.{Monad, SemigroupK}
1110
import scynamo.StackFrame.Index
1211
import scynamo.generic.auto.AutoDerivationUnlocked
1312
import scynamo.generic.{GenericScynamoDecoder, SemiautoDerivationDecoder}
14-
import shapeless.Lazy
13+
import shapeless.{tag, Lazy}
14+
import shapeless.tag.@@
1515
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
1616

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

127+
implicit val instantTtlDecoder: ScynamoDecoder[Instant @@ TimeToLive] =
128+
attributeValue =>
129+
for {
130+
nstring <- attributeValue.asEither(ScynamoType.Number)
131+
result <- convert(nstring, "Long")(_.toLong)
132+
} yield tag[TimeToLive][Instant](Instant.ofEpochSecond(result))
133+
127134
implicit def seqDecoder[A: ScynamoDecoder]: ScynamoDecoder[scala.collection.immutable.Seq[A]] = iterableDecoder
128135

129136
implicit def listDecoder[A: ScynamoDecoder]: ScynamoDecoder[List[A]] = iterableDecoder

src/main/scala/scynamo/ScynamoEncoder.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package scynamo
22

3-
import java.time.Instant
4-
import java.util.UUID
5-
63
import cats.data.EitherNec
74
import cats.syntax.either._
85
import cats.syntax.parallel._
96
import scynamo.StackFrame.{Index, MapKey}
107
import scynamo.generic.auto.AutoDerivationUnlocked
118
import scynamo.generic.{GenericScynamoEncoder, SemiautoDerivationEncoder}
129
import shapeless._
10+
import shapeless.tag.@@
1311
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
1412

13+
import java.time.Instant
14+
import java.util.UUID
1515
import scala.concurrent.duration.{Duration, FiniteDuration}
1616
import scala.jdk.CollectionConverters._
1717

@@ -46,6 +46,9 @@ trait DefaultScynamoEncoderInstances extends ScynamoIterableEncoder {
4646

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

49+
implicit val instantTtlEncoder: ScynamoEncoder[Instant @@ TimeToLive] =
50+
numberStringEncoder.contramap[Instant @@ TimeToLive](_.getEpochSecond.toString)
51+
4952
implicit val uuidEncoder: ScynamoEncoder[UUID] = stringEncoder.contramap[UUID](_.toString)
5053

5154
implicit def seqEncoder[A: ScynamoEncoder]: ScynamoEncoder[scala.collection.immutable.Seq[A]] =
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package scynamo
2+
3+
sealed trait TimeToLive

src/test/scala/scynamo/ScynamoCodecProps.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package scynamo
22

3-
import java.time.Instant
4-
import java.util.UUID
5-
63
import org.scalacheck.Prop.propBoolean
74
import org.scalacheck.{Gen, Prop, Properties}
85
import scynamo.ScynamoCodecProps.Shape
96
import scynamo.generic.semiauto._
107
import scynamo.wrapper.{ScynamoNumberSet, ScynamoStringSet}
8+
import shapeless.tag
119

10+
import java.time.Instant
11+
import java.time.temporal.ChronoUnit
12+
import java.util.UUID
1213
import scala.concurrent.duration.Duration
1314

1415
class ScynamoCodecProps extends Properties("ScynamoCodec") {
@@ -46,6 +47,11 @@ class ScynamoCodecProps extends Properties("ScynamoCodec") {
4647
decodeAfterEncodeIsIdentity(value)
4748
}
4849

50+
propertyWithSeed("decode.encode === id (instant @@ ttl)", propertySeed) =
51+
Prop.forAll(Gen.calendar.map(_.toInstant.truncatedTo(ChronoUnit.SECONDS)).map(tag[TimeToLive][Instant](_))) { value =>
52+
decodeAfterEncodeIsIdentity(value)
53+
}
54+
4955
propertyWithSeed("decode.encode === id (seq)", propertySeed) = Prop.forAll { value: scala.collection.immutable.Seq[Int] =>
5056
decodeAfterEncodeIsIdentity(value)
5157
}

0 commit comments

Comments
 (0)