Skip to content

Commit 8ce232e

Browse files
committed
Helper decode functions
Signed-off-by: Adam Fowler <adamfowler71@gmail.com>
1 parent 9deef57 commit 8ce232e

File tree

3 files changed

+52
-18
lines changed

3 files changed

+52
-18
lines changed

Sources/Valkey/RESP/RESPToken.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public struct RESPToken: Hashable, Sendable {
3737
}
3838
}
3939

40+
@usableFromInline
4041
internal func asMap() throws -> Map {
4142
guard (self.count & 1) == 0 else { throw RESPParsingError(code: .invalidElementCount, buffer: self.buffer) }
4243
return Map(count: self.count / 2, buffer: self.buffer)

Sources/Valkey/RESP/RESPTokenDecodable.swift

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,7 @@ extension Dictionary: RESPTokenDecodable where Value: RESPTokenDecodable, Key: R
298298
public init(fromRESP token: RESPToken) throws {
299299
switch token.value {
300300
case .map(let respMap), .attribute(let respMap):
301-
var array: [(Key, Value)] = []
302-
for respElement in respMap {
303-
let key = try Key(fromRESP: respElement.key)
304-
let value = try Value(fromRESP: respElement.value)
305-
array.append((key, value))
306-
}
307-
self = .init(array) { first, _ in first }
301+
self = try respMap.decode(as: Self.self)
308302
default:
309303
throw RESPDecodeError.tokenMismatch(expected: [.map], token: token)
310304
}
@@ -358,7 +352,7 @@ extension RESPToken.Array: RESPTokenDecodable {
358352
return try (repeat decodeOptionalRESPToken(iterator.next(), as: (each Value).self))
359353
}
360354

361-
/// Convert RESP3Token Array to a tuple of values
355+
/// Convert RESPToken Array to a tuple of values
362356
/// - Parameter type: Tuple of types to convert to
363357
/// - Throws: RESPDecodeError
364358
/// - Returns: Tuple of decoded values
@@ -378,6 +372,16 @@ extension RESPToken.Array: RESPTokenDecodable {
378372
return (repeat decodeOptionalRESPToken(iterator.next(), as: (each Value).self))
379373
}
380374

375+
/// Decode RESPToken Array consisting of alternating key, value entries
376+
/// - Parameter as: Array of key value pairs type
377+
/// - Returns: Array of key value pairs
378+
@inlinable
379+
public func decodeKeyValuePairs<Key: RESPTokenDecodable, Value: RESPTokenDecodable>(
380+
as: [(Key, Value)].Type = [(Key, Value)].self
381+
) throws -> [(Key, Value)] {
382+
try self.asMap().decode()
383+
}
384+
381385
@inlinable
382386
func _parameterPackTypeSize<each Value>(
383387
_ type: (repeat (each Value)).Type
@@ -402,18 +406,28 @@ extension RESPToken.Map: RESPTokenDecodable {
402406
}
403407
}
404408

405-
/// Convert RESPToken Map to a Dictionary with String keys
409+
/// Convert RESPToken Map to a Dictionary
406410
/// - Parameter type: Type to convert to
407411
/// - Throws: ValkeyClientError.unexpectedType
408412
/// - Returns: String value dictionary
409413
@inlinable
410-
public func decode<Value: RESPTokenDecodable>(as type: [String: Value].Type = [String: Value].self) throws -> [String: Value] {
411-
var array: [(String, Value)] = []
412-
for respElement in self {
413-
let key = try String(fromRESP: respElement.key)
414-
let value = try Value(fromRESP: respElement.value)
415-
array.append((key, value))
416-
}
414+
public func decode<Key: RESPTokenDecodable & Hashable, Value: RESPTokenDecodable>(
415+
as type: [Key: Value].Type = [Key: Value].self
416+
) throws -> [Key: Value] {
417+
let array = try self.decode(as: [(Key, Value)].self)
417418
return .init(array) { first, _ in first }
418419
}
420+
421+
/// Convert RESPToken Map to a Array of Key Value pairs
422+
/// - Parameter type: Type to convert to
423+
/// - Throws: ValkeyClientError.unexpectedType
424+
/// - Returns: String value dictionary
425+
@inlinable
426+
public func decode<Key: RESPTokenDecodable, Value: RESPTokenDecodable>(
427+
as type: [(Key, Value)].Type = [(Key, Value)].self
428+
) throws -> [(Key, Value)] {
429+
try self.map {
430+
try (Key(fromRESP: $0.key), Value(fromRESP: $0.value))
431+
}
432+
}
419433
}

Tests/ValkeyTests/RESPTokenRepresentableTests.swift renamed to Tests/ValkeyTests/RESPTokenDecodableTests.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import NIOCore
99
import Testing
1010
import Valkey
1111

12-
struct RESPTokenRepresentableTests {
12+
struct RESPTokenDecodableTests {
1313
@Test(arguments: [
1414
("+SimpleString\r\n", "SimpleString"),
1515
("$10\r\nBulkString\r\n", "BulkString"),
@@ -124,10 +124,29 @@ struct RESPTokenRepresentableTests {
124124
("*2\r\n:1\r\n:45\r\n", [1...45]),
125125
("*2\r\n*2\r\n:1\r\n:6\r\n*2\r\n:8\r\n:34\r\n", [1...6, 8...34]),
126126
])
127-
func arrayOfArrays(testValues: (String, [ClosedRange<Int>])) throws {
127+
func arrayOfRanges(testValues: (String, [ClosedRange<Int>])) throws {
128128
var buffer = ByteBuffer(string: testValues.0)
129129
let token = try #require(try RESPToken(consuming: &buffer))
130130
let value = try [ClosedRange<Int>](fromRESP: token)
131131
#expect(value == testValues.1)
132132
}
133+
134+
@Test(arguments: [
135+
("*2\r\n$3\r\none\r\n$1\r\n1\r\n", [("one", "1")]),
136+
("*4\r\n$3\r\none\r\n$1\r\n1\r\n$3\r\ntwo\r\n$1\r\n2\r\n", [("one", "1"), ("two", "2")]),
137+
])
138+
func arrayOfKeyValuePairs(testValues: (String, [(String, String)])) throws {
139+
var buffer = ByteBuffer(string: testValues.0)
140+
let token = try #require(try RESPToken(consuming: &buffer))
141+
switch token.value {
142+
case .array(let array):
143+
let values = try array.decodeKeyValuePairs(as: [(String, String)].self)
144+
#expect(values.count == testValues.1.count)
145+
for i in 0..<values.count {
146+
#expect(values[i] == testValues.1[i])
147+
}
148+
default:
149+
Issue.record("Token is not an array")
150+
}
151+
}
133152
}

0 commit comments

Comments
 (0)