diff --git a/README.md b/README.md index e82f5ce7f..1d6c919e4 100644 --- a/README.md +++ b/README.md @@ -40,19 +40,20 @@ libraryDependencies ++= Seq( ```scala import zio._ import zio.redis._ +import zio.redis.options.NonNegativeLong import zio.schema._ import zio.schema.codec._ object ZIORedisExample extends ZIOAppDefault { - + object ProtobufCodecSupplier extends CodecSupplier { def get[A: Schema]: BinaryCodec[A] = ProtobufCodec.protobufCodec } - - val myApp: ZIO[Redis, RedisError, Unit] = + + val myApp: ZIO[Redis, RedisError, Unit] = for { redis <- ZIO.service[Redis] - _ <- redis.set("myKey", 8L, Some(1.minutes)) + _ <- redis.set("myKey", 8L, expireAt = Some(SetExpire.SetExpireSeconds(NonNegativeLong(60)))) v <- redis.get("myKey").returning[Long] _ <- Console.printLine(s"Value of myKey: $v").orDie _ <- redis.hSet("myHash", ("k1", 6), ("k2", 2)) @@ -60,7 +61,7 @@ object ZIORedisExample extends ZIOAppDefault { _ <- redis.sAdd("mySet", "a", "b", "a", "c") } yield () - override def run = + override def run = myApp.provide(Redis.local, ZLayer.succeed[CodecSupplier](ProtobufCodecSupplier)) } ``` diff --git a/docs/index.md b/docs/index.md index b9478ea0f..c441e41a6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,6 +40,7 @@ libraryDependencies ++= Seq( ```scala mdoc:compile-only import zio._ import zio.redis._ +import zio.redis.options.NonNegativeLong import zio.schema._ import zio.schema.codec._ @@ -49,10 +50,10 @@ object ZIORedisExample extends ZIOAppDefault { def get[A: Schema]: BinaryCodec[A] = ProtobufCodec.protobufCodec } - val myApp: ZIO[Redis, RedisError, Unit] = + val myApp: ZIO[Redis, RedisError, Unit] = for { redis <- ZIO.service[Redis] - _ <- redis.set("myKey", 8L, Some(1.minutes)) + _ <- redis.set("myKey", 8L, expireAt = Some(SetExpire.Seconds(NonNegativeLong(60)))) v <- redis.get("myKey").returning[Long] _ <- Console.printLine(s"Value of myKey: $v").orDie _ <- redis.hSet("myHash", ("k1", 6), ("k2", 2)) diff --git a/modules/example/src/main/scala/zio/redis/example/ContributorsCache.scala b/modules/example/src/main/scala/zio/redis/example/ContributorsCache.scala index 4a666169a..67524a5bf 100644 --- a/modules/example/src/main/scala/zio/redis/example/ContributorsCache.scala +++ b/modules/example/src/main/scala/zio/redis/example/ContributorsCache.scala @@ -23,6 +23,7 @@ import zio._ import zio.json._ import zio.redis._ import zio.redis.example.ApiError._ +import zio.redis.options.PositiveLong trait ContributorsCache { def fetchAll(repository: Repository): IO[ApiError, Contributors] @@ -40,7 +41,7 @@ object ContributorsCache { ZIO .fromOption(NonEmptyChunk.fromChunk(contributors)) .map(Contributors(_).toJson) - .flatMap(data => redis.set(repository.key, data, Some(1.minute)).orDie) + .flatMap(data => redis.set(repository.key, data, expireAt = Some(SetExpire.Seconds(PositiveLong(60)))).orDie) .ignore private def read(repository: Repository): IO[ApiError, Contributors] = diff --git a/modules/redis-it/src/test/scala/zio/redis/HyperLogLogSpec.scala b/modules/redis-it/src/test/scala/zio/redis/HyperLogLogSpec.scala index 05738907d..cb860871a 100644 --- a/modules/redis-it/src/test/scala/zio/redis/HyperLogLogSpec.scala +++ b/modules/redis-it/src/test/scala/zio/redis/HyperLogLogSpec.scala @@ -28,7 +28,7 @@ trait HyperLogLogSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid value <- uuid - _ <- redis.set(key, value, None, None, None) + _ <- redis.set(key, value, None, None) add <- redis.pfAdd(key, "one", "two", "three").either } yield assert(add)(isLeft(isSubtype[RedisError.WrongType](anything))) } @@ -63,7 +63,7 @@ trait HyperLogLogSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid value <- uuid - _ <- redis.set(key, value, None, None, None) + _ <- redis.set(key, value, None, None) count <- redis.pfCount(key).either } yield assert(count)(isLeft) } @@ -101,7 +101,7 @@ trait HyperLogLogSpec extends IntegrationSpec { value <- uuid key2 <- uuid key3 <- uuid - _ <- redis.set(key, value, None, None, None) + _ <- redis.set(key, value, None, None) _ <- redis.pfAdd(key2, "five", "six", "seven") merge <- redis.pfMerge(key3, key2, key).either } yield assert(merge)(isLeft) diff --git a/modules/redis-it/src/test/scala/zio/redis/KeysSpec.scala b/modules/redis-it/src/test/scala/zio/redis/KeysSpec.scala index eddc8d940..994e496a3 100644 --- a/modules/redis-it/src/test/scala/zio/redis/KeysSpec.scala +++ b/modules/redis-it/src/test/scala/zio/redis/KeysSpec.scala @@ -3,6 +3,7 @@ package zio.redis import com.dimafeng.testcontainers.DockerComposeContainer import zio._ import zio.redis.RedisError.ProtocolError +import zio.redis.options.PositiveLong import zio.test.Assertion.{exists => _, _} import zio.test.TestAspect.{restore => _, _} import zio.test._ @@ -244,7 +245,7 @@ trait KeysSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid value <- uuid - _ <- redis.pSetEx(key, 1000.millis, value) + _ <- redis.set(key, value, expireAt = Some(SetExpire.Milliseconds(PositiveLong(1000)))) ttl <- redis.ttl(key).either } yield assert(ttl)(isRight) } @@ flaky, @@ -260,7 +261,7 @@ trait KeysSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid value <- uuid - _ <- redis.pSetEx(key, 1000.millis, value) + _ <- redis.set(key, value, expireAt = Some(SetExpire.Milliseconds(PositiveLong(1000)))) pTtl <- redis.pTtl(key).either } yield assert(pTtl)(isRight) } @@ flaky, diff --git a/modules/redis-it/src/test/scala/zio/redis/SortedSetsSpec.scala b/modules/redis-it/src/test/scala/zio/redis/SortedSetsSpec.scala index 5e1b72685..1331b3412 100644 --- a/modules/redis-it/src/test/scala/zio/redis/SortedSetsSpec.scala +++ b/modules/redis-it/src/test/scala/zio/redis/SortedSetsSpec.scala @@ -183,7 +183,10 @@ trait SortedSetsSpec extends IntegrationSpec { key <- uuid _ <- redis.zAdd(key)(MemberScore("v1", 3d)) _ <- redis.zAdd(key)(MemberScore("v2", 4d)) - added <- redis.zAdd(key, update = Some(Update.SetLessThan))(MemberScore("v3", 1d), MemberScore("v1", 2d)) + added <- redis.zAdd(key, updateByScore = Some(UpdateByScore.SetLessThan))( + MemberScore("v3", 1d), + MemberScore("v1", 2d) + ) result <- redis.zRange(key, 0 to -1).returning[String] } yield assert(added)(equalTo(1L)) && assert(result.toList)(equalTo(List("v3", "v1", "v2"))) }, @@ -193,7 +196,10 @@ trait SortedSetsSpec extends IntegrationSpec { key <- uuid _ <- redis.zAdd(key)(MemberScore("v1", 1d)) _ <- redis.zAdd(key)(MemberScore("v2", 2d)) - added <- redis.zAdd(key, update = Some(Update.SetGreaterThan))(MemberScore("v3", 1d), MemberScore("v1", 3d)) + added <- redis.zAdd(key, updateByScore = Some(UpdateByScore.SetGreaterThan))( + MemberScore("v3", 1d), + MemberScore("v1", 3d) + ) result <- redis.zRange(key, 0 to -1).returning[String] } yield assert(added)(equalTo(1L)) && assert(result.toList)(equalTo(List("v3", "v2", "v1"))) }, @@ -203,7 +209,7 @@ trait SortedSetsSpec extends IntegrationSpec { key <- uuid _ <- redis.zAdd(key)(MemberScore("v1", 1d)) _ <- redis.zAdd(key)(MemberScore("v2", 2d)) - added <- redis.zAdd(key, update = Some(Update.SetGreaterThan), change = Some(Changed))( + added <- redis.zAdd(key, updateByScore = Some(UpdateByScore.SetGreaterThan), change = Some(Changed))( MemberScore("v3", 1d), MemberScore("v1", 3d) ) @@ -431,9 +437,7 @@ trait SortedSetsSpec extends IntegrationSpec { _ <- redis.zAdd(second)(MemberScore("b", 2d), MemberScore("b", 2d), MemberScore("d", 4d)) _ <- redis.zAdd(third)(MemberScore("a", 1d), MemberScore("b", 2d), MemberScore("c", 3d)) members <- redis.zInter(first, second, third)().returning[String] - } yield assert(members)( - equalTo(Chunk("b")) - ) + } yield assert(members)(equalTo(Chunk("b"))) }, test("error when first parameter is not set") { for { diff --git a/modules/redis-it/src/test/scala/zio/redis/StringsSpec.scala b/modules/redis-it/src/test/scala/zio/redis/StringsSpec.scala index 48ddd36b1..8598b3d1d 100644 --- a/modules/redis-it/src/test/scala/zio/redis/StringsSpec.scala +++ b/modules/redis-it/src/test/scala/zio/redis/StringsSpec.scala @@ -1,9 +1,11 @@ package zio.redis import zio._ +import zio.prelude.Newtype.unsafeWrap import zio.redis.RedisError.{ProtocolError, WrongType} +import zio.redis.options.PositiveLong import zio.test.Assertion.{exists => _, _} -import zio.test.TestAspect.{flaky, ignore} +import zio.test.TestAspect.flaky import zio.test._ trait StringsSpec extends IntegrationSpec { @@ -315,7 +317,7 @@ trait StringsSpec extends IntegrationSpec { for { redis <- ZIO.service[Redis] key <- uuid - _ <- redis.set(key, "value", None, None, None) + _ <- redis.set(key, "value", None, None) result <- redis.bitField( key, BitFieldCommand.BitFieldSet(BitFieldType.UnsignedInt(8), 8, 98L), @@ -333,9 +335,9 @@ trait StringsSpec extends IntegrationSpec { for { redis <- ZIO.service[Redis] key1 <- uuid - _ <- redis.set(key1, str1, None, None, None) + _ <- redis.set(key1, str1, None, None) key2 <- uuid - _ <- redis.set(key2, str2, None, None, None) + _ <- redis.set(key2, str2, None, None) result <- redis.lcs(key1, key2) } yield assert(result)(equalTo(Lcs.PlainLcs("fo"))) }, @@ -346,9 +348,9 @@ trait StringsSpec extends IntegrationSpec { for { redis <- ZIO.service[Redis] key1 <- uuid - _ <- redis.set(key1, str1, None, None, None) + _ <- redis.set(key1, str1, None, None) key2 <- uuid - _ <- redis.set(key2, str2, None, None, None) + _ <- redis.set(key2, str2, None, None) result <- redis.lcs("unknown", "unknown") } yield assert(result)(equalTo(Lcs.PlainLcs(""))) }, @@ -359,9 +361,9 @@ trait StringsSpec extends IntegrationSpec { for { redis <- ZIO.service[Redis] key1 <- uuid - _ <- redis.set(key1, str1, None, None, None) + _ <- redis.set(key1, str1, None, None) key2 <- uuid - _ <- redis.set(key2, str2, None, None, None) + _ <- redis.set(key2, str2, None, None) result <- redis.lcs(key1, key2, Some(LcsQueryType.Len)) } yield assert(result)(equalTo(Lcs.Length(2))) }, @@ -372,9 +374,9 @@ trait StringsSpec extends IntegrationSpec { for { redis <- ZIO.service[Redis] key1 <- uuid - _ <- redis.set(key1, str1, None, None, None) + _ <- redis.set(key1, str1, None, None) key2 <- uuid - _ <- redis.set(key2, str2, None, None, None) + _ <- redis.set(key2, str2, None, None) result <- redis.lcs("unknown", "unknown", Some(LcsQueryType.Len)) } yield assert(result)(equalTo(Lcs.Length(0))) }, @@ -1425,56 +1427,6 @@ trait StringsSpec extends IntegrationSpec { } yield assert(set)(isFalse) } ) @@ clusterExecutorUnsupported, - suite("pSetEx")( - test("new value with 1000 milliseconds") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - _ <- redis.pSetEx(key, 1000.millis, value) - existsBefore <- redis.exists(key) - fiber <- ZIO.sleep(1010.millis).fork <* TestClock.adjust(1010.millis) - _ <- fiber.join - existsAfter <- redis.exists(key) - } yield assert(existsBefore)(equalTo(1L)) && assert(existsAfter)(equalTo(0L)) - } @@ flaky, - test("override existing string") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - _ <- redis.set(key, "value") - _ <- redis.pSetEx(key, 1000.millis, value) - currentVal <- redis.get(key).returning[String] - } yield assert(currentVal)(isSome(equalTo(value))) - }, - test("override not string") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - _ <- redis.sAdd(key, "a") - _ <- redis.pSetEx(key, 1000.millis, value) - currentVal <- redis.get(key).returning[String] - } yield assert(currentVal)(isSome(equalTo(value))) - }, - test("error when 0 milliseconds") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - result <- redis.pSetEx(key, 0.millis, value).either - } yield assert(result)(isLeft(isSubtype[ProtocolError](anything))) - }, - test("error when negative milliseconds") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - result <- redis.pSetEx(key, (-1).millis, value).either - } yield assert(result)(isLeft(isSubtype[ProtocolError](anything))) - } - ), suite("set")( test("new value") { for { @@ -1507,7 +1459,7 @@ trait StringsSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid value <- uuid - result <- redis.set(key, value, Some(1.second)) + result <- redis.set(key, value, expireAt = Some(SetExpire.Seconds(PositiveLong(1)))) } yield assert(result)(isTrue) }, test("new value with ttl 100 milliseconds") { @@ -1515,17 +1467,9 @@ trait StringsSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid value <- uuid - result <- redis.set(key, value, Some(100.milliseconds)) + result <- redis.set(key, value, expireAt = Some(SetExpire.Milliseconds(PositiveLong(100)))) } yield assert(result)(isTrue) }, - test("error when negative ttl") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - result <- redis.set(key, value, Some((-1).millisecond)).either - } yield assert(result)(isLeft(isSubtype[ProtocolError](anything))) - }, test("new value with SetNew parameter") { for { redis <- ZIO.service[Redis] @@ -1569,33 +1513,24 @@ trait StringsSpec extends IntegrationSpec { result <- redis.set(key, value, update = Some(Update.SetExisting)) } yield assert(result)(isTrue) }, - // next three tests include KEEPTTL parameter that is valid for Redis version >= 6 + // next two tests include KEEPTTL parameter that is valid for Redis version >= 6 test("new value with KeepTtl parameter") { for { redis <- ZIO.service[Redis] key <- uuid value <- uuid - result <- redis.set(key, value, keepTtl = Some(KeepTtl)) + result <- redis.set(key, value, expireAt = Some(SetExpire.KeepTtl)) } yield assert(result)(isTrue) - } @@ ignore, + }, test("existing value with KeepTtl parameter") { for { redis <- ZIO.service[Redis] key <- uuid value <- uuid - _ <- redis.set(key, "value", Some(1.second)) - result <- redis.set(key, value, keepTtl = Some(KeepTtl)) + _ <- redis.set(key, "value", expireAt = Some(SetExpire.Seconds(PositiveLong(1)))) + result <- redis.set(key, value, expireAt = Some(SetExpire.KeepTtl)) } yield assert(result)(isTrue) - } @@ ignore, - test("existing value with both ttl and KeepTtl parameters") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - _ <- redis.set(key, "value", Some(1.second)) - result <- redis.set(key, value, Some(1.second), keepTtl = Some(KeepTtl)) - } yield assert(result)(isTrue) - } @@ ignore + } ), suite("setBit")( test("for existing key") { @@ -1644,7 +1579,7 @@ trait StringsSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid value <- uuid - _ <- redis.setEx(key, 1.second, value) + _ <- redis.set(key, value, expireAt = Some(SetExpire.Seconds(PositiveLong(1)))) existsBefore <- redis.exists(key) fiber <- ZIO.sleep(1010.millis).fork <* TestClock.adjust(1010.millis) _ <- fiber.join @@ -1657,7 +1592,7 @@ trait StringsSpec extends IntegrationSpec { key <- uuid value <- uuid _ <- redis.set(key, "value") - _ <- redis.setEx(key, 1.second, value) + _ <- redis.set(key, value, expireAt = Some(SetExpire.Seconds(PositiveLong(1)))) existsBefore <- redis.exists(key) fiber <- ZIO.sleep(1010.millis).fork <* TestClock.adjust(1010.millis) _ <- fiber.join @@ -1670,28 +1605,12 @@ trait StringsSpec extends IntegrationSpec { key <- uuid value <- uuid _ <- redis.sAdd(key, "a") - _ <- redis.setEx(key, 1.second, value) + _ <- redis.set(key, value, expireAt = Some(SetExpire.Seconds(PositiveLong(1)))) existsBefore <- redis.exists(key) fiber <- ZIO.sleep(1010.millis).fork <* TestClock.adjust(1010.millis) _ <- fiber.join existsAfter <- redis.exists(key) } yield assert(existsBefore)(equalTo(1L)) && assert(existsAfter)(equalTo(0L)) - }, - test("error when 0 seconds ttl") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - result <- redis.setEx(key, 0.seconds, value).either - } yield assert(result)(isLeft(isSubtype[ProtocolError](anything))) - }, - test("error when negative ttl") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - result <- redis.setEx(key, (-1).second, value).either - } yield assert(result)(isLeft(isSubtype[ProtocolError](anything))) } ), suite("setNx")( @@ -1700,7 +1619,7 @@ trait StringsSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid value <- uuid - result <- redis.setNx(key, value) + result <- redis.set(key, value, update = Some(Update.SetNew)) } yield assert(result)(isTrue) }, test("existing value") { @@ -1709,7 +1628,7 @@ trait StringsSpec extends IntegrationSpec { key <- uuid value <- uuid _ <- redis.set(key, "value") - result <- redis.setNx(key, value) + result <- redis.set(key, value, update = Some(Update.SetNew)) } yield assert(result)(isFalse) }, test("not string") { @@ -1718,7 +1637,7 @@ trait StringsSpec extends IntegrationSpec { key <- uuid value <- uuid _ <- redis.sAdd(key, "a") - result <- redis.setNx(key, value) + result <- redis.set(key, value, update = Some(Update.SetNew)) } yield assert(result)(isFalse) } ), @@ -1788,29 +1707,17 @@ trait StringsSpec extends IntegrationSpec { } ), suite("getEx")( - test("value exists after removing ttl") { - for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - _ <- redis.pSetEx(key, 10.millis, value) - exists <- redis.getEx(key, persist = true).returning[String] - fiber <- ZIO.sleep(20.millis).fork <* TestClock.adjust(20.millis) - _ <- fiber.join - res <- redis.get(key).returning[String] - } yield assert(res.isDefined)(equalTo(true)) && assert(exists)(equalTo(Some(value))) - } @@ flaky, test("not found value when set seconds ttl") { for { redis <- ZIO.service[Redis] key <- uuid value <- uuid _ <- redis.set(key, value) - exists <- redis.getEx(key, Expire.SetExpireSeconds, 1.second).returning[String] + exists <- redis.getEx(key, GetExpire.Seconds(PositiveLong(1))).returning[String] fiber <- ZIO.sleep(1020.millis).fork <* TestClock.adjust(1020.millis) _ <- fiber.join res <- redis.get(key).returning[String] - } yield assert(res.isDefined)(equalTo(false)) && assert(exists)(equalTo(Some(value))) + } yield assert(res.isDefined)(equalTo(false)) && assert(exists)(isSome(equalTo(value))) } @@ flaky, test("not found value when set milliseconds ttl") { for { @@ -1818,49 +1725,51 @@ trait StringsSpec extends IntegrationSpec { key <- uuid value <- uuid _ <- redis.set(key, value) - exists <- redis.getEx(key, Expire.SetExpireMilliseconds, 10.millis).returning[String] + exists <- redis.getEx(key, GetExpire.Milliseconds(PositiveLong(10))).returning[String] fiber <- ZIO.sleep(20.millis).fork <* TestClock.adjust(20.millis) _ <- fiber.join res <- redis.get(key).returning[String] - } yield assert(res.isDefined)(equalTo(false)) && assert(exists)(equalTo(Some(value))) + } yield assert(res.isDefined)(equalTo(false)) && assert(exists)(isSome(equalTo(value))) } @@ flaky, test("not found value when set seconds timestamp") { for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - _ <- redis.set(key, value) - expiresAt <- Clock.instant.map(_.plusMillis(10.millis.toMillis)) - exists <- redis.getEx(key, ExpiredAt.SetExpireAtSeconds, expiresAt).returning[String] - fiber <- ZIO.sleep(20.millis).fork <* TestClock.adjust(20.millis) - _ <- fiber.join - res <- redis.get(key).returning[String] - } yield assert(res.isDefined)(equalTo(false)) && assert(exists)(equalTo(Some(value))) + redis <- ZIO.service[Redis] + key <- uuid + value <- uuid + _ <- redis.set(key, value) + expiresAt <- Clock.instant.map(_.plusMillis(10.millis.toMillis)) + expiresAtSeconds = unsafeWrap(PositiveLong)(expiresAt.getEpochSecond) + exists <- redis.getEx(key, GetExpire.UnixTimeSeconds(expiresAtSeconds)).returning[String] + fiber <- ZIO.sleep(20.millis).fork <* TestClock.adjust(20.millis) + _ <- fiber.join + res <- redis.get(key).returning[String] + } yield assert(res.isDefined)(equalTo(false)) && assert(exists)(isSome(equalTo(value))) } @@ flaky, test("not found value when set milliseconds timestamp") { for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - _ <- redis.set(key, value) - expiresAt <- Clock.instant.map(_.plusMillis(10.millis.toMillis)) - exists <- redis.getEx(key, ExpiredAt.SetExpireAtMilliseconds, expiresAt).returning[String] - fiber <- ZIO.sleep(20.millis).fork <* TestClock.adjust(20.millis) - _ <- fiber.join - res <- redis.get(key).returning[String] - } yield assert(res.isDefined)(equalTo(false)) && assert(exists)(equalTo(Some(value))) + redis <- ZIO.service[Redis] + key <- uuid + value <- uuid + _ <- redis.set(key, value) + expiresAt <- Clock.instant.map(_.plusMillis(10.millis.toMillis)) + expiresAtMillis = unsafeWrap(PositiveLong)(expiresAt.toEpochMilli) + exists <- redis.getEx(key, GetExpire.UnixTimeMilliseconds(expiresAtMillis)).returning[String] + fiber <- ZIO.sleep(20.millis).fork <* TestClock.adjust(20.millis) + _ <- fiber.join + res <- redis.get(key).returning[String] + } yield assert(res.isDefined)(equalTo(false)) && assert(exists)(isSome(equalTo(value))) } @@ flaky, test("key not found") { for { - redis <- ZIO.service[Redis] - key <- uuid - value <- uuid - _ <- redis.set(key, value) - expiresAt <- Clock.instant.map(_.plusMillis(10.millis.toMillis)) - res <- redis.getEx(value, ExpiredAt.SetExpireAtMilliseconds, expiresAt).returning[String] - res2 <- redis.getEx(value, Expire.SetExpireMilliseconds, 10.millis).returning[String] - res3 <- redis.getEx(value, persist = true).returning[String] - } yield assert(res)(equalTo(None)) && assert(res2)(equalTo(None)) && assert(res3)(equalTo(None)) + redis <- ZIO.service[Redis] + key <- uuid + value <- uuid + _ <- redis.set(key, value) + expiresAt <- Clock.instant.map(_.plusMillis(10.millis.toMillis)) + expiresAtMillis = unsafeWrap(PositiveLong)(expiresAt.toEpochMilli) + res <- redis.getEx(value, GetExpire.UnixTimeMilliseconds(expiresAtMillis)).returning[String] + res2 <- redis.getEx(value, GetExpire.Milliseconds(PositiveLong(10))).returning[String] + } yield assert(res)(isNone) && assert(res2)(isNone) } @@ flaky ), suite("getDel")( @@ -1877,7 +1786,7 @@ trait StringsSpec extends IntegrationSpec { redis <- ZIO.service[Redis] key <- uuid res <- redis.getDel(key).returning[String] - } yield assert(res)(equalTo(None)) + } yield assert(res)(isNone) }, test("get and remove key") { for { @@ -1887,7 +1796,7 @@ trait StringsSpec extends IntegrationSpec { _ <- redis.set(key, value) res <- redis.getDel(key).returning[String] notFound <- redis.getDel(key).returning[String] - } yield assert(res)(equalTo(Some(value))) && assert(notFound)(equalTo(None)) + } yield assert(res)(isSome(equalTo(value))) && assert(notFound)(isNone) } ) ) diff --git a/modules/redis/src/main/scala/zio/redis/Input.scala b/modules/redis/src/main/scala/zio/redis/Input.scala index 5cde6684b..9de35da98 100644 --- a/modules/redis/src/main/scala/zio/redis/Input.scala +++ b/modules/redis/src/main/scala/zio/redis/Input.scala @@ -126,7 +126,7 @@ object Input { case object DbInput extends Input[Long] { def encode(db: Long): RespCommand = - RespCommand(RespCommandArgument.Literal("DB"), RespCommandArgument.Value(db.toString())) + RespCommand(RespCommandArgument.Literal("DB"), RespCommandArgument.Value(db.toString)) } case object BoolInput extends Input[Boolean] { @@ -212,40 +212,38 @@ object Input { RespCommand(RespCommandArgument.Literal("GET"), RespCommandArgument.Value(data)) } - final case class GetExInput[K: BinaryCodec]() extends Input[(K, Expire, Duration)] { - def encode(data: (K, Expire, Duration)): RespCommand = + final case class GetExInput[K: BinaryCodec]() extends Input[(K, GetExpire)] { + def encode(data: (K, GetExpire)): RespCommand = data match { - case (key, Expire.SetExpireSeconds, duration) => - RespCommand(RespCommandArgument.Key(key), RespCommandArgument.Literal("EX")) ++ DurationSecondsInput.encode( - duration + case (key, GetExpire.Milliseconds(milliseconds)) => + RespCommand( + RespCommandArgument.Key(key), + RespCommandArgument.Literal("PX"), + RespCommandArgument.Value(milliseconds.toString) ) - case (key, Expire.SetExpireMilliseconds, duration) => - RespCommand(RespCommandArgument.Key(key), RespCommandArgument.Literal("PX")) ++ DurationMillisecondsInput - .encode(duration) - } - } - - final case class GetExAtInput[K: BinaryCodec]() extends Input[(K, ExpiredAt, Instant)] { - def encode(data: (K, ExpiredAt, Instant)): RespCommand = - data match { - case (key, ExpiredAt.SetExpireAtSeconds, instant) => - RespCommand(RespCommandArgument.Key(key), RespCommandArgument.Literal("EXAT")) ++ TimeSecondsInput.encode( - instant + case (key, GetExpire.Persist) => + RespCommand(RespCommandArgument.Key(key), RespCommandArgument.Literal("PERSIST")) + case (key, GetExpire.Seconds(seconds)) => + RespCommand( + RespCommandArgument.Key(key), + RespCommandArgument.Literal("EX"), + RespCommandArgument.Value(seconds.toString) + ) + case (key, GetExpire.UnixTimeMilliseconds(milliseconds)) => + RespCommand( + RespCommandArgument.Key(key), + RespCommandArgument.Literal("PXAT"), + RespCommandArgument.Value(milliseconds.toString) + ) + case (key, GetExpire.UnixTimeSeconds(seconds)) => + RespCommand( + RespCommandArgument.Key(key), + RespCommandArgument.Literal("EXAT"), + RespCommandArgument.Value(seconds.toString) ) - case (key, ExpiredAt.SetExpireAtMilliseconds, instant) => - RespCommand(RespCommandArgument.Key(key), RespCommandArgument.Literal("PXAT")) ++ TimeMillisecondsInput - .encode(instant) } } - final case class GetExPersistInput[K: BinaryCodec]() extends Input[(K, Boolean)] { - def encode(data: (K, Boolean)): RespCommand = - RespCommand( - if (data._2) Chunk(RespCommandArgument.Key(data._1), RespCommandArgument.Literal("PERSIST")) - else Chunk(RespCommandArgument.Key(data._1)) - ) - } - case object GetKeywordInput extends Input[GetKeyword] { def encode(data: GetKeyword): RespCommand = RespCommand(RespCommandArgument.Literal(data.asString)) @@ -285,11 +283,6 @@ object Input { RespCommand(RespCommandArgument.Value(data.toString)) } - case object KeepTtlInput extends Input[KeepTtl] { - def encode(data: KeepTtl): RespCommand = - RespCommand(RespCommandArgument.Literal(data.asString)) - } - case object LcsQueryTypeInput extends Input[LcsQueryType] { def encode(data: LcsQueryType): RespCommand = data match { case LcsQueryType.Len => RespCommand(RespCommandArgument.Literal("LEN")) @@ -423,6 +416,22 @@ object Input { RespCommand(RespCommandArgument.Literal(data.asString)) } + case object SetExpireInput extends Input[SetExpire] { + def encode(data: SetExpire): RespCommand = + data match { + case SetExpire.KeepTtl => + RespCommand(RespCommandArgument.Literal("KEEPTTL")) + case SetExpire.Milliseconds(milliseconds) => + RespCommand(RespCommandArgument.Literal("PX"), RespCommandArgument.Value(milliseconds.toString)) + case SetExpire.Seconds(seconds) => + RespCommand(RespCommandArgument.Literal("EX"), RespCommandArgument.Value(seconds.toString)) + case SetExpire.UnixTimeMilliseconds(milliseconds) => + RespCommand(RespCommandArgument.Literal("PXAT"), RespCommandArgument.Value(milliseconds.toString)) + case SetExpire.UnixTimeSeconds(seconds) => + RespCommand(RespCommandArgument.Literal("EXAT"), RespCommandArgument.Value(seconds.toString)) + } + } + case object SideInput extends Input[Side] { def encode(data: Side): RespCommand = RespCommand(RespCommandArgument.Literal(data.asString)) @@ -585,6 +594,11 @@ object Input { RespCommand(RespCommandArgument.Value(data.asString)) } + case object UpdateByScoreInput extends Input[UpdateByScore] { + def encode(data: UpdateByScore): RespCommand = + RespCommand(RespCommandArgument.Value(data.asString)) + } + case object ValueInput extends Input[Chunk[Byte]] { def encode(data: Chunk[Byte]): RespCommand = RespCommand(RespCommandArgument.Value(data)) diff --git a/modules/redis/src/main/scala/zio/redis/api/Connection.scala b/modules/redis/src/main/scala/zio/redis/api/Connection.scala index 501154ba5..0261a207d 100644 --- a/modules/redis/src/main/scala/zio/redis/api/Connection.scala +++ b/modules/redis/src/main/scala/zio/redis/api/Connection.scala @@ -26,7 +26,7 @@ trait Connection[G[+_]] extends RedisEnvironment[G] { /** * Authenticates the current connection to the server in two cases: - * - If the Redis server is password protected via the the ''requirepass'' option + * - If the Redis server is password protected via the ''requirepass'' option * - If a Redis 6.0 instance, or greater, is using the [[https://redis.io/topics/acl Redis ACL system]]. In this * case it is assumed that the implicit username is ''default''. * diff --git a/modules/redis/src/main/scala/zio/redis/api/SortedSets.scala b/modules/redis/src/main/scala/zio/redis/api/SortedSets.scala index 98516baf6..d1d7c4169 100644 --- a/modules/redis/src/main/scala/zio/redis/api/SortedSets.scala +++ b/modules/redis/src/main/scala/zio/redis/api/SortedSets.scala @@ -110,31 +110,39 @@ trait SortedSets[G[+_]] extends RedisEnvironment[G] { * Key of set to add to * @param update * Set existing and never add elements or always set new elements and don't update existing elements + * @param updateByScore + * Set update existing elements if the new score is less than the current score or greater than the current score * @param change * Modify the return value from the number of new elements added, to the total number of elements change * @param memberScore * Score that should be added to specific element for a given sorted set key * @param memberScores - * Rest scores that should be added to specific elements fr a given sorted set key + * Rest scores that should be added to specific elements for a given sorted set key * @return * The number of elements added to the sorted set, not including elements already existing for which the score was * updated. */ - final def zAdd[K: Schema, M: Schema](key: K, update: Option[Update] = None, change: Option[Changed] = None)( + final def zAdd[K: Schema, M: Schema]( + key: K, + update: Option[Update] = None, + updateByScore: Option[UpdateByScore] = None, + change: Option[Changed] = None + )( memberScore: MemberScore[M], memberScores: MemberScore[M]* ): G[Long] = { val command = RedisCommand( ZAdd, - Tuple4( + Tuple5( ArbitraryKeyInput[K](), OptionalInput(UpdateInput), + OptionalInput(UpdateByScoreInput), OptionalInput(ChangedInput), NonEmptyList(MemberScoreInput[M]()) ), LongOutput ) - command.run((key, update, change, (memberScore, memberScores.toList))) + command.run((key, update, updateByScore, change, (memberScore, memberScores.toList))) } /** @@ -144,6 +152,8 @@ trait SortedSets[G[+_]] extends RedisEnvironment[G] { * Key of set to add to. * @param update * Set existing and never add elements or always set new elements and don't update existing elements + * @param updateByScore + * Set update existing elements if the new score is less than the current score or greater than the current score * @param change * Modify the return value from the number of new elements added, to the total number of elements change * @param increment @@ -156,23 +166,29 @@ trait SortedSets[G[+_]] extends RedisEnvironment[G] { * The new score of member (a double precision floating point number), or None if the operation was aborted (when * called with either the XX or the NX option). */ - final def zAddWithIncr[K: Schema, M: Schema](key: K, update: Option[Update] = None, change: Option[Changed] = None)( + final def zAddWithIncr[K: Schema, M: Schema]( + key: K, + update: Option[Update] = None, + updateByScore: Option[UpdateByScore] = None, + change: Option[Changed] = None + )( increment: Increment, memberScore: MemberScore[M], memberScores: MemberScore[M]* ): G[Option[Double]] = { val command = RedisCommand( ZAdd, - Tuple5( + Tuple6( ArbitraryKeyInput[K](), OptionalInput(UpdateInput), + OptionalInput(UpdateByScoreInput), OptionalInput(ChangedInput), IncrementInput, NonEmptyList(MemberScoreInput[M]()) ), OptionalOutput(DoubleOutput) ) - command.run((key, update, change, increment, (memberScore, memberScores.toList))) + command.run((key, update, updateByScore, change, increment, (memberScore, memberScores.toList))) } /** @@ -753,7 +769,7 @@ trait SortedSets[G[+_]] extends RedisEnvironment[G] { * @param members * Rest members to be removed * @return - * The number of members removed from the sorted set, not including non existing members. + * The number of members removed from the sorted set, not including non-existing members. */ final def zRem[K: Schema, M: Schema](key: K, member: M, members: M*): G[Long] = { val command = diff --git a/modules/redis/src/main/scala/zio/redis/api/Streams.scala b/modules/redis/src/main/scala/zio/redis/api/Streams.scala index b6a8cc4de..fc7af7caf 100644 --- a/modules/redis/src/main/scala/zio/redis/api/Streams.scala +++ b/modules/redis/src/main/scala/zio/redis/api/Streams.scala @@ -730,7 +730,7 @@ trait Streams[G[+_]] extends RedisEnvironment[G] { Tuple3(OptionalInput(CountInput), OptionalInput(BlockInput), StreamsInput[SK, I]()), ChunkOutput(StreamOutput[SK, I, RK, RV]()) ) - command.run((count.map(Count(_)), block, (stream, Chunk.fromIterable(streams)))) + command.run((count.map(Count), block, (stream, Chunk.fromIterable(streams)))) } } @@ -781,7 +781,7 @@ trait Streams[G[+_]] extends RedisEnvironment[G] { ) val noAckOpt = if (noAck) Some(NoAck) else None - command.run((group, consumer, count.map(Count(_)), block, noAckOpt, (stream, Chunk.fromIterable(streams)))) + command.run((group, consumer, count.map(Count), block, noAckOpt, (stream, Chunk.fromIterable(streams)))) } } diff --git a/modules/redis/src/main/scala/zio/redis/api/Strings.scala b/modules/redis/src/main/scala/zio/redis/api/Strings.scala index a2c50d78b..3bdf9e76d 100644 --- a/modules/redis/src/main/scala/zio/redis/api/Strings.scala +++ b/modules/redis/src/main/scala/zio/redis/api/Strings.scala @@ -24,8 +24,6 @@ import zio.redis._ import zio.redis.internal.{RedisCommand, RedisEnvironment} import zio.schema.Schema -import java.time.Instant - trait Strings[G[+_]] extends RedisEnvironment[G] { import Strings._ @@ -216,50 +214,13 @@ trait Strings[G[+_]] extends RedisEnvironment[G] { * @param expire * The option which can modify command behavior. e.g. use `Expire.SetExpireSeconds` set the specified expire time in * seconds - * @param expireTime - * Time in seconds/milliseconds until the string should expire - * @return - * Returns the value of the string or None if it did not previously have a value. - */ - final def getEx[K: Schema](key: K, expire: Expire, expireTime: Duration): ResultBuilder1[Option, G] = - new ResultBuilder1[Option, G] { - def returning[R: Schema]: G[Option[R]] = - RedisCommand(GetEx, GetExInput[K](), OptionalOutput(ArbitraryOutput[R]())).run((key, expire, expireTime)) - } - - /** - * Get the value of key and set its expiration. - * - * @param key - * Key to get the value of - * @param expiredAt - * The option which can modify command behavior. e.g. use `Expire.SetExpireAtSeconds` set the specified Unix time at - * which the key will expire in seconds - * @param timestamp - * an absolute Unix timestamp (seconds/milliseconds since January 1, 1970) - * @return - * Returns the value of the string or None if it did not previously have a value. - */ - final def getEx[K: Schema](key: K, expiredAt: ExpiredAt, timestamp: Instant): ResultBuilder1[Option, G] = - new ResultBuilder1[Option, G] { - def returning[R: Schema]: G[Option[R]] = - RedisCommand(GetEx, GetExAtInput[K](), OptionalOutput(ArbitraryOutput[R]())).run((key, expiredAt, timestamp)) - } - - /** - * Get the value of key and remove the time to live associated with the key. - * - * @param key - * Key to get the value of - * @param persist - * if true, remove the time to live associated with the key, otherwise not * @return * Returns the value of the string or None if it did not previously have a value. */ - final def getEx[K: Schema](key: K, persist: Boolean): ResultBuilder1[Option, G] = + final def getEx[K: Schema](key: K, expire: GetExpire): ResultBuilder1[Option, G] = new ResultBuilder1[Option, G] { def returning[R: Schema]: G[Option[R]] = - RedisCommand(GetEx, GetExPersistInput[K](), OptionalOutput(ArbitraryOutput[R]())).run((key, persist)) + RedisCommand(GetEx, GetExInput[K](), OptionalOutput(ArbitraryOutput[R]())).run((key, expire)) } /** @@ -426,30 +387,6 @@ trait Strings[G[+_]] extends RedisEnvironment[G] { command.run((keyValue, keyValues.toList)) } - /** - * Set the value and expiration in milliseconds of a key. - * - * @param key - * Key of the string to set the expiry time on - * @param milliseconds - * Time in milliseconds until the string should expire - * @param value - * Value to set - */ - final def pSetEx[K: Schema, V: Schema]( - key: K, - milliseconds: Duration, - value: V - ): G[Unit] = { - val command = - RedisCommand( - PSetEx, - Tuple3(ArbitraryKeyInput[K](), DurationMillisecondsInput, ArbitraryValueInput[V]()), - UnitOutput - ) - command.run((key, milliseconds, value)) - } - /** * Set the string value of a key. * @@ -457,12 +394,10 @@ trait Strings[G[+_]] extends RedisEnvironment[G] { * Key of the string to set * @param value * Value to set - * @param expireTime - * Time until the string expires * @param update * Update can be Update.SetExisting which only sets the key if it exists, or Update.SetNew which nly sets the key if * it does not exist - * @param keepTtl + * @param expireAt * When set any previously set expire time remains unchanged * @return * true if set was executed correctly, false otherwise. @@ -470,21 +405,17 @@ trait Strings[G[+_]] extends RedisEnvironment[G] { final def set[K: Schema, V: Schema]( key: K, value: V, - expireTime: Option[Duration] = None, update: Option[Update] = None, - keepTtl: Option[KeepTtl] = None + expireAt: Option[SetExpire] = None ): G[Boolean] = { - val input = - Tuple5( - ArbitraryKeyInput[K](), - ArbitraryValueInput[V](), - OptionalInput(DurationTtlInput), - OptionalInput(UpdateInput), - OptionalInput(KeepTtlInput) - ) - + val input = Tuple4( + ArbitraryKeyInput[K](), + ArbitraryValueInput[V](), + OptionalInput(UpdateInput), + OptionalInput(SetExpireInput) + ) val command = RedisCommand(Set, input, SetOutput) - command.run((key, value, expireTime, update, keepTtl)) + command.run((key, value, update, expireAt)) } /** @@ -505,26 +436,6 @@ trait Strings[G[+_]] extends RedisEnvironment[G] { command.run((key, offset, value)) } - /** - * Set the value and expiration of a key. - * - * @param key - * Key of the value to update - * @param expiration - * Expiration time for the value - * @param value - * New value to set - */ - final def setEx[K: Schema, V: Schema]( - key: K, - expiration: Duration, - value: V - ): G[Unit] = { - val command = - RedisCommand(SetEx, Tuple3(ArbitraryKeyInput[K](), DurationSecondsInput, ArbitraryValueInput[V]()), UnitOutput) - command.run((key, expiration, value)) - } - /** * Set the string value of a key with a 'GET' option. * @@ -532,12 +443,10 @@ trait Strings[G[+_]] extends RedisEnvironment[G] { * Key of the string to set * @param value * Value to set - * @param expireTime - * Time until the string expires * @param update * Update can be Update.SetExisting which only sets the key if it exists, or Update.SetNew which nly sets the key if * it does not exist - * @param keepTtl + * @param expireAt * When set any previously set expire time remains unchanged * @return * the old value stored at key, or None if key did not exist @@ -545,38 +454,18 @@ trait Strings[G[+_]] extends RedisEnvironment[G] { final def setGet[K: Schema, V: Schema]( key: K, value: V, - expireTime: Option[Duration] = None, update: Option[Update] = None, - keepTtl: Option[KeepTtl] = None + expireAt: Option[SetExpire] = None ): G[Option[V]] = { - val input = - Tuple6( - ArbitraryKeyInput[K](), - ArbitraryValueInput[V](), - OptionalInput(DurationTtlInput), - OptionalInput(UpdateInput), - OptionalInput(KeepTtlInput), - GetKeywordInput - ) - + val input = Tuple5( + ArbitraryKeyInput[K](), + ArbitraryValueInput[V](), + OptionalInput(UpdateInput), + OptionalInput(SetExpireInput), + GetKeywordInput + ) val command = RedisCommand(Set, input, OptionalOutput(ArbitraryOutput[V]())) - command.run((key, value, expireTime, update, keepTtl, GetKeyword)) - } - - /** - * Set the value of a key, only if the key does not exist. - * - * @param key - * Key of the value to set if the key does not exist - * @param value - * Value to set - * @return - * Returns 1 if the key was set. 0 if the key was not set. - */ - final def setNx[K: Schema, V: Schema](key: K, value: V): G[Boolean] = { - val command = - RedisCommand(SetNx, Tuple2(ArbitraryKeyInput[K](), ArbitraryValueInput[V]()), BoolOutput) - command.run((key, value)) + command.run((key, value, update, expireAt, GetKeyword)) } /** @@ -632,11 +521,8 @@ private[redis] object Strings { final val MGet = "MGET" final val MSet = "MSET" final val MSetNx = "MSETNX" - final val PSetEx = "PSETEX" final val Set = "SET" final val SetBit = "SETBIT" - final val SetEx = "SETEX" - final val SetNx = "SETNX" final val SetRange = "SETRANGE" final val StrLen = "STRLEN" } diff --git a/modules/redis/src/main/scala/zio/redis/options/Shared.scala b/modules/redis/src/main/scala/zio/redis/options/Shared.scala index 2139779ba..c7aaef268 100644 --- a/modules/redis/src/main/scala/zio/redis/options/Shared.scala +++ b/modules/redis/src/main/scala/zio/redis/options/Shared.scala @@ -17,25 +17,10 @@ package zio.redis.options trait Shared { - sealed trait Update { self => - private[redis] final def asString: String = - self match { - case Update.SetExisting => "XX" - case Update.SetNew => "NX" - case Update.SetLessThan => "LT" - case Update.SetGreaterThan => "GT" - } - } - - object Update { - case object SetExisting extends Update - case object SetNew extends Update - case object SetLessThan extends Update - case object SetGreaterThan extends Update - } - sealed case class Count(count: Long) + sealed case class Limit(offset: Long, count: Long) + sealed trait Order { self => private[redis] final def asString: String = self match { @@ -49,9 +34,34 @@ trait Shared { case object Descending extends Order } - sealed case class Limit(offset: Long, count: Long) + sealed case class Pattern(pattern: String) sealed case class Store(key: String) - sealed case class Pattern(pattern: String) + sealed trait Update { self => + private[redis] final def asString: String = + self match { + case Update.SetExisting => "XX" + case Update.SetNew => "NX" + } + } + + object Update { + case object SetExisting extends Update + case object SetNew extends Update + } + + sealed trait UpdateByScore { self => + private[redis] final def asString: String = + self match { + case UpdateByScore.SetLessThan => "LT" + case UpdateByScore.SetGreaterThan => "GT" + } + } + + object UpdateByScore { + case object SetLessThan extends UpdateByScore + case object SetGreaterThan extends UpdateByScore + } + } diff --git a/modules/redis/src/main/scala/zio/redis/options/Strings.scala b/modules/redis/src/main/scala/zio/redis/options/Strings.scala index 6efd60809..7d365648c 100644 --- a/modules/redis/src/main/scala/zio/redis/options/Strings.scala +++ b/modules/redis/src/main/scala/zio/redis/options/Strings.scala @@ -21,20 +21,20 @@ trait Strings { sealed trait Lcs object Lcs { - case class PlainLcs(lcs: String) extends Lcs - case class Length(length: Long) extends Lcs - case class Matches(matches: List[Match], length: Long) extends Lcs + sealed case class Length(length: Long) extends Lcs + sealed case class Matches(matches: List[Match], length: Long) extends Lcs + sealed case class PlainLcs(lcs: String) extends Lcs } sealed trait LcsQueryType object LcsQueryType { - case object Len extends LcsQueryType - case class Idx(minMatchLength: Int = 1, withMatchLength: Boolean = false) extends LcsQueryType + case object Len extends LcsQueryType + sealed case class Idx(minMatchLength: Int = 1, withMatchLength: Boolean = false) extends LcsQueryType } - case class MatchIdx(start: Long, end: Long) - case class Match(matchIdxA: MatchIdx, matchIdxB: MatchIdx, matchLength: Option[Long] = None) + sealed case class MatchIdx(start: Long, end: Long) + sealed case class Match(matchIdxA: MatchIdx, matchIdxB: MatchIdx, matchLength: Option[Long] = None) sealed trait BitFieldCommand @@ -93,36 +93,24 @@ trait Strings { sealed case class BitPosRange(start: Long, end: Option[Long]) - case object KeepTtl { - private[redis] def asString: String = "KEEPTTL" - } - - type KeepTtl = KeepTtl.type + sealed trait SetExpire - sealed trait Expire { self => - private[redis] final def asString: String = - self match { - case Expire.SetExpireSeconds => "EX" - case Expire.SetExpireMilliseconds => "PX" - } + object SetExpire { + case object KeepTtl extends SetExpire + sealed case class Milliseconds(milliseconds: PositiveLong) extends SetExpire + sealed case class Seconds(seconds: PositiveLong) extends SetExpire + sealed case class UnixTimeMilliseconds(milliseconds: PositiveLong) extends SetExpire + sealed case class UnixTimeSeconds(seconds: PositiveLong) extends SetExpire } - object Expire { - case object SetExpireSeconds extends Expire - case object SetExpireMilliseconds extends Expire - } - - sealed trait ExpiredAt { self => - private[redis] final def asString: String = - self match { - case ExpiredAt.SetExpireAtSeconds => "EXAT" - case ExpiredAt.SetExpireAtMilliseconds => "PXAT" - } - } + sealed trait GetExpire - object ExpiredAt { - case object SetExpireAtSeconds extends ExpiredAt - case object SetExpireAtMilliseconds extends ExpiredAt + object GetExpire { + case object Persist extends GetExpire + sealed case class Milliseconds(milliseconds: PositiveLong) extends GetExpire + sealed case class Seconds(seconds: PositiveLong) extends GetExpire + sealed case class UnixTimeMilliseconds(milliseconds: PositiveLong) extends GetExpire + sealed case class UnixTimeSeconds(seconds: PositiveLong) extends GetExpire } case object GetKeyword { diff --git a/modules/redis/src/main/scala/zio/redis/options/package.scala b/modules/redis/src/main/scala/zio/redis/options/package.scala new file mode 100644 index 000000000..8ba7b1d6f --- /dev/null +++ b/modules/redis/src/main/scala/zio/redis/options/package.scala @@ -0,0 +1,12 @@ +package zio.redis + +import zio.prelude.Assertion.greaterThanOrEqualTo +import zio.prelude.Newtype + +package object options { + object PositiveLong extends Newtype[Long] { + override def assertion = assert(greaterThanOrEqualTo(1L)) // scalafix:ok + } + + type PositiveLong = PositiveLong.Type +} diff --git a/modules/redis/src/test/scala/zio/redis/InputSpec.scala b/modules/redis/src/test/scala/zio/redis/InputSpec.scala index d753f895f..e6ab36fe6 100644 --- a/modules/redis/src/test/scala/zio/redis/InputSpec.scala +++ b/modules/redis/src/test/scala/zio/redis/InputSpec.scala @@ -1,9 +1,11 @@ package zio.redis import zio._ +import zio.prelude.Newtype.unsafeWrap import zio.redis.Input._ import zio.redis.internal.RespCommand import zio.redis.internal.RespCommandArgument._ +import zio.redis.options.PositiveLong import zio.test.Assertion._ import zio.test._ @@ -414,13 +416,6 @@ object InputSpec extends BaseSpec { } yield assert(result)(equalTo(RespCommand(Value("0")))) } ), - suite("KeepTtl")( - test("valid value") { - for { - result <- ZIO.attempt(KeepTtlInput.encode(KeepTtl)) - } yield assert(result)(equalTo(RespCommand(Literal("KEEPTTL")))) - } - ), suite("LexRange")( test("with unbound min and unbound max") { for { @@ -1029,6 +1024,18 @@ object InputSpec extends BaseSpec { } yield assert(result)(equalTo(RespCommand(Value("NX")))) } ), + suite("UpdateByScore")( + test("set greater than") { + for { + result <- ZIO.attempt(UpdateByScoreInput.encode(UpdateByScore.SetGreaterThan)) + } yield assert(result)(equalTo(RespCommand(Value("GT")))) + }, + test("set less than") { + for { + result <- ZIO.attempt(UpdateByScoreInput.encode(UpdateByScore.SetLessThan)) + } yield assert(result)(equalTo(RespCommand(Value("LT")))) + } + ), suite("Id")( test("valid value") { for { @@ -1294,42 +1301,28 @@ object InputSpec extends BaseSpec { suite("GetEx")( test("GetExInput - valid value") { for { - resultSeconds <- - ZIO.attempt(GetExInput[String]().encode(scala.Tuple3.apply("key", Expire.SetExpireSeconds, 1.second))) - resultMilliseconds <- - ZIO.attempt(GetExInput[String]().encode(scala.Tuple3("key", Expire.SetExpireMilliseconds, 100.millis))) - } yield assert(resultSeconds)(equalTo(RespCommand(Key("key"), Literal("EX"), Value("1")))) && assert( - resultMilliseconds - )( - equalTo(RespCommand(Key("key"), Literal("PX"), Value("100"))) - ) - }, - test("GetExAtInput - valid value") { - for { - resultSeconds <- + resultSeconds <- + ZIO.attempt(GetExInput[String]().encode(scala.Tuple2("key", GetExpire.Seconds(PositiveLong(1))))) + resultMilliseconds <- ZIO.attempt( - GetExAtInput[String]().encode( - scala.Tuple3("key", ExpiredAt.SetExpireAtSeconds, Instant.parse("2021-04-06T00:00:00Z")) - ) + GetExInput[String]().encode(scala.Tuple2("key", GetExpire.Milliseconds(PositiveLong(100)))) ) - resultMilliseconds <- + unixTimeSeconds = unsafeWrap(PositiveLong)(Instant.parse("2021-04-06T00:00:00Z").getEpochSecond) + resultUnixTimeSeconds <- + ZIO.attempt(GetExInput[String]().encode(scala.Tuple2("key", GetExpire.UnixTimeSeconds(unixTimeSeconds)))) + unixTimeMilliseconds = unsafeWrap(PositiveLong)(Instant.parse("2021-04-06T00:00:00Z").toEpochMilli) + resultUnixTimeMilliseconds <- ZIO.attempt( - GetExAtInput[String]().encode( - scala.Tuple3("key", ExpiredAt.SetExpireAtMilliseconds, Instant.parse("2021-04-06T00:00:00Z")) - ) + GetExInput[String]().encode(scala.Tuple2("key", GetExpire.UnixTimeMilliseconds(unixTimeMilliseconds))) ) - } yield assert(resultSeconds)( - equalTo(RespCommand(Key("key"), Literal("EXAT"), Value("1617667200"))) - ) && assert(resultMilliseconds)( - equalTo(RespCommand(Key("key"), Literal("PXAT"), Value("1617667200000"))) + resultPersist <- ZIO.attempt(GetExInput[String]().encode(scala.Tuple2("key", GetExpire.Persist))) + } yield assertTrue( + resultSeconds == RespCommand(Key("key"), Literal("EX"), Value("1")), + resultMilliseconds == RespCommand(Key("key"), Literal("PX"), Value("100")), + resultUnixTimeSeconds == RespCommand(Key("key"), Literal("EXAT"), Value("1617667200")), + resultUnixTimeMilliseconds == RespCommand(Key("key"), Literal("PXAT"), Value("1617667200000")), + resultPersist == RespCommand(Key("key"), Literal("PERSIST")) ) - }, - test("GetExPersistInput - valid value") { - for { - result <- ZIO.attempt(GetExPersistInput[String]().encode("key" -> true)) - resultWithoutOption <- ZIO.attempt(GetExPersistInput[String]().encode("key" -> false)) - } yield assert(result)(equalTo(RespCommand(Key("key"), Literal("PERSIST")))) && - assert(resultWithoutOption)(equalTo(RespCommand(Key("key")))) } ) )