Skip to content

Instantly share code, notes, and snippets.

@amosavian
Last active March 6, 2024 06:37
Show Gist options
  • Save amosavian/a1540789283f206c85aca0b487839048 to your computer and use it in GitHub Desktop.
Save amosavian/a1540789283f206c85aca0b487839048 to your computer and use it in GitHub Desktop.
//
// RedisValueHelper.swift
//
//
// Created by Amir Abbas Mousavian on 01/01/2020.
//
import Foundation
import Vapor
import NIO
import RediStack
// General
extension RedisClient {
/// `AUTH` command
public func authenticate(password: String) -> EventLoopFuture<Void> {
return send(command: "AUTH", with: [password].toResp())
.mapOK()
}
/// `INFO` command
public func info(section: RedisConnection.InfoSection = .default) -> EventLoopFuture<String> {
return send(command: "INFO", with: [section.rawValue].toResp())
.convertFromRESPValue()
}
public func databaseSize() -> EventLoopFuture<Int> {
return send(command: "DBSIZE")
.convertFromRESPValue()
}
/// Regarding all parameter, will be `FLUSHALL` or `FLUSHDB`` command
public func flush(all: Bool = false, async: Bool = false) -> EventLoopFuture<String> {
var command: String = all ? "FLUSHALL" : "FLUSHDB"
if async { command += " ASYNC" }
return send(command: command)
.convertFromRESPValue()
}
}
// Getting keys
extension RedisClient {
/// `KEYS` command
public func allKeys(pattern: String) -> EventLoopFuture<[String]> {
return send(command: "KEYS", with: [pattern].toResp()).convertFromRESPValue(to: [String].self)
}
/// 'EXISTS` command
public func exists(key: String) -> EventLoopFuture<Bool> {
return send(command: "EXISTS", with: [key].toResp())
.mapBool()
}
/// 'EXISTS` command
public func exist(keys: [String]) -> EventLoopFuture<Int> {
return send(command: "EXISTS", with: keys.toResp())
.convertFromRESPValue()
}
/// 'EXISTS` command
public func exist(keys: String...) -> EventLoopFuture<Int> {
return send(command: "EXISTS", with: keys.toResp())
.convertFromRESPValue()
}
/// `GET` command
public func getValue<T: RESPValueConvertible>(of key: String) -> EventLoopFuture<T> {
return send(command: "GET", with: [key].toResp())
.convertFromRESPValue()
}
}
// Manupulating values
extension RedisClient {
/// `DEL` command
public func deleteValue(of keys: [String]) -> EventLoopFuture<Int> {
return send(command: "DEL", with: keys.toResp())
.convertFromRESPValue()
}
/// `DEL` command
public func deleteValue(of keys: String...) -> EventLoopFuture<Int> {
return delete(keys)
}
/// `SET` command when `override` is `true` else `SETNX` command.
/// Default for `override` is `true`.
public func setValue<T: RESPValueConvertible>(_ value: T, forKey key: String, override: Bool = true) -> EventLoopFuture<Void> {
return send(command: override ? "SET" : "SETNX", with: [.init(key), value.convertedToRESPValue()])
.mapOK()
}
/// `PSETEX` command
public func setValue<T: RESPValueConvertible, Time: MillisecondsConvertible>(_ value: T, forKey key: String, expiresWithin: Time) -> EventLoopFuture<Void> {
return send(command: "PSETEX", with: [.init(key), .init(expiresWithin.milliSeconds), value.convertedToRESPValue()])
.mapOK()
}
/// `MSET` command when `override` is `true` else `MSETNX` command.
/// Default for `override` is `true`.
public func set(_ dict: [String: RESPValue], override: Bool = false) -> EventLoopFuture<Void> {
var args = [RESPValue]()
args.reserveCapacity(dict.count * 2)
dict.forEach {
args.append(.init($0))
args.append($1)
}
return send(command: override ? "MSET" : "MSETNX", with: args)
.mapOK()
}
/// `GETSET` command
public func updateValue<T: RESPValueConvertible>(_ value: T, forKey key: String) -> EventLoopFuture<T> {
return send(command: "GETSET", with: [.init(key), value.convertedToRESPValue()]).convertFromRESPValue()
}
/// `INCRBY` command when value is not equal to 1, otherwise `INCR` command
public func incrementValue<N: FixedWidthInteger & RESPValueConvertible>(of key: String, by value: N = 1) -> EventLoopFuture<N> {
switch value {
case 1:
return send(command: "INCR", with: [key].toResp())
.convertFromRESPValue()
default:
return send(command: "INCRBY", with: [.init(key), value.convertedToRESPValue()])
.convertFromRESPValue()
}
}
/// `INCRBYFLOAT` command when value is not equal to 1, otherwise `INCR` command
public func incrementValue<N: BinaryFloatingPoint & RESPValueConvertible>(of key: String, by value: Double) -> EventLoopFuture<N> {
return send(command: "INCRBYFLOAT", with: [.init(key), .init(value)])
.convertFromRESPValue()
}
/// `DECR` command
public func decrementValue(of key: String) -> EventLoopFuture<Int64> {
return send(command: "DECR", with: [key].toResp())
.convertFromRESPValue()
}
}
// Expiring key
extension RedisClient {
/// `PTTL` command
public func expiresAt(key: String) -> EventLoopFuture<TimeInterval> {
return send(command: "PTTL", with: [key].toResp()).flatMapThrowing { (value) -> TimeInterval in
guard let intValue = value.int else {
throw RedisClientError.failedRESPConversion(to: TimeInterval.self)
}
switch intValue {
case 0...:
return TimeInterval(intValue) / 1000
case -1:
throw RedisError(reason: "key does not exist")
case -2:
throw RedisError(reason: "key exists but has no associated expire")
default:
throw RedisError(reason: "unknown error")
}
}
}
/// `PEXPIREAT` command
public func setExpirationDate(of key: String, at date: Date) -> EventLoopFuture<Bool> {
return send(command: "PEXPIREAT", with: [.init(key), .init(Int64(date.timeIntervalSince1970 * 1000))])
.mapBool()
}
/// `PEXPIRE` command
public func setExpirationInterval<Time: MillisecondsConvertible>(of key: String, to interval: Time) -> EventLoopFuture<Bool> {
return send(command: "PEXPIRE", with: [.init(key), .init(interval.milliSeconds)])
.mapBool()
}
/// `PERSIST` command
public func removeExpiration(of key: String) -> EventLoopFuture<Void> {
return send(command: "PERSIST", with: [key].toResp())
.flatMapThrowing { (value) -> Void in
guard let value = Int64(fromRESP: value) else {
throw RedisClientError.failedRESPConversion(to: Int64.self)
}
if value == 0 {
throw RedisError(reason: "key does not exist or does not have an associated timeout")
}
}
}
}
extension RedisConnection {
public enum InfoSection: String {
case server, clients, memory, persistence, stats, replication, cpu, commandstats, cluster, keyspace
case all, `default`
}
}
extension EventLoopFuture where Value == RESPValue {
fileprivate func mapOK() -> EventLoopFuture<Void> {
return flatMapThrowing { (value) -> Void in
guard let valString = value.string else {
throw RedisClientError.failedRESPConversion(to: String.self)
}
if value.string != "OK" {
throw RedisError(reason: valString)
}
}
}
fileprivate func mapBool() -> EventLoopFuture<Bool> {
return flatMapThrowing { (value) -> Bool in
guard let intValue = value.int else {
throw RedisClientError.failedRESPConversion(to: Bool.self)
}
return intValue != 0
}
}
}
extension Array where Element == String {
fileprivate func toResp() -> [RESPValue] {
return map(RESPValue.init)
}
}
public protocol MillisecondsConvertible {
var milliSeconds: Int64 { get }
}
extension TimeInterval: MillisecondsConvertible {
public var milliSeconds: Int64 {
return Int64(self * 1000)
}
}
extension TimeAmount: MillisecondsConvertible {
public var milliSeconds: Int64 {
return Int64(self.nanoseconds / 1000)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment