Skip to content

Commit 2211dbf

Browse files
LukasaMordil
authored andcommitted
Detect and throw on invalid integer.
Motivation: parseInteger did not distinguish between not having enough bytes for an integer and not being able to parse the integer that was present. This was a bit tricky for code internally, where some call sites had extra code looking for spooky action at a distance in order to determine if the integer failed to parse. This is unnecessary: parseInteger is sufficiently aware of what's going on to address this problem itself. Modifications: - Added a new parser error (acceptable as we haven't tagged 1.0 yet). - Throw it from parseInteger if the integer is invalid. Result: parseInteger clearly communicates if the integer failed to parse.
1 parent 638fbb0 commit 2211dbf

File tree

3 files changed

+32
-25
lines changed

3 files changed

+32
-25
lines changed

Sources/RediStack/RESP/RESPTranslator.swift

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,15 @@ extension RESPTranslator {
104104
case invalidToken
105105
case invalidBulkStringSize
106106
case bulkStringSizeMismatch
107+
case invalidIntegerFormat
107108

108109
/// See `LocalizedError.errorDescription`
109110
public var errorDescription: String? {
110111
switch self {
111112
case .invalidToken: return "Cannot parse RESP: Invalid Token"
112113
case .invalidBulkStringSize: return "Cannot parse RESP Bulk String: Received invalid size."
113114
case .bulkStringSizeMismatch: return "Cannot parse RESP Bulk String: Declared Size and Content Size do not match."
115+
case .invalidIntegerFormat: return "Cannot parse RESP integer: invalid integer format"
114116
}
115117
}
116118
}
@@ -133,7 +135,7 @@ extension RESPTranslator {
133135
result = .simpleString(value)
134136

135137
case .colon:
136-
guard let value = self.parseInteger(from: &copy) else { return nil }
138+
guard let value = try self.parseInteger(from: &copy) else { return nil }
137139
result = .integer(value)
138140

139141
case .dollar:
@@ -182,23 +184,19 @@ extension RESPTranslator {
182184
}
183185

184186
/// See [https://redis.io/topics/protocol#resp-integers](https://redis.io/topics/protocol#resp-integers)
185-
internal func parseInteger(from buffer: inout ByteBuffer) -> Int? {
187+
internal func parseInteger(from buffer: inout ByteBuffer) throws -> Int? {
186188
guard
187189
var stringBuffer = parseSimpleString(from: &buffer),
188190
let string = stringBuffer.readString(length: stringBuffer.readableBytes)
189191
else { return nil }
190-
return Int(string)
192+
193+
guard let result = Int(string) else { throw ParsingError.invalidIntegerFormat }
194+
return result
191195
}
192196

193197
/// See [https://redis.io/topics/protocol#resp-bulk-strings](https://redis.io/topics/protocol#resp-bulk-strings)
194198
internal func parseBulkString(from buffer: inout ByteBuffer) throws -> RESPValue? {
195-
let startingReaderIndex = buffer.readerIndex
196-
197-
guard let size = self.parseInteger(from: &buffer) else {
198-
// if the reader index changed, that means that a valid string parse happened, but it's not a number.
199-
guard startingReaderIndex == buffer.readerIndex else {
200-
throw ParsingError.invalidBulkStringSize
201-
}
199+
guard let size = try self.parseInteger(from: &buffer) else {
202200
return nil
203201
}
204202

@@ -237,7 +235,7 @@ extension RESPTranslator {
237235

238236
/// See [https://redis.io/topics/protocol#resp-arrays](https://redis.io/topics/protocol#resp-arrays)
239237
internal func parseArray(from buffer: inout ByteBuffer) throws -> RESPValue? {
240-
guard let elementCount = parseInteger(from: &buffer) else { return nil }
238+
guard let elementCount = try parseInteger(from: &buffer) else { return nil }
241239
guard elementCount > -1 else { return .null } // '*-1\r\n'
242240
guard elementCount > 0 else { return .array([]) } // '*0\r\n'
243241

Tests/RediStackTests/RESPTranslatorTests.swift

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -317,41 +317,49 @@ extension RESPTranslatorTests {
317317

318318
extension RESPTranslatorTests {
319319
func testParsing_integer_withAllBytes() {
320-
XCTAssertEqual(integerParseTest(":100\r\n"), 100)
321-
XCTAssertEqual(integerParseTest(":-100\r\n"), -100)
322-
XCTAssertEqual(integerParseTest(":\(Int.min)\r\n"), Int.min)
323-
XCTAssertEqual(integerParseTest(":\(Int.max)\r\n"), Int.max)
320+
XCTAssertEqual(try integerParseTest(":100\r\n"), 100)
321+
XCTAssertEqual(try integerParseTest(":-100\r\n"), -100)
322+
XCTAssertEqual(try integerParseTest(":\(Int.min)\r\n"), Int.min)
323+
XCTAssertEqual(try integerParseTest(":\(Int.max)\r\n"), Int.max)
324324
}
325325

326326
func testParsing_integer_missingBytes() {
327-
XCTAssertNil(integerParseTest(":\r"))
328-
XCTAssertNil(integerParseTest(":"))
329-
XCTAssertNil(integerParseTest(":\n"))
330-
XCTAssertNil(integerParseTest(":\r\n"))
327+
XCTAssertNil(try integerParseTest(":\r"))
328+
XCTAssertNil(try integerParseTest(":"))
329+
XCTAssertNil(try integerParseTest(":\n"))
330+
XCTAssertThrowsError(try integerParseTest(":\r\n")) { error in
331+
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat)
332+
}
331333
}
332334

333-
func testParsing_integer_recursively() {
335+
func testParsing_integer_recursively() throws {
334336
let testString = ":1\r\n:300\r\n"
335337

336338
var buffer = allocator.buffer(capacity: testString.count)
337339
buffer.writeString(testString)
338340

339341
buffer.mimicTokenParse()
340-
let first = parser.parseInteger(from: &buffer)
342+
let first = try parser.parseInteger(from: &buffer)
341343
XCTAssertEqual(buffer.readerIndex, 4) // position of the 2nd ':'
342344
XCTAssertEqual(first, 1)
343345

344346
buffer.mimicTokenParse()
345-
let second = parser.parseInteger(from: &buffer)
347+
let second = try parser.parseInteger(from: &buffer)
346348
XCTAssertEqual(buffer.readerIndex, 10)
347349
XCTAssertEqual(second, 300)
348350
}
351+
352+
func testParsing_integer_nonBase10() throws {
353+
XCTAssertThrowsError(try integerParseTest(":0xa\r\n")) { error in
354+
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat)
355+
}
356+
}
349357

350-
private func integerParseTest(_ inputRESP: String) -> Int? {
358+
private func integerParseTest(_ inputRESP: String) throws -> Int? {
351359
var buffer = allocator.buffer(capacity: inputRESP.count)
352360
buffer.writeString(inputRESP)
353361
buffer.mimicTokenParse()
354-
return parser.parseInteger(from: &buffer)
362+
return try parser.parseInteger(from: &buffer)
355363
}
356364
}
357365

@@ -381,7 +389,7 @@ extension RESPTranslatorTests {
381389
buffer.writeString("$FOO\r\nwhat\r\n")
382390
buffer.mimicTokenParse()
383391
XCTAssertThrowsError(try self.parser.parseBulkString(from: &buffer)) { error in
384-
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidBulkStringSize)
392+
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat)
385393
}
386394
}
387395

Tests/RediStackTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extension RESPTranslatorTests {
2828
("testParsing_error", testParsing_error),
2929
("testParsing_integer_chunked", testParsing_integer_chunked),
3030
("testParsing_integer_missingBytes", testParsing_integer_missingBytes),
31+
("testParsing_integer_nonBase10", testParsing_integer_nonBase10),
3132
("testParsing_integer_recursively", testParsing_integer_recursively),
3233
("testParsing_integer_withAllBytes", testParsing_integer_withAllBytes),
3334
("testParsing_integer", testParsing_integer),

0 commit comments

Comments
 (0)