use std::error::Error;
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use crate::{RPC_MAX_CONTROL_PAYLOAD_BYTES, RPC_SEARCH_RESPONSE_FRAME_BYTES};
pub const REALTIME_READ_ENVELOPE_FORMAT_VERSION: u16 = 1;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum RealtimeReadKind {
PointGet,
SearchSlice,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct RealtimeReadEnvelope<'a> {
format_version: u16,
kind: RealtimeReadKind,
#[serde(borrow)]
payload: &'a RawValue,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RealtimeReadEnvelopeError {
MessageTooLarge {
actual: usize,
maximum: usize,
},
Malformed,
NonCanonical,
UnsupportedVersion(u16),
UnexpectedKind {
expected: RealtimeReadKind,
actual: RealtimeReadKind,
},
}
impl Display for RealtimeReadEnvelopeError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::MessageTooLarge { actual, maximum } => {
write!(
formatter,
"realtime-read envelope is {actual} bytes; maximum is {maximum}"
)
}
Self::Malformed => formatter.write_str("realtime-read envelope is malformed"),
Self::NonCanonical => formatter.write_str("realtime-read envelope is not canonical"),
Self::UnsupportedVersion(version) => {
write!(formatter, "realtime-read envelope version {version} is unsupported")
}
Self::UnexpectedKind { expected, actual } => {
write!(
formatter,
"realtime-read envelope kind {actual:?} does not match {expected:?}"
)
}
}
}
}
impl Error for RealtimeReadEnvelopeError {}
pub fn encode_realtime_read_request(
kind: RealtimeReadKind,
payload: &[u8],
) -> Result<Vec<u8>, RealtimeReadEnvelopeError> {
encode_envelope(kind, payload, RPC_MAX_CONTROL_PAYLOAD_BYTES)
}
pub fn decode_realtime_read_request(
expected: RealtimeReadKind,
bytes: &[u8],
) -> Result<Vec<u8>, RealtimeReadEnvelopeError> {
decode_envelope(expected, bytes, RPC_MAX_CONTROL_PAYLOAD_BYTES)
}
pub fn encode_realtime_read_response(
kind: RealtimeReadKind,
payload: &[u8],
) -> Result<Vec<u8>, RealtimeReadEnvelopeError> {
encode_envelope(kind, payload, RPC_SEARCH_RESPONSE_FRAME_BYTES)
}
pub fn decode_realtime_read_response(
expected: RealtimeReadKind,
bytes: &[u8],
) -> Result<Vec<u8>, RealtimeReadEnvelopeError> {
decode_envelope(expected, bytes, RPC_SEARCH_RESPONSE_FRAME_BYTES)
}
fn encode_envelope(
kind: RealtimeReadKind,
payload: &[u8],
maximum: usize,
) -> Result<Vec<u8>, RealtimeReadEnvelopeError> {
let payload = RawValue::from_string(
std::str::from_utf8(payload)
.map_err(|_| RealtimeReadEnvelopeError::Malformed)?
.to_owned(),
)
.map_err(|_| RealtimeReadEnvelopeError::Malformed)?;
let envelope = RealtimeReadEnvelope {
format_version: REALTIME_READ_ENVELOPE_FORMAT_VERSION,
kind,
payload: &payload,
};
let encoded = serde_json::to_vec(&envelope).map_err(|_| RealtimeReadEnvelopeError::Malformed)?;
check_size(encoded.len(), maximum)?;
Ok(encoded)
}
fn decode_envelope(
expected: RealtimeReadKind,
bytes: &[u8],
maximum: usize,
) -> Result<Vec<u8>, RealtimeReadEnvelopeError> {
check_size(bytes.len(), maximum)?;
let envelope =
serde_json::from_slice::<RealtimeReadEnvelope<'_>>(bytes).map_err(|_| RealtimeReadEnvelopeError::Malformed)?;
if envelope.format_version != REALTIME_READ_ENVELOPE_FORMAT_VERSION {
return Err(RealtimeReadEnvelopeError::UnsupportedVersion(envelope.format_version));
}
if envelope.kind != expected {
return Err(RealtimeReadEnvelopeError::UnexpectedKind {
expected,
actual: envelope.kind,
});
}
let canonical = serde_json::to_vec(&envelope).map_err(|_| RealtimeReadEnvelopeError::Malformed)?;
if canonical != bytes {
return Err(RealtimeReadEnvelopeError::NonCanonical);
}
Ok(envelope.payload.get().as_bytes().to_vec())
}
fn check_size(actual: usize, maximum: usize) -> Result<(), RealtimeReadEnvelopeError> {
if actual > maximum {
Err(RealtimeReadEnvelopeError::MessageTooLarge { actual, maximum })
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_is_canonical_and_kind_fenced() -> Result<(), Box<dyn Error>> {
let encoded = encode_realtime_read_request(RealtimeReadKind::SearchSlice, br#"{"value":1}"#)?;
assert_eq!(
decode_realtime_read_request(RealtimeReadKind::SearchSlice, &encoded)?,
br#"{"value":1}"#
);
assert!(matches!(
decode_realtime_read_request(RealtimeReadKind::PointGet, &encoded),
Err(RealtimeReadEnvelopeError::UnexpectedKind { .. })
));
let noncanonical = std::str::from_utf8(&encoded)?.replace("{\"format_version\"", "{ \"format_version\"");
assert!(matches!(
decode_realtime_read_request(RealtimeReadKind::SearchSlice, noncanonical.as_bytes()),
Err(RealtimeReadEnvelopeError::NonCanonical)
));
let mut future: serde_json::Value = serde_json::from_slice(&encoded)?;
future["format_version"] = serde_json::json!(2);
assert!(matches!(
decode_realtime_read_request(RealtimeReadKind::SearchSlice, &serde_json::to_vec(&future)?),
Err(RealtimeReadEnvelopeError::UnsupportedVersion(2))
));
let mut unknown: serde_json::Value = serde_json::from_slice(&encoded)?;
unknown["unexpected"] = serde_json::json!(true);
assert!(matches!(
decode_realtime_read_request(RealtimeReadKind::SearchSlice, &serde_json::to_vec(&unknown)?),
Err(RealtimeReadEnvelopeError::Malformed)
));
assert!(matches!(
decode_realtime_read_request(
RealtimeReadKind::SearchSlice,
&vec![b'x'; RPC_MAX_CONTROL_PAYLOAD_BYTES + 1],
),
Err(RealtimeReadEnvelopeError::MessageTooLarge { .. })
));
Ok(())
}
}