use crate::{
Result,
resp::{
ARRAY_TAG, BULK_ERROR_TAG, BULK_STRING_TAG, DOUBLE_TAG, INTEGER_TAG, MAP_TAG, PUSH_TAG,
RespCollectionView, RespResponse, RespView, SET_TAG, SIMPLE_ERROR_TAG, SIMPLE_STRING_TAG,
},
};
use std::{fmt, io::Write as _};
#[derive(Clone, PartialEq, Eq)]
pub struct RawResponse(Box<[u8]>);
const DEBUG_RENDER_LIMIT: usize = 1000;
impl RawResponse {
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[inline]
pub fn into_vec(self) -> Vec<u8> {
self.0.into_vec()
}
#[inline]
pub fn is_error(&self) -> bool {
matches!(self.0.first(), Some(&SIMPLE_ERROR_TAG | &BULK_ERROR_TAG))
}
#[inline]
pub(crate) fn from_slice(bytes: &[u8]) -> RawResponse {
RawResponse(Box::from(bytes))
}
#[inline]
pub(crate) fn from_vec(bytes: Vec<u8>) -> RawResponse {
RawResponse(bytes.into_boxed_slice())
}
}
impl fmt::Debug for RawResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let shown = self.0.get(..DEBUG_RENDER_LIMIT).unwrap_or(&self.0);
write!(f, "{:?}", String::from_utf8_lossy(shown))?;
if shown.len() < self.0.len() {
f.write_str("<truncated>")?;
}
Ok(())
}
}
pub(crate) fn write_response(response: &RespResponse, out: &mut Vec<u8>) -> Result<()> {
if let Some(frame) = response.wire_bytes() {
out.extend_from_slice(frame);
return Ok(());
}
write_view(&response.view()?, out)
}
fn write_view(view: &RespView<'_>, out: &mut Vec<u8>) -> Result<()> {
match view {
RespView::Array(elements) => return write_collection(ARRAY_TAG, elements, out),
RespView::Set(elements) => return write_collection(SET_TAG, elements, out),
RespView::Push(elements) => return write_collection(PUSH_TAG, elements, out),
RespView::Map(elements) => {
write_header(MAP_TAG, elements.len() / 2, out);
return write_elements(elements, out);
}
RespView::OwnedArray(elements) => {
write_header(ARRAY_TAG, elements.len(), out);
for element in *elements {
write_response(element, out)?;
}
return Ok(());
}
RespView::SimpleString(value) => write_line(SIMPLE_STRING_TAG, value, out),
RespView::Error(message) => write_line(SIMPLE_ERROR_TAG, message, out),
RespView::Integer(value, text) => {
if text.is_empty() {
let mut buffer = itoa::Buffer::new();
write_line(INTEGER_TAG, buffer.format(*value).as_bytes(), out);
} else {
write_line(INTEGER_TAG, text, out);
}
}
RespView::Double(value, text) => {
if text.is_empty() {
out.push(DOUBLE_TAG);
let _ = write!(out, "{value}");
out.extend_from_slice(b"\r\n");
} else {
write_line(DOUBLE_TAG, text, out);
}
}
RespView::BulkString(value) => {
write_header(BULK_STRING_TAG, value.len(), out);
write_line_body(value, out);
}
RespView::Boolean(value) => {
out.extend_from_slice(if *value { b"#t\r\n" } else { b"#f\r\n" })
}
RespView::Null => out.extend_from_slice(b"_\r\n"),
RespView::IntegerArray(values) => {
write_header(ARRAY_TAG, values.len(), out);
let mut buffer = itoa::Buffer::new();
for value in *values {
write_line(INTEGER_TAG, buffer.format(*value).as_bytes(), out);
}
}
}
Ok(())
}
fn write_collection(tag: u8, elements: &RespCollectionView<'_>, out: &mut Vec<u8>) -> Result<()> {
write_header(tag, elements.len(), out);
write_elements(elements, out)
}
fn write_elements(elements: &RespCollectionView<'_>, out: &mut Vec<u8>) -> Result<()> {
for element in elements.clone() {
write_view(&element?, out)?;
}
Ok(())
}
fn write_header(tag: u8, count: usize, out: &mut Vec<u8>) {
let mut buffer = itoa::Buffer::new();
out.push(tag);
out.extend_from_slice(buffer.format(count).as_bytes());
out.extend_from_slice(b"\r\n");
}
fn write_line(tag: u8, value: &[u8], out: &mut Vec<u8>) {
out.push(tag);
write_line_body(value, out);
}
fn write_line_body(value: &[u8], out: &mut Vec<u8>) {
out.extend_from_slice(value);
out.extend_from_slice(b"\r\n");
}