///|
/// Raw Redis response passed to custom command decoders.
///
/// Use the `as_*` helpers to turn a raw response into the type your custom
/// command should return.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"DBSIZE"], value => value.as_int())
/// ```
struct RawValue {
value : @resp.Value
}
///|
fn RawValue::from_resp(value : @resp.Value) -> RawValue {
{ value, }
}
///|
fn RawValue::desc(self : RawValue) -> @resp.ValueDesc {
self.value.desc
}
///|
fn @resp.Value::raw(self : @resp.Value) -> RawValue {
RawValue::from_resp(self)
}
///|
/// Reads the response as a UTF-8 string.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"PING"], value => value.as_string())
/// ```
pub fn RawValue::as_string(self : RawValue) -> String raise {
match self.desc() {
Error(err) => raise response_error(err)
String(value, hint=_) => decode_utf8(value)
_ => raise UnexpectedResponse("expected string")
}
}
///|
/// Reads the response as raw bytes.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"GET", b"blob"], value => value.as_bytes())
/// ```
pub fn RawValue::as_bytes(self : RawValue) -> Bytes raise {
match self.desc() {
Error(err) => raise response_error(err)
String(value, hint=_) => value
_ => raise UnexpectedResponse("expected string")
}
}
///|
/// Returns whether the response is Redis null.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"GET", b"missing"], value => value.is_null())
/// ```
pub fn RawValue::is_null(self : RawValue) -> Bool {
self.desc() is Null
}
///|
/// Reads the response as `Int`.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"DBSIZE"], value => value.as_int())
/// ```
pub fn RawValue::as_int(self : RawValue) -> Int raise {
match self.desc() {
Error(err) => raise response_error(err)
Integer(value) => int64_to_int(value)
_ => raise UnexpectedResponse("expected integer")
}
}
///|
/// Reads the response as `Int64`.
pub fn RawValue::as_int64(self : RawValue) -> Int64 raise {
match self.desc() {
Error(err) => raise response_error(err)
Integer(value) => value
_ => raise UnexpectedResponse("expected integer")
}
}
///|
/// Reads the response as `Bool`.
///
/// This accepts Redis boolean replies and integer replies where `0` means
/// `false` and `1` means `true`.
pub fn RawValue::as_bool(self : RawValue) -> Bool raise {
match self.desc() {
Error(err) => raise response_error(err)
Boolean(value) => value
Integer(0L) => false
Integer(1L) => true
_ => raise UnexpectedResponse("expected boolean")
}
}
///|
fn RawValue::as_bool_int(self : RawValue) -> Bool raise {
match self.desc() {
Error(err) => raise response_error(err)
Integer(0L) => false
Integer(1L) => true
_ => raise UnexpectedResponse("expected boolean integer")
}
}
///|
/// Reads the response as `Double`.
pub fn RawValue::as_double(self : RawValue) -> Double raise {
match self.desc() {
Error(err) => raise response_error(err)
Double(value) => value
String(value, hint=_) =>
@string.parse_double(decode_utf8(value)) catch {
_ => raise UnexpectedResponse("expected double")
}
_ => raise UnexpectedResponse("expected double")
}
}
///|
/// Succeeds only when the response is `OK`.
pub fn RawValue::expect_ok(self : RawValue) -> Unit raise {
guard self.as_string() == "OK" else {
raise UnexpectedResponse("expected OK")
}
}
///|
/// Reads the response as an array by decoding each item with `convert`.
pub fn[T] RawValue::as_array(
self : RawValue,
convert : (RawValue) -> T raise,
) -> Array[T] raise {
match self.desc() {
Error(err) => raise response_error(err)
Array(values) | Set(values) => {
let out : Array[T] = Array::new(capacity=values.length())
for value in values {
out.push(convert(value.raw()))
}
out
}
_ => raise UnexpectedResponse("expected array or set")
}
}
///|
/// Reads Redis null as `None`; otherwise decodes the value with `convert`.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"GET", b"name"], value => {
/// value.as_option(item => item.as_string())
/// })
/// ```
pub fn[T] RawValue::as_option(
self : RawValue,
convert : (RawValue) -> T raise,
) -> T? raise {
match self.desc() {
Error(err) => raise response_error(err)
Null => None
_ => Some(convert(self))
}
}
///|
/// Reads the response as a `HashSet` by decoding each item with `convert`.
pub fn[T : Hash + Eq] RawValue::as_set(
self : RawValue,
convert : (RawValue) -> T raise,
) -> @hashset.HashSet[T] raise {
let set : @hashset.HashSet[T] = HashSet([])
match self.desc() {
Error(err) => raise response_error(err)
Set(values) | Array(values) =>
for value in values {
set.add(convert(value.raw()))
}
_ => raise UnexpectedResponse("expected array or set")
}
set
}
///|
fn RawValue::as_string_array_or_empty(self : RawValue) -> Array[String] raise {
self
.as_option(value => value.as_array(item => item.as_string()))
.unwrap_or([])
}
///|
/// Reads the response as a string map.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"HGETALL", b"user:1"], value => value.as_string_map())
/// ```
pub fn RawValue::as_string_map(self : RawValue) -> Map[String, String] raise {
let map : Map[String, String] = Map([])
match self.desc() {
Error(err) => raise response_error(err)
Array(values) => {
guard values.length() % 2 == 0 else {
raise UnexpectedResponse("expected even string array")
}
for i in 0..<(values.length() / 2) {
map[values[i * 2].raw().as_string()] = values[i * 2 + 1]
.raw()
.as_string()
}
}
Map(entries) =>
for entry in entries {
map[entry.0.raw().as_string()] = entry.1.raw().as_string()
}
_ => raise UnexpectedResponse("expected string map")
}
map
}
///|
fn RawValue::as_score_pairs(self : RawValue) -> Array[(String, Double)] raise {
match self.desc() {
Error(err) => raise response_error(err)
Array(values) =>
// RESP3 returns WITHSCORES as nested [member, score] pairs, while RESP2
// returns a flat [member, score, ...] array.
if values.all(value => value.desc is Array(_)) {
let out : Array[(String, Double)] = Array::new(capacity=values.length())
for value in values {
match value.desc {
Array([value, score]) =>
out.push((value.raw().as_string(), score.raw().as_double()))
_ => raise UnexpectedResponse("expected scored pair array")
}
}
out
} else {
guard values.length() % 2 == 0 else {
raise UnexpectedResponse("expected even scored array")
}
let out : Array[(String, Double)] = Array::new(
capacity=values.length() / 2,
)
for i in 0..<(values.length() / 2) {
out.push(
(
values[i * 2].raw().as_string(),
values[i * 2 + 1].raw().as_double(),
),
)
}
out
}
_ => raise UnexpectedResponse("expected scored array")
}
}
///|
fn RawValue::as_scan_strings(self : RawValue) -> (String, Array[String]) raise {
match self.desc() {
Error(err) => raise response_error(err)
Array([cursor, values]) =>
(
cursor.raw().as_string(),
values.raw().as_array(value => value.as_string()),
)
_ => raise UnexpectedResponse("expected scan response")
}
}
///|
fn RawValue::as_scan_map(
self : RawValue,
) -> (String, Map[String, String]) raise {
match self.desc() {
Error(err) => raise response_error(err)
Array([cursor, values]) =>
(cursor.raw().as_string(), values.raw().as_string_map())
_ => raise UnexpectedResponse("expected hash scan response")
}
}
///|
fn RawValue::as_scan_set(
self : RawValue,
) -> (String, @hashset.HashSet[String]) raise {
match self.desc() {
Error(err) => raise response_error(err)
Array([cursor, values]) =>
(
cursor.raw().as_string(),
values.raw().as_set(value => value.as_string()),
)
_ => raise UnexpectedResponse("expected set scan response")
}
}
///|
fn RawValue::as_scan_score_pairs(
self : RawValue,
) -> (String, Array[(String, Double)]) raise {
match self.desc() {
Error(err) => raise response_error(err)
Array([cursor, values]) =>
(cursor.raw().as_string(), values.raw().as_score_pairs())
_ => raise UnexpectedResponse("expected sorted set scan response")
}
}