eventuary_core/
payload.rs1use std::fmt;
2use std::str::FromStr;
3
4use bytes::Bytes;
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7
8use crate::error::{Error, Result};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum ContentType {
12 #[serde(rename = "application/json")]
13 Json,
14 #[serde(rename = "text/plain")]
15 PlainText,
16 #[serde(rename = "application/octet-stream")]
17 Binary,
18}
19
20impl fmt::Display for ContentType {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 ContentType::Json => write!(f, "application/json"),
24 ContentType::PlainText => write!(f, "text/plain"),
25 ContentType::Binary => write!(f, "application/octet-stream"),
26 }
27 }
28}
29
30impl FromStr for ContentType {
31 type Err = Error;
32
33 fn from_str(s: &str) -> Result<Self> {
34 match s {
35 "application/json" => Ok(ContentType::Json),
36 "text/plain" => Ok(ContentType::PlainText),
37 "application/octet-stream" => Ok(ContentType::Binary),
38 other => Err(Error::InvalidPayload(format!(
39 "unknown content type: {other}"
40 ))),
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
46pub struct Payload {
47 data: Bytes,
48 content_type: ContentType,
49}
50
51impl Payload {
52 pub fn from_json<T: Serialize>(val: &T) -> Result<Self> {
53 let data = serde_json::to_vec(val).map_err(|e| Error::Serialization(e.to_string()))?;
54 Ok(Self {
55 data: Bytes::from(data),
56 content_type: ContentType::Json,
57 })
58 }
59
60 pub fn from_string(s: impl Into<String>) -> Self {
61 Self {
62 data: Bytes::from(s.into().into_bytes()),
63 content_type: ContentType::PlainText,
64 }
65 }
66
67 pub fn from_bytes(data: impl Into<Bytes>) -> Self {
68 Self {
69 data: data.into(),
70 content_type: ContentType::Binary,
71 }
72 }
73
74 pub fn from_raw(data: impl Into<Bytes>, content_type: ContentType) -> Self {
75 Self {
76 data: data.into(),
77 content_type,
78 }
79 }
80
81 pub fn to_json<T: DeserializeOwned>(&self) -> Result<T> {
82 if self.content_type != ContentType::Json {
83 return Err(Error::InvalidPayload("not JSON content".into()));
84 }
85 serde_json::from_slice(&self.data).map_err(|e| Error::Serialization(e.to_string()))
86 }
87
88 pub fn data(&self) -> &[u8] {
89 &self.data
90 }
91
92 pub fn content_type(&self) -> ContentType {
93 self.content_type
94 }
95
96 pub fn size(&self) -> usize {
97 self.data.len()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn json_round_trip() {
107 let payload = Payload::from_json(&serde_json::json!({"k": "v"})).unwrap();
108 assert_eq!(payload.content_type(), ContentType::Json);
109 let value: serde_json::Value = payload.to_json().unwrap();
110 assert_eq!(value, serde_json::json!({"k": "v"}));
111 }
112
113 #[test]
114 fn plain_text_payload() {
115 let payload = Payload::from_string("hello");
116 assert_eq!(payload.content_type(), ContentType::PlainText);
117 assert_eq!(payload.data(), b"hello");
118 assert_eq!(payload.size(), 5);
119 }
120
121 #[test]
122 fn binary_payload_preserves_bytes() {
123 let bytes = vec![0xff, 0x00, 0x01, 0xfe];
124 let payload = Payload::from_bytes(bytes.clone());
125 assert_eq!(payload.content_type(), ContentType::Binary);
126 assert_eq!(payload.data(), bytes.as_slice());
127 }
128
129 #[test]
130 fn from_bytes_accepts_static_slice() {
131 let payload = Payload::from_bytes(Bytes::from_static(b"\x00\x01\x02"));
132 assert_eq!(payload.data(), &[0x00, 0x01, 0x02]);
133 }
134
135 #[test]
136 fn clone_does_not_copy_data() {
137 let payload = Payload::from_bytes(vec![1u8; 1024]);
138 let cloned = payload.clone();
139 assert_eq!(payload.data().as_ptr(), cloned.data().as_ptr());
140 }
141
142 #[test]
143 fn to_json_fails_on_non_json() {
144 let payload = Payload::from_string("hello");
145 let res: Result<serde_json::Value> = payload.to_json();
146 assert!(matches!(res, Err(Error::InvalidPayload(_))));
147 }
148
149 #[test]
150 fn content_type_round_trip() {
151 for ct in [
152 ContentType::Json,
153 ContentType::PlainText,
154 ContentType::Binary,
155 ] {
156 let s = ct.to_string();
157 let parsed: ContentType = s.parse().unwrap();
158 assert_eq!(parsed, ct);
159 }
160 }
161}