Skip to content

Commit

Permalink
set should not update expiration if key already exist and expire is n…
Browse files Browse the repository at this point in the history
…il (#31)
  • Loading branch information
adam-fowler authored Sep 23, 2024
1 parent f6fa43b commit ff016fc
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 92 deletions.
18 changes: 14 additions & 4 deletions Sources/HummingbirdRedis/Persist+Redis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct RedisPersistDriver: PersistDriver {
/// create new key with value. If key already exist throw `PersistError.duplicate` error
public func create(key: String, value: some Codable, expires: Duration?) async throws {
let expiration: RedisSetCommandExpiration? = expires.map { .seconds(Int($0.components.seconds)) }
let result = try await self.redisConnectionPool.set(.init(key), toJSON: value, onCondition: .keyDoesNotExist, expiration: expiration).get()
let result = try await self.redisConnectionPool.set(.init(key), toJSON: value, onCondition: .keyDoesNotExist, expiration: expiration)
switch result {
case .ok:
return
Expand All @@ -39,15 +39,25 @@ public struct RedisPersistDriver: PersistDriver {
public func set(key: String, value: some Codable, expires: Duration?) async throws {
if let expires {
let expiration = Int(expires.components.seconds)
return try await self.redisConnectionPool.setex(.init(key), toJSON: value, expirationInSeconds: expiration).get()
_ = try await self.redisConnectionPool.set(
.init(key),
toJSON: value,
onCondition: .none,
expiration: .seconds(expiration)
)
} else {
return try await self.redisConnectionPool.set(.init(key), toJSON: value).get()
_ = try await self.redisConnectionPool.set(
.init(key),
toJSON: value,
onCondition: .none,
expiration: .keepExisting
)
}
}

/// get value for key
public func get<Object: Codable>(key: String, as object: Object.Type) async throws -> Object? {
try await self.redisConnectionPool.get(.init(key), asJSON: object).get()
try await self.redisConnectionPool.get(.init(key), asJSON: object)
}

/// remove key
Expand Down
95 changes: 9 additions & 86 deletions Sources/HummingbirdRedis/Redis+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ import RediStack

extension RedisClient {
/// Decodes the value associated with this keyfrom JSON.
public func get<D: Decodable>(_ key: RedisKey, asJSON type: D.Type) -> EventLoopFuture<D?> {
return self.get(key, as: Data.self).flatMapThrowing { data in
try data.map { try JSONDecoder().decode(D.self, from: $0) }
}
public func get<D: Decodable>(_ key: RedisKey, asJSON type: D.Type) async throws -> D? {
guard let data = try await self.get(key, as: Data.self).get() else { return nil }
return try JSONDecoder().decode(D.self, from: data)
}

/// Sets the value stored in the key provided, overwriting the previous value.
Expand All @@ -36,12 +35,8 @@ extension RedisClient {
/// - value: The value to set the key to.
/// - Returns: An `EventLoopFuture` that resolves if the operation was successful.
@inlinable
public func set(_ key: RedisKey, toJSON value: some Encodable) -> EventLoopFuture<Void> {
do {
return try self.set(key, to: JSONEncoder().encode(value))
} catch {
return self.eventLoop.makeFailedFuture(error)
}
public func set(_ key: RedisKey, toJSON value: some Encodable) async throws {
try await self.set(key, to: JSONEncoder().encode(value)).get()
}

/// Sets the key to the provided value with options to control how it is set.
Expand All @@ -60,85 +55,13 @@ extension RedisClient {
/// `.ok` if the operation was successful and `.conditionNotMet` if the specified `condition` was not met.
///
/// If the condition `.none` was used, then the result value will always be `.ok`.
@_disfavoredOverload
public func set(
_ key: RedisKey,
toJSON value: some Encodable,
onCondition condition: RedisSetCommandCondition,
onCondition condition: RedisSetCommandCondition = .none,
expiration: RedisSetCommandExpiration? = nil
) -> EventLoopFuture<RedisSetCommandResult> {
do {
return try self.set(key, to: JSONEncoder().encode(value), onCondition: condition, expiration: expiration)
} catch {
return self.eventLoop.makeFailedFuture(error)
}
}

/// Sets the key to the provided value if the key does not exist.
///
/// [https://redis.io/commands/setnx](https://redis.io/commands/setnx)
/// - Important: Regardless of the type of data stored at the key, it will be overwritten to a "string" data type.
///
/// ie. If the key is a reference to a Sorted Set, its value will be overwritten to be a "string" data type.
/// - Parameters:
/// - key: The key to use to uniquely identify this value.
/// - value: The value to set the key to.
/// - Returns: `true` if the operation successfully completed.
@inlinable
public func setnx(_ key: RedisKey, toJSON value: some Encodable) -> EventLoopFuture<Bool> {
do {
return try self.setnx(key, to: JSONEncoder().encode(value))
} catch {
return self.eventLoop.makeFailedFuture(error)
}
}

/// Sets a key to the provided value and an expiration timeout in seconds.
///
/// See [https://redis.io/commands/setex](https://redis.io/commands/setex)
/// - Important: Regardless of the type of data stored at the key, it will be overwritten to a "string" data type.
///
/// ie. If the key is a reference to a Sorted Set, its value will be overwritten to be a "string" data type.
/// - Important: The actual expiration used will be the specified value or `1`, whichever is larger.
/// - Parameters:
/// - key: The key to use to uniquely identify this value.
/// - value: The value to set the key to.
/// - expiration: The number of seconds after which to expire the key.
/// - Returns: A `NIO.EventLoopFuture` that resolves if the operation was successful.
@inlinable
public func setex(
_ key: RedisKey,
toJSON value: some Encodable,
expirationInSeconds expiration: Int
) -> EventLoopFuture<Void> {
do {
return try self.setex(key, to: JSONEncoder().encode(value), expirationInSeconds: expiration)
} catch {
return self.eventLoop.makeFailedFuture(error)
}
}

/// Sets a key to the provided value and an expiration timeout in milliseconds.
///
/// See [https://redis.io/commands/psetex](https://redis.io/commands/psetex)
/// - Important: Regardless of the type of data stored at the key, it will be overwritten to a "string" data type.
///
/// ie. If the key is a reference to a Sorted Set, its value will be overwritten to be a "string" data type.
/// - Important: The actual expiration used will be the specified value or `1`, whichever is larger.
/// - Parameters:
/// - key: The key to use to uniquely identify this value.
/// - value: The value to set the key to.
/// - expiration: The number of milliseconds after which to expire the key.
/// - Returns: A `NIO.EventLoopFuture` that resolves if the operation was successful.
@inlinable
public func psetex(
_ key: RedisKey,
toJSON value: some Encodable,
expirationInMilliseconds expiration: Int
) -> EventLoopFuture<Void> {
do {
return try self.psetex(key, to: JSONEncoder().encode(value), expirationInMilliseconds: expiration)
} catch {
return self.eventLoop.makeFailedFuture(error)
}
) async throws -> RedisSetCommandResult {
try await self.set(key, to: JSONEncoder().encode(value), onCondition: condition, expiration: expiration).get()
}
}
4 changes: 2 additions & 2 deletions Tests/HummingbirdRedisTests/PersistTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@ final class PersistTests: XCTestCase {
try await app.test(.router) { client in

let tag = UUID().uuidString
try await client.execute(uri: "/persist/\(tag)/0", method: .put, body: ByteBufferAllocator().buffer(string: "ThisIsTest1")) { _ in }
try await client.execute(uri: "/persist/\(tag)/0", method: .put, body: ByteBuffer(string: "ThisIsTest1")) { _ in }
try await Task.sleep(nanoseconds: 1_000_000_000)
try await client.execute(uri: "/persist/\(tag)", method: .get) { response in
XCTAssertEqual(response.status, .noContent)
}
try await client.execute(uri: "/persist/\(tag)/10", method: .put, body: ByteBufferAllocator().buffer(string: "ThisIsTest1")) { response in
try await client.execute(uri: "/persist/\(tag)/10", method: .put, body: ByteBuffer(string: "ThisIsTest1")) { response in
XCTAssertEqual(response.status, .ok)
}
try await client.execute(uri: "/persist/\(tag)", method: .get) { response in
Expand Down

0 comments on commit ff016fc

Please sign in to comment.