-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresp.mbt
More file actions
333 lines (306 loc) · 9.38 KB
/
Copy pathresp.mbt
File metadata and controls
333 lines (306 loc) · 9.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
// RESP — the Redis serialization protocol, the wire a redis client speaks. This is
// the self-built codec the redis-backed discovery driver rides on: a command goes out
// as a RESP array of bulk strings, and every reply shape (RESP2's five types plus
// RESP3's null, boolean, double, big number, bulk error, verbatim string, map, set,
// and push) decodes back into a `RespValue`. The codec is pure bytes-in/bytes-out, so
// it runs and is tested on every backend; the socket client that carries these bytes
// to a real redis lives in the native `discov` driver.
///|
/// A malformed or truncated RESP frame.
pub suberror RespError {
RespError(String)
}
///|
/// A decoded RESP value. RESP2's null bulk string (`$-1`) and null array (`*-1`) both
/// decode to `Null`, unifying with RESP3's explicit null (`_`). `BulkString` carries
/// raw bytes (redis values are binary-safe); the text-line types carry the decoded
/// string.
pub(all) enum RespValue {
SimpleString(String)
Error(String)
Integer(Int64)
BulkString(Bytes)
Null
Array(Array[RespValue])
Boolean(Bool)
Double(Double)
BigNumber(String)
BulkError(String)
VerbatimString(String, Bytes)
RespMap(Array[(RespValue, RespValue)])
RespSet(Array[RespValue])
Push(Array[RespValue])
} derive(Eq, Debug)
///|
/// A short name for a value's RESP type, for error messages ("unexpected `<kind>`
/// reply") — `RespValue` derives `Eq`/`Debug` but not a renderable form.
pub fn resp_kind(value : RespValue) -> String {
match value {
SimpleString(_) => "simple-string"
Error(_) => "error"
Integer(_) => "integer"
BulkString(_) => "bulk-string"
Null => "null"
Array(_) => "array"
Boolean(_) => "boolean"
Double(_) => "double"
BigNumber(_) => "big-number"
BulkError(_) => "bulk-error"
VerbatimString(_, _) => "verbatim-string"
RespMap(_) => "map"
RespSet(_) => "set"
Push(_) => "push"
}
}
///|
/// Encode a command as the client frame redis expects: an array of bulk strings, one
/// per argument (`*<n>\r\n$<len>\r\n<arg>\r\n`...). Arguments are raw bytes, so binary
/// keys and values round-trip unchanged.
pub fn resp_encode_command(args : Array[Bytes]) -> Bytes {
let buf = Buffer()
buf.write_byte(b'*')
resp_write_int(buf, args.length().to_int64())
for arg in args {
buf.write_byte(b'$')
resp_write_int(buf, arg.length().to_int64())
buf.write_bytes(arg)
resp_write_crlf(buf)
}
buf.to_bytes()
}
///|
/// Encode a command given string arguments (the common case — command names and keys
/// are text), UTF-8 encoding each.
pub fn resp_command(args : Array[String]) -> Bytes {
resp_encode_command(args.map(a => @utf8.encode(a)))
}
///|
/// Decode exactly one RESP value from `data`, requiring it to consume the whole input.
/// Trailing bytes after a complete value are a framing error.
pub fn RespValue::decode(data : Bytes) -> RespValue raise RespError {
let reader = RespReader::new(data)
let value = reader.read()
if !reader.at_end() {
raise RespError("trailing bytes after RESP value")
}
value
}
///|
/// A cursor over a RESP byte stream, decoding one value at a time. A socket client
/// feeds it a buffered reply; `read` advances past exactly one value, so several
/// pipelined replies decode in sequence.
pub struct RespReader {
data : Bytes
mut pos : Int
}
///|
/// A reader positioned at the start of `data`.
pub fn RespReader::new(data : Bytes) -> RespReader {
{ data, pos: 0, }
}
///|
/// Whether every byte has been consumed.
pub fn RespReader::at_end(self : RespReader) -> Bool {
self.pos >= self.data.length()
}
///|
/// Decode the next RESP value, advancing the cursor past it.
pub fn RespReader::read(self : RespReader) -> RespValue raise RespError {
let marker = self.next_byte()
match marker {
b'+' => SimpleString(self.read_line_text())
b'-' => Error(self.read_line_text())
b':' => Integer(self.read_line_int())
b',' => resp_parse_double(self.read_line_text())
b'(' => BigNumber(self.read_line_text())
b'#' => Boolean(self.read_bool())
b'_' => {
self.expect_crlf()
Null
}
b'$' => self.read_bulk(false)
b'!' => self.read_bulk(true)
b'=' => self.read_verbatim()
b'*' => {
let count = self.read_count()
if count < 0 {
Null
} else {
Array(self.read_elems(count))
}
}
b'~' => RespSet(self.read_elems(self.read_count()))
b'>' => Push(self.read_elems(self.read_count()))
b'%' => RespMap(self.read_map())
_ => raise RespError("unknown RESP marker 0x" + marker.to_int().to_string())
}
}
///|
/// The next raw byte, or an error at end of input.
fn RespReader::next_byte(self : RespReader) -> Byte raise RespError {
if self.pos >= self.data.length() {
raise RespError("unexpected end of RESP input")
}
let b = self.data[self.pos]
self.pos += 1
b
}
///|
/// The bytes up to the next CRLF (exclusive), advancing past the CRLF. A line with no
/// terminating CRLF is a framing error.
fn RespReader::read_line(self : RespReader) -> Bytes raise RespError {
let start = self.pos
let n = self.data.length()
for i = start; i < n - 1; i = i + 1 {
if self.data[i] == b'\r' && self.data[i + 1] == b'\n' {
let line = self.data[start:i].to_owned()
self.pos = i + 2
return line
}
}
raise RespError("RESP line not terminated by CRLF")
}
///|
/// The next line decoded as text.
fn RespReader::read_line_text(self : RespReader) -> String raise RespError {
@utf8.decode_lossy(self.read_line()[:])
}
///|
/// The next line parsed as a signed integer.
fn RespReader::read_line_int(self : RespReader) -> Int64 raise RespError {
let text = self.read_line_text()
@string.parse_int64(text) catch {
_ => raise RespError("invalid RESP integer: " + text)
}
}
///|
/// The boolean line `#t` / `#f`.
fn RespReader::read_bool(self : RespReader) -> Bool raise RespError {
let text = self.read_line_text()
match text {
"t" => true
"f" => false
_ => raise RespError("invalid RESP boolean: " + text)
}
}
///|
/// A bulk string (`$`) or bulk error (`!`): a length line then that many bytes and a
/// CRLF. Length `-1` on a bulk string is the RESP2 null.
fn RespReader::read_bulk(
self : RespReader,
is_error : Bool,
) -> RespValue raise RespError {
let len = self.read_line_int().to_int()
if len < 0 {
return Null
}
let body = self.take(len)
self.expect_crlf()
if is_error {
BulkError(@utf8.decode_lossy(body[:]))
} else {
BulkString(body)
}
}
///|
/// A verbatim string (`=`): a length line then `<fmt>:<data>` and a CRLF, where `fmt`
/// is a three-character content type (`txt`, `mkd`, ...).
fn RespReader::read_verbatim(self : RespReader) -> RespValue raise RespError {
let len = self.read_line_int().to_int()
if len < 4 {
raise RespError("verbatim string too short")
}
let body = self.take(len)
self.expect_crlf()
let fmt = @utf8.decode_lossy(body[0:3])
VerbatimString(fmt, body[4:len].to_owned())
}
///|
/// An aggregate's element count (the line after its marker), as a signed `Int` so the
/// caller can tell a RESP2 null array (`-1`) from an empty one (`0`).
fn RespReader::read_count(self : RespReader) -> Int raise RespError {
self.read_line_int().to_int()
}
///|
/// Read exactly `count` values (a negative count reads none), for an array/set/push.
fn RespReader::read_elems(
self : RespReader,
count : Int,
) -> Array[RespValue] raise RespError {
let out : Array[RespValue] = []
if count <= 0 {
return out
}
for _i = 0; _i < count; _i = _i + 1 {
out.push(self.read())
}
out
}
///|
/// The `n` key/value pairs of a map (`%`): a count line then `2n` values.
fn RespReader::read_map(
self : RespReader,
) -> Array[(RespValue, RespValue)] raise RespError {
let count = self.read_line_int().to_int()
let out : Array[(RespValue, RespValue)] = []
if count <= 0 {
return out
}
for _i = 0; _i < count; _i = _i + 1 {
let key = self.read()
let value = self.read()
out.push((key, value))
}
out
}
///|
/// Take exactly `n` bytes, advancing the cursor; an error if fewer remain.
fn RespReader::take(self : RespReader, n : Int) -> Bytes raise RespError {
if self.pos + n > self.data.length() {
raise RespError("RESP bulk length exceeds available bytes")
}
let body = self.data[self.pos:self.pos + n].to_owned()
self.pos += n
body
}
///|
/// Require the next two bytes to be CRLF.
fn RespReader::expect_crlf(self : RespReader) -> Unit raise RespError {
if self.pos + 2 > self.data.length() ||
self.data[self.pos] != b'\r' ||
self.data[self.pos + 1] != b'\n' {
raise RespError("expected CRLF")
}
self.pos += 2
}
///|
/// Parse a RESP3 double line, honouring the `inf` / `-inf` / `nan` spellings.
fn resp_parse_double(text : String) -> RespValue raise RespError {
let value = match text {
"inf" => @double.infinity
"+inf" => @double.infinity
"-inf" => @double.neg_infinity
"nan" => @double.not_a_number
_ =>
@string.parse_double(text) catch {
_ => raise RespError("invalid RESP double: " + text)
}
}
Double(value)
}
///|
/// Append a signed decimal integer to `buf`.
fn resp_write_int(buf : Buffer, n : Int64) -> Unit {
buf.write_bytes(@utf8.encode(n.to_string()))
resp_write_crlf(buf)
}
///|
/// Append a CRLF to `buf`.
fn resp_write_crlf(buf : Buffer) -> Unit {
buf.write_byte(b'\r')
buf.write_byte(b'\n')
}
///|
pub extend RespValue with Debug::{to_repr}
///|
pub extend RespValue with Eq::{not_equal, equal}