mirror of
https://github.com/swift-server/RediStack.git
synced 2026-06-02 07:37:33 +00:00
Clarify and make public RESPEncoder
This commit is contained in:
@@ -1,20 +1,17 @@
|
||||
import Foundation
|
||||
import NIO
|
||||
|
||||
/// Handles outgoing `RESPValue` on the wire by encoding it to the Redis RESP protocol.
|
||||
/// Translates `RedisValue` into raw bytes, formatted according to the Redis Serialization Protocol (RESP).
|
||||
///
|
||||
/// See: https://redis.io/topics/protocol
|
||||
internal final class RESPEncoder: MessageToByteEncoder {
|
||||
/// See `MessageToByteEncoder.OutboundIn`
|
||||
typealias OutboundIn = RESPValue
|
||||
|
||||
/// See `RESPEncoder.encode(ctx:data:out:)`
|
||||
func encode(ctx: ChannelHandlerContext, data: RESPValue, out: inout ByteBuffer) throws {
|
||||
out.write(bytes: _encode(data: data))
|
||||
}
|
||||
|
||||
func _encode(data: RESPValue) -> Data {
|
||||
switch data {
|
||||
public final class RESPEncoder {
|
||||
/// Encodes the `RedisValue` to bytes, following the RESP specification.
|
||||
///
|
||||
/// See https://redis.io/topics/protocol
|
||||
/// - Parameter value: The `RESPValue` to encode.
|
||||
/// - Returns: The encoded value as a collection of bytes.
|
||||
public func encode(_ value: RESPValue) -> Data {
|
||||
switch value {
|
||||
case .simpleString(let string):
|
||||
return "+\(string)\r\n".convertedToData()
|
||||
|
||||
@@ -31,8 +28,20 @@ internal final class RESPEncoder: MessageToByteEncoder {
|
||||
return "-\(error.description)\r\n".convertedToData()
|
||||
|
||||
case .array(let array):
|
||||
let encodedArray = array.map { _encode(data: $0) }.joined()
|
||||
let encodedArray = array.map(encode).joined()
|
||||
return "*\(array.count)\r\n".convertedToData() + encodedArray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: MessageToByteEncoder
|
||||
|
||||
extension RESPEncoder: MessageToByteEncoder {
|
||||
/// See `MessageToByteEncoder.OutboundIn`
|
||||
public typealias OutboundIn = RESPValue
|
||||
|
||||
/// See `RESPEncoder.encode(ctx:data:out:)`
|
||||
public func encode(ctx: ChannelHandlerContext, data: RESPValue, out: inout ByteBuffer) throws {
|
||||
out.write(bytes: encode(data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ final class RESPEncoderParsingTests: XCTestCase {
|
||||
|
||||
func testSimpleStrings() {
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: .simpleString("Test1")),
|
||||
encoder.encode(.simpleString("Test1")),
|
||||
"+Test1\r\n".convertedToData()
|
||||
)
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: .simpleString("®in§³¾")),
|
||||
encoder.encode(.simpleString("®in§³¾")),
|
||||
"+®in§³¾\r\n".convertedToData()
|
||||
)
|
||||
}
|
||||
@@ -20,40 +20,40 @@ final class RESPEncoderParsingTests: XCTestCase {
|
||||
func testBulkStrings() {
|
||||
let t1 = Data(bytes: [0x01, 0x02, 0x0a, 0x1b, 0xaa])
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: .bulkString(t1)),
|
||||
encoder.encode(.bulkString(t1)),
|
||||
"$5\r\n".convertedToData() + t1 + "\r\n".convertedToData()
|
||||
)
|
||||
let t2 = "®in§³¾".convertedToData()
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: .bulkString(t2)),
|
||||
encoder.encode(.bulkString(t2)),
|
||||
"$10\r\n".convertedToData() + t2 + "\r\n".convertedToData()
|
||||
)
|
||||
let t3 = "".convertedToData()
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: .bulkString(t3)),
|
||||
encoder.encode(.bulkString(t3)),
|
||||
"$0\r\n\r\n".convertedToData()
|
||||
)
|
||||
}
|
||||
|
||||
func testIntegers() {
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: .integer(Int.min)),
|
||||
encoder.encode(.integer(Int.min)),
|
||||
":\(Int.min)\r\n".convertedToData()
|
||||
)
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: .integer(0)),
|
||||
encoder.encode(.integer(0)),
|
||||
":0\r\n".convertedToData()
|
||||
)
|
||||
}
|
||||
|
||||
func testArrays() {
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: .array([])),
|
||||
encoder.encode(.array([])),
|
||||
"*0\r\n".convertedToData()
|
||||
)
|
||||
let a1: RESPValue = .array([.integer(3), .simpleString("foo")])
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: a1),
|
||||
encoder.encode(a1),
|
||||
"*2\r\n:3\r\n+foo\r\n".convertedToData()
|
||||
)
|
||||
let bytes = Data(bytes: [ 0x0a, 0x1a, 0x1b, 0xff ])
|
||||
@@ -62,19 +62,19 @@ final class RESPEncoderParsingTests: XCTestCase {
|
||||
.bulkString(bytes)
|
||||
])])
|
||||
XCTAssertEqual(
|
||||
encoder._encode(data: a2),
|
||||
encoder.encode(a2),
|
||||
"*1\r\n*2\r\n:3\r\n$4\r\n".convertedToData() + bytes + "\r\n".convertedToData()
|
||||
)
|
||||
}
|
||||
|
||||
func testError() {
|
||||
let error = RedisError(identifier: "testError", reason: "Manual error")
|
||||
let result = encoder._encode(data: .error(error))
|
||||
let result = encoder.encode(.error(error))
|
||||
XCTAssertEqual(result, "-\(error.description)\r\n".convertedToData())
|
||||
}
|
||||
|
||||
func testNull() {
|
||||
XCTAssertEqual(encoder._encode(data: .null), "$-1\r\n".convertedToData())
|
||||
XCTAssertEqual(encoder.encode(.null), "$-1\r\n".convertedToData())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user