Last active
March 6, 2024 06:37
-
-
Save amosavian/a1540789283f206c85aca0b487839048 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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