///|
/// A decoded RESP2 reply.
pub(all) enum RespValue {
Simple(String)
Error(String)
Integer(Int64)
Bulk(String?)
Array(Array[RespValue])
} derive(Eq, Debug)
///|
pub extend RespValue with Eq::{equal, not_equal}
///|
pub extend RespValue with @debug.Debug::{to_repr}
///|
/// Encode a command as an array of bulk strings (the client request format).
pub fn encode_command(args : Array[String]) -> Bytes {
let mut out = "*\{args.length()}\r\n"
for arg in args {
out = out + "$\{byte_length(arg)}\r\n\{arg}\r\n"
}
encode_utf8(out)
}
///|
fn byte_length(s : String) -> Int32 {
@encoding.encode(UTF8, s).length()
}
///|
fn encode_utf8(s : String) -> Bytes {
@encoding.encode(UTF8, s)
}
///|
/// Decode UTF-8 bytes into a string (lossy on invalid input).
fn bytes_to_string(b : BytesView) -> String {
let sb = StringBuilder()
@encoding.decode_to(b, sb, encoding=UTF8) catch {
_err => ()
}
sb.to_string()
}
///|
/// Outcome of trying to decode one reply from a buffer.
pub(all) enum Decoded {
/// A complete reply and the number of bytes it consumed.
Complete(RespValue, Int32)
/// The buffer does not yet hold a full reply.
Incomplete
/// The buffer starts with a syntactically invalid reply.
Malformed(String)
} derive(Eq, Debug)
///|
pub extend Decoded with Eq::{equal, not_equal}
///|
pub extend Decoded with @debug.Debug::{to_repr}
///|
/// Try to decode a single RESP2 reply from the front of `buf`.
pub fn decode(buf : Bytes) -> Decoded {
if buf.length() == 0 {
return Incomplete
}
match buf[0] {
43 =>
match line_text(buf, 1) {
Some((message, consumed)) => Complete(Simple(message), consumed)
None => Incomplete
}
45 =>
match line_text(buf, 1) {
Some((message, consumed)) => Complete(Error(message), consumed)
None => Incomplete
}
58 =>
match line_text(buf, 1) {
Some((message, consumed)) =>
match parse_int(message) {
Some(n) => Complete(Integer(n), consumed)
None => Malformed("invalid integer reply")
}
None => Incomplete
}
36 => decode_bulk(buf)
42 => decode_array(buf)
first => Malformed("unknown reply type \{first.to_int()}")
}
}
///|
/// The text of a line reply (after the type byte, before CRLF) and the
/// total number of bytes from `start`... consumed from the buffer start
/// including the CRLF.
fn line_text(buf : Bytes, start : Int32) -> (String, Int32)? {
match find_crlf(buf, start) {
Some(at) => Some((bytes_to_string(buf[start:at]), at + 2))
None => None
}
}
///|
fn decode_bulk(buf : Bytes) -> Decoded {
match find_crlf(buf, 1) {
None => Incomplete
Some(at) =>
match parse_int(bytes_to_string(buf[1:at])) {
None => Malformed("invalid bulk length")
Some(-1L) => Complete(Bulk(None), at + 2)
Some(length) => {
let len = length.to_int()
let body = at + 2
if len < 0 {
Malformed("negative bulk length")
} else if buf.length() < body + len + 2 {
Incomplete
} else {
Complete(
Bulk(Some(bytes_to_string(buf[body:body + len]))),
body + len + 2,
)
}
}
}
}
}
///|
fn decode_array(buf : Bytes) -> Decoded {
match find_crlf(buf, 1) {
None => Incomplete
Some(at) =>
match parse_int(bytes_to_string(buf[1:at])) {
None => Malformed("invalid array length")
Some(-1L) => Complete(Array([]), at + 2)
Some(count) => {
let n = count.to_int()
if n < 0 {
Malformed("negative array length")
} else {
decode_array_items(buf, at + 2, n, [])
}
}
}
}
}
///|
fn decode_array_items(
buf : Bytes,
offset : Int32,
remaining : Int32,
collected : Array[RespValue],
) -> Decoded {
if remaining == 0 {
return Complete(Array(collected), offset)
}
let rest = buf[offset:].to_owned()
match decode(rest) {
Incomplete => Incomplete
Malformed(reason) => Malformed(reason)
Complete(value, used) => {
collected.push(value)
decode_array_items(buf, offset + used, remaining - 1, collected)
}
}
}
///|
fn find_crlf(buf : Bytes, from : Int32) -> Int32? {
let mut i = from
while i + 1 < buf.length() {
if buf[i] == 13 && buf[i + 1] == 10 {
return Some(i)
}
i = i + 1
}
None
}
///|
fn parse_int(s : String) -> Int64? {
if s.length() == 0 {
return None
}
let mut negative = false
let mut value = 0L
for ch in s {
if ch == '-' && value == 0L && !negative {
negative = true
} else if ch >= '0' && ch <= '9' {
value = value * 10L + (ch.to_int() - 48).to_int64()
} else {
return None
}
}
Some(if negative { -value } else { value })
}