From bcc40319cf3eb3e7f93e2be0aead0747767eb342 Mon Sep 17 00:00:00 2001 From: Nathan Harris Date: Fri, 14 Dec 2018 14:22:18 -0800 Subject: [PATCH] Add decoding of arrays --- .../NIORedis/Coders/RedisDataDecoder.swift | 27 +++++++ .../RedisDataDecoder+ParsingTests.swift | 71 ++++++++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/Sources/NIORedis/Coders/RedisDataDecoder.swift b/Sources/NIORedis/Coders/RedisDataDecoder.swift index 7ab92fd..6e36c4f 100644 --- a/Sources/NIORedis/Coders/RedisDataDecoder.swift +++ b/Sources/NIORedis/Coders/RedisDataDecoder.swift @@ -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.. 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 { diff --git a/Tests/NIORedisTests/RedisDataDecoder+ParsingTests.swift b/Tests/NIORedisTests/RedisDataDecoder+ParsingTests.swift index 95b0452..7090d23 100644 --- a/Tests/NIORedisTests/RedisDataDecoder+ParsingTests.swift +++ b/Tests/NIORedisTests/RedisDataDecoder+ParsingTests.swift @@ -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), ] }