Skip to content

Commit a17bfff

Browse files
authored
Merge pull request #12 from Mordil/9-direct-bytebuffer
Update `RESPEncoder` to write to `ByteBuffer` directly
2 parents 0e2808c + 9c5db40 commit a17bfff

10 files changed

+93
-81
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import NIO
2+
3+
extension RESPEncoder: MessageToByteEncoder {
4+
/// See `MessageToByteEncoder.OutboundIn`
5+
public typealias OutboundIn = RESPValue
6+
7+
/// See `RESPEncoder.encode(data:out:)`
8+
public func encode(data: RESPValue, out: inout ByteBuffer) throws {
9+
encode(data, into: &out)
10+
}
11+
}

Sources/NIORedis/ChannelHandlers/RESPEncoder+MessageToByte.swift

Lines changed: 0 additions & 11 deletions
This file was deleted.

Sources/NIORedis/Extensions/NIO/ClientBootstrap.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extension ClientBootstrap {
1313
.channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
1414
.channelInitializer { channel in
1515
let handlers: [ChannelHandler] = [
16-
RESPEncoder(),
16+
MessageToByteHandler(RESPEncoder()),
1717
ByteToMessageHandler(RESPDecoder()),
1818
RedisCommandHandler()
1919
]
Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import Foundation
2-
3-
/// Translates `RedisValue` into raw bytes, formatted according to the Redis Serialization Protocol (RESP).
1+
/// Encodes `RedisValue` into a raw `ByteBuffer`, formatted according to the Redis Serialization Protocol (RESP).
42
///
5-
/// See: https://redis.io/topics/protocol
3+
/// See: [https://redis.io/topics/protocol](https://redis.io/topics/protocol)
64
public final class RESPEncoder {
75
public init() { }
86

@@ -11,26 +9,38 @@ public final class RESPEncoder {
119
/// See https://redis.io/topics/protocol
1210
/// - Parameter value: The `RESPValue` to encode.
1311
/// - Returns: The encoded value as a collection of bytes.
14-
public func encode(_ value: RESPValue) -> Data {
12+
public func encode(_ value: RESPValue, into buffer: inout ByteBuffer) {
1513
switch value {
1614
case .simpleString(let string):
17-
return "+\(string)\r\n".convertedToData()
15+
buffer.writeStaticString("+")
16+
buffer.writeString(string)
17+
buffer.writeStaticString("\r\n")
1818

1919
case .bulkString(let data):
20-
return "$\(data.count)\r\n".convertedToData() + data + "\r\n".convertedToData()
20+
buffer.writeStaticString("$")
21+
buffer.writeString(data.count.description)
22+
buffer.writeStaticString("\r\n")
23+
buffer.writeBytes(data)
24+
buffer.writeString("\r\n")
2125

2226
case .integer(let number):
23-
return ":\(number)\r\n".convertedToData()
27+
buffer.writeStaticString(":")
28+
buffer.writeString(number.description)
29+
buffer.writeStaticString("\r\n")
2430

2531
case .null:
26-
return "$-1\r\n".convertedToData()
32+
buffer.writeStaticString("$-1\r\n")
2733

2834
case .error(let error):
29-
return "-\(error.description)\r\n".convertedToData()
35+
buffer.writeStaticString("-")
36+
buffer.writeString(error.description)
37+
buffer.writeStaticString("\r\n")
3038

3139
case .array(let array):
32-
let encodedArray = array.map(encode).reduce(into: Data(), { $0.append($1) })
33-
return "*\(array.count)\r\n".convertedToData() + encodedArray
40+
buffer.writeStaticString("*")
41+
buffer.writeString(array.count.description)
42+
buffer.writeStaticString("\r\n")
43+
array.forEach { self.encode($0, into: &buffer) }
3444
}
3545
}
3646
}

Sources/NIORedis/RESP/RESPValueConvertible.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ extension RESPValue: RESPValueConvertible {
1919
}
2020
}
2121

22+
extension RedisError: RESPValueConvertible {
23+
public init?(_ value: RESPValue) {
24+
guard let error = value.error else { return nil }
25+
self = error
26+
}
27+
28+
/// See `RESPValueConvertible.convertedToRESPValue()`
29+
public func convertedToRESPValue() -> RESPValue {
30+
return .error(self)
31+
}
32+
}
33+
2234
extension String: RESPValueConvertible {
2335
public init?(_ value: RESPValue) {
2436
guard let string = value.string else { return nil }

Tests/NIORedisTests/ChannelHandlers/RESPDecoderTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ final class RESPDecoderTests: XCTestCase {
114114
var buffer = allocator.buffer(capacity: 256)
115115
buffer.writeBytes(input)
116116
try embeddedChannel.writeInbound(buffer)
117-
return (embeddedChannel.readInbound(), embeddedChannel.readInbound())
117+
return try (embeddedChannel.readInbound(), embeddedChannel.readInbound())
118118
}
119119

120120
private func arraysAreEqual(_ lhs: [RESPValue]?, expected right: [RESPValue]) -> Bool {
@@ -183,7 +183,7 @@ extension RESPDecoderTests {
183183

184184
var results = [RESPValue?]()
185185
for _ in 0..<AllData.messages.count {
186-
results.append(embeddedChannel.readInbound())
186+
results.append(try embeddedChannel.readInbound())
187187
}
188188

189189
XCTAssertEqual(results[0]?.string, AllData.expectedString)

Tests/NIORedisTests/ChannelHandlers/RESPEncoder+ParsingTests.swift

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,74 +7,66 @@ final class RESPEncoderParsingTests: XCTestCase {
77
private let encoder = RESPEncoder()
88

99
func testSimpleStrings() {
10-
XCTAssertEqual(
11-
encoder.encode(.simpleString("Test1")),
12-
"+Test1\r\n".convertedToData()
13-
)
14-
XCTAssertEqual(
15-
encoder.encode(.simpleString("®in§³¾")),
16-
"+®in§³¾\r\n".convertedToData()
17-
)
10+
XCTAssertTrue(testPass(input: .simpleString("Test1"), expected: "+Test1\r\n"))
11+
XCTAssertTrue(testPass(input: .simpleString("®in§³¾"), expected: "+®in§³¾\r\n"))
1812
}
1913

2014
func testBulkStrings() {
21-
let t1 = Data([0x01, 0x02, 0x0a, 0x1b, 0xaa])
22-
XCTAssertEqual(
23-
encoder.encode(.bulkString(t1)),
24-
"$5\r\n".convertedToData() + t1 + "\r\n".convertedToData()
25-
)
26-
let t2 = "®in§³¾".convertedToData()
27-
XCTAssertEqual(
28-
encoder.encode(.bulkString(t2)),
29-
"$10\r\n".convertedToData() + t2 + "\r\n".convertedToData()
30-
)
31-
let t3 = "".convertedToData()
32-
XCTAssertEqual(
33-
encoder.encode(.bulkString(t3)),
34-
"$0\r\n\r\n".convertedToData()
35-
)
15+
let bytes = Data([0x01, 0x02, 0x0a, 0x1b, 0xaa])
16+
XCTAssertTrue(testPass(input: .bulkString(bytes), expected: Data("$5\r\n".utf8) + bytes + Data("\r\n".utf8)))
17+
XCTAssertTrue(testPass(input: .init(bulk: "®in§³¾"), expected: "$10\r\n®in§³¾\r\n"))
18+
XCTAssertTrue(testPass(input: .init(bulk: ""), expected: "$0\r\n\r\n"))
3619
}
3720

3821
func testIntegers() {
39-
XCTAssertEqual(
40-
encoder.encode(.integer(Int.min)),
41-
":\(Int.min)\r\n".convertedToData()
42-
)
43-
XCTAssertEqual(
44-
encoder.encode(.integer(0)),
45-
":0\r\n".convertedToData()
46-
)
22+
XCTAssertTrue(testPass(input: .integer(Int.min), expected: ":\(Int.min)\r\n"))
23+
XCTAssertTrue(testPass(input: .integer(0), expected: ":0\r\n"))
4724
}
4825

4926
func testArrays() {
50-
XCTAssertEqual(
51-
encoder.encode(.array([])),
52-
"*0\r\n".convertedToData()
53-
)
54-
let a1: RESPValue = .array([.integer(3), .simpleString("foo")])
55-
XCTAssertEqual(
56-
encoder.encode(a1),
57-
"*2\r\n:3\r\n+foo\r\n".convertedToData()
58-
)
27+
XCTAssertTrue(testPass(input: .array([]), expected: "*0\r\n"))
28+
XCTAssertTrue(testPass(
29+
input: .array([ .integer(3), .simpleString("foo") ]),
30+
expected: "*2\r\n:3\r\n+foo\r\n"
31+
))
5932
let bytes = Data([ 0x0a, 0x1a, 0x1b, 0xff ])
60-
let a2: RESPValue = .array([.array([
61-
.integer(3),
62-
.bulkString(bytes)
63-
])])
64-
XCTAssertEqual(
65-
encoder.encode(a2),
66-
"*1\r\n*2\r\n:3\r\n$4\r\n".convertedToData() + bytes + "\r\n".convertedToData()
67-
)
33+
XCTAssertTrue(testPass(
34+
input: .array([ .array([ .integer(10), .bulkString(bytes) ]) ]),
35+
expected: Data("*1\r\n*2\r\n:10\r\n$4\r\n".utf8) + bytes + Data("\r\n".utf8)
36+
))
6837
}
6938

7039
func testError() {
7140
let error = RedisError(identifier: "testError", reason: "Manual error")
72-
let result = encoder.encode(.error(error))
73-
XCTAssertEqual(result, "-\(error.description)\r\n".convertedToData())
41+
XCTAssertTrue(testPass(input: .error(error), expected: "-\(error.description)\r\n"))
7442
}
7543

7644
func testNull() {
77-
XCTAssertEqual(encoder.encode(.null), "$-1\r\n".convertedToData())
45+
XCTAssertTrue(testPass(input: .null, expected: "$-1\r\n"))
46+
}
47+
48+
private func testPass(input: RESPValue, expected: Data) -> Bool {
49+
let allocator = ByteBufferAllocator()
50+
51+
var comparisonBuffer = allocator.buffer(capacity: expected.count)
52+
comparisonBuffer.writeBytes(expected)
53+
54+
var buffer = allocator.buffer(capacity: expected.count)
55+
encoder.encode(input.convertedToRESPValue(), into: &buffer)
56+
57+
return buffer == comparisonBuffer
58+
}
59+
60+
private func testPass(input: RESPValue, expected: String) -> Bool {
61+
let allocator = ByteBufferAllocator()
62+
63+
var comparisonBuffer = allocator.buffer(capacity: expected.count)
64+
comparisonBuffer.writeString(expected)
65+
66+
var buffer = allocator.buffer(capacity: expected.count)
67+
encoder.encode(input.convertedToRESPValue(), into: &buffer)
68+
69+
return buffer == comparisonBuffer
7870
}
7971
}
8072

Tests/NIORedisTests/ChannelHandlers/RESPEncoderTests.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ final class RESPEncoderTests: XCTestCase {
1313
encoder = RESPEncoder()
1414
allocator = ByteBufferAllocator()
1515
channel = EmbeddedChannel()
16-
_ = try? channel.pipeline.addHandler(encoder).wait()
16+
_ = try? channel.pipeline.addHandler(MessageToByteHandler(encoder)).wait()
1717
}
1818

1919
override func tearDown() {
@@ -90,10 +90,8 @@ final class RESPEncoderTests: XCTestCase {
9090
}
9191

9292
private func runEncodePass(with input: RESPValue, _ validation: (ByteBuffer) -> Void) throws {
93-
let context = try channel.pipeline.context(handler: encoder).wait()
94-
9593
var buffer = allocator.buffer(capacity: 256)
96-
try encoder.encode(context: context, data: input, out: &buffer)
94+
try encoder.encode(data: input, out: &buffer)
9795
validation(buffer)
9896
}
9997
}

0 commit comments

Comments
 (0)