@@ -11,51 +11,60 @@ extension UInt8 {
11
11
static let colon : UInt8 = 0x3A
12
12
}
13
13
14
- /// Handles incoming byte messages from Redis and decodes them according to the RESP protocol.
14
+ /// Provides methods for translating between byte streams and Swift types
15
+ /// according to Redis Serialization Protocol (RESP).
15
16
///
16
- /// See: [https://redis.io/topics/protocol](https://redis.io/topics/protocol)
17
- public final class RESPDecoder {
18
- /// Representation of a `RESPDecoder.parse(at:from:) result, with either a decoded `RESPValue` or an indicator
19
- /// that the buffer contains an incomplete RESP message from the position provided.
20
- public enum ParsingState {
21
- case notYetParsed
17
+ /// See [https://redis.io/topics/protocol](https://redis.io/topics/protocol)
18
+ public enum RESPTranslator { }
19
+
20
+ // MARK: From Bytes
21
+
22
+ extension RESPTranslator {
23
+ /// Representation of the result of a parse attempt on a byte stream.
24
+ /// - incomplete: The stream contains an incomplete RESP message from the position provided.
25
+ /// - parsed: The parsed `RESPValue`
26
+ public enum ParsingResult {
27
+ case incomplete
22
28
case parsed( RESPValue )
23
29
}
24
30
25
- /// Representation of an `Swift.Error` found during RESP decoding .
26
- public enum Error : LocalizedError {
31
+ /// Representation of a `Swift.Error` found during RESP parsing .
32
+ public enum ParsingError : LocalizedError {
27
33
case invalidToken
28
34
case arrayRecursion
29
35
30
36
public var errorDescription : String ? {
31
- return " RESPDecoding : \( self ) "
37
+ return " RESPTranslator : \( self ) "
32
38
}
33
39
}
34
40
35
- public init ( ) { }
36
-
37
- /// Attempts to parse the `ByteBuffer`, starting at the specified position, following the RESP specification.
41
+ /// Attempts to parse the `ByteBuffer`, starting at the specified position,
42
+ /// following the RESP specification.
43
+ /// - Important: As this par
38
44
///
39
45
/// See [https://redis.io/topics/protocol](https://redis.io/topics/protocol)
40
46
/// - Parameters:
41
- /// - buffer: The buffer that contains the bytes that need to be decoded.
42
- /// - position: The index of the buffer that should be considered the "front" to begin message parsing.
43
- public func parse( from buffer: inout ByteBuffer , index position: inout Int ) throws -> ParsingState {
47
+ /// - buffer: The buffer that contains the bytes that need to be parsed.
48
+ /// - position: The index of the buffer that should contain the first byte of the message.
49
+ public static func parseBytes(
50
+ _ buffer: inout ByteBuffer ,
51
+ fromIndex position: inout Int
52
+ ) throws -> ParsingResult {
44
53
let offset = position + buffer. readerIndex
45
54
guard
46
55
let token = buffer. viewBytes ( at: offset, length: 1 ) ? . first,
47
56
var slice = buffer. getSlice ( at: offset, length: buffer. readableBytes - position)
48
- else { return . notYetParsed }
57
+ else { return . incomplete }
49
58
50
59
position += 1
51
60
52
61
switch token {
53
62
case . plus:
54
- guard let result = parseSimpleString ( & slice, & position) else { return . notYetParsed }
63
+ guard let result = parseSimpleString ( & slice, & position) else { return . incomplete }
55
64
return . parsed( . simpleString( result) )
56
65
57
66
case . colon:
58
- guard let value = parseInteger ( & slice, & position) else { return . notYetParsed }
67
+ guard let value = parseInteger ( & slice, & position) else { return . incomplete }
59
68
return . parsed( . integer( value) )
60
69
61
70
case . dollar:
@@ -68,44 +77,15 @@ public final class RESPDecoder {
68
77
guard
69
78
let stringBuffer = parseSimpleString ( & slice, & position) ,
70
79
let message = stringBuffer. getString ( at: 0 , length: stringBuffer. readableBytes)
71
- else { return . notYetParsed }
80
+ else { return . incomplete }
72
81
return . parsed( . error( RedisError ( reason: message) ) )
73
82
74
- default : throw Error . invalidToken
75
- }
76
- }
77
- }
78
-
79
- extension RESPDecoder : ByteToMessageDecoder {
80
- /// `ByteToMessageDecoder.InboundOut`
81
- public typealias InboundOut = RESPValue
82
-
83
- /// See `ByteToMessageDecoder.decode(context:buffer:)`
84
- public func decode( context: ChannelHandlerContext , buffer: inout ByteBuffer ) throws -> DecodingState {
85
- var position = 0
86
-
87
- switch try parse ( from: & buffer, index: & position) {
88
- case . notYetParsed: return . needMoreData
89
- case let . parsed( value) :
90
- context. fireChannelRead ( wrapInboundOut ( value) )
91
- buffer. moveReaderIndex ( forwardBy: position)
92
- return . continue
83
+ default : throw ParsingError . invalidToken
93
84
}
94
85
}
95
86
96
- /// See `ByteToMessageDecoder.decodeLast(context:buffer:seenEOF)`
97
- public func decodeLast(
98
- context: ChannelHandlerContext ,
99
- buffer: inout ByteBuffer ,
100
- seenEOF: Bool
101
- ) throws -> DecodingState { return . needMoreData }
102
- }
103
-
104
- // MARK: Parsing
105
-
106
- extension RESPDecoder {
107
87
/// See [https://redis.io/topics/protocol#resp-simple-strings](https://redis.io/topics/protocol#resp-simple-strings)
108
- func parseSimpleString( _ buffer: inout ByteBuffer , _ position: inout Int ) -> ByteBuffer ? {
88
+ static func parseSimpleString( _ buffer: inout ByteBuffer , _ position: inout Int ) -> ByteBuffer ? {
109
89
guard
110
90
let bytes = buffer. viewBytes ( at: position, length: buffer. readableBytes - position) ,
111
91
let newlineIndex = bytes. firstIndex ( of: . newline) ,
@@ -121,16 +101,16 @@ extension RESPDecoder {
121
101
}
122
102
123
103
/// See [https://redis.io/topics/protocol#resp-integers](https://redis.io/topics/protocol#resp-integers)
124
- func parseInteger( _ buffer: inout ByteBuffer , _ position: inout Int ) -> Int ? {
104
+ static func parseInteger( _ buffer: inout ByteBuffer , _ position: inout Int ) -> Int ? {
125
105
guard let stringBuffer = parseSimpleString ( & buffer, & position) else { return nil }
126
106
return stringBuffer. withUnsafeReadableBytes { ptr in
127
107
Int ( strtoll ( ptr. bindMemory ( to: Int8 . self) . baseAddress!, nil , 10 ) )
128
108
}
129
109
}
130
110
131
111
/// See [https://redis.io/topics/protocol#resp-bulk-strings](https://redis.io/topics/protocol#resp-bulk-strings)
132
- func parseBulkString( _ buffer: inout ByteBuffer , _ position: inout Int ) -> ParsingState {
133
- guard let size = parseInteger ( & buffer, & position) else { return . notYetParsed }
112
+ static func parseBulkString( _ buffer: inout ByteBuffer , _ position: inout Int ) -> ParsingResult {
113
+ guard let size = parseInteger ( & buffer, & position) else { return . incomplete }
134
114
135
115
// Redis sends '$-1\r\n' to represent a null bulk string
136
116
guard size > - 1 else { return . parsed( . null) }
@@ -140,7 +120,7 @@ extension RESPDecoder {
140
120
// even if the content is empty, Redis send '$0\r\n\r\n'
141
121
let readableByteCount = buffer. readableBytes - position
142
122
let expectedRemainingMessageSize = size + 2
143
- guard readableByteCount >= expectedRemainingMessageSize else { return . notYetParsed }
123
+ guard readableByteCount >= expectedRemainingMessageSize else { return . incomplete }
144
124
145
125
// empty bulk strings, different from null strings, are represented as .bulkString(nil)
146
126
guard size > 0 else {
@@ -150,7 +130,7 @@ extension RESPDecoder {
150
130
}
151
131
152
132
guard let bytes = buffer. viewBytes ( at: position, length: expectedRemainingMessageSize) else {
153
- return . notYetParsed
133
+ return . incomplete
154
134
}
155
135
156
136
// move the parsing position to the newline for recursive parsing
@@ -162,31 +142,79 @@ extension RESPDecoder {
162
142
}
163
143
164
144
/// See [https://redis.io/topics/protocol#resp-arrays](https://redis.io/topics/protocol#resp-arrays)
165
- func parseArray( _ buffer: inout ByteBuffer , _ position: inout Int ) throws -> ParsingState {
166
- guard let elementCount = parseInteger ( & buffer, & position) else { return . notYetParsed }
145
+ static func parseArray( _ buffer: inout ByteBuffer , _ position: inout Int ) throws -> ParsingResult {
146
+ guard let elementCount = parseInteger ( & buffer, & position) else { return . incomplete }
167
147
guard elementCount > - 1 else { return . parsed( . null) } // '*-1\r\n'
168
148
guard elementCount > 0 else { return . parsed( . array( [ ] ) ) } // '*0\r\n'
169
149
170
- var results = [ ParsingState ] ( repeating: . notYetParsed , count: elementCount)
150
+ var results = [ ParsingResult ] ( repeating: . incomplete , count: elementCount)
171
151
for index in 0 ..< elementCount {
172
152
guard
173
153
var slice = buffer. getSlice ( at: position, length: buffer. readableBytes - position)
174
- else { return . notYetParsed }
154
+ else { return . incomplete }
175
155
176
156
var subPosition = 0
177
- let result = try parse ( from : & slice, index : & subPosition)
157
+ let result = try parseBytes ( & slice, fromIndex : & subPosition)
178
158
switch result {
179
159
case . parsed: results [ index] = result
180
- default : return . notYetParsed
160
+ default : return . incomplete
181
161
}
182
162
183
163
position += subPosition
184
164
}
185
165
186
166
let values = try results. map { state -> RESPValue in
187
- guard case let . parsed( value) = state else { throw Error . arrayRecursion }
167
+ guard case let . parsed( value) = state else { throw ParsingError . arrayRecursion }
188
168
return value
189
169
}
190
170
return . parsed( . array( ContiguousArray < RESPValue > ( values) ) )
191
171
}
192
172
}
173
+
174
+ // MARK: To Bytes
175
+
176
+ extension RESPTranslator {
177
+ /// Writes the `RESPValue` into the provided `ByteBuffer` following the RESP specification.
178
+ ///
179
+ /// See [https://redis.io/topics/protocol](https://redis.io/topics/protocol)
180
+ /// - Parameters:
181
+ /// - value: The value to write to the buffer.
182
+ /// - out: The buffer being written to.
183
+ public static func writeValue( _ value: RESPValue , into out: inout ByteBuffer ) {
184
+ switch value {
185
+ case . simpleString( var buffer) :
186
+ out. writeStaticString ( " + " )
187
+ out. writeBuffer ( & buffer)
188
+ out. writeStaticString ( " \r \n " )
189
+
190
+ case . bulkString( . some( var buffer) ) :
191
+ out. writeStaticString ( " $ " )
192
+ out. writeString ( buffer. readableBytes. description)
193
+ out. writeStaticString ( " \r \n " )
194
+ out. writeBuffer ( & buffer)
195
+ out. writeString ( " \r \n " )
196
+
197
+ case . bulkString( . none) :
198
+ out. writeStaticString ( " $0 \r \n \r \n " )
199
+
200
+ case . integer( let number) :
201
+ out. writeStaticString ( " : " )
202
+ out. writeString ( number. description)
203
+ out. writeStaticString ( " \r \n " )
204
+
205
+ case . null:
206
+ out. writeStaticString ( " $-1 \r \n " )
207
+
208
+ case . error( let error) :
209
+ out. writeStaticString ( " - " )
210
+ out. writeString ( error. message)
211
+ out. writeStaticString ( " \r \n " )
212
+
213
+ case . array( let array) :
214
+ out. writeStaticString ( " * " )
215
+ out. writeString ( array. count. description)
216
+ out. writeStaticString ( " \r \n " )
217
+ array. forEach { writeValue ( $0, into: & out) }
218
+ }
219
+ }
220
+ }
0 commit comments