Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/Valkey/Commands/Custom/GeoCustomCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ extension GEOSEARCH {
case .array(let array):
var arrayIterator = array.makeIterator()
guard let member = arrayIterator.next() else {
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.invalidArraySize(array)
}
self.member = try String(fromRESP: member)
self.attributes = array.dropFirst().map { $0 }
Expand All @@ -56,7 +56,7 @@ extension GEOSEARCH {
self.attributes = []

default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array, .bulkString], token: token)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Valkey/Commands/Custom/ListCustomCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extension LMPOP {
case .array(let array):
(self.key, self.values) = try array.decodeElements()
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand Down
20 changes: 10 additions & 10 deletions Sources/Valkey/Commands/Custom/ServerCustomCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//
extension ROLE {
public enum Response: RESPTokenDecodable, Sendable {
struct DecodeError: Error {}
struct MissingValueDecodeError: Error {}
public struct Primary: Sendable {
public struct Replica: RESPTokenDecodable, Sendable {
public let ip: String
Expand All @@ -23,7 +23,7 @@ extension ROLE {

init(arrayIterator: inout RESPToken.Array.Iterator) throws {
guard let replicationOffsetToken = arrayIterator.next(), let replicasToken = arrayIterator.next() else {
throw DecodeError()
throw MissingValueDecodeError()
}
self.replicationOffset = try .init(fromRESP: replicationOffsetToken)
self.replicas = try .init(fromRESP: replicasToken)
Expand All @@ -39,7 +39,7 @@ extension ROLE {
public init(fromRESP token: RESPToken) throws {
let string = try String(fromRESP: token)
guard let state = State(rawValue: string) else {
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError(.unexpectedToken, token: token)
}
self = state
}
Expand All @@ -55,7 +55,7 @@ extension ROLE {
let stateToken = arrayIterator.next(),
let replicationToken = arrayIterator.next()
else {
throw DecodeError()
throw MissingValueDecodeError()
}
self.primaryIP = try .init(fromRESP: primaryIPToken)
self.primaryPort = try .init(fromRESP: primaryPortToken)
Expand All @@ -67,7 +67,7 @@ extension ROLE {
public let primaryNames: [String]

init(arrayIterator: inout RESPToken.Array.Iterator) throws {
guard let primaryNamesToken = arrayIterator.next() else { throw DecodeError() }
guard let primaryNamesToken = arrayIterator.next() else { throw MissingValueDecodeError() }
self.primaryNames = try .init(fromRESP: primaryNamesToken)
}
}
Expand All @@ -81,7 +81,7 @@ extension ROLE {
do {
var iterator = array.makeIterator()
guard let roleToken = iterator.next() else {
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.invalidArraySize(array)
}
let role = try String(fromRESP: roleToken)
switch role {
Expand All @@ -95,13 +95,13 @@ extension ROLE {
let sentinel = try Sentinel(arrayIterator: &iterator)
self = .sentinel(sentinel)
default:
throw DecodeError()
throw RESPDecodeError(.unexpectedToken, token: token)
}
} catch {
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
} catch is MissingValueDecodeError {
throw RESPDecodeError.invalidArraySize(array)
}
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/Valkey/Commands/Custom/SetCustomCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ extension SSCAN {

public init(fromRESP token: RESPToken) throws {
// cursor is encoded as a bulkString, but should be
let (cursorString, elements) = try token.decodeArrayElements(as: (String, RESPToken.Array).self)
guard let cursor = Int(cursorString) else { throw RESPParsingError(code: .unexpectedType, buffer: token.base) }
let (cursor, elements) = try token.decodeArrayElements(as: (Int, RESPToken.Array).self)
self.cursor = cursor
self.elements = elements
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Valkey/Commands/Custom/SortedSetCustomCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public struct SortedSetEntry: RESPTokenDecodable, Sendable {
case .array(let array):
(self.value, self.score) = try array.decodeElements()
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand Down Expand Up @@ -56,7 +56,7 @@ extension ZMPOP {
case .array(let array):
(self.key, self.values) = try array.decodeElements()
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand Down
18 changes: 9 additions & 9 deletions Sources/Valkey/Commands/Custom/StreamCustomCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public struct XREADMessage: RESPTokenDecodable, Sendable {
self.id = id
self.fields = keyValuePairs
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}

Expand Down Expand Up @@ -77,7 +77,7 @@ public struct XREADGroupMessage: RESPTokenDecodable, Sendable {
self.id = id
self.fields = keyValuePairs
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand All @@ -100,7 +100,7 @@ public struct XREADStreams<Message>: RESPTokenDecodable, Sendable where Message:
return Stream(key: key, messages: messages)
}
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.map], token: token)
}
}
}
Expand All @@ -116,7 +116,7 @@ public struct XAUTOCLAIMResponse: RESPTokenDecodable, Sendable {
case .array(let array):
(self.streamID, self.messages, self.deletedMessages) = try array.decodeElements()
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand All @@ -143,7 +143,7 @@ public enum XCLAIMResponse: RESPTokenDecodable, Sendable {
self = try .ids(array.decode())
}
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand All @@ -164,7 +164,7 @@ public enum XPENDINGResponse: RESPTokenDecodable, Sendable {
case .array(let array):
(self.consumer, self.count) = try array.decodeElements()
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand All @@ -178,7 +178,7 @@ public enum XPENDINGResponse: RESPTokenDecodable, Sendable {
case .array(let array):
(self.pendingMessageCount, self.minimumID, self.maximumID, self.consumers) = try array.decodeElements()
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand All @@ -194,7 +194,7 @@ public enum XPENDINGResponse: RESPTokenDecodable, Sendable {
case .array(let array):
(self.id, self.consumer, self.millisecondsSinceDelivered, self.numberOfTimesDelivered) = try array.decodeElements()
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand All @@ -205,7 +205,7 @@ public enum XPENDINGResponse: RESPTokenDecodable, Sendable {
case .array(let array):
self.messages = try array.decode(as: [PendingMessage].self)
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.array], token: token)
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions Sources/Valkey/Commands/Custom/StringCustomCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ extension LCS {
default: break
}
}
guard let matches else { throw RESPParsingError(code: .unexpectedType, buffer: token.base) }
guard let length else { throw RESPParsingError(code: .unexpectedType, buffer: token.base) }
guard let matches else { throw RESPDecodeError.missingToken(key: "matches", token: token) }
guard let length else { throw RESPDecodeError.missingToken(key: "length", token: token) }
self = .matches(length: numericCast(length), matches: matches)
default:
throw RESPParsingError(code: .unexpectedType, buffer: token.base)
throw RESPDecodeError.tokenMismatch(expected: [.bulkString, .integer, .map], token: token)

}
}
}
Expand Down
81 changes: 81 additions & 0 deletions Sources/Valkey/RESP/RESPDecodeError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// This source file is part of the valkey-swift project
// Copyright (c) 2025 the valkey-swift project authors
//
// See LICENSE.txt for license information
// SPDX-License-Identifier: Apache-2.0
//
/// Error returned when decoding a RESPToken.
/// Error thrown when decoding RESPTokens
public struct RESPDecodeError: Error {
/// Error code for decode error
public struct ErrorCode: Sendable, Equatable, CustomStringConvertible {
fileprivate enum Code: Sendable, Equatable {
case tokenMismatch
case invalidArraySize
case missingToken
case cannotParseInteger
case cannotParseDouble
case unexpectedToken
}

fileprivate let code: Code
fileprivate init(_ code: Code) {
self.code = code
}

public var description: String { String(describing: self.code) }

/// Token does not match one of the expected tokens
public static var tokenMismatch: Self { .init(.tokenMismatch) }
/// Does not match the expected array size
public static var invalidArraySize: Self { .init(.invalidArraySize) }
/// Token is missing
public static var missingToken: Self { .init(.missingToken) }
/// Failed to parse an integer
public static var cannotParseInteger: Self { .init(.cannotParseInteger) }
/// Failed to parse a double
public static var cannotParseDouble: Self { .init(.cannotParseDouble) }
/// Token is not as expected
public static var unexpectedToken: Self { .init(.unexpectedToken) }
}
public let errorCode: ErrorCode
public let message: String?
public let token: RESPToken.Value

public init(_ errorCode: ErrorCode, token: RESPToken.Value, message: String? = nil) {
self.errorCode = errorCode
self.token = token
self.message = message
}

public init(_ errorCode: ErrorCode, token: RESPToken, message: String? = nil) {
self = .init(errorCode, token: token.value, message: message)
}

/// Token does not match one of the expected tokens
public static func tokenMismatch(expected: [RESPTypeIdentifier], token: RESPToken) -> Self {
if expected.count == 0 {
return .init(.tokenMismatch, token: token, message: "Found unexpected token while decoding")
} else if expected.count == 1 {
return .init(.tokenMismatch, token: token, message: "Expected to find a \(expected[0])")
} else {
let expectedTokens = "\(expected.dropLast().map { "\($0)" }.joined(separator: ", ")) or \(expected.last!)"
return .init(.tokenMismatch, token: token, message: "Expected to find a \(expectedTokens) token")
}
}
/// Does not match the expected array size
public static func invalidArraySize(_ array: RESPToken.Array) -> Self {
.init(.invalidArraySize, token: .array(array))
}
/// Token associated with key is missing
public static func missingToken(key: String, token: RESPToken) -> Self {
.init(.missingToken, token: token, message: "Expected map to contain token with key \"\(key)\"")
}
}

extension RESPDecodeError: CustomStringConvertible {
public var description: String {
"Error: \"\(self.message ?? String(describing: self.errorCode))\", token: \(self.token.debugDescription)"
}
}
Loading
Loading