Use `TimeAmount` for any `timeout` command arguments

Motivation:

The goal is to have a strong-typed API for type-safety in arbitrary values, such as trying to use
Int to represent time - as '3' could mean any unit of time, leaving many places for errors and bugs.

Modifications:

Switch all current APIs that accept a `timeout` argument to use `NIO.TimeAmount` instead of a plain `Int`.

Result:

Developers will have an easier time reasoning about their own code as to what values might mean when working with
timeouts in Redis APIs.
parent 0007a086
Pipeline #105877626 passed with stage
in 3 minutes and 32 seconds
......@@ -118,10 +118,9 @@ extension RedisClient {
/// - Returns: `true` if the expiration was set.
@inlinable
public func expire(_ key: RedisKey, after timeout: TimeAmount) -> EventLoopFuture<Bool> {
let amount = timeout.nanoseconds / 1_000_000_000
let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: amount.description)
.init(bulk: timeout.seconds)
]
return send(command: "EXPIRE", with: args)
.convertFromRESPValue(to: Int.self)
......
......@@ -161,19 +161,19 @@ extension RedisClient {
/// - Parameters:
/// - source: The key of the list to pop from.
/// - dest: The key of the list to push to.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0` seconds means to wait indefinitely.
/// - Returns: The element popped from the source list and pushed to the destination,
/// or `nil` if the timeout was reached.
@inlinable
public func brpoplpush(
from source: RedisKey,
to dest: RedisKey,
timeout: Int = 0
timeout: TimeAmount = .seconds(0)
) -> EventLoopFuture<RESPValue?> {
let args: [RESPValue] = [
.init(bulk: source),
.init(bulk: dest),
.init(bulk: timeout)
.init(bulk: timeout.seconds)
]
return send(command: "BRPOPLPUSH", with: args)
.map { $0.isNull ? nil: $0 }
......@@ -378,9 +378,10 @@ extension RedisClient {
/// See [https://redis.io/commands/blpop](https://redis.io/commands/blpop)
/// - Parameters:
/// - key: The key of the list to pop from.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns: The element that was popped from the list, or `nil` if the timout was reached.
@inlinable
public func blpop(from key: RedisKey, timeout: Int = 0) -> EventLoopFuture<RESPValue?> {
public func blpop(from key: RedisKey, timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<RESPValue?> {
return blpop(from: [key], timeout: timeout)
.map { $0?.1 }
}
......@@ -397,13 +398,13 @@ extension RedisClient {
/// See [https://redis.io/commands/blpop](https://redis.io/commands/blpop)
/// - Parameters:
/// - keys: The keys of lists in Redis that should be popped from.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns:
/// If timeout was reached, `nil`.
///
/// Otherwise, the key of the list the element was removed from and the popped element.
@inlinable
public func blpop(from keys: [RedisKey], timeout: Int = 0) -> EventLoopFuture<(RedisKey, RESPValue)?> {
public func blpop(from keys: [RedisKey], timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<(RedisKey, RESPValue)?> {
return _bpop(command: "BLPOP", keys, timeout)
}
......@@ -419,13 +420,13 @@ extension RedisClient {
/// See [https://redis.io/commands/blpop](https://redis.io/commands/blpop)
/// - Parameters:
/// - keys: The keys of lists in Redis that should be popped from.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns:
/// If timeout was reached, `nil`.
///
/// Otherwise, the key of the list the element was removed from and the popped element.
@inlinable
public func blpop(from keys: RedisKey..., timeout: Int = 0) -> EventLoopFuture<(RedisKey, RESPValue)?> {
public func blpop(from keys: RedisKey..., timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<(RedisKey, RESPValue)?> {
return self.blpop(from: keys, timeout: timeout)
}
......@@ -441,9 +442,10 @@ extension RedisClient {
/// See [https://redis.io/commands/brpop](https://redis.io/commands/brpop)
/// - Parameters:
/// - key: The key of the list to pop from.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns: The element that was popped from the list, or `nil` if the timout was reached.
@inlinable
public func brpop(from key: RedisKey, timeout: Int = 0) -> EventLoopFuture<RESPValue?> {
public func brpop(from key: RedisKey, timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<RESPValue?> {
return brpop(from: [key], timeout: timeout)
.map { $0?.1 }
}
......@@ -460,13 +462,13 @@ extension RedisClient {
/// See [https://redis.io/commands/brpop](https://redis.io/commands/brpop)
/// - Parameters:
/// - keys: The keys of lists in Redis that should be popped from.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns:
/// If timeout was reached, `nil`.
///
/// Otherwise, the key of the list the element was removed from and the popped element.
@inlinable
public func brpop(from keys: [RedisKey], timeout: Int = 0) -> EventLoopFuture<(RedisKey, RESPValue)?> {
public func brpop(from keys: [RedisKey], timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<(RedisKey, RESPValue)?> {
return _bpop(command: "BRPOP", keys, timeout)
}
......@@ -482,13 +484,13 @@ extension RedisClient {
/// See [https://redis.io/commands/brpop](https://redis.io/commands/brpop)
/// - Parameters:
/// - keys: The keys of lists in Redis that should be popped from.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns:
/// If timeout was reached, `nil`.
///
/// Otherwise, the key of the list the element was removed from and the popped element.
@inlinable
public func brpop(from keys: RedisKey..., timeout: Int = 0) -> EventLoopFuture<(RedisKey, RESPValue)?> {
public func brpop(from keys: RedisKey..., timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<(RedisKey, RESPValue)?> {
return self.brpop(from: keys, timeout: timeout)
}
......@@ -496,10 +498,10 @@ extension RedisClient {
func _bpop(
command: String,
_ keys: [RedisKey],
_ timeout: Int
_ timeout: TimeAmount
) -> EventLoopFuture<(RedisKey, RESPValue)?> {
var args = keys.map(RESPValue.init)
args.append(.init(bulk: timeout))
args.append(.init(bulk: timeout.seconds))
return send(command: command, with: args)
.flatMapThrowing {
......
......@@ -367,14 +367,14 @@ extension RedisClient {
/// See [https://redis.io/commands/bzpopmin](https://redis.io/commands/bzpopmin)
/// - Parameters:
/// - key: The key identifying the sorted set in Redis.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns:
/// The element and its associated score that was popped from the sorted set,
/// or `nil` if the timeout was reached.
@inlinable
public func bzpopmin(
from key: RedisKey,
timeout: Int = 0
timeout: TimeAmount = .seconds(0)
) -> EventLoopFuture<(Double, RESPValue)?> {
return bzpopmin(from: [key], timeout: timeout)
.map {
......@@ -396,7 +396,7 @@ extension RedisClient {
/// See [https://redis.io/commands/bzpopmin](https://redis.io/commands/bzpopmin)
/// - Parameters:
/// - keys: A list of sorted set keys in Redis.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns:
/// If timeout was reached, `nil`.
///
......@@ -405,7 +405,7 @@ extension RedisClient {
@inlinable
public func bzpopmin(
from keys: [RedisKey],
timeout: Int = 0
timeout: TimeAmount = .seconds(0)
) -> EventLoopFuture<(String, Double, RESPValue)?> {
return self._bzpop(command: "BZPOPMIN", keys, timeout)
}
......@@ -423,14 +423,14 @@ extension RedisClient {
/// See [https://redis.io/commands/bzpopmax](https://redis.io/commands/bzpopmax)
/// - Parameters:
/// - key: The key identifying the sorted set in Redis.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns:
/// The element and its associated score that was popped from the sorted set,
/// or `nil` if the timeout was reached.
@inlinable
public func bzpopmax(
from key: RedisKey,
timeout: Int = 0
timeout: TimeAmount = .seconds(0)
) -> EventLoopFuture<(Double, RESPValue)?> {
return self.bzpopmax(from: [key], timeout: timeout)
.map {
......@@ -452,7 +452,7 @@ extension RedisClient {
/// See [https://redis.io/commands/bzpopmax](https://redis.io/commands/bzpopmax)
/// - Parameters:
/// - keys: A list of sorted set keys in Redis.
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
/// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
/// - Returns:
/// If timeout was reached, `nil`.
///
......@@ -461,7 +461,7 @@ extension RedisClient {
@inlinable
public func bzpopmax(
from keys: [RedisKey],
timeout: Int = 0
timeout: TimeAmount = .seconds(0)
) -> EventLoopFuture<(String, Double, RESPValue)?> {
return self._bzpop(command: "BZPOPMAX", keys, timeout)
}
......@@ -470,10 +470,10 @@ extension RedisClient {
func _bzpop(
command: String,
_ keys: [RedisKey],
_ timeout: Int
_ timeout: TimeAmount
) -> EventLoopFuture<(String, Double, RESPValue)?> {
var args = keys.map(RESPValue.init)
args.append(.init(bulk: timeout))
args.append(.init(bulk: timeout.seconds))
return send(command: command, with: args)
// per the Redis docs,
......
......@@ -37,6 +37,16 @@ extension EventLoopFuture where Value == RESPValue {
}
}
extension TimeAmount {
/// The seconds representation of the TimeAmount.
@usableFromInline
internal var seconds: Int64 {
return self.nanoseconds / 1_000_000_000
}
}
// MARK: Setting up a Redis connection
extension Channel {
/// Adds the baseline `ChannelHandlers` needed to support sending and receiving messages in Redis Serialization Protocol (RESP) format to the pipeline.
///
......
......@@ -133,7 +133,7 @@ final class ListCommandsTests: RediStackIntegrationTestCase {
}
func test_blpop() throws {
let nilPop = try connection.blpop(from: #function, timeout: 1).wait()
let nilPop = try connection.blpop(from: #function, timeout: .seconds(1)).wait()
XCTAssertNil(nilPop)
_ = try connection.lpush([10, 20, 30], into: "first").wait()
......@@ -192,7 +192,7 @@ final class ListCommandsTests: RediStackIntegrationTestCase {
}
func test_brpop() throws {
let nilPop = try connection.brpop(from: #function, timeout: 1).wait()
let nilPop = try connection.brpop(from: #function, timeout: .seconds(1)).wait()
XCTAssertNil(nilPop)
_ = try connection.lpush([10, 20, 30], into: "first").wait()
......
......@@ -152,7 +152,7 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase {
}
func test_bzpopmin() throws {
let nilMin = try connection.bzpopmin(from: #function, timeout: 1).wait()
let nilMin = try connection.bzpopmin(from: #function, timeout: .seconds(1)).wait()
XCTAssertNil(nilMin)
let min1 = try connection.bzpopmin(from: key).wait()
......@@ -186,7 +186,7 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase {
}
func test_bzpopmax() throws {
let nilMax = try connection.bzpopmax(from: #function, timeout: 1).wait()
let nilMax = try connection.bzpopmax(from: #function, timeout: .seconds(1)).wait()
XCTAssertNil(nilMax)
let max1 = try connection.bzpopmax(from: key).wait()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment