use crate::{
inputs::ObjectInput,
objects::{
list::list_object::ListObject, parse_utils::try_get_int, types::object_output::ObjectOutput,
},
};
const RESP_ERR_GENERIC_INDEX_OUT_RANGE: &[u8] = b"ERR index out of range";
use crate::resp::cmd_strings::{
RESP_ERR_GENERIC_NOSUCHKEY, RESP_ERR_GENERIC_SYNTAX_ERROR, RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER,
};
#[inline]
fn arg<'a>(input: &ObjectInput, i: usize) -> &'a [u8] {
input.parse_state.get_arg_slice_by_ref(i).as_slice()
}
impl ListObject {
pub(crate) fn list_remove(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let count = input.arg1;
output.result1 = i32::MIN as i64;
let item_span = arg(input, 0);
let mut removed_count = 0_i64;
output.result1 = 0;
if count == 0 {
let mut i = 0;
while i < self.list.len() {
if self.list[i].as_slice() == item_span {
let value = self.list.remove(i).unwrap();
self.update_size(&value, false);
removed_count += 1;
} else {
i += 1;
}
}
} else {
let from_head_to_tail = count > 0;
let count = if count == i32::MIN {
i32::MAX as i64
} else {
(count).abs() as i64
};
let mut idx = if from_head_to_tail {
0
} else {
self.list.len() as i64 - 1
};
while removed_count < count && (0..self.list.len() as i64).contains(&idx) {
let matches = self.list[idx as usize].as_slice() == item_span;
if matches {
let value = self.list.remove(idx as usize).unwrap();
self.update_size(&value, false);
removed_count += 1;
if !from_head_to_tail {
idx -= 1;
}
} else {
idx += if from_head_to_tail { 1 } else { -1 };
}
}
}
output.result1 = removed_count;
}
pub(crate) fn list_insert(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
output.result1 = i32::MIN as i64;
if !self.list.is_empty() {
let position = arg(input, 0);
let pivot = arg(input, 1);
let item = arg(input, 2).to_vec();
let insert_before = position.eq_ignore_ascii_case(b"BEFORE");
output.result1 = -1;
if let Some(pos) = self.list.iter().position(|v| v.as_slice() == pivot) {
let at = if insert_before { pos } else { pos + 1 };
self.list.insert(at, item.clone());
self.update_size(&item, true);
output.result1 = self.list.len() as i64;
}
}
}
pub(crate) fn list_index(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
resp_protocol_version: u8,
) {
let index = input.arg1;
output.result1 = -1;
let len = self.list.len() as i64;
let index = if index < 0 {
len + i64::from(index)
} else {
i64::from(index)
};
if let Some(item) = self.list.get(index as usize) {
output.write_bulk_string(item);
output.result1 = 1;
}
let _ = resp_protocol_version;
}
pub(crate) fn list_range(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
_resp_protocol_version: u8,
) {
let start = input.arg1;
let stop = input.arg2;
if self.list.is_empty() {
output.write_empty_array();
return;
}
let len = self.list.len() as i64;
let mut start = i64::from(start);
let mut stop = i64::from(stop);
start = if start < 0 { len + start } else { start };
if start < 0 {
start = 0;
}
stop = if stop < 0 { len + stop } else { stop };
if stop >= len {
stop = len - 1;
}
if start > stop {
output.write_empty_array();
return;
}
let count = (stop - start + 1) as usize;
output.write_array_length(count);
for item in self.list.iter().skip(start as usize).take(count) {
output.write_bulk_string(item);
}
output.result1 = count as i64;
}
pub(crate) fn list_trim(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let start = input.arg1;
let end = input.arg2;
if !self.list.is_empty() {
let len = self.list.len() as i64;
let mut start = i64::from(start);
let mut end = i64::from(end);
start = if start < 0 { len + start } else { start };
end = if end < 0 { len + end } else { end };
if start > end || start >= len || end < 0 {
let removed: Vec<Vec<u8>> = self.list.drain(..).collect();
for value in removed {
self.update_size(&value, false);
}
} else {
start = start.max(0);
end = if end >= len { len } else { end + 1 };
if start == 0 {
let num_deletes = len - end;
for _ in 0..num_deletes {
if let Some(value) = self.list.pop_back() {
self.update_size(&value, false);
}
}
output.result1 = num_deletes;
} else {
let doomed: Vec<usize> = (0..len as usize)
.filter(|i| !(*i >= start as usize && *i < end as usize))
.collect();
for (offset, i) in doomed.iter().enumerate() {
let value = self.list.remove(i - offset).unwrap();
self.update_size(&value, false);
}
output.result1 = len;
}
}
}
}
pub(crate) fn list_length(&mut self, output: &mut ObjectOutput) {
output.result1 = self.list.len() as i64;
}
pub(crate) fn list_push(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
f_add_at_head: bool,
) {
output.result1 = 0;
for i in 0..input.parse_state.count {
let value = arg(input, i).to_vec();
if f_add_at_head {
self.list.push_front(value.clone());
} else {
self.list.push_back(value.clone());
}
self.update_size(&value, true);
}
output.result1 = self.list.len() as i64;
}
pub(crate) fn list_pop(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
resp_protocol_version: u8,
f_del_at_head: bool,
) {
let mut count = i64::from(input.arg1);
if (self.list.len() as i64) < count {
count = self.list.len() as i64;
}
if self.list.is_empty() {
output.write_null(resp_protocol_version);
count = 0;
} else if count <= 0 {
output.write_empty_array();
} else if count > 1 {
output.write_array_length(count as usize);
}
let mut removed = 0_i64;
while count > 0 && !self.list.is_empty() {
let value = if f_del_at_head {
self.list.pop_front()
} else {
self.list.pop_back()
};
if let Some(value) = value {
self.update_size(&value, false);
output.write_bulk_string(&value);
}
count -= 1;
removed += 1;
}
output.result1 = removed;
}
pub(crate) fn list_set(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
_resp_protocol_version: u8,
) {
if self.list.is_empty() {
output.write_error(RESP_ERR_GENERIC_NOSUCHKEY.as_bytes());
return;
}
let Some(index) = try_get_int(arg(input, 0)) else {
output.write_error(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
return;
};
let len = self.list.len() as i64;
let index = if index < 0 {
len + i64::from(index)
} else {
i64::from(index)
};
if index > len - 1 || index < 0 {
output.write_error(RESP_ERR_GENERIC_INDEX_OUT_RANGE);
return;
}
let element = arg(input, 1).to_vec();
let old = self.list[index as usize].clone();
self.update_size(&old, false);
self.update_size(&element, true);
self.list[index as usize] = element;
output.payload.extend_from_slice(b"+OK\r\n");
output.result1 = 1;
}
pub(crate) fn list_position(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
_resp_protocol_version: u8,
) {
let element = arg(input, 0);
let mut params = ListPositionParams::default();
if let Err(error) = read_list_position_input(input, &mut params) {
output.write_error(error);
return;
}
if params.count < 0 || params.maxlen < 0 || params.rank == 0 {
output.write_error(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
return;
}
let count = if params.count == 0 {
self.list.len() as i64
} else {
params.count
};
let mut found: Vec<i64> = Vec::new();
if params.rank > 0 {
let mut rank = params.rank;
let len = self.list.len() as i64;
let maxlen_index = if params.maxlen == 0 {
len
} else {
params.maxlen
};
for (current_index, item) in self.list.iter().enumerate().take(maxlen_index as usize) {
if item.as_slice() == element {
if rank == 1 {
found.push(current_index as i64);
if found.len() as i64 == count {
break;
}
} else {
rank -= 1;
}
}
}
} else {
let mut rank = params.rank.unsigned_abs() as i64;
let len = self.list.len() as i64;
let maxlen_index = if params.maxlen == 0 {
0
} else {
len - params.maxlen
};
let mut current_index = len - 1;
while current_index >= maxlen_index && current_index >= 0 {
if self.list[current_index as usize].as_slice() == element {
if rank == 1 {
found.push(current_index);
if found.len() as i64 == count {
break;
}
} else {
rank -= 1;
}
}
current_index -= 1;
}
}
let found_len = found.len();
if params.is_default_count {
if found.is_empty() {
output.write_null(2);
} else {
output.write_int64(found[0]);
}
} else if found.is_empty() {
output.write_empty_array();
} else {
output.write_array_length(found_len);
for index in found {
output.write_int64(index);
}
}
output.result1 = found_len as i64;
}
}
#[derive(Debug, Clone, Copy)]
struct ListPositionParams {
rank: i64,
count: i64,
is_default_count: bool,
maxlen: i64,
}
impl Default for ListPositionParams {
fn default() -> Self {
Self {
rank: 1,
count: 1,
is_default_count: true,
maxlen: 0,
}
}
}
fn read_list_position_input(
input: &ObjectInput,
params: &mut ListPositionParams,
) -> Result<(), &'static [u8]> {
let mut curr_token_idx = 1;
while curr_token_idx < input.parse_state.count {
let sb_param = arg(input, curr_token_idx);
curr_token_idx += 1;
if sb_param == b"RANK" || sb_param == b"rank" {
let Some(rank) = try_get_int(arg(input, curr_token_idx)) else {
return Err(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
};
curr_token_idx += 1;
params.rank = i64::from(rank);
} else if sb_param == b"COUNT" || sb_param == b"count" {
let Some(count) = try_get_int(arg(input, curr_token_idx)) else {
return Err(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
};
curr_token_idx += 1;
params.count = i64::from(count);
params.is_default_count = false;
} else if sb_param == b"MAXLEN" || sb_param == b"maxlen" {
let Some(maxlen) = try_get_int(arg(input, curr_token_idx)) else {
return Err(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
};
curr_token_idx += 1;
params.maxlen = i64::from(maxlen);
} else {
return Err(RESP_ERR_GENERIC_SYNTAX_ERROR.as_bytes());
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
arg_slice::ArgSlice,
input_header::RespInputHeader,
objects::list::list_object::ListOperation,
session_parse_state::SessionParseState,
types::{GarnetObjectType, RespInputFlags},
};
fn make_input(
op: ListOperation,
args: &[&[u8]],
arg1: i32,
arg2: i32,
) -> (ObjectInput, Vec<Vec<u8>>) {
let backing: Vec<Vec<u8>> = args.iter().map(|a| a.to_vec()).collect();
let slices: Vec<ArgSlice> = backing
.iter()
.map(|b| ArgSlice::new(b.as_ptr(), b.len()))
.collect();
let mut parse_state = SessionParseState::new();
parse_state.initialize_with_args(&slices);
let mut header =
RespInputHeader::new_with_type(GarnetObjectType::List, RespInputFlags::empty());
header.set_sub_id(op as u8);
(
ObjectInput::new_with_state(header, &mut parse_state, arg1, arg2),
backing,
)
}
fn obj_with(items: &[&str]) -> ListObject {
let mut obj = ListObject::new();
for item in items {
obj.list.push_back(item.as_bytes().to_vec());
}
obj
}
#[test]
fn push_pop_len() {
let mut obj = ListObject::new();
let (input, _b) = make_input(ListOperation::Rpush, &[b"a", b"b", b"c"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_push(&input, &mut out, false);
assert_eq!(out.result1, 3);
let (input, _b) = make_input(ListOperation::Lpush, &[b"H"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_push(&input, &mut out, true);
assert_eq!(out.result1, 4);
assert_eq!(
obj.to_items(),
[b"H".to_vec(), b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]
);
let (input, _b) = make_input(ListOperation::Lpop, &[], 1, 0);
let mut out = ObjectOutput::new();
obj.list_pop(&input, &mut out, 2, true);
assert_eq!(out.payload, b"$1\r\nH\r\n");
assert_eq!(out.result1, 1);
let (input, _b) = make_input(ListOperation::Rpop, &[], 2, 0);
let mut out = ObjectOutput::new();
obj.list_pop(&input, &mut out, 2, false);
assert_eq!(out.payload, b"*2\r\n$1\r\nc\r\n$1\r\nb\r\n");
let (_input, _b) = make_input(ListOperation::Llen, &[], 0, 0);
let mut out = ObjectOutput::new();
obj.list_length(&mut out);
assert_eq!(out.result1, 1);
let (input, _b) = make_input(ListOperation::Lpop, &[], 1, 0);
let mut out = ObjectOutput::new();
obj.list_pop(&input, &mut out, 2, true);
assert_eq!(out.payload, b"$1\r\na\r\n");
let (input, _b) = make_input(ListOperation::Lpop, &[], 1, 0);
let mut out = ObjectOutput::new();
obj.operate(&input, &mut out, 2);
assert_eq!(out.payload, b"$-1\r\n");
assert!(out.has_remove_key());
let mut obj = obj_with(&["x"]);
let (input, _b) = make_input(ListOperation::Lpop, &[], 0, 0);
let mut out = ObjectOutput::new();
obj.list_pop(&input, &mut out, 2, true);
assert_eq!(out.payload, b"*0\r\n");
assert_eq!(out.result1, 0);
assert_eq!(obj.list.len(), 1);
}
#[test]
fn range_index_trim() {
let mut obj = obj_with(&["a", "b", "c", "d"]);
let (input, _b) = make_input(ListOperation::Lrange, &[], 1, 2);
let mut out = ObjectOutput::new();
obj.list_range(&input, &mut out, 2);
assert_eq!(out.payload, b"*2\r\n$1\r\nb\r\n$1\r\nc\r\n");
assert_eq!(out.result1, 2);
let (input, _b) = make_input(ListOperation::Lrange, &[], -2, -1);
let mut out = ObjectOutput::new();
obj.list_range(&input, &mut out, 2);
assert_eq!(out.payload, b"*2\r\n$1\r\nc\r\n$1\r\nd\r\n");
let (input, _b) = make_input(ListOperation::Lrange, &[], 2, 1);
let mut out = ObjectOutput::new();
obj.list_range(&input, &mut out, 2);
assert_eq!(out.payload, b"*0\r\n");
let (input, _b) = make_input(ListOperation::Lindex, &[], -1, 0);
let mut out = ObjectOutput::new();
obj.list_index(&input, &mut out, 2);
assert_eq!(out.payload, b"$1\r\nd\r\n");
assert_eq!(out.result1, 1);
let (input, _b) = make_input(ListOperation::Lindex, &[], 9, 0);
let mut out = ObjectOutput::new();
obj.list_index(&input, &mut out, 2);
assert!(out.payload.is_empty());
assert_eq!(out.result1, -1);
let (input, _b) = make_input(ListOperation::Ltrim, &[], 1, 2);
let mut out = ObjectOutput::new();
obj.list_trim(&input, &mut out);
assert_eq!(obj.to_items(), [b"b".to_vec(), b"c".to_vec()]);
let (input, _b) = make_input(ListOperation::Ltrim, &[], 5, 9);
let mut out = ObjectOutput::new();
obj.list_trim(&input, &mut out);
assert!(obj.list.is_empty());
let mut obj = obj_with(&["a", "b", "c"]);
let (input, _b) = make_input(ListOperation::Ltrim, &[], 0, 1);
let mut out = ObjectOutput::new();
obj.list_trim(&input, &mut out);
assert_eq!(out.result1, 1);
assert_eq!(obj.to_items(), [b"a".to_vec(), b"b".to_vec()]);
let mut obj = obj_with(&["a", "b", "c", "d"]);
let (input, _b) = make_input(ListOperation::Ltrim, &[], 1, 2);
let mut out = ObjectOutput::new();
obj.list_trim(&input, &mut out);
assert_eq!(out.result1, 4);
assert_eq!(obj.to_items(), [b"b".to_vec(), b"c".to_vec()]);
let mut obj = ListObject::new();
let (input, _b) = make_input(ListOperation::Ltrim, &[], 0, 1);
let mut out = ObjectOutput::new();
obj.list_trim(&input, &mut out);
assert!(out.payload.is_empty());
}
#[test]
fn insert_remove() {
let mut obj = obj_with(&["a", "b", "c", "b"]);
let (input, _b) = make_input(ListOperation::Linsert, &[b"BEFORE", b"b", b"X"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_insert(&input, &mut out);
assert_eq!(out.result1, 5);
assert_eq!(
obj.to_items(),
["a", "X", "b", "c", "b"]
.iter()
.map(|s| s.as_bytes().to_vec())
.collect::<Vec<_>>()
);
let (input, _b) = make_input(ListOperation::Linsert, &[b"after", b"b", b"Y"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_insert(&input, &mut out);
assert_eq!(out.result1, 6);
assert_eq!(
obj.to_items(),
["a", "X", "b", "Y", "c", "b"]
.iter()
.map(|s| s.as_bytes().to_vec())
.collect::<Vec<_>>()
);
let (input, _b) = make_input(ListOperation::Linsert, &[b"BEFORE", b"zz", b"Q"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_insert(&input, &mut out);
assert_eq!(out.result1, -1);
let mut empty = ListObject::new();
let (input, _b) = make_input(ListOperation::Linsert, &[b"BEFORE", b"b", b"X"], 0, 0);
let mut out = ObjectOutput::new();
empty.list_insert(&input, &mut out);
assert_eq!(out.result1, i32::MIN as i64);
let mut obj = obj_with(&["b", "a", "b", "c", "b"]);
let (input, _b) = make_input(ListOperation::Lrem, &[b"b"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_remove(&input, &mut out);
assert_eq!(out.result1, 3);
assert_eq!(obj.to_items(), [b"a".to_vec(), b"c".to_vec()]);
let mut obj = obj_with(&["b", "a", "b"]);
let (input, _b) = make_input(ListOperation::Lrem, &[b"b"], 1, 0);
let mut out = ObjectOutput::new();
obj.list_remove(&input, &mut out);
assert_eq!(out.result1, 1);
assert_eq!(obj.to_items(), [b"a".to_vec(), b"b".to_vec()]);
let mut obj = obj_with(&["b", "a", "b", "c", "b"]);
let (input, _b) = make_input(ListOperation::Lrem, &[b"b"], -2, 0);
let mut out = ObjectOutput::new();
obj.list_remove(&input, &mut out);
assert_eq!(out.result1, 2);
assert_eq!(
obj.to_items(),
[b"b".to_vec(), b"a".to_vec(), b"c".to_vec()]
);
}
#[test]
fn set_index() {
let mut obj = obj_with(&["a", "b", "c"]);
let (input, _b) = make_input(ListOperation::Lset, &[b"1", b"B"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_set(&input, &mut out, 2);
assert_eq!(out.payload, b"+OK\r\n");
assert_eq!(out.result1, 1);
assert_eq!(
obj.to_items(),
[b"a".to_vec(), b"B".to_vec(), b"c".to_vec()]
);
let (input, _b) = make_input(ListOperation::Lset, &[b"-1", b"C"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_set(&input, &mut out, 2);
assert_eq!(obj.to_items()[2], b"C".to_vec());
let (input, _b) = make_input(ListOperation::Lset, &[b"9", b"Z"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_set(&input, &mut out, 2);
assert_eq!(out.payload, b"-ERR index out of range\r\n");
let (input, _b) = make_input(ListOperation::Lset, &[b"x", b"Z"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_set(&input, &mut out, 2);
assert_eq!(
out.payload,
b"-ERR value is not an integer or out of range.\r\n"
);
let mut empty = ListObject::new();
let (input, _b) = make_input(ListOperation::Lset, &[b"0", b"Z"], 0, 0);
let mut out = ObjectOutput::new();
empty.list_set(&input, &mut out, 2);
assert_eq!(out.payload, b"-ERR no such key\r\n");
}
#[test]
fn position_matrix() {
let mut obj = obj_with(&["a", "b", "a", "c", "a", "b"]);
let (input, _b) = make_input(ListOperation::Lpos, &[b"a"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b":0\r\n");
assert_eq!(out.result1, 1);
let (input, _b) = make_input(ListOperation::Lpos, &[b"a", b"RANK", b"2"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b":2\r\n");
let (input, _b) = make_input(ListOperation::Lpos, &[b"a", b"rank", b"-1"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b":4\r\n");
let (input, _b) = make_input(ListOperation::Lpos, &[b"a", b"COUNT", b"0"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b"*3\r\n:0\r\n:2\r\n:4\r\n");
let (input, _b) = make_input(ListOperation::Lpos, &[b"a", b"count", b"2"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b"*2\r\n:0\r\n:2\r\n");
let (input, _b) = make_input(ListOperation::Lpos, &[b"c", b"MAXLEN", b"2"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b"$-1\r\n");
let (input, _b) = make_input(ListOperation::Lpos, &[b"zz", b"COUNT", b"2"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b"*0\r\n");
let (input, _b) = make_input(ListOperation::Lpos, &[b"a", b"RANK", b"0"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(
out.payload,
b"-ERR value is not an integer or out of range.\r\n"
);
let (input, _b) = make_input(ListOperation::Lpos, &[b"a", b"Rank", b"1"], 0, 0);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b"-ERR syntax error\r\n");
let (input, _b) = make_input(
ListOperation::Lpos,
&[b"a", b"RANK", b"-2", b"COUNT", b"2"],
0,
0,
);
let mut out = ObjectOutput::new();
obj.list_position(&input, &mut out, 2);
assert_eq!(out.payload, b"*2\r\n:2\r\n:0\r\n");
}
#[test]
fn operate_dispatch_smoke() {
let mut obj = ListObject::new();
let (input, _b) = make_input(ListOperation::Lpush, &[b"x"], 0, 0);
let mut out = ObjectOutput::new();
assert!(obj.operate(&input, &mut out, 2));
assert_eq!(out.result1, 1);
let (input, _b) = make_input(ListOperation::Lpop, &[], 1, 0);
let mut out = ObjectOutput::new();
obj.operate(&input, &mut out, 2);
assert!(out.has_remove_key());
let (input, _b) = make_input(ListOperation::Lmove, &[], 0, 0);
let mut out = ObjectOutput::new();
obj.operate(&input, &mut out, 2);
assert_eq!(out.payload, b"-ERR unsupported operation\r\n");
let (input, _b) = make_input(ListOperation::Llen, &[], 0, 0);
let mut input = input;
input.header.data[0] = GarnetObjectType::Hash as u8;
let mut out = ObjectOutput::new();
obj.operate(&input, &mut out, 2);
assert!(out.has_wrong_type());
}
}