Skip to content

Commit 03c2ea5

Browse files
committed
Command array arguments that render with a token for each element
- Add RESPArrayWithToken that renders each element with token prefix - Use RESPArrayWithToken for elements with flag `multiple_token` - Use `CLIENT TRACKING` to test this Signed-off-by: Adam Fowler <[email protected]>
1 parent f748b9f commit 03c2ea5

File tree

10 files changed

+85
-12
lines changed

10 files changed

+85
-12
lines changed

Sources/Valkey/Commands/BitmapCommands.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ public struct BITFIELDRO: ValkeyCommand {
306306
public var isReadOnly: Bool { true }
307307

308308
@inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) {
309-
commandEncoder.encodeArray("BITFIELD_RO", key, RESPWithToken("GET", getBlocks))
309+
commandEncoder.encodeArray("BITFIELD_RO", key, RESPArrayWithToken("GET", getBlocks))
310310
}
311311
}
312312

Sources/Valkey/Commands/ConnectionCommands.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ extension CLIENT {
776776
"TRACKING",
777777
status,
778778
RESPWithToken("REDIRECT", clientId),
779-
RESPWithToken("PREFIX", prefixes),
779+
RESPArrayWithToken("PREFIX", prefixes),
780780
RESPPureToken("BCAST", bcast),
781781
RESPPureToken("OPTIN", optin),
782782
RESPPureToken("OPTOUT", optout),

Sources/Valkey/Commands/GenericCommands.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,7 @@ public struct SORT: ValkeyCommand {
836836
key,
837837
RESPWithToken("BY", byPattern),
838838
RESPWithToken("LIMIT", limit),
839-
RESPWithToken("GET", getPatterns),
839+
RESPArrayWithToken("GET", getPatterns),
840840
order,
841841
RESPPureToken("ALPHA", sorting),
842842
RESPWithToken("STORE", destination)
@@ -920,7 +920,7 @@ public struct SORTRO: ValkeyCommand {
920920
key,
921921
RESPWithToken("BY", byPattern),
922922
RESPWithToken("LIMIT", limit),
923-
RESPWithToken("GET", getPatterns),
923+
RESPArrayWithToken("GET", getPatterns),
924924
order,
925925
RESPPureToken("ALPHA", sorting)
926926
)

Sources/Valkey/Commands/HashCommands.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,6 @@ public struct HRANDFIELD: ValkeyCommand {
807807
RESPPureToken("WITHVALUES", withvalues).encode(into: &commandEncoder)
808808
}
809809
}
810-
811810
@inlinable public static var name: String { "HRANDFIELD" }
812811

813812
public var key: ValkeyKey

Sources/Valkey/Commands/ServerCommands.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ public enum MODULE {
944944
}
945945

946946
@inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) {
947-
commandEncoder.encodeArray("MODULE", "LOADEX", RESPBulkString(path), RESPWithToken("CONFIG", configs), RESPWithToken("ARGS", args))
947+
commandEncoder.encodeArray("MODULE", "LOADEX", RESPBulkString(path), RESPArrayWithToken("CONFIG", configs), RESPWithToken("ARGS", args))
948948
}
949949
}
950950

Sources/Valkey/RESP/RESPRenderableHelpers.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@ package struct RESPWithToken<Value: RESPRenderable>: RESPRenderable {
5757
}
5858
}
5959

60+
@usableFromInline
61+
package struct RESPArrayWithToken<Element: RESPRenderable>: RESPRenderable {
62+
@usableFromInline
63+
let array: [Element]
64+
@usableFromInline
65+
let token: String
66+
67+
@inlinable
68+
package init(_ token: String, _ array: [Element]) {
69+
self.array = array
70+
self.token = token
71+
}
72+
@inlinable
73+
package var respEntries: Int {
74+
self.array.respEntries + self.array.count
75+
}
76+
@inlinable
77+
package func encode(into commandEncoder: inout ValkeyCommandEncoder) {
78+
for element in self.array {
79+
token.encode(into: &commandEncoder)
80+
element.encode(into: &commandEncoder)
81+
}
82+
}
83+
}
84+
6085
@usableFromInline
6186
package struct RESPArrayWithCount<Element: RESPRenderable>: RESPRenderable {
6287
@usableFromInline

Sources/_ValkeyCommandsBuilder/ValkeyCommandJSON.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ struct ValkeyCommand: Decodable {
3232
struct InternalArgument: Decodable {
3333
let name: String
3434
let type: ArgumentType
35-
let multiple: Bool?
36-
let optional: Bool?
35+
let multiple: Bool
36+
let optional: Bool
3737
let token: String?
38+
let multipleToken: Bool
3839
let arguments: [Argument]?
3940
let keySpecIndex: Int?
4041

@@ -51,6 +52,7 @@ struct ValkeyCommand: Decodable {
5152
}
5253
self.multiple = multiple
5354
self.optional = try container.decodeIfPresent(Bool.self, forKey: .optional) ?? false
55+
self.multipleToken = try container.decodeIfPresent(Bool.self, forKey: .multipleToken) ?? false
5456
var token = try container.decodeIfPresent(String.self, forKey: .token)
5557
if token == "\"\"" {
5658
token = ""
@@ -66,6 +68,7 @@ struct ValkeyCommand: Decodable {
6668
case multiple
6769
case optional
6870
case token
71+
case multipleToken = "multiple_token"
6972
case arguments
7073
case keySpecIndex = "key_spec_index"
7174
}
@@ -75,15 +78,17 @@ struct ValkeyCommand: Decodable {
7578
let type: ArgumentType
7679
let multiple: Bool
7780
let optional: Bool
81+
let multipleToken: Bool
7882
let token: String?
7983
let arguments: [Argument]?
8084
let combinedWithCount: Bool?
8185

8286
init(argument: InternalArgument, keySpec: KeySpec?) {
8387
self.name = argument.name
8488
self.type = argument.type
85-
self.multiple = argument.multiple ?? false
86-
self.optional = argument.optional ?? false
89+
self.multiple = argument.multiple
90+
self.optional = argument.optional
91+
self.multipleToken = argument.multipleToken
8792
self.token = argument.token
8893
self.arguments = argument.arguments
8994
self.combinedWithCount =
@@ -107,6 +112,7 @@ struct ValkeyCommand: Decodable {
107112
}
108113
self.multiple = multiple
109114
self.optional = try container.decodeIfPresent(Bool.self, forKey: .optional) ?? false
115+
self.multipleToken = try container.decodeIfPresent(Bool.self, forKey: .multipleToken) ?? false
110116
var token = try container.decodeIfPresent(String.self, forKey: .token)
111117
if token == "\"\"" {
112118
token = ""
@@ -121,6 +127,7 @@ struct ValkeyCommand: Decodable {
121127
case type
122128
case multiple
123129
case optional
130+
case multipleToken = "multiple_token"
124131
case token
125132
case arguments
126133
}

Sources/_ValkeyCommandsBuilder/ValkeyCommandsRender.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ private let disableResponseCalculationCommands: Set<String> = [
2929
"GEODIST",
3030
"GEOPOS",
3131
"GEOSEARCH",
32+
"HRANDFIELD",
3233
"HSCAN",
3334
"ROLE",
3435
"LMOVE",
@@ -847,8 +848,12 @@ extension ValkeyCommand.Argument {
847848
}
848849
}
849850
if let token = self.token {
850-
return "RESPWithToken(\"\(token)\", \(variable))"
851-
} else if multiple, combinedWithCount == true {
851+
if self.multiple, self.multipleToken {
852+
return "RESPArrayWithToken(\"\(token)\", \(variable))"
853+
} else {
854+
return "RESPWithToken(\"\(token)\", \(variable))"
855+
}
856+
} else if self.multiple, self.combinedWithCount == true {
852857
if isArray {
853858
return "RESPArrayWithCount(\(variable))"
854859
} else {

Tests/ValkeyTests/CommandTests.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,31 @@ import Valkey
1515
///
1616
/// Generally the commands being tested here are ones we have written custom responses for
1717
struct CommandTests {
18+
struct ConnectionCommands {
19+
@Test
20+
@available(valkeySwift 1.0, *)
21+
func clientTracking() async throws {
22+
try await testCommandEncodesDecodes(
23+
(
24+
request: .command(["CLIENT", "TRACKING", "OFF"]),
25+
response: .simpleString("OK")
26+
),
27+
(
28+
request: .command(["CLIENT", "TRACKING", "ON", "REDIRECT", "25", "PREFIX", "test"]),
29+
response: .simpleString("OK")
30+
),
31+
(
32+
request: .command(["CLIENT", "TRACKING", "ON", "REDIRECT", "25", "PREFIX", "test", "PREFIX", "this"]),
33+
response: .simpleString("OK")
34+
)
35+
) { connection in
36+
try await connection.clientTracking(status: .off)
37+
try await connection.clientTracking(status: .on, clientId: 25, prefixes: ["test"])
38+
try await connection.clientTracking(status: .on, clientId: 25, prefixes: ["test", "this"])
39+
}
40+
}
41+
}
42+
1843
struct ScriptCommands {
1944
@Test
2045
@available(valkeySwift 1.0, *)

Tests/ValkeyTests/RESPTokenRenderableTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ struct RESPTokenRenderableTests {
3838
#expect(commandEncoder.buffer.readableBytes == 0)
3939
}
4040

41+
@Test
42+
func testRESPArrayWithTokens() async throws {
43+
var commandEncoder = ValkeyCommandEncoder()
44+
var commandEncoder2 = ValkeyCommandEncoder()
45+
RESPArrayWithToken("test", ["john", "jane"]).encode(into: &commandEncoder)
46+
"test".encode(into: &commandEncoder2)
47+
"john".encode(into: &commandEncoder2)
48+
"test".encode(into: &commandEncoder2)
49+
"jane".encode(into: &commandEncoder2)
50+
#expect(String(buffer: commandEncoder.buffer) == String(buffer: commandEncoder2.buffer))
51+
}
52+
4153
@Test
4254
func testRESPArrayWithCount() async throws {
4355
var commandEncoder = ValkeyCommandEncoder()

0 commit comments

Comments
 (0)