use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, ts_rs::TS, Clone, Debug, PartialEq, Eq)]
pub struct Payload {
content_type: ContentType,
#[serde(with = "crate::payload_bytes")]
#[ts(type = "string | Array<number> | PayloadElision")]
bytes: Vec<u8>,
}
#[derive(Serialize, ts_rs::TS, Clone, Copy, Debug, PartialEq, Eq)]
pub struct PayloadElision {
#[serde(rename = "__elided")]
#[ts(type = "true")]
elided: bool,
size_bytes: u64,
}
impl PayloadElision {
#[must_use]
pub const fn new(size_bytes: u64) -> Self {
Self {
elided: true,
size_bytes,
}
}
}
impl Payload {
#[must_use]
pub fn new(content_type: ContentType, bytes: Vec<u8>) -> Self {
Self {
content_type,
bytes,
}
}
pub fn from_json(value: &serde_json::Value) -> Result<Self, PayloadError> {
let bytes = serde_json::to_vec(value)?;
Ok(Self::new(ContentType::Json, bytes))
}
#[must_use]
pub fn json_null() -> Self {
Self::new(ContentType::Json, b"null".to_vec())
}
pub fn to_json(&self) -> Result<serde_json::Value, PayloadError> {
match self.content_type {
ContentType::Json => Ok(serde_json::from_slice(&self.bytes)?),
}
}
#[must_use]
pub const fn content_type(&self) -> &ContentType {
&self.content_type
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
}
#[derive(Serialize, Deserialize, ts_rs::TS, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ContentType {
Json,
}
#[derive(thiserror::Error, Debug)]
pub enum PayloadError {
#[error("json payload conversion failed: {0}")]
Json(#[from] serde_json::Error),
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{ContentType, Payload};
#[test]
fn json_values_round_trip_losslessly() -> Result<(), Box<dyn std::error::Error>> {
let values = [
serde_json::Value::Null,
json!(true),
json!(123.45),
json!("hello"),
json!([null, false, 7, "item"]),
json!({"nested": {"value": 1}, "array": [true, false]}),
];
for value in values {
let payload = Payload::from_json(&value)?;
assert_eq!(payload.content_type(), &ContentType::Json);
assert_eq!(payload.to_json()?, value);
}
Ok(())
}
#[test]
fn json_null_is_the_serialized_null_document() -> Result<(), Box<dyn std::error::Error>> {
let payload = Payload::json_null();
assert_eq!(payload.content_type(), &ContentType::Json);
assert_eq!(payload.bytes(), b"null");
assert_eq!(payload.to_json()?, serde_json::Value::Null);
assert_eq!(payload, Payload::from_json(&serde_json::Value::Null)?);
Ok(())
}
}