Skip to content

Commit f065fc2

Browse files
authored
Add documentation for decoding RESPToken (#245)
* RESPToken decoding docs Signed-off-by: Adam Fowler <[email protected]> * Extend pipeline docs to include dynamic pipelines Signed-off-by: Adam Fowler <[email protected]> * resurrect Array.decode(as:) Signed-off-by: Adam Fowler <[email protected]> --------- Signed-off-by: Adam Fowler <[email protected]>
1 parent 5079b73 commit f065fc2

File tree

4 files changed

+97
-1
lines changed

4 files changed

+97
-1
lines changed

Sources/Valkey/Documentation.docc/Pipelining.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
Sending multiple commands at once without waiting for the response of each command.
44

55
## Overview
6-
=======
76

87
Valkey pipelining is a technique for improving performance by issuing multiple commands at once without waiting for the response to each individual command. Pipelining not only reduces the latency cost of waiting for the result of each command it also reduces the cost to the server as it reduces I/O costs. Multiple commands can be read with a single syscall, and multiple results are delivered with a single syscall.
98

@@ -23,6 +22,27 @@ if let result = try getResult.get().map({ String(buffer: $0) }) {
2322
}
2423
```
2524

25+
### Dynamic pipelines
26+
27+
The parameter pack implementation of pipelining allows for creation of static pipelines built at compile time. It doesn't provide much scope for generating more dynamic pipelines based on runtime conditions. To get around this an API that takes an array of existential `ValkeyCommands` and returns an array of `Result<RESPToken, Error>` is available. It allows you to build your pipeline at runtime. The downside of this method is you are returned a `Result` holding a ``RESPToken`` which needs decoding.
28+
29+
```swift
30+
// create command array
31+
var commands: [any ValkeyCommand] = []
32+
commands.append(SET("foo", value: "100"))
33+
commands.append(INCR("foo"))
34+
commands.append(GET("foo"))
35+
// execute commands
36+
let results = await valkeyClient.execute(commands)
37+
// get result and decode. We decode as an optional String
38+
// to avoid an error being thrown if the response is a null token
39+
if let value = results[2].get().decode(as: String?.self) {
40+
print(value)
41+
}
42+
```
43+
44+
You can find out more about decoding `RESPToken` in <doc:RESPToken-Decoding>.
45+
2646
### Pipelining and Concurrency
2747

2848
Being able to have multiple requests in transit on a single connection means we can have multiple tasks use that connection concurrently. Each request is added to a queue and as each response comes back the first request on the queue is popped off and given the response. By using a single connection across multiple tasks you can reduce the number of connections to your database.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Decoding RESP
2+
3+
Decoding the contents of RESPToken
4+
5+
## Overview
6+
7+
The wire protocol valkey-swift uses is RESP3. It is simple, human readable and a fast protocol to parse. It can be used to serialize many different types including strings, integers, doubles, array and maps. More can be found out about RESP in the [Valkey Documentation](https://valkey.io/topics/protocol/).
8+
9+
We represent a raw RESP token using the type ``RESPToken``. A parsed RESP value is represented by the enum ``RESPToken/Value``. This includes cases for the different datatypes a RESP token can represent.
10+
11+
The majority of the Valkey commands return the Swift types equivalent to their expected response. eg `GET` returns a `ByteBuffer` containing the contents of the key, `STRLEN` returns the length of the contents as an `Int`. But there are a number of reasons for commands to not have a defined return type and in these cases a command may return the type ``RESPToken`` or one of the sequence types ``RESPToken/Array`` or ``RESPToken/Map``.
12+
13+
## Decoding RESPToken
14+
15+
A `RESPToken` contains the raw serialized bytes returned by the Valkey server. Valkey-swift introduces a protocol ``RESPTokenDecodable`` for types that can be decoded from a `RESPToken`. Many of Swift core types have been extended to conform to `RESPTokenDecodable`. There are two ways to decode a `RESPToken`. You can call ``RESPTokenDecodable/init(fromRESP:)``.
16+
17+
```swift
18+
let string = String(fromRESP: respToken)
19+
```
20+
21+
Or you can call the `RESPToken` method ``RESPToken/decode(as:)``. This can be chained onto the end of a command call eg `RPOP` can return a single value or an array of values so the function returns a `RESPToken` and the user should decode it based on whether they asked for multiple or a single value to be popped.
22+
23+
```swift
24+
let string = try await valkeyClient.rpop("myList")?.decode(as: String.self)
25+
```
26+
27+
## Decoding RESPToken.Array
28+
29+
When a command returns an array it is returned as an ``RESPToken/Array``. This can be to avoid the additional memory allocation of creating a Swift `Array` or because the array represents a more complex type. `RESPToken.Array` conforms to `Sequence` and its element type is a `RESPToken`. You can iterate over its contents and decode each element as follows.
30+
31+
```swift
32+
let values = try await valkeyClient.smembers("mySet")
33+
for value in values {
34+
let string = value.decode(as: String.self)
35+
print(string)
36+
}
37+
```
38+
39+
Alternatively if you don't mind the additional allocation you can decode as a Swift `Array`
40+
41+
```swift
42+
let values = try await valkeyClient.smembers("mySet").decode(as: [String].self)
43+
```
44+
45+
The type of each element of a `RESPToken.Array` is not fixed. It is possible for values in the same array to represent different types. Decoding different types from an array is done using either `RESPToken.Array` method ``RESPToken/Array/decodeElements(as:)`` or `RESPToken` method ``RESPToken/decodeArrayElements(as:)``. The following code decodes the first element of an array as a `String` and the second as an `Int`.
46+
47+
```swift
48+
let (member, score) = respToken.decodeArrayElements(as: (String, Int).self)
49+
```
50+
51+
## Decoding RESPToken.Map
52+
53+
When a command returns a dictionary it is returned as a ``RESPToken/Map``. This can be to avoid the additional memory allocation of creating a Swift `Dictionary`, or a more complex type is being represented. `RESPToken.Map` conforms to `Sequence` and its element type is a key value pair of two `RESPToken`. You can iterate over its contents and decode its elements as follows.
54+
55+
```swift
56+
let values = try await client.configGet(parameters: ["*max-*-entries*"])
57+
for (keyToken, valueToken) in values {
58+
let key = try keyToken.decode(as: String.self)
59+
let value = try valueToken.decode(as: String.self)
60+
...
61+
}
62+
```
63+
64+
Alternatively if you don't mind the additional allocation you can decode as a Swift `Dictionary`
65+
66+
```swift
67+
let values = try await client.configGet(parameters: ["*max-*-entries*"])
68+
.decode(as: [String: String].self)
69+
```

Sources/Valkey/Documentation.docc/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Valkey-swift is a swift based client for Valkey, the high-performance key/value
1212

1313
- <doc:getting-started>
1414
- <doc:Pipelining>
15+
- <doc:RESPToken-Decoding>
1516
- <doc:Pubsub>
1617
- <doc:Transactions>
1718

@@ -23,6 +24,7 @@ Valkey-swift is a swift based client for Valkey, the high-performance key/value
2324
- ``ValkeyServerAddress``
2425
- ``ValkeyConnection``
2526
- ``ValkeyConnectionConfiguration``
27+
- ``ValkeyTracingConfiguration``
2628

2729
### Commands
2830

@@ -42,10 +44,14 @@ Valkey-swift is a swift based client for Valkey, the high-performance key/value
4244

4345
- ``ValkeySubscription``
4446
- ``ValkeySubscriptionMessage``
47+
- ``ValkeySubscribeCommand``
48+
- ``ValkeySubscriptionFilter``
4549

4650
### Errors
4751

4852
- ``ValkeyClientError``
53+
- ``ValkeyClusterError``
54+
- ``RESPDecodeError``
4955
- ``RESPParsingError``
5056

5157
### Cluster

Sources/Valkey/RESP/RESPTokenDecodable.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import NIOCore
99

1010
/// A type that can decode from a response token.
1111
public protocol RESPTokenDecodable {
12+
/// Initialize from RESPToken
1213
init(fromRESP: RESPToken) throws
1314
}
1415

0 commit comments

Comments
 (0)