From 37cd88d6f59b5e9767fec2d767566538162f6531 Mon Sep 17 00:00:00 2001 From: d2a4u Date: Mon, 11 Dec 2023 22:40:10 +0700 Subject: [PATCH] Support Scala3 #major (#301) * Support Scala3 #major - Remove Scanamo module as it is blocking Scala3 support - Made some deprecated classes private - Fixed scalac warning - Upgrade libs to latest * Remove test that checks number of internal requests Since retrieve no longer takes limit, it returns a `fs2.Stream` instead. Limit is now set by caller by calling `.take(x)` on the stream. * update to Scala v3.3.1 * Bump scala 2.13 to latest * Bump to latest sbt * Remove mentioning of `scanamo` from documentation is it has been removed as project's dependencies. * Clean up left over plugin during Scala 3 migration. --------- Co-authored-by: marko asplund --- awssdk/src/it/scala/DeleteOpsSpec.scala | 4 +- awssdk/src/it/scala/GetOpsSpec.scala | 4 +- awssdk/src/it/scala/PutOpsSpec.scala | 14 +- awssdk/src/it/scala/RetrieveOpsSpec.scala | 44 ++---- awssdk/src/it/scala/UpdateOpsSpec.scala | 14 +- awssdk/src/it/scala/Util.scala | 21 +-- .../src/it/scala/api/hi/SimpleIndexSpec.scala | 32 ----- awssdk/src/main/scala/Client.scala | 51 ++++--- awssdk/src/main/scala/DefaultClient.scala | 21 --- awssdk/src/main/scala/api/BatchWriteOps.scala | 12 +- .../src/main/scala/api/hi/SimpleIndex.scala | 23 --- awssdk/src/main/scala/models.scala | 12 +- awssdk/src/test/scala/CodecSpec.scala | 19 +-- awssdk/src/test/scala/api/DedupOpsSpec.scala | 4 +- build.sbt | 40 ++---- docker-compose.yml | 2 +- dynosaur/src/test/scala/ConversionsSpec.scala | 22 +-- project/build.properties | 2 +- scanamo/src/main/scala/conversions.scala | 27 ---- scanamo/src/test/scala/ConversionsSpec.scala | 133 ------------------ .../src/test/scala/ConversionsUsageSpec.scala | 22 --- website/content/docs/codec/meteor/index.md | 2 +- website/content/docs/codec/scanamo/index.md | 38 ----- .../docs/introduction/getstarted/index.md | 13 +- 24 files changed, 110 insertions(+), 466 deletions(-) delete mode 100644 scanamo/src/main/scala/conversions.scala delete mode 100644 scanamo/src/test/scala/ConversionsSpec.scala delete mode 100644 scanamo/src/test/scala/ConversionsUsageSpec.scala delete mode 100644 website/content/docs/codec/scanamo/index.md diff --git a/awssdk/src/it/scala/DeleteOpsSpec.scala b/awssdk/src/it/scala/DeleteOpsSpec.scala index 54eace0c..21d99065 100644 --- a/awssdk/src/it/scala/DeleteOpsSpec.scala +++ b/awssdk/src/it/scala/DeleteOpsSpec.scala @@ -9,7 +9,7 @@ class DeleteOpsSpec extends ITSpec { behavior.of("delete operation") it should "delete an item when using both keys" in forAll { - test: TestData => + (test: TestData) => compositeKeysTable[IO].use { case (client, table) => val put = client.put[TestData](table.tableName, test) @@ -26,7 +26,7 @@ class DeleteOpsSpec extends ITSpec { } it should "delete an item when using partition key only (table doesn't have range key)" in forAll { - test: TestDataSimple => + (test: TestDataSimple) => partitionKeyTable[IO].use { case (client, table) => val put = client.put[TestDataSimple](table.tableName, test) diff --git a/awssdk/src/it/scala/GetOpsSpec.scala b/awssdk/src/it/scala/GetOpsSpec.scala index ba64d386..b71e8730 100644 --- a/awssdk/src/it/scala/GetOpsSpec.scala +++ b/awssdk/src/it/scala/GetOpsSpec.scala @@ -9,7 +9,7 @@ class GetOpsSpec extends ITSpec { behavior.of("get operation") it should "return inserted item using partition key and range key" in forAll { - test: TestData => + (test: TestData) => compositeKeysTable[IO].use[Option[TestData]] { case (client, table) => client.put[TestData](table.tableName, test) >> @@ -23,7 +23,7 @@ class GetOpsSpec extends ITSpec { } it should "return inserted item using partition key only (table doesn't have range key)" in forAll { - test: TestDataSimple => + (test: TestDataSimple) => partitionKeyTable[IO].use[Option[TestDataSimple]] { case (client, table) => client.put[TestDataSimple](table.tableName, test) >> diff --git a/awssdk/src/it/scala/PutOpsSpec.scala b/awssdk/src/it/scala/PutOpsSpec.scala index 585c1440..4e154959 100644 --- a/awssdk/src/it/scala/PutOpsSpec.scala +++ b/awssdk/src/it/scala/PutOpsSpec.scala @@ -10,7 +10,7 @@ class PutOpsSpec extends ITSpec { behavior.of("put operation") it should "success inserting item with both keys" in forAll { - test: TestData => + (test: TestData) => compositeKeysTable[IO].use { case (client, table) => client.put[TestData](table.tableName, test) @@ -18,7 +18,7 @@ class PutOpsSpec extends ITSpec { } it should "return old value after successfully inserting item with both keys" in forAll { - old: TestData => + (old: TestData) => val updated = old.copy(str = old.str + "-updated") compositeKeysTable[IO].use { case (client, table) => @@ -33,7 +33,7 @@ class PutOpsSpec extends ITSpec { } it should "return none if there isn't a previous record with the same keys" in forAll { - test: TestData => + (test: TestData) => compositeKeysTable[IO].use { case (client, table) => client.put[TestData, TestData](table.tableName, test) @@ -41,7 +41,7 @@ class PutOpsSpec extends ITSpec { } it should "success inserting item without sort key" in forAll { - test: TestDataSimple => + (test: TestDataSimple) => val result = partitionKeyTable[IO].use { case (client, table) => client.put[TestDataSimple](table.tableName, test) @@ -50,7 +50,7 @@ class PutOpsSpec extends ITSpec { } it should "return old value after successfully inserting item without sort key" in forAll { - old: TestDataSimple => + (old: TestDataSimple) => val updated = old.copy(data = old.data + "-updated") partitionKeyTable[IO].use { case (client, table) => @@ -62,7 +62,7 @@ class PutOpsSpec extends ITSpec { } it should "success inserting item if key(s) doesn't exist by using condition expression" in forAll { - test: TestData => + (test: TestData) => compositeKeysTable[IO].use { case (client, table) => client.put[TestData]( @@ -78,7 +78,7 @@ class PutOpsSpec extends ITSpec { } it should "fail inserting item if key(s) exists by using condition expression" in forAll { - test: TestData => + (test: TestData) => val result = compositeKeysTable[IO].use { case (client, table) => client.put[TestData]( diff --git a/awssdk/src/it/scala/RetrieveOpsSpec.scala b/awssdk/src/it/scala/RetrieveOpsSpec.scala index 26b74a87..855e7b95 100644 --- a/awssdk/src/it/scala/RetrieveOpsSpec.scala +++ b/awssdk/src/it/scala/RetrieveOpsSpec.scala @@ -12,7 +12,7 @@ class RetrieveOpsSpec extends ITSpec { behavior.of("retrieve operation") it should "return multiple items of the same partition key" in forAll { - test: TestData => + (test: TestData) => val partitionKey = Id("def") val input = List( @@ -24,8 +24,7 @@ class RetrieveOpsSpec extends ITSpec { val retrieval = client.retrieve[Id, TestData]( table, partitionKey, - consistentRead = false, - Int.MaxValue + consistentRead = false ).compile.toList input.traverse(i => client.put[TestData](table.tableName, i) @@ -36,7 +35,7 @@ class RetrieveOpsSpec extends ITSpec { } it should "exact item by EqualTo key expression" in forAll { - test: TestData => + (test: TestData) => val result = compositeKeysTable[IO].use[TestData] { case (client, table) => val retrieval = client.retrieve[Id, Range, TestData]( @@ -45,8 +44,7 @@ class RetrieveOpsSpec extends ITSpec { test.id, SortKeyQuery.EqualTo(test.range) ), - consistentRead = false, - Int.MaxValue + consistentRead = false ).compile.lastOrError client.put[TestData](table.tableName, test) >> retrieval }.unsafeToFuture().futureValue @@ -54,7 +52,7 @@ class RetrieveOpsSpec extends ITSpec { } it should "query secondary index" in forAll { - test: TestData => + (test: TestData) => val input = test.copy(str = "test", int = 0) val result = compositeKeysWithSecondaryIndexTable[IO].use { @@ -65,8 +63,7 @@ class RetrieveOpsSpec extends ITSpec { input.str, SortKeyQuery.EqualTo(input.int) ), - consistentRead = false, - Int.MaxValue + consistentRead = false ).compile.lastOrError client.put[TestData](table.tableName, input) >> retrieval }.unsafeToFuture().futureValue @@ -74,7 +71,7 @@ class RetrieveOpsSpec extends ITSpec { } it should "filter results by given filter expression for PartitionKeyTable" in forAll { - test: TestData => + (test: TestData) => partitionKeyTable[IO].use { case (client, table) => @@ -109,7 +106,7 @@ class RetrieveOpsSpec extends ITSpec { } it should "filter results by given filter expression for CompositeKeysTable" in forAll { - test: List[TestData] => + (test: List[TestData]) => val unique = test.map(t => (t.id, t.range) -> t).toMap.values.toList val partitionKey = Id(Instant.now.toString) val testUpdated = unique.map(t => t.copy(id = partitionKey)) @@ -131,34 +128,11 @@ class RetrieveOpsSpec extends ITSpec { ) ) ), - consistentRead = false, - Int.MaxValue + consistentRead = false ).compile.toList testUpdated.traverse(i => client.put[TestData](table.tableName, i) ) >> Util.retryOf(retrieval)(_.size == input.length) }.unsafeToFuture().futureValue should contain theSameElementsAs input } - - it should "limit internal requests" in forAll { - (test1: TestData, test2: TestData) => - val partitionKey = Id("def") - val input = - List(test1.copy(id = partitionKey), test2.copy(id = partitionKey)) - val result = compositeKeysTable[IO].use[List[fs2.Chunk[TestData]]] { - case (client, table) => - val retrieval = client.retrieve[Id, Range, TestData]( - table, - Query[Id, Range](partitionKey, SortKeyQuery.empty[Range]), - consistentRead = false, - 1 - ).chunks.compile.toList - input.traverse(i => - client.put[TestData](table.tableName, i) - ) >> Util.retryOf(retrieval)( - _.size == input.length - ) - }.unsafeToFuture().futureValue - result.forall(_.size == 1) shouldBe true - } } diff --git a/awssdk/src/it/scala/UpdateOpsSpec.scala b/awssdk/src/it/scala/UpdateOpsSpec.scala index c52467a3..e0688135 100644 --- a/awssdk/src/it/scala/UpdateOpsSpec.scala +++ b/awssdk/src/it/scala/UpdateOpsSpec.scala @@ -12,11 +12,11 @@ class UpdateOpsSpec extends ITSpec { behavior.of("update operation") it should "return new value when condition is met" in forAll { - data: TestData => + (test: TestData) => // condition is int > 0 val newInt = 2 - val input = data.copy(int = 1) - val expected = data.copy(int = newInt) + val input = test.copy(int = 1) + val expected = test.copy(int = newInt) val result = compositeKeysTable[IO].use { case (client, table) => client.put[TestData](table.tableName, input) >> @@ -41,10 +41,10 @@ class UpdateOpsSpec extends ITSpec { } it should "return old value when condition is met" in forAll { - data: TestData => + (test: TestData) => // condition is int > 0 val newInt = 2 - val input = data.copy(int = 1) + val input = test.copy(int = 1) val result = compositeKeysTable[IO].use { case (client, table) => client.put[TestData](table.tableName, input) >> @@ -69,10 +69,10 @@ class UpdateOpsSpec extends ITSpec { } it should "raise error when condition is not met" in forAll { - data: TestData => + (test: TestData) => // condition is int > 0 val newInt = 2 - val input = data.copy(int = -1) + val input = test.copy(int = -1) val result = compositeKeysTable[IO].use { case (client, table) => client.put[TestData](table.tableName, input) >> diff --git a/awssdk/src/it/scala/Util.scala b/awssdk/src/it/scala/Util.scala index c6d80812..8009fbdf 100644 --- a/awssdk/src/it/scala/Util.scala +++ b/awssdk/src/it/scala/Util.scala @@ -48,24 +48,18 @@ private[meteor] object Util { def partitionKeyTable[F[_]: Async] : Resource[F, (Client[F], PartitionKeyTable[Id])] = internalSimpleResources[F].map { - case (c, t, _, _, _, _) => (c, t) + case (c, t, _, _, _) => (c, t) } def simpleTable[F[_]: Async]: Resource[F, SimpleTable[F, Id]] = internalSimpleResources[F].map { - case (_, _, t, _, _, _) => t - } - - def secondarySimpleIndex[F[_]: Async] - : Resource[F, (SimpleTable[F, Id], SecondarySimpleIndex[F, Range])] = - internalSimpleResources[F].map { - case (_, _, _, t, i, _) => (t, i) + case (_, _, t, _, _) => t } def globalSecondarySimpleIndex[F[_]: Async] : Resource[F, (SimpleTable[F, Id], GlobalSecondarySimpleIndex[F, Range])] = internalSimpleResources[F].map { - case (_, _, _, t, _, i) => (t, i) + case (_, _, _, t, i) => (t, i) } def compositeKeysTable[F[_]: Async] @@ -120,7 +114,6 @@ private[meteor] object Util { PartitionKeyTable[Id], SimpleTable[F, Id], SimpleTableWithGlobIndex[F, Id], - SecondarySimpleIndex[F, Range], GlobalSecondarySimpleIndex[F, Range] ) ] = { @@ -146,12 +139,6 @@ private[meteor] object Util { hashKey1, jClient ) - ssi = SecondarySimpleIndex[F, Range]( - simpleRandomNameWithGlobIndex, - simpleRandomIndexName, - glob2ndHashKey, - jClient - ) gssi = GlobalSecondarySimpleIndex[F, Range]( simpleRandomNameWithGlobIndex, simpleRandomIndexName, @@ -186,7 +173,7 @@ private[meteor] object Util { ) ) )(_ => client.deleteTable(simpleRandomNameWithGlobIndex)) - } yield (client, pkt, st, stgi, ssi, gssi) + } yield (client, pkt, st, stgi, gssi) } private def internalCompositeResources[F[_]: Async]: Resource[ diff --git a/awssdk/src/it/scala/api/hi/SimpleIndexSpec.scala b/awssdk/src/it/scala/api/hi/SimpleIndexSpec.scala index e651b74b..efd6fa54 100644 --- a/awssdk/src/it/scala/api/hi/SimpleIndexSpec.scala +++ b/awssdk/src/it/scala/api/hi/SimpleIndexSpec.scala @@ -40,36 +40,4 @@ class SimpleIndexSpec extends ITSpec { fail() } } - - "SecondarySimpleIndex" should "filter results by given filter expression" in { - def retrieval(index: SimpleIndex[IO, Range], cond: Boolean) = - index.retrieve[TestData]( - Query( - data.range, - Expression( - "#b = :bool", - Map("#b" -> "bool"), - Map( - ":bool" -> Encoder[Boolean].write(cond) - ) - ) - ), - consistentRead = false - ) - - secondarySimpleIndex[IO].use { - case (table, index) => - val read = for { - some <- retrieval(index, data.bool) - none <- retrieval(index, !data.bool) - } yield (some, none) - table.put[TestData](data) >> read - }.unsafeToFuture().futureValue match { - case (Some(d), None) => - d shouldEqual data - - case _ => - fail() - } - } } diff --git a/awssdk/src/main/scala/Client.scala b/awssdk/src/main/scala/Client.scala index 2f0a36af..1eeb2463 100644 --- a/awssdk/src/main/scala/Client.scala +++ b/awssdk/src/main/scala/Client.scala @@ -43,6 +43,7 @@ trait Client[F[_]] { /** Get a single item of type U from a table by partition key P. */ + @annotation.nowarn def get[P: Encoder, U: Decoder]( table: PartitionKeyTable[P], partitionKey: P, @@ -51,6 +52,7 @@ trait Client[F[_]] { /** Get a single value from a table by partition key P and sort key S. */ + @annotation.nowarn def get[P: Encoder, S: Encoder, U: Decoder]( table: CompositeKeysTable[P, S], partitionKey: P, @@ -60,6 +62,7 @@ trait Client[F[_]] { /** Retrieve values from a table using a query. */ + @annotation.nowarn def retrieve[P: Encoder, U: Decoder]( index: PartitionKeyIndex[P], query: Query[P, Nothing], @@ -68,16 +71,7 @@ trait Client[F[_]] { /** Retrieve values from a table using a query. */ - @deprecated("Use retrieve without limit", "2022-04-24") - def retrieve[P: Encoder, S: Encoder, U: Decoder]( - index: CompositeKeysIndex[P, S], - query: Query[P, S], - consistentRead: Boolean, - limit: Int - ): fs2.Stream[F, U] - - /** Retrieve values from a table using a query. - */ + @annotation.nowarn def retrieve[P: Encoder, S: Encoder, U: Decoder]( index: CompositeKeysIndex[P, S], query: Query[P, S], @@ -86,19 +80,7 @@ trait Client[F[_]] { /** Retrieve values from a table by partition key P. */ - @deprecated("Use retrieve without limit", "2022-04-24") - def retrieve[ - P: Encoder, - U: Decoder - ]( - index: CompositeKeysIndex[P, _], - partitionKey: P, - consistentRead: Boolean, - limit: Int - ): fs2.Stream[F, U] - - /** Retrieve values from a table by partition key P. - */ + @annotation.nowarn def retrieve[ P: Encoder, U: Decoder @@ -140,6 +122,7 @@ trait Client[F[_]] { /** Delete an item from a table by partition key P and sort key S. */ + @annotation.nowarn def delete[P: Encoder, S: Encoder]( table: CompositeKeysTable[P, S], partitionKey: P, @@ -148,6 +131,7 @@ trait Client[F[_]] { /** Delete an item from a table by partition key P. */ + @annotation.nowarn def delete[P: Encoder]( table: PartitionKeyTable[P], partitionKey: P @@ -173,6 +157,7 @@ trait Client[F[_]] { /** Update an item by partition key P given an update expression. * A Codec of U is required to deserialize return value. */ + @annotation.nowarn def update[P: Encoder, U: Decoder]( table: PartitionKeyTable[P], partitionKey: P, @@ -184,6 +169,7 @@ trait Client[F[_]] { * when it fulfills a condition expression. * A Codec of U is required to deserialize return value. */ + @annotation.nowarn def update[P: Encoder, U: Decoder]( table: PartitionKeyTable[P], partitionKey: P, @@ -195,6 +181,7 @@ trait Client[F[_]] { /** Update an item by partition key P given an update expression. * Return Unit (ReturnValue.NONE). */ + @annotation.nowarn def update[P: Encoder]( table: PartitionKeyTable[P], partitionKey: P, @@ -205,6 +192,7 @@ trait Client[F[_]] { * when it fulfills a condition expression. * Return Unit (ReturnValue.NONE). */ + @annotation.nowarn def update[P: Encoder]( table: PartitionKeyTable[P], partitionKey: P, @@ -215,6 +203,7 @@ trait Client[F[_]] { /** Update an item by partition key P and a sort key S, given an update expression. * A Codec of U is required to deserialize return value. */ + @annotation.nowarn def update[P: Encoder, S: Encoder, U: Decoder]( table: CompositeKeysTable[P, S], partitionKey: P, @@ -227,6 +216,7 @@ trait Client[F[_]] { * when it fulfills a condition expression. * A Codec of U is required to deserialize return value. */ + @annotation.nowarn def update[P: Encoder, S: Encoder, U: Decoder]( table: CompositeKeysTable[P, S], partitionKey: P, @@ -239,6 +229,7 @@ trait Client[F[_]] { /** Update an item by partition key P and a sort key S, given an update expression. * Return Unit (ReturnValue.NONE). */ + @annotation.nowarn def update[P: Encoder, S: Encoder]( table: CompositeKeysTable[P, S], partitionKey: P, @@ -250,6 +241,7 @@ trait Client[F[_]] { * when it fulfills a condition expression. * Return Unit (ReturnValue.NONE). */ + @annotation.nowarn def update[P: Encoder, S: Encoder]( table: CompositeKeysTable[P, S], partitionKey: P, @@ -272,6 +264,7 @@ trait Client[F[_]] { /** Batch get items from a table by partition key P. * A Codec of U is required to deserialize return value based on projection expression. */ + @annotation.nowarn def batchGet[P: Encoder, U: Decoder]( table: PartitionKeyTable[P], consistentRead: Boolean, @@ -284,6 +277,7 @@ trait Client[F[_]] { /** Batch get items from a table primary keys P and S. * A Codec of U is required to deserialize return value based on projection expression. */ + @annotation.nowarn def batchGet[P: Encoder, S: Encoder, U: Decoder]( table: CompositeKeysTable[P, S], consistentRead: Boolean, @@ -296,6 +290,7 @@ trait Client[F[_]] { /** Batch get items from a table by partition keys P. * A Codec of U is required to deserialize return value based on projection expression. */ + @annotation.nowarn def batchGet[P: Encoder, U: Decoder]( table: PartitionKeyTable[P], consistentRead: Boolean, @@ -308,6 +303,7 @@ trait Client[F[_]] { /** Batch get items from a table by primary keys P and S. * A Codec of U is required to deserialize return value based on projection expression. */ + @annotation.nowarn def batchGet[P: Encoder, S: Encoder, U: Decoder]( table: CompositeKeysTable[P, S], consistentRead: Boolean, @@ -320,6 +316,7 @@ trait Client[F[_]] { /** Batch get items from a table by partition keys P. * A Codec of U is required to deserialize return value based on projection expression. */ + @annotation.nowarn def batchGet[P: Encoder, U: Decoder]( table: PartitionKeyTable[P], consistentRead: Boolean, @@ -331,6 +328,7 @@ trait Client[F[_]] { /** Batch get items from a table by primary keys P and S. * A Codec of U is required to deserialize return value based on projection expression. */ + @annotation.nowarn def batchGet[P: Encoder, S: Encoder, U: Decoder]( table: CompositeKeysTable[P, S], consistentRead: Boolean, @@ -347,6 +345,7 @@ trait Client[F[_]] { * same key. Order from upstream is preserved to ensure that only putting the last version of T * within a batch. */ + @annotation.nowarn def batchPut[T: Encoder]( table: Index[_], maxBatchWait: FiniteDuration, @@ -361,6 +360,7 @@ trait Client[F[_]] { * same key. Order from upstream is preserved to ensure that only putting the last version of T * within a batch. */ + @annotation.nowarn def batchPut[T: Encoder]( table: Index[_], items: Iterable[T], @@ -369,6 +369,7 @@ trait Client[F[_]] { /** Batch put unique items into a table. */ + @annotation.nowarn def batchPutUnordered[T: Encoder]( table: Index[_], items: Set[T], @@ -382,6 +383,7 @@ trait Client[F[_]] { * * within a batch, only send deletion request for one P key and discard all duplicates. */ + @annotation.nowarn def batchDelete[P: Encoder]( table: PartitionKeyTable[P], maxBatchWait: FiniteDuration, @@ -395,6 +397,7 @@ trait Client[F[_]] { * * within a batch, only send deletion request for one (P, S) key pair and discard all duplicates. */ + @annotation.nowarn def batchDelete[P: Encoder, S: Encoder]( table: CompositeKeysTable[P, S], maxBatchWait: FiniteDuration, @@ -415,6 +418,7 @@ trait Client[F[_]] { * If the last item for that key is a put, then only perform put, * discard all other actions on that key. */ + @annotation.nowarn def batchWrite[DP: Encoder, P: Encoder]( table: PartitionKeyTable[DP], maxBatchWait: FiniteDuration, @@ -435,6 +439,7 @@ trait Client[F[_]] { * If the last item for that key is a put, then only perform put, * discard all other actions on that key. */ + @annotation.nowarn def batchWrite[DP: Encoder, DS: Encoder, P: Encoder]( table: CompositeKeysTable[DP, DS], maxBatchWait: FiniteDuration, diff --git a/awssdk/src/main/scala/DefaultClient.scala b/awssdk/src/main/scala/DefaultClient.scala index 72dd32c8..42a62db5 100644 --- a/awssdk/src/main/scala/DefaultClient.scala +++ b/awssdk/src/main/scala/DefaultClient.scala @@ -46,14 +46,6 @@ private[meteor] class DefaultClient[F[_]: Async: RaiseThrowable]( ): F[Option[U]] = retrieveOp[F, P, U](index, query, consistentRead)(jClient) - def retrieve[P: Encoder, S: Encoder, U: Decoder]( - index: CompositeKeysIndex[P, S], - query: Query[P, S], - consistentRead: Boolean, - limit: Int - ): fs2.Stream[F, U] = - retrieveOp[F, P, S, U](index, query, consistentRead, limit.some)(jClient) - def retrieve[P: Encoder, S: Encoder, U: Decoder]( index: CompositeKeysIndex[P, S], query: Query[P, S], @@ -61,19 +53,6 @@ private[meteor] class DefaultClient[F[_]: Async: RaiseThrowable]( ): fs2.Stream[F, U] = retrieveOp[F, P, S, U](index, query, consistentRead, None)(jClient) - def retrieve[ - P: Encoder, - U: Decoder - ]( - index: CompositeKeysIndex[P, _], - partitionKey: P, - consistentRead: Boolean, - limit: Int - ): fs2.Stream[F, U] = - retrieveOp[F, P, U](index, partitionKey, consistentRead, limit.some)( - jClient - ) - def retrieve[ P: Encoder, U: Decoder diff --git a/awssdk/src/main/scala/api/BatchWriteOps.scala b/awssdk/src/main/scala/api/BatchWriteOps.scala index 836f235d..240216ef 100644 --- a/awssdk/src/main/scala/api/BatchWriteOps.scala +++ b/awssdk/src/main/scala/api/BatchWriteOps.scala @@ -28,7 +28,7 @@ private[meteor] trait SharedBatchWriteOps extends DedupOps { table: Index[_], maxBatchWait: FiniteDuration, backoffStrategy: BackoffStrategy - )(jClient: DynamoDbAsyncClient): Pipe[F, I, Unit] = { in: Stream[F, I] => + )(jClient: DynamoDbAsyncClient): Pipe[F, I, Unit] = { (in: Stream[F, I]) => mkPutRequestInOrdered[F, I](table, maxBatchWait).apply( in ).flatMap { @@ -42,7 +42,7 @@ private[meteor] trait SharedBatchWriteOps extends DedupOps { maxBatchWait: FiniteDuration, parallelism: Int, backoffStrategy: BackoffStrategy - )(jClient: DynamoDbAsyncClient): Pipe[F, I, Unit] = { in: Stream[F, I] => + )(jClient: DynamoDbAsyncClient): Pipe[F, I, Unit] = { (in: Stream[F, I]) => in.groupWithin(MaxBatchWriteSize, maxBatchWait).map { chunk => val reqs = chunk.foldLeft(Map.empty[I, jMap[String, AttributeValue]]) { @@ -217,7 +217,7 @@ private[meteor] trait CompositeKeysBatchWriteOps extends SharedBatchWriteOps { parallelism: Int, backoffStrategy: BackoffStrategy )(jClient: DynamoDbAsyncClient): Pipe[F, (P, S), Unit] = { - in: Stream[F, (P, S)] => + (in: Stream[F, (P, S)]) => mkDeleteRequestOutOrdered[F, P, S]( table, maxBatchWait, @@ -238,7 +238,7 @@ private[meteor] trait CompositeKeysBatchWriteOps extends SharedBatchWriteOps { maxBatchWait: FiniteDuration, backoffStrategy: BackoffStrategy )(jClient: DynamoDbAsyncClient): Pipe[F, Either[(P, S), I], Unit] = { - in: Stream[F, Either[(P, S), I]] => + (in: Stream[F, Either[(P, S), I]]) => mkRequestInOrdered[F, P, S, I](table, maxBatchWait).apply( in ).flatMap { @@ -326,7 +326,7 @@ private[meteor] trait PartitionKeyBatchWriteOps extends SharedBatchWriteOps { maxBatchWait: FiniteDuration, parallelism: Int, backoffStrategy: BackoffStrategy - )(jClient: DynamoDbAsyncClient): Pipe[F, P, Unit] = { in: Stream[F, P] => + )(jClient: DynamoDbAsyncClient): Pipe[F, P, Unit] = { (in: Stream[F, P]) => mkDeleteRequestOutOrdered[F, P](table, maxBatchWait).apply(in).map { req => sendHandleLeftOver(req, backoffStrategy)(jClient) @@ -342,7 +342,7 @@ private[meteor] trait PartitionKeyBatchWriteOps extends SharedBatchWriteOps { maxBatchWait: FiniteDuration, backoffStrategy: BackoffStrategy )(jClient: DynamoDbAsyncClient): Pipe[F, Either[DP, P], Unit] = { - in: Stream[F, Either[DP, P]] => + (in: Stream[F, Either[DP, P]]) => mkRequestInOrdered[F, DP, P](table, maxBatchWait).apply( in ).flatMap { diff --git a/awssdk/src/main/scala/api/hi/SimpleIndex.scala b/awssdk/src/main/scala/api/hi/SimpleIndex.scala index 84eb97e4..2221309f 100644 --- a/awssdk/src/main/scala/api/hi/SimpleIndex.scala +++ b/awssdk/src/main/scala/api/hi/SimpleIndex.scala @@ -39,29 +39,6 @@ private[meteor] sealed abstract class SimpleIndex[F[_]: Async, P: Encoder] )(jClient) } -/** Represent a secondary index where the index has only partition key and no sort key. - * - * @param tableName table's name - * @param indexName index's name - * @param partitionKeyDef partition key definition - * @param jClient DynamoDB java async client - * @tparam F effect type - * @tparam P partition key type - */ -@deprecated( - "use meteor.hi.GlobalSecondarySimpleIndex instead - see https://github.com/d2a4u/meteor/issues/229 for more details", - "2022-04-21" -) -case class SecondarySimpleIndex[F[_]: Async, P: Encoder]( - tableName: String, - indexName: String, - partitionKeyDef: KeyDef[P], - jClient: DynamoDbAsyncClient -) extends SimpleIndex[F, P] { - val index: PartitionKeyIndex[P] = - PartitionKeySecondaryIndex[P](tableName, indexName, partitionKeyDef) -} - /** Represent a table where the index has only partition key and no sort key. * * @param tableName table's name diff --git a/awssdk/src/main/scala/models.scala b/awssdk/src/main/scala/models.scala index 21ee95fc..2dc89d1b 100644 --- a/awssdk/src/main/scala/models.scala +++ b/awssdk/src/main/scala/models.scala @@ -135,8 +135,7 @@ private[meteor] sealed trait CompositeKeysIndex[P, S] extends Index[P] { * @param partitionKeyDef partition key's definition * @tparam P partition key's type */ -@deprecated("use meteor.api.hi.SimpleTable instead", "2021-05-24") -case class PartitionKeyTable[P]( +private[meteor] case class PartitionKeyTable[P]( tableName: String, partitionKeyDef: KeyDef[P] ) extends PartitionKeyIndex[P] @@ -149,8 +148,7 @@ case class PartitionKeyTable[P]( * @tparam P partition key's type * @tparam S sort key's type */ -@deprecated("use meteor.api.hi.CompositeTable instead", "2021-05-24") -case class CompositeKeysTable[P, S]( +private[meteor] case class CompositeKeysTable[P, S]( tableName: String, partitionKeyDef: KeyDef[P], sortKeyDef: KeyDef[S] @@ -163,8 +161,7 @@ case class CompositeKeysTable[P, S]( * @param partitionKeyDef partition key's definition * @tparam P partition key's type */ -@deprecated("use meteor.api.hi.SecondarySimpleIndex instead", "2021-05-24") -case class PartitionKeySecondaryIndex[P]( +private[meteor] case class PartitionKeySecondaryIndex[P]( tableName: String, indexName: String, partitionKeyDef: KeyDef[P] @@ -179,8 +176,7 @@ case class PartitionKeySecondaryIndex[P]( * @tparam P partition key's type * @tparam S sort key's type */ -@deprecated("use meteor.api.hi.SecondaryCompositeIndex instead", "2021-05-24") -case class CompositeKeysSecondaryIndex[P, S]( +private[meteor] case class CompositeKeysSecondaryIndex[P, S]( tableName: String, indexName: String, partitionKeyDef: KeyDef[P], diff --git a/awssdk/src/test/scala/CodecSpec.scala b/awssdk/src/test/scala/CodecSpec.scala index e04970a4..6cd7a5e5 100644 --- a/awssdk/src/test/scala/CodecSpec.scala +++ b/awssdk/src/test/scala/CodecSpec.scala @@ -20,28 +20,29 @@ class CodecSpec with ScalaCheckDrivenPropertyChecks { behavior.of("Encoder and Decoder") - it should "successful round trip for Int" in forAll { int: Int => + it should "successful round trip for Int" in forAll { (int: Int) => roundTrip(int) shouldEqual Right(int) } - it should "successful round trip for String" in forAll { str: String => + it should "successful round trip for String" in forAll { (str: String) => roundTrip(str) shouldEqual Right(str) } - it should "successful round trip for UUID" in forAll { uuid: UUID => + it should "successful round trip for UUID" in forAll { (uuid: UUID) => roundTrip(uuid) shouldEqual Right(uuid) } - it should "successful round trip for Boolean" in forAll { bool: Boolean => + it should "successful round trip for Boolean" in forAll { (bool: Boolean) => roundTrip(bool) shouldEqual Right(bool) } - it should "successful round trip for Long" in forAll { long: Long => + it should "successful round trip for Long" in forAll { (long: Long) => roundTrip(long) shouldEqual Right(long) } - it should "successful round trip for Instant" in forAll { instant: Instant => - roundTrip(instant) shouldEqual Right(instant) + it should "successful round trip for Instant" in forAll { + (instant: Instant) => + roundTrip(instant) shouldEqual Right(instant) } it should "successful round trip for Seq[String]" in forAll { @@ -77,7 +78,7 @@ class CodecSpec } it should "successful round trip for Array[Byte]" in forAll { - bytes: Array[Byte] => + (bytes: Array[Byte]) => roundTrip[Array[Byte]](bytes) match { case Right(b) => b should contain theSameElementsAs bytes case _ => fail() @@ -85,7 +86,7 @@ class CodecSpec } it should "successful round trip for Seq[Array[Byte]]" in forAll { - bytes: List[Array[Byte]] => + (bytes: List[Array[Byte]]) => roundTrip[immutable.List[Array[Byte]]](bytes) match { case Right(b) => b should contain theSameElementsAs bytes case Left(err) => fail(err) diff --git a/awssdk/src/test/scala/api/DedupOpsSpec.scala b/awssdk/src/test/scala/api/DedupOpsSpec.scala index 45ca0d01..b01884e7 100644 --- a/awssdk/src/test/scala/api/DedupOpsSpec.scala +++ b/awssdk/src/test/scala/api/DedupOpsSpec.scala @@ -15,7 +15,7 @@ class DedupOpsSpec with ScalaCheckDrivenPropertyChecks { "deduplication" should "remove duplicated items" in forAll { - input: List[Int] => + (input: List[Int]) => val expect = input.distinct val dedupped = DedupOps.dedupInOrdered[Try, Int, Int, Int]( @@ -25,7 +25,7 @@ class DedupOpsSpec } it should "not remove none duplicated items" in forAll { - input: List[Int] => + (input: List[Int]) => val expect = input.distinct val dedupped = BatchGetOps.dedupInOrdered[Try, Int, Int, Int](Chunk(input: _*))( diff --git a/build.sbt b/build.sbt index defdd2fd..5c366d30 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,4 @@ import sbt.Keys.organization -import sbt.addCompilerPlugin val catsVersion = "2.10.0" val catsEffectVersion = "3.5.2" @@ -22,13 +21,13 @@ lazy val testDependencies = Seq( lazy val ItTest = config("it").extend(Test) +lazy val scala3 = "3.3.1" lazy val scala213 = "2.13.12" -lazy val scala212 = "2.12.18" lazy val commonSettings = Seq( ThisBuild / organization := "io.github.d2a4u", - scalaVersion := scala213, - crossScalaVersions ++= Seq(scala212, scala213), + scalaVersion := scala3, + crossScalaVersions += scala213, Test / parallelExecution := true, scalafmtOnCompile := true, licenses += ("MIT", url("http://opensource.org/licenses/MIT")), @@ -55,9 +54,6 @@ lazy val commonSettings = Seq( Global / releaseEarlyWith := SonatypePublisher, sonatypeProfileName := "io.github.d2a4u", releaseEarlyEnableSyncToMaven := true, - addCompilerPlugin( - "org.typelevel" % "kind-projector" % "0.13.2" cross CrossVersion.full - ), Test / scalacOptions ~= filterConsoleScalacOptions, Compile / scalacOptions ~= filterConsoleScalacOptions ) @@ -67,10 +63,19 @@ lazy val root = project .settings(name := "meteor", commonSettings, noPublish) .dependsOn( awssdk % "compile->compile;test->test", - scanamo % "compile->compile;test->test", dynosaur % "compile->compile;test->test" ) - .aggregate(awssdk, scanamo, dynosaur) + .settings( + Compile / scalacOptions ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((3, _)) => + Seq("-Ykind-projector:underscores") + case Some((2, 13)) => + Seq("-Xsource:3", "-P:kind-projector:underscore-placeholders") + } + } + ) + .aggregate(awssdk, dynosaur) lazy val noPublish = Seq(publish := {}, publishLocal := {}, publishArtifact := false) @@ -108,20 +113,3 @@ lazy val dynosaur = project commonSettings ).dependsOn(awssdk) -lazy val scanamo = project - .in(file("scanamo")) - .settings( - inConfig(Test)(Defaults.testSettings), - Test / testOptions += Tests.Argument( - "-oD" - ) // enabled time measurement for each test - ) - .settings( - name := "meteor-scanamo", - libraryDependencies ++= dependencies ++ testDependencies.map( - _ % "test" - ) ++ Seq( - "org.scanamo" %% "scanamo" % "1.0.0-M30" - ), - commonSettings - ).dependsOn(awssdk) diff --git a/docker-compose.yml b/docker-compose.yml index 61d404c0..a1669b1f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: dynamodb-local: - image: amazon/dynamodb-local:1.18.0 + image: amazon/dynamodb-local:1.21.0 container_name: dynamodb-local ports: - "8000:8000" diff --git a/dynosaur/src/test/scala/ConversionsSpec.scala b/dynosaur/src/test/scala/ConversionsSpec.scala index 36e74538..f1926968 100644 --- a/dynosaur/src/test/scala/ConversionsSpec.scala +++ b/dynosaur/src/test/scala/ConversionsSpec.scala @@ -44,42 +44,42 @@ class ConversionsSpec //TODO: test for Array[Byte] it should "cross read/write from Schema to Codec for Int" in forAll { - int: Int => + (int: Int) => roundTrip(int) shouldBe true } it should "cross read/write from Schema to Codec for non empty String" in forAll { - str: String => + (str: String) => roundTrip(str) shouldBe true } it should "cross read/write from Schema to Codec for Boolean" in forAll { - bool: Boolean => + (bool: Boolean) => roundTrip(bool) shouldBe true } it should "cross read/write from Schema to Codec for Long" in forAll { - long: Long => + (long: Long) => roundTrip(long) shouldBe true } it should "cross read/write from Schema to Codec for Float" in forAll { - float: Float => + (float: Float) => roundTrip(float) shouldBe true } it should "cross read/write from Schema to Codec for Double" in forAll { - double: Double => + (double: Double) => roundTrip(double) shouldBe true } it should "cross read/write from Schema to Codec for Short" in forAll { - short: Short => + (short: Short) => roundTrip(short) shouldBe true } it should "cross read/write from Schema to Codec for Option[Int]" in forAll { - opt: Option[Int] => + (opt: Option[Int]) => roundTripOpt[Int]( Encoder[Option[Int]], Schema.nullable[Int], @@ -88,17 +88,17 @@ class ConversionsSpec } it should "cross read/write from Schema to Codec for List[Int]" in forAll { - list: List[Int] => + (list: List[Int]) => roundTrip(list) shouldBe true } it should "cross read/write from Schema to Codec for Seq[Int]" in forAll { - seq: immutable.Seq[Int] => + (seq: immutable.Seq[Int]) => roundTrip(seq) shouldBe true } it should "cross read/write from Schema to Codec for Map[String, Int]" in forAll { - map: Map[String, Int] => + (map: Map[String, Int]) => roundTrip(map) shouldBe true } } diff --git a/project/build.properties b/project/build.properties index f6acff8b..303541e5 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.6.2 +sbt.version = 1.9.6 diff --git a/scanamo/src/main/scala/conversions.scala b/scanamo/src/main/scala/conversions.scala deleted file mode 100644 index 5f848e31..00000000 --- a/scanamo/src/main/scala/conversions.scala +++ /dev/null @@ -1,27 +0,0 @@ -package meteor -package scanamo -package formats - -import meteor.codec.{Decoder, Encoder} -import meteor.errors.DecoderError -import org.scanamo.{DynamoFormat, DynamoReadError} -import software.amazon.awssdk.services.dynamodb.model.AttributeValue - -object conversions { - - implicit def dynamoFormatToDecoder[T](implicit - df: DynamoFormat[T]): Decoder[T] = - new Decoder[T] { - def read(av: AttributeValue): Either[DecoderError, T] = - df.read(av).left.map { err => - DecoderError(DynamoReadError.describe(err), None) - } - } - - implicit def dynamoFormatToEncoder[T](implicit - df: DynamoFormat[T]): Encoder[T] = - new Encoder[T] { - def write(a: T): AttributeValue = - df.write(a).toAttributeValue - } -} diff --git a/scanamo/src/test/scala/ConversionsSpec.scala b/scanamo/src/test/scala/ConversionsSpec.scala deleted file mode 100644 index 7021612f..00000000 --- a/scanamo/src/test/scala/ConversionsSpec.scala +++ /dev/null @@ -1,133 +0,0 @@ -package meteor -package scanamo -package formats - -import meteor.codec.{Codec, Decoder, Encoder} -import meteor.syntax._ -import meteor.scanamo.formats.conversions._ -import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks -import org.scanamo.DynamoReadError -import org.scanamo.{DynamoFormat, DynamoValue} - -import scala.collection.immutable - -class ConversionsSpec - extends AnyFlatSpec - with Matchers - with ScalaCheckDrivenPropertyChecks { - behavior.of("DynamoFormat conversion") - - implicit val genNonEmptyString: Gen[String] = - Gen.nonEmptyListOf(Gen.alphaNumChar).map(_.mkString("")) - - implicit val arbNonEmptyString: Arbitrary[String] = - Arbitrary(genNonEmptyString) - - def roundTrip[T: Codec: DynamoFormat](t: T): Boolean = { - val dynamoFormatWrite = DynamoFormat[T].write(t).toAttributeValue - val encoderWrite = Encoder[T].write(t) - - val round1 = Decoder[T].read(dynamoFormatWrite).toOption - val round2 = DynamoFormat[T].read(encoderWrite).toOption - round1.get == round2.get - } - - def roundTripOpt[T: Decoder]( - t: Option[T] - )(implicit enc: Encoder[Option[T]], fmt: DynamoFormat[Option[T]]): Boolean = { - val dynamoFormatWrite = fmt.write(t).toAttributeValue - val encoderWrite = enc.write(t) - - val round1 = dynamoFormatWrite.asOpt[T].toOption - val round2 = fmt.read(encoderWrite).toOption - round1.flatten == round2.flatten - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Int" in forAll { - int: Int => - roundTrip(int) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for non empty String" in forAll { - str: String => - roundTrip(str) shouldBe true - } - - // test to demonstrate Scanamo's issue where it writes None and Some("") both to Dynamo' null, which - // throws away original type and make it not possible to figure out which is which when reading - it should "scanano cannot distinguish between None and Some of empty String" ignore { - val format = DynamoFormat[Option[String]] - format.read(format.write(None)) shouldEqual Right(None) - format.read(format.write(Some(""))) shouldEqual Right(Some("")) - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Boolean" in forAll { - bool: Boolean => - roundTrip(bool) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Long" in forAll { - long: Long => - roundTrip(long) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Float" in forAll { - float: Float => - roundTrip(float) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Byte" in forAll { - byte: Byte => - roundTrip(byte) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Double" in forAll { - double: Double => - roundTrip(double) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Short" in forAll { - short: Short => - roundTrip(short) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for BigDecimal" in forAll { - bd: BigDecimal => - roundTrip(bd) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Option[Int]" in forAll { - opt: Option[Int] => - roundTripOpt(opt) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for List[Int]" in forAll { - list: List[Int] => - roundTrip(list) shouldBe true - } - - val seqFmt = implicitly[DynamoFormat[Seq[Int]]] - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Seq[Int]" in forAll { - seq: immutable.Seq[Int] => - implicit val immutableFmt: DynamoFormat[immutable.Seq[Int]] = - new DynamoFormat[immutable.Seq[Int]] { - override def read(av: DynamoValue) - : Either[DynamoReadError, immutable.Seq[Int]] = - seqFmt.read(av).map(_.toList) - - override def write(t: immutable.Seq[Int]): DynamoValue = - seqFmt.write(t) - } - implicitly[DynamoFormat[Seq[Int]]] - roundTrip(seq) shouldBe true - } - - it should "cross read/write from DynamoFormat and Encoder/Decoder for Map[String, Int]" in forAll { - map: Map[String, Int] => - roundTrip(map) shouldBe true - } -} diff --git a/scanamo/src/test/scala/ConversionsUsageSpec.scala b/scanamo/src/test/scala/ConversionsUsageSpec.scala deleted file mode 100644 index 198cff9b..00000000 --- a/scanamo/src/test/scala/ConversionsUsageSpec.scala +++ /dev/null @@ -1,22 +0,0 @@ -package meteor -package scanamo -package formats - -import cats.syntax.all._ -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -import org.scanamo.DynamoFormat -import org.scanamo.generic.semiauto._ -import meteor.codec.Codec - -case class Book(title: String, author: String) - -class ConversionsUsageSpec extends AnyFlatSpec with Matchers { - it should "support implicit usage" in { - implicit val bookFormat: DynamoFormat[Book] = deriveDynamoFormat - - import conversions._ - - implicitly[Codec[Book]] should not be null - } -} diff --git a/website/content/docs/codec/meteor/index.md b/website/content/docs/codec/meteor/index.md index 8288d2cb..21f18c40 100644 --- a/website/content/docs/codec/meteor/index.md +++ b/website/content/docs/codec/meteor/index.md @@ -153,6 +153,6 @@ whereas it can be as simple as: * When using auto derivation, conversion is usually required to map between different layers of model. With the explicit codec approach, we need fewer layers of model but conversion gets more - boilerplate. I think the problems are the same with 2 approaches, they just being solved at + boilerplate. The problems are the same with 2 approaches, they just being solved at different places. \ No newline at end of file diff --git a/website/content/docs/codec/scanamo/index.md b/website/content/docs/codec/scanamo/index.md deleted file mode 100644 index f7aaf1a1..00000000 --- a/website/content/docs/codec/scanamo/index.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: "Scanamo Format" -description: "" -lead: "" -date: 2021-01-26T22:19:00Z -lastmod: 2021-01-26T22:19:00Z -draft: false -images: [] -menu: - docs: - parent: "codec" -weight: 1002 -toc: true ---- - -The `meteor-scanamo` module provides integration with [Scanamo format library](https://www.scanamo.org/dynamo-format.html). -It provides implicit construction from `scanamo`'s `DynamoFormat` to `meteor`'s codec. - -**Note:** The module uses `scanamo-format`'s version `1.0.0-M11` instead of latest because in my -experience, this is the most stable version. however, because it is an older version when DynamoDB -did not support empty String, the following scenario: - -- Empty string: `""` -- Optional none: `None` -- Optional some of empty string: `Some("")` - -are all serialized to an `AttributeValue` of `null`. This is problematic because once the value is -written down, reading it back is difficult. The same reason why `meteor` doesn't provide a `Decoder` -instance for `Option[T]` but only provides syntax to help to deal with optional value. - -```scala -import meteor.scanamo.formats.conversions._ -import meteor.codec.Codec -import org.scanamo.DynamoFormat - -implicit val bookFormat: DynamoFormat[Book] = ... -val bookCodec: Codec[Book] = implicitly[Codec[Book]] -``` diff --git a/website/content/docs/introduction/getstarted/index.md b/website/content/docs/introduction/getstarted/index.md index 70ec8d9d..5091959c 100644 --- a/website/content/docs/introduction/getstarted/index.md +++ b/website/content/docs/introduction/getstarted/index.md @@ -24,7 +24,7 @@ DynamoDB's actions. * auto remove duplication in batch actions * support DynamoDB's single table design * provides codec as a simple abstraction and syntax on top of `AttributeValue` -* support multiple codec libraries including `Dynosaur` and `Scanamo` +* support `Dynosaur` codec library ## Installation @@ -37,17 +37,6 @@ libraryDependencies += "io.github.d2a4u" %% "meteor-awssdk" % "LATEST_VERSION" ### Modules -#### [Scanamo Format](https://github.com/scanamo/scanamo) - -**Note:** Only version `1.0.0-M11` is supported because in my experience, this is the most stable version of -`Scanamo`. However, because it is an older version when DynamoDB's did not support empty -String, this version of `Scanamo` serializes these cases: `""`, `None` and `Some("")` to Dynamo's -`NULL`. This is problematic because once the value is written down, reading it back is difficult. - -```scala -libraryDependencies += "io.github.d2a4u" %% "meteor-scanamo" % "LATEST_VERSION" -``` - #### [Dynosaur Codecs](https://systemfw.org/dynosaur) ```scala