use std::any::Any;
use std::fmt;
use std::sync::OnceLock;
use buffa::Message;
use buffa::view::{MessageView, OwnedView};
use bytes::Bytes;
use crate::codec::{
CodecFormat, JsonDeserialize, JsonSerialize, decode_json, decode_proto_with_options,
encode_json, encode_proto,
};
use crate::error::ConnectError;
pub trait AnyMessage: Send + Sync + 'static {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
fn encode(&self, format: CodecFormat) -> Result<Bytes, ConnectError>;
fn type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
impl<T> AnyMessage for T
where
T: Message + JsonSerialize + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn encode(&self, format: CodecFormat) -> Result<Bytes, ConnectError> {
match format {
CodecFormat::Proto => encode_proto(self),
CodecFormat::Json => encode_json(self),
}
}
}
pub struct Payload {
bytes: Bytes,
format: CodecFormat,
decoded: OnceLock<Box<dyn AnyMessage>>,
replaced: Option<Box<Replacement>>,
decode_options: buffa::DecodeOptions,
}
struct Replacement {
message: Box<dyn AnyMessage>,
encoded: OnceLock<Bytes>,
}
impl Replacement {
fn new(message: impl AnyMessage) -> Box<Self> {
Box::new(Self {
message: Box::new(message),
encoded: OnceLock::new(),
})
}
}
impl Payload {
pub fn new(bytes: Bytes, format: CodecFormat) -> Self {
Self {
bytes,
format,
decoded: OnceLock::new(),
replaced: None,
decode_options: buffa::DecodeOptions::new(),
}
}
pub fn from_message<M: AnyMessage>(message: M, format: CodecFormat) -> Self {
let mut payload = Self::new(Bytes::new(), format);
payload.replaced = Some(Replacement::new(message));
payload
}
pub fn try_clone(&self) -> Result<Self, ConnectError> {
let copy = Self::new(self.encoded()?, self.format);
Ok(if self.replaced.is_some() {
copy
} else {
copy.with_decode_options(self.decode_options.clone())
})
}
#[doc(hidden)] #[must_use]
pub fn with_decode_options(mut self, options: buffa::DecodeOptions) -> Self {
self.decode_options = options;
self
}
#[doc(hidden)]
#[must_use]
pub fn decode_options(&self) -> &buffa::DecodeOptions {
&self.decode_options
}
pub fn bytes(&self) -> &Bytes {
&self.bytes
}
pub fn format(&self) -> CodecFormat {
self.format
}
pub fn message<M>(&self) -> Result<&M, ConnectError>
where
M: Message + JsonSerialize + JsonDeserialize + 'static,
{
if let Some(replaced) = &self.replaced {
let replaced = &replaced.message;
return replaced.as_any().downcast_ref::<M>().ok_or_else(|| {
ConnectError::internal(format!(
"payload replacement is a {}, not a {}",
replaced.type_name(),
std::any::type_name::<M>()
))
});
}
if self.decoded.get().is_none() {
let m: M = match self.format {
CodecFormat::Proto => decode_proto_with_options(&self.bytes, &self.decode_options)?,
CodecFormat::Json => decode_json(&self.bytes)?,
};
let _ = self.decoded.set(Box::new(m));
}
let cached = self.decoded.get().expect("decoded cell populated above");
cached.as_any().downcast_ref::<M>().ok_or_else(|| {
ConnectError::internal(format!(
"payload was previously decoded as a {}, not a {}",
cached.type_name(),
std::any::type_name::<M>()
))
})
}
pub fn take_message<M>(self) -> Result<M, ConnectError>
where
M: Message + JsonDeserialize + 'static,
{
if let Some(replaced) = self.replaced {
let replaced = replaced.message;
let type_name = replaced.type_name();
return replaced
.into_any()
.downcast::<M>()
.map(|b| *b)
.map_err(|_| {
ConnectError::internal(format!(
"payload replacement is a {}, not a {}",
type_name,
std::any::type_name::<M>()
))
});
}
if let Some(cached) = self.decoded.into_inner() {
let type_name = cached.type_name();
return cached.into_any().downcast::<M>().map(|b| *b).map_err(|_| {
ConnectError::internal(format!(
"payload was previously decoded as a {}, not a {}",
type_name,
std::any::type_name::<M>()
))
});
}
match self.format {
CodecFormat::Proto => decode_proto_with_options(&self.bytes, &self.decode_options),
CodecFormat::Json => decode_json(&self.bytes),
}
}
pub fn view<V>(&self) -> Result<OwnedView<V>, ConnectError>
where
V: MessageView<'static>,
{
if let Some(replaced) = &self.replaced {
let bytes = match self.format {
CodecFormat::Proto => self.encoded()?,
CodecFormat::Json => replaced.message.encode(CodecFormat::Proto)?,
};
return OwnedView::decode(bytes).map_err(|e| {
ConnectError::internal(format!("failed to decode replacement as view: {e}"))
});
}
if self.format != CodecFormat::Proto {
return Err(ConnectError::internal(
"Payload::view requires a proto-encoded wire; use Payload::message for JSON",
));
}
OwnedView::decode_with_options(self.bytes.clone(), &self.decode_options).map_err(|e| {
ConnectError::invalid_argument(format!("failed to decode payload as view: {e}"))
})
}
pub fn set_message<M>(&mut self, message: M)
where
M: AnyMessage,
{
self.replaced = Some(Replacement::new(message));
self.decoded.take();
}
pub fn encoded(&self) -> Result<Bytes, ConnectError> {
let Some(replaced) = &self.replaced else {
return Ok(self.bytes.clone());
};
if let Some(cached) = replaced.encoded.get() {
return Ok(cached.clone());
}
let bytes = replaced.message.encode(self.format)?;
Ok(replaced.encoded.get_or_init(|| bytes).clone())
}
pub fn encoded_as(&self, format: CodecFormat) -> Result<Bytes, ConnectError> {
if format == self.format {
return self.encoded();
}
match &self.replaced {
Some(replaced) => replaced.message.encode(format),
None => Err(ConnectError::internal(format!(
"payload carries {:?} wire bytes but the call negotiated {format:?}",
self.format
))),
}
}
}
impl fmt::Debug for Payload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Payload")
.field("wire_len", &self.bytes.len())
.field("format", &self.format)
.field("decoded", &self.decoded.get().is_some())
.field("replaced", &self.replaced.is_some())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use buffa_types::google::protobuf::__buffa::view::StringValueView;
use buffa_types::google::protobuf::StringValue;
fn proto_payload(value: &str) -> Payload {
let msg = StringValue {
value: value.into(),
..Default::default()
};
Payload::new(encode_proto(&msg).unwrap(), CodecFormat::Proto)
}
#[test]
fn message_decodes_and_caches() {
let p = proto_payload("hello");
let m1: &StringValue = p.message().unwrap();
assert_eq!(m1.value, "hello");
let m2: &StringValue = p.message().unwrap();
assert!(std::ptr::eq(m1, m2), "second call should hit the cache");
}
#[cfg(feature = "json")]
#[test]
fn message_decodes_json() {
let bytes = encode_json(&StringValue {
value: "json".into(),
..Default::default()
})
.unwrap();
let p = Payload::new(bytes, CodecFormat::Json);
let m: &StringValue = p.message().unwrap();
assert_eq!(m.value, "json");
}
#[test]
fn view_zero_copy_proto() {
let p = proto_payload("zero copy");
let v = p.view::<StringValueView>().unwrap();
assert_eq!(v.reborrow().value, "zero copy");
let value_ptr = v.reborrow().value.as_ptr() as usize;
let bytes_range =
p.bytes().as_ptr() as usize..p.bytes().as_ptr() as usize + p.bytes().len();
assert!(
bytes_range.contains(&value_ptr),
"view should borrow from the payload's wire bytes"
);
}
#[cfg(feature = "json")]
#[test]
fn view_errors_on_json() {
let bytes = encode_json(&StringValue {
value: "x".into(),
..Default::default()
})
.unwrap();
let p = Payload::new(bytes, CodecFormat::Json);
let err = p.view::<StringValueView>().unwrap_err();
assert!(
err.message
.as_deref()
.unwrap_or_default()
.contains("requires a proto-encoded wire"),
"{err:?}"
);
}
#[test]
fn set_message_round_trips() {
let mut p = proto_payload("before");
p.set_message(StringValue {
value: "after".into(),
..Default::default()
});
let m: &StringValue = p.message().unwrap();
assert_eq!(m.value, "after");
let v = p.view::<StringValueView>().unwrap();
assert_eq!(v.reborrow().value, "after");
let encoded = p.encoded().unwrap();
let rt: StringValue = crate::codec::decode_proto(&encoded).unwrap();
assert_eq!(rt.value, "after");
let orig: StringValue = crate::codec::decode_proto(p.bytes()).unwrap();
assert_eq!(orig.value, "before");
}
#[cfg(feature = "json")]
#[test]
fn set_message_round_trips_json_format() {
let bytes = encode_json(&StringValue {
value: "before".into(),
..Default::default()
})
.unwrap();
let mut p = Payload::new(bytes, CodecFormat::Json);
p.set_message(StringValue {
value: "after".into(),
..Default::default()
});
let encoded = p.encoded().unwrap();
let rt: StringValue = decode_json(&encoded).unwrap();
assert_eq!(rt.value, "after");
}
#[test]
fn encoded_without_replacement_returns_original() {
let p = proto_payload("x");
assert!(std::ptr::eq(
p.encoded().unwrap().as_ptr(),
p.bytes().as_ptr()
));
}
#[test]
fn from_message_is_lazily_encoded() {
let p = Payload::from_message(StringValue::from("typed"), CodecFormat::Proto);
assert!(
p.bytes().is_empty(),
"no peer bytes for a from_message payload"
);
assert!(
!p.encoded().unwrap().is_empty(),
"encoded() is the real body"
);
let dbg = format!("{p:?}");
assert!(
dbg.contains("wire_len: 0") && dbg.contains("replaced: true"),
"{dbg}"
);
assert_eq!(p.format(), CodecFormat::Proto);
let m: &StringValue = p.message().unwrap();
assert_eq!(m.value, "typed");
assert_eq!(
p.view::<StringValueView>().unwrap().reborrow().value,
"typed"
);
let rt: StringValue = crate::codec::decode_proto(&p.encoded().unwrap()).unwrap();
assert_eq!(rt.value, "typed");
let owned: StringValue = p.take_message().unwrap();
assert_eq!(owned.value, "typed");
}
#[cfg(feature = "json")]
#[test]
fn from_message_encodes_in_requested_format() {
let p = Payload::from_message(StringValue::from("j"), CodecFormat::Json);
let rt: StringValue = decode_json(&p.encoded().unwrap()).unwrap();
assert_eq!(rt.value, "j");
}
#[cfg(feature = "json")]
#[test]
fn encoded_as_transcodes_a_message_payload() {
let p = Payload::from_message(StringValue::from("x"), CodecFormat::Proto);
let json = p.encoded_as(CodecFormat::Json).unwrap();
let rt: StringValue = decode_json(&json).unwrap();
assert_eq!(rt.value, "x");
let a = p.encoded_as(CodecFormat::Proto).unwrap();
let b = p.encoded().unwrap();
assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
}
#[test]
fn encoded_as_rejects_wire_bytes_in_another_format() {
let p = proto_payload("x");
let err = p.encoded_as(CodecFormat::Json).unwrap_err();
assert_eq!(err.code, crate::ErrorCode::Internal, "{err:?}");
assert!(
err.message.as_deref().unwrap_or("").contains("negotiated"),
"{err:?}"
);
}
#[test]
fn from_message_wrong_type_is_internal_error() {
let p = Payload::from_message(StringValue::from("s"), CodecFormat::Proto);
let err = p
.message::<buffa_types::google::protobuf::Int64Value>()
.unwrap_err();
assert_eq!(err.code, crate::ErrorCode::Internal, "{err:?}");
}
#[test]
fn try_clone_copies_body_not_cache() {
let p = proto_payload("orig");
let _ = p.message::<StringValue>().unwrap(); let c = p.try_clone().unwrap();
assert!(
std::ptr::eq(c.bytes().as_ptr(), p.bytes().as_ptr()),
"no replacement: clone is a Bytes refcount bump"
);
assert!(
c.decoded.get().is_none(),
"clone starts with an empty cache"
);
assert_eq!(c.message::<StringValue>().unwrap().value, "orig");
let mut replaced = proto_payload("orig");
replaced.set_message(StringValue::from("new"));
let c = replaced.try_clone().unwrap();
assert!(c.replaced.is_none(), "replacement is baked into bytes");
let rt: StringValue = crate::codec::decode_proto(c.bytes()).unwrap();
assert_eq!(rt.value, "new");
}
#[test]
fn replacement_encode_is_memoized_and_invalidated() {
let mut p = Payload::from_message(StringValue::from("once"), CodecFormat::Proto);
let first = p.encoded().unwrap();
let again = p.encoded().unwrap();
assert!(
std::ptr::eq(first.as_ptr(), again.as_ptr()),
"second encoded() must be a refcount bump, not a re-encode"
);
let clone = p.try_clone().unwrap();
assert!(
std::ptr::eq(clone.bytes().as_ptr(), first.as_ptr()),
"try_clone shares the memoized encode"
);
let v = p.view::<StringValueView>().unwrap();
let ptr = v.reborrow().value.as_ptr() as usize;
let range = first.as_ptr() as usize..first.as_ptr() as usize + first.len();
assert!(
range.contains(&ptr),
"view should borrow the memoized bytes"
);
p.set_message(StringValue::from("twice"));
let rt: StringValue = crate::codec::decode_proto(&p.encoded().unwrap()).unwrap();
assert_eq!(rt.value, "twice", "set_message must invalidate the memo");
}
#[test]
fn try_clone_preserves_decode_options() {
let opts = buffa::DecodeOptions::new().with_recursion_limit(7);
let p = proto_payload("x").with_decode_options(opts.clone());
let c = p.try_clone().unwrap();
assert_eq!(format!("{:?}", c.decode_options()), format!("{:?}", opts));
assert_ne!(
format!("{:?}", c.decode_options()),
format!("{:?}", buffa::DecodeOptions::new()),
"fixture must differ from the default for this test to mean anything"
);
}
#[cfg(feature = "json")]
#[test]
fn try_clone_json_replacement_is_wire_equal_not_api_equal() {
let mut p = Payload::new(
encode_json(&StringValue::from("before")).unwrap(),
CodecFormat::Json,
);
p.set_message(StringValue::from("after"));
assert!(
p.view::<StringValueView>().is_ok(),
"original: replacement can be viewed"
);
let c = p.try_clone().unwrap();
assert_eq!(c.encoded().unwrap(), p.encoded().unwrap(), "wire-equal");
assert_eq!(c.format(), CodecFormat::Json);
assert!(
c.view::<StringValueView>().is_err(),
"copy: JSON bytes cannot back a view"
);
assert_eq!(c.message::<StringValue>().unwrap().value, "after");
}
#[test]
fn message_wrong_type_errors() {
use buffa_types::google::protobuf::Int32Value;
let p = proto_payload("x");
let _: &StringValue = p.message().unwrap();
let err = p.message::<Int32Value>().unwrap_err();
let msg = err.message.as_deref().unwrap_or_default();
assert!(msg.contains("previously decoded as a"), "{err:?}");
assert!(msg.contains("StringValue"), "{err:?}");
assert!(msg.contains("Int32Value"), "{err:?}");
}
#[test]
fn message_decode_error_is_invalid_argument() {
use crate::ErrorCode;
let p = Payload::new(Bytes::from_static(&[0xff, 0xff, 0xff]), CodecFormat::Proto);
let err = p.message::<StringValue>().unwrap_err();
assert_eq!(err.code, ErrorCode::InvalidArgument, "{err:?}");
}
#[cfg(not(feature = "json"))]
#[test]
fn message_json_format_is_unimplemented_without_feature() {
let p = Payload::new(Bytes::from_static(b"{}"), CodecFormat::Json);
assert_eq!(
p.message::<StringValue>().unwrap_err().code,
crate::ErrorCode::Unimplemented
);
assert!(proto_payload("ok").message::<StringValue>().is_ok());
}
#[cfg(not(feature = "json"))]
#[test]
fn take_message_json_format_is_unimplemented_without_feature() {
let p = Payload::new(Bytes::from_static(b"{}"), CodecFormat::Json);
assert_eq!(
p.take_message::<StringValue>().unwrap_err().code,
crate::ErrorCode::Unimplemented
);
assert!(proto_payload("ok").take_message::<StringValue>().is_ok());
}
#[test]
fn message_replacement_wrong_type_errors() {
use buffa_types::google::protobuf::Int32Value;
let mut p = proto_payload("x");
p.set_message(Int32Value {
value: 7,
..Default::default()
});
let err = p.message::<StringValue>().unwrap_err();
let msg = err.message.as_deref().unwrap_or_default();
assert!(msg.contains("replacement is a"), "{err:?}");
assert!(msg.contains("Int32Value"), "{err:?}");
assert!(msg.contains("StringValue"), "{err:?}");
}
#[cfg(feature = "json")]
#[test]
fn view_replaced_json_format_payload() {
let bytes = encode_json(&StringValue::from("before")).unwrap();
let mut p = Payload::new(bytes, CodecFormat::Json);
p.set_message(StringValue::from("after"));
let v = p.view::<StringValueView>().unwrap();
assert_eq!(v.reborrow().value, "after");
}
#[test]
fn set_message_twice_supersedes() {
let mut p = proto_payload("original");
p.set_message(StringValue::from("first"));
p.set_message(StringValue::from("second"));
let m: &StringValue = p.message().unwrap();
assert_eq!(m.value, "second");
}
#[test]
fn take_message_decodes_fresh_when_no_cache() {
let p = proto_payload("fresh");
let m: StringValue = p.take_message().unwrap();
assert_eq!(m.value, "fresh");
}
#[test]
fn take_message_reuses_cache() {
let p = proto_payload("cached");
let _ = p.message::<StringValue>().unwrap();
let m: StringValue = p.take_message().unwrap();
assert_eq!(m.value, "cached");
}
#[test]
fn take_message_returns_replacement() {
let mut p = Payload::new(Bytes::from_static(&[0xff, 0xff, 0xff]), CodecFormat::Proto);
p.set_message(StringValue::from("replaced"));
let m: StringValue = p.take_message().unwrap();
assert_eq!(m.value, "replaced");
}
#[test]
fn take_message_wrong_cached_type_errors() {
use buffa_types::google::protobuf::Int32Value;
let p = proto_payload("x");
let _: &StringValue = p.message().unwrap();
let err = p.take_message::<Int32Value>().unwrap_err();
let msg = err.message.as_deref().unwrap_or_default();
assert!(msg.contains("previously decoded as a"), "{err:?}");
assert!(msg.contains("StringValue"), "{err:?}");
assert!(msg.contains("Int32Value"), "{err:?}");
}
#[test]
fn take_message_wrong_replacement_type_errors() {
use buffa_types::google::protobuf::Int32Value;
let mut p = proto_payload("x");
p.set_message(Int32Value {
value: 7,
..Default::default()
});
let err = p.take_message::<StringValue>().unwrap_err();
let msg = err.message.as_deref().unwrap_or_default();
assert!(msg.contains("replacement is a"), "{err:?}");
}
#[test]
fn take_message_decode_error_is_invalid_argument() {
use crate::ErrorCode;
let p = Payload::new(Bytes::from_static(&[0xff, 0xff, 0xff]), CodecFormat::Proto);
let err = p.take_message::<StringValue>().unwrap_err();
assert_eq!(err.code, ErrorCode::InvalidArgument, "{err:?}");
}
#[test]
fn payload_debug_redacts_body() {
let p = proto_payload("secret");
let dbg = format!("{p:?}");
assert!(!dbg.contains("secret"), "Debug must not leak body: {dbg}");
assert!(dbg.contains("Proto"), "{dbg}");
}
#[test]
fn payload_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Payload>();
assert_send_sync::<Box<dyn AnyMessage>>();
}
#[test]
fn message_concurrent_same_type() {
let p = proto_payload("race");
std::thread::scope(|s| {
let handles: Vec<_> = (0..16)
.map(|_| {
let p = &p;
s.spawn(move || p.message::<StringValue>().unwrap() as *const _ as usize)
})
.collect();
let addrs: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
assert!(
addrs.iter().all(|&a| a == addrs[0]),
"all callers should observe the same cached value"
);
});
}
}