Skip to content

Commit e3c850e

Browse files
committed
Add RedisDataEncoder with unit tests
1 parent 99463bf commit e3c850e

File tree

5 files changed

+240
-3
lines changed

5 files changed

+240
-3
lines changed

Sources/NIORedis/Coders/RedisDataDecoder.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ final class RedisDataDecoder: ByteToMessageDecoder {
88
/// `ByteToMessageDecoder`
99
public typealias InboundOut = RedisData
1010

11-
/// See `ByteToMessageDecoder.cumulationBuffer`
12-
var cumulationBuffer: ByteBuffer?
13-
1411
/// See `ByteToMessageDecoder.decode(ctx:buffer:)`
1512
func decode(ctx: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
1613
var position = 0
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Foundation
2+
import NIO
3+
4+
/// Handles outgoing `RedisData` on the wire by encoding it to the Redis RESP protocol.
5+
///
6+
/// See: https://redis.io/topics/protocol
7+
final class RedisDataEncoder: MessageToByteEncoder {
8+
/// See `MessageToByteEncoder.OutboundIn`
9+
typealias OutboundIn = RedisData
10+
11+
/// See `RedisDataEncoder.encode(ctx:data:out:)`
12+
func encode(ctx: ChannelHandlerContext, data: RedisData, out: inout ByteBuffer) throws {
13+
out.write(bytes: _encode(data: data))
14+
}
15+
16+
func _encode(data: RedisData) -> Data {
17+
switch data {
18+
case .simpleString(let string):
19+
return "+\(string)\r\n".convertedToData()
20+
21+
case .bulkString(let data):
22+
return "$\(data.count)\r\n".convertedToData() + data + "\r\n".convertedToData()
23+
24+
case .integer(let number):
25+
return ":\(number)\r\n".convertedToData()
26+
27+
case .null:
28+
return "$-1\r\n".convertedToData()
29+
30+
case .error(let error):
31+
return "-\(error.description)\r\n".convertedToData()
32+
33+
case .array(let array):
34+
let encodedArray = array.map { _encode(data: $0) }.joined()
35+
return "*\(array.count)\r\n".convertedToData() + encodedArray
36+
}
37+
}
38+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import Foundation
2+
import NIO
3+
@testable import NIORedis
4+
import XCTest
5+
6+
final class RedisDataEncoderParsingTests: XCTestCase {
7+
private let encoder = RedisDataEncoder()
8+
9+
func testSimpleStrings() {
10+
XCTAssertEqual(
11+
encoder._encode(data: .simpleString("Test1")),
12+
"+Test1\r\n".convertedToData()
13+
)
14+
XCTAssertEqual(
15+
encoder._encode(data: .simpleString("®in§³¾")),
16+
"+®in§³¾\r\n".convertedToData()
17+
)
18+
}
19+
20+
func testBulkStrings() {
21+
let t1 = Data(bytes: [0x01, 0x02, 0x0a, 0x1b, 0xaa])
22+
XCTAssertEqual(
23+
encoder._encode(data: .bulkString(t1)),
24+
"$5\r\n".convertedToData() + t1 + "\r\n".convertedToData()
25+
)
26+
let t2 = "®in§³¾".convertedToData()
27+
XCTAssertEqual(
28+
encoder._encode(data: .bulkString(t2)),
29+
"$10\r\n".convertedToData() + t2 + "\r\n".convertedToData()
30+
)
31+
let t3 = "".convertedToData()
32+
XCTAssertEqual(
33+
encoder._encode(data: .bulkString(t3)),
34+
"$0\r\n\r\n".convertedToData()
35+
)
36+
}
37+
38+
func testIntegers() {
39+
XCTAssertEqual(
40+
encoder._encode(data: .integer(Int.min)),
41+
":\(Int.min)\r\n".convertedToData()
42+
)
43+
XCTAssertEqual(
44+
encoder._encode(data: .integer(0)),
45+
":0\r\n".convertedToData()
46+
)
47+
}
48+
49+
func testArrays() {
50+
XCTAssertEqual(
51+
encoder._encode(data: .array([])),
52+
"*0\r\n".convertedToData()
53+
)
54+
let a1: RedisData = .array([.integer(3), .simpleString("foo")])
55+
XCTAssertEqual(
56+
encoder._encode(data: a1),
57+
"*2\r\n:3\r\n+foo\r\n".convertedToData()
58+
)
59+
let bytes = Data(bytes: [ 0x0a, 0x1a, 0x1b, 0xff ])
60+
let a2: RedisData = .array([.array([
61+
.integer(3),
62+
.bulkString(bytes)
63+
])])
64+
XCTAssertEqual(
65+
encoder._encode(data: a2),
66+
"*1\r\n*2\r\n:3\r\n$4\r\n".convertedToData() + bytes + "\r\n".convertedToData()
67+
)
68+
}
69+
70+
func testError() {
71+
let error = RedisError(identifier: "testError", reason: "Manual error")
72+
let result = encoder._encode(data: .error(error))
73+
XCTAssertEqual(result, "-\(error.description)\r\n".convertedToData())
74+
}
75+
76+
func testNull() {
77+
XCTAssertEqual(encoder._encode(data: .null), "$-1\r\n".convertedToData())
78+
}
79+
}
80+
81+
extension RedisDataEncoderParsingTests {
82+
static var allTests = [
83+
("testSimpleStrings", testSimpleStrings),
84+
("testBulkStrings", testBulkStrings),
85+
("testIntegers", testIntegers),
86+
("testArrays", testArrays),
87+
("testError", testError),
88+
("testNull", testNull),
89+
]
90+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import NIO
2+
@testable import NIORedis
3+
import XCTest
4+
5+
final class RedisDataEncoderTests: XCTestCase {
6+
private var encoder: RedisDataEncoder!
7+
private var allocator: ByteBufferAllocator!
8+
private var channel: EmbeddedChannel!
9+
10+
override func setUp() {
11+
super.setUp()
12+
13+
encoder = RedisDataEncoder()
14+
allocator = ByteBufferAllocator()
15+
channel = EmbeddedChannel()
16+
_ = try? channel.pipeline.add(handler: encoder).wait()
17+
}
18+
19+
override func tearDown() {
20+
super.tearDown()
21+
22+
_ = try? channel.finish()
23+
}
24+
25+
func testSimpleStrings() throws {
26+
let simpleString1 = RedisData.simpleString("Test1")
27+
try runEncodePass(with: simpleString1) { XCTAssertEqual($0.readableBytes, 8) }
28+
XCTAssertNoThrow(try channel.writeOutbound(simpleString1))
29+
30+
let simpleString2 = RedisData.simpleString("®in§³¾")
31+
try runEncodePass(with: simpleString2) { XCTAssertEqual($0.readableBytes, 13) }
32+
XCTAssertNoThrow(try channel.writeOutbound(simpleString2))
33+
}
34+
35+
func testBulkStrings() throws {
36+
let bs1 = RedisData.bulkString(Data(bytes: [0x01, 0x02, 0x0a, 0x1b, 0xaa]))
37+
try runEncodePass(with: bs1) { XCTAssertEqual($0.readableBytes, 11) }
38+
XCTAssertNoThrow(try channel.writeOutbound(bs1))
39+
40+
let bs2 = RedisData.bulkString("®in§³¾".convertedToData())
41+
try runEncodePass(with: bs2) { XCTAssertEqual($0.readableBytes, 17) }
42+
XCTAssertNoThrow(try channel.writeOutbound(bs2))
43+
44+
let bs3 = RedisData.bulkString("".convertedToData())
45+
try runEncodePass(with: bs3) { XCTAssertEqual($0.readableBytes, 6) }
46+
XCTAssertNoThrow(try channel.writeOutbound(bs3))
47+
}
48+
49+
func testIntegers() throws {
50+
let i1 = RedisData.integer(Int.min)
51+
try runEncodePass(with: i1) { XCTAssertEqual($0.readableBytes, 23) }
52+
XCTAssertNoThrow(try channel.writeOutbound(i1))
53+
54+
let i2 = RedisData.integer(0)
55+
try runEncodePass(with: i2) { XCTAssertEqual($0.readableBytes, 4) }
56+
XCTAssertNoThrow(try channel.writeOutbound(i2))
57+
}
58+
59+
func testArrays() throws {
60+
let a1 = RedisData.array([])
61+
try runEncodePass(with: a1) { XCTAssertEqual($0.readableBytes, 4) }
62+
XCTAssertNoThrow(try channel.writeOutbound(a1))
63+
64+
let a2: RedisData = .array([.integer(3), .simpleString("foo")])
65+
try runEncodePass(with: a2) { XCTAssertEqual($0.readableBytes, 14) }
66+
XCTAssertNoThrow(try channel.writeOutbound(a2))
67+
68+
let bytes = Data(bytes: [ 0x0a, 0x1a, 0x1b, 0xff ])
69+
let a3: RedisData = .array([.array([
70+
.integer(3),
71+
.bulkString(bytes)
72+
])])
73+
try runEncodePass(with: a3) { XCTAssertEqual($0.readableBytes, 22) }
74+
XCTAssertNoThrow(try channel.writeOutbound(a3))
75+
}
76+
77+
func testError() throws {
78+
let error = RedisError(identifier: "testError", reason: "Manual error")
79+
let data = RedisData.error(error)
80+
try runEncodePass(with: data) {
81+
XCTAssertEqual($0.readableBytes, "-\(error.description)\r\n".convertedToData().count)
82+
}
83+
XCTAssertNoThrow(try channel.writeOutbound(data))
84+
}
85+
86+
func testNull() throws {
87+
let null = RedisData.null
88+
try runEncodePass(with: null) { XCTAssertEqual($0.readableBytes, 5) }
89+
XCTAssertNoThrow(try channel.writeOutbound(null))
90+
}
91+
92+
private func runEncodePass(with input: RedisData, _ validation: (ByteBuffer) -> Void) throws {
93+
let context = try channel.pipeline.context(handler: encoder).wait()
94+
95+
var buffer = allocator.buffer(capacity: 256)
96+
try encoder.encode(ctx: context, data: input, out: &buffer)
97+
validation(buffer)
98+
}
99+
}
100+
101+
extension RedisDataEncoderTests {
102+
static var allTests = [
103+
("testSimpleStrings", testSimpleStrings),
104+
("testBulkStrings", testBulkStrings),
105+
("testIntegers", testIntegers),
106+
("testArrays", testArrays),
107+
("testError", testError),
108+
("testNull", testNull),
109+
]
110+
}

Tests/NIORedisTests/XCTestManifests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ public func allTests() -> [XCTestCaseEntry] {
77
testCase(RedisDataDecoderTests.allTests),
88
testCase(RedisDataDecoderParsingTests.allTests),
99
testCase(RedisDataDecoderByteToMessageDecoderTests.allTests),
10+
testCase(RedisDataEncoderTests.allTests),
11+
testCase(RedisDataEncoderParsingTests.allTests),
1012
]
1113
}
1214
#endif

0 commit comments

Comments
 (0)