Add decoding of arrays

This commit is contained in:
Nathan Harris
2018-12-14 14:22:18 -08:00
parent a87ce04e25
commit bcc40319cf
2 changed files with 97 additions and 1 deletions
@@ -59,6 +59,8 @@ extension RedisDataDecoder {
return .parsed
case .dollar:
return try _parseBulkString(at: &position, from: buffer)
case .asterisk:
return try _parseArray(at: &position, from: buffer)
default: return .notYetParsed
}
}
@@ -139,6 +141,31 @@ extension RedisDataDecoder {
return .parsed // bulkString(Data(bytes[ ..<(size - 1) ]))
}
/// See https://redis.io/topics/protocol#resp-arrays
func _parseArray(at position: inout Int, from buffer: ByteBuffer) throws -> _RedisDataDecodingState {
guard let arraySize = try _parseInteger(at: &position, from: buffer) else { return .notYetParsed }
#warning("TODO: return null array")
guard arraySize > -1 else { return .parsed }
#warning("TODO: return empty array")
guard arraySize > 0 else { return .parsed }
var array = [_RedisDataDecodingState](repeating: .notYetParsed, count: arraySize)
for index in 0..<arraySize {
guard buffer.readableBytes - position > 0 else { return .notYetParsed }
let parseResult = try _parse(at: &position, from: buffer)
switch parseResult {
case .parsed:
array[index] = parseResult
default:
return .notYetParsed
}
}
#warning("TODO: Mapping to data and return the array of values")
return .parsed
}
}
private extension ByteBuffer {
@@ -41,6 +41,18 @@ final class RedisDataDecoderParsingTests: XCTestCase {
])
}
func testParsing_with_arrays() throws {
try parseTest_singleValue(input: "*1\r\n+!\r\n")
try parseTest_singleValue(input: "*2\r\n*1\r\n:1\r\n:3\r\n")
try parseTest_singleValue(input: "*0\r\n".convertedToData())
try parseTest_singleValue(input: "*-1\r\n".convertedToData())
}
func testParsing_with_arrays_recursively() throws {
try parseTest_recursive(withChunks: ["*2\r", "\n+a\r\n+a\r\n*", "0\r\n"])
try parseTest_recursive(withChunks: ["*-1\r".convertedToData(), "\n".convertedToData()])
}
/// See parse_Test_singleValue(input:) String
private func parseTest_singleValue(input: String) throws {
try parseTest_singleValue(input: input.convertedToData())
@@ -208,7 +220,7 @@ extension RedisDataDecoderParsingTests {
}
func testParsing_bulkString_handlesLargeSizes() throws {
let bytes = [UInt8].init(repeating: .dollar, count: 10_000_000)
let bytes = [UInt8].init(repeating: .dollar, count: 1_000_000)
let data = "$\(bytes.count)\r\n".convertedToData() + Data(bytes: bytes) + "\r\n".convertedToData()
XCTAssertEqual(try parseTestBulkString(data), .parsed)
}
@@ -227,6 +239,55 @@ extension RedisDataDecoderParsingTests {
}
}
// MARK: Array Parsing
extension RedisDataDecoderParsingTests {
func testParsing_array_whenNull_returnsNil() {
XCTAssertEqual(try parseTestArray("*-1\r\n"), .parsed)
}
func testParsing_array_whenEmpty_returnsEmpty() {
XCTAssertEqual(try parseTestArray("*0\r\n"), .parsed)
}
func testParsing_array_handlesLargeSizes() {
let range = 0..<1000
var data = "*\(range.endIndex)\r\n".convertedToData()
range.forEach { _ in
data += "$5\r\n".convertedToData()
data += Data(bytes: [0xaa, 0xbb, 0xcc, 0xab, 0xff])
data += "\r\n".convertedToData()
}
XCTAssertEqual(try parseTestArray(data), .parsed)
}
func testParsing_array_handlesMixedTypes() {
XCTAssertEqual(try parseTestArray("*3\r\n:3\r\n+OK\r\n$1\r\na\r\n"), .parsed)
}
func testParsing_array_handlesNullElements() {
XCTAssertEqual(try parseTestArray("*3\r\n:3\r\n$-1\r\n:30\r\n"), .parsed)
}
func testParsing_array_handlesNestedArrays() {
XCTAssertEqual(try parseTestArray("*2\r\n:3\r\n*2\r\n:30\r\n:15\r\n"), .parsed)
}
private func parseTestArray(_ input: String) throws -> RedisDataDecoder._RedisDataDecodingState {
return try parseTestArray(input.convertedToData())
}
private func parseTestArray(_ input: Data) throws -> RedisDataDecoder._RedisDataDecodingState {
var buffer = allocator.buffer(capacity: input.count)
buffer.write(bytes: input)
var position = 1 // "trim" token
return try RedisDataDecoder()._parseArray(at: &position, from: buffer)
}
}
extension RedisDataDecoderParsingTests {
static var allTests = [
("testParsing_with_simpleString", testParsing_with_simpleString),
@@ -235,6 +296,8 @@ extension RedisDataDecoderParsingTests {
("testParsing_with_integer_recursively", testParsing_with_integer_recursively),
("testParsing_with_bulkString", testParsing_with_bulkString),
("testParsing_with_bulkString_recursively", testParsing_with_bulkString_recursively),
("testParsing_with_arrays", testParsing_with_arrays),
("testParsing_with_arrays_recursively", testParsing_with_arrays_recursively),
("testParsing_simpleString_missingEndings_returnsNil", testParsing_simpleString_missingEndings_returnsNil),
("testParsing_simpleString_withNoContent_returnsEmpty", testParsing_simpleString_withNoContent_returnsEmpty),
("testParsing_simpleString_withContent_returnsExpectedContent", testParsing_simpleString_withContent_returnsExpectedContent),
@@ -247,5 +310,11 @@ extension RedisDataDecoderParsingTests {
("testParsing_bulkString_withNull_returnsNil", testParsing_bulkString_withNull_returnsNil),
("testParsing_bulkString_handlesRawBytes", testParsing_bulkString_handlesRawBytes),
("testParsing_bulkString_handlesLargeSizes", testParsing_bulkString_handlesLargeSizes),
("testParsing_array_whenNull_returnsNil", testParsing_array_whenNull_returnsNil),
("testParsing_array_whenEmpty_returnsEmpty", testParsing_array_whenEmpty_returnsEmpty),
("testParsing_array_handlesLargeSizes", testParsing_array_handlesLargeSizes),
("testParsing_array_handlesMixedTypes", testParsing_array_handlesMixedTypes),
("testParsing_array_handlesNullElements", testParsing_array_handlesNullElements),
("testParsing_array_handlesNestedArrays", testParsing_array_handlesNestedArrays),
]
}