Skip to content

Commit 03fe496

Browse files
authored
Throw error when command returns an error (#20)
1 parent f91acdc commit 03fe496

File tree

6 files changed

+77
-10
lines changed

6 files changed

+77
-10
lines changed

Sources/Valkey/Connection/ValkeyChannelHandler.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,18 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
140140
}
141141

142142
func handleToken(context: ChannelHandlerContext, token: RESPToken) {
143-
guard let promise = commands.popFirst() else {
144-
preconditionFailure("Unexpected response")
143+
switch token.identifier {
144+
case .simpleError, .bulkError:
145+
guard let promise = commands.popFirst() else {
146+
preconditionFailure("Unexpected response")
147+
}
148+
promise.fail(ValkeyClientError(.commandError, message: token.errorString.map { String(buffer: $0) }))
149+
default:
150+
guard let promise = commands.popFirst() else {
151+
preconditionFailure("Unexpected response")
152+
}
153+
promise.succeed(token)
145154
}
146-
promise.succeed(token)
147155
}
148156

149157
func handleError(context: ChannelHandlerContext, error: Error) {

Sources/Valkey/RESP/RESPToken.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public struct RESPToken: Hashable, Sendable {
117117

118118
return .bulkString(local.readSlice(length: length)!)
119119

120-
case .blobError:
120+
case .bulkError:
121121
var lengthSlice = try! local.readCRLFTerminatedSlice2()!
122122
let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes)!
123123
let length = Int(lengthString)!
@@ -197,7 +197,7 @@ public struct RESPToken: Hashable, Sendable {
197197
case .simpleError:
198198
let slice = try! local.readCRLFTerminatedSlice2()!
199199
return slice
200-
case .blobError:
200+
case .bulkError:
201201
var lengthSlice = try! local.readCRLFTerminatedSlice2()!
202202
let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes)!
203203
let length = Int(lengthString)!
@@ -207,6 +207,10 @@ public struct RESPToken: Hashable, Sendable {
207207
}
208208
}
209209

210+
public var identifier: RESPTypeIdentifier {
211+
self.base.getValidatedRESP3TypeIdentifier()
212+
}
213+
210214
public init?(consuming buffer: inout ByteBuffer) throws {
211215
try self.init(consuming: &buffer, depth: 0)
212216
}
@@ -223,7 +227,7 @@ public struct RESPToken: Hashable, Sendable {
223227

224228
case .some(.bulkString),
225229
.some(.verbatimString),
226-
.some(.blobError):
230+
.some(.bulkError):
227231
validated = try buffer.readRESPBlobStringSlice()
228232

229233
case .some(.simpleString),
@@ -260,7 +264,7 @@ public struct RESPToken: Hashable, Sendable {
260264
}
261265

262266
extension ByteBuffer {
263-
fileprivate mutating func getRESP3TypeIdentifier(at index: Int) throws -> RESPTypeIdentifier? {
267+
fileprivate func getRESP3TypeIdentifier(at index: Int) throws -> RESPTypeIdentifier? {
264268
guard let int = self.getInteger(at: index, as: UInt8.self) else {
265269
return nil
266270
}
@@ -272,6 +276,11 @@ extension ByteBuffer {
272276
return id
273277
}
274278

279+
fileprivate func getValidatedRESP3TypeIdentifier() -> RESPTypeIdentifier {
280+
let int = self.getInteger(at: self.readerIndex, as: UInt8.self)!
281+
return RESPTypeIdentifier(rawValue: int)!
282+
}
283+
275284
fileprivate mutating func readValidatedRESP3TypeIdentifier() -> RESPTypeIdentifier {
276285
let int = self.readInteger(as: UInt8.self)!
277286
return RESPTypeIdentifier(rawValue: int)!
@@ -311,7 +320,7 @@ extension ByteBuffer {
311320

312321
fileprivate mutating func readRESPBlobStringSlice() throws -> ByteBuffer? {
313322
let marker = try self.getRESP3TypeIdentifier(at: self.readerIndex)!
314-
precondition(marker == .bulkString || marker == .verbatimString || marker == .blobError)
323+
precondition(marker == .bulkString || marker == .verbatimString || marker == .bulkError)
315324
guard var lengthSlice = try self.getCRLFTerminatedSlice(at: self.readerIndex + 1) else {
316325
return nil
317326
}

Sources/Valkey/RESP/RESPTypeIdentifier.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public enum RESPTypeIdentifier: UInt8 {
1818
case simpleString = 43 // UInt8.plus
1919
case simpleError = 45 // UInt8.min
2020
case bulkString = 36 // UInt8.dollar
21-
case blobError = 33 // UInt8.exclamationMark
21+
case bulkError = 33 // UInt8.exclamationMark
2222
case verbatimString = 61 // UInt8.equals
2323
case boolean = 35 // UInt8.pound
2424
case null = 95 // UInt8.underscore

Sources/Valkey/ValkeyClientError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
/// Errors returned by ``ValkeyClient``
16-
public struct ValkeyClientError: Error, CustomStringConvertible {
16+
public struct ValkeyClientError: Error, CustomStringConvertible, Equatable {
1717
public struct ErrorCode: Equatable, Sendable {
1818
fileprivate enum _Internal: Equatable, Sendable {
1919
case connectionClosed

Tests/IntegrationTests/ValkeyTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ struct GeneratedCommands {
171171
}
172172
}
173173

174+
@Test("Test command error is thrown")
175+
func testCommandError() async throws {
176+
var logger = Logger(label: "Valkey")
177+
logger.logLevel = .debug
178+
try await ValkeyClient(.hostname(valkeyHostname, port: 6379), logger: logger).withConnection(logger: logger) { connection in
179+
try await withKey(connection: connection) { key in
180+
_ = try await connection.set(key: key, value: "Hello")
181+
await #expect(throws: ValkeyClientError.self) { _ = try await connection.rpop(key: key) }
182+
}
183+
}
184+
}
185+
174186
@Test
175187
func testMultiplexing() async throws {
176188
var logger = Logger(label: "Valkey")

Tests/ValkeyTests/ValkeyConnectionTests.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,42 @@ struct ConnectionTests {
3535
try await channel.writeInbound(ByteBuffer(string: "$3\r\nBar\r\n"))
3636
#expect(try await fooResult == "Bar")
3737
}
38+
39+
@Test
40+
func testSimpleError() async throws {
41+
let channel = NIOAsyncTestingChannel()
42+
let logger = Logger(label: "test")
43+
let connection = try await ValkeyConnection.setupChannel(channel, configuration: .init(), logger: logger)
44+
45+
async let fooResult = connection.get(key: "foo")
46+
_ = try await channel.waitForOutboundWrite(as: ByteBuffer.self)
47+
48+
try await channel.writeInbound(ByteBuffer(string: "-Error!\r\n"))
49+
do {
50+
_ = try await fooResult
51+
Issue.record()
52+
} catch let error as ValkeyClientError {
53+
#expect(error.errorCode == .commandError)
54+
#expect(error.message == "Error!")
55+
}
56+
}
57+
58+
@Test
59+
func testBulkError() async throws {
60+
let channel = NIOAsyncTestingChannel()
61+
let logger = Logger(label: "test")
62+
let connection = try await ValkeyConnection.setupChannel(channel, configuration: .init(), logger: logger)
63+
64+
async let fooResult = connection.get(key: "foo")
65+
_ = try await channel.waitForOutboundWrite(as: ByteBuffer.self)
66+
67+
try await channel.writeInbound(ByteBuffer(string: "!10\r\nBulkError!\r\n"))
68+
do {
69+
_ = try await fooResult
70+
Issue.record()
71+
} catch let error as ValkeyClientError {
72+
#expect(error.errorCode == .commandError)
73+
#expect(error.message == "BulkError!")
74+
}
75+
}
3876
}

0 commit comments

Comments
 (0)