Skip to main content

laser_sdk/
message.rs

1use crate::types::MessageId;
2use std::collections::BTreeMap;
3
4/// A generic message read off the log: raw payload, the source `MessageId`, and
5/// the user-headers decoded as strings. No agentic decoding (no `Provenance`).
6/// The agent layer reconstructs that on top from the same `headers`.
7#[derive(Clone, Debug)]
8pub struct Message {
9    /// The raw message body. Owned `Vec<u8>` so the public API never leaks the
10    /// `bytes` crate.
11    pub payload: Vec<u8>,
12    /// Where the message sits on the log (partition + offset).
13    pub id: MessageId,
14    /// User headers decoded to strings (non-UTF-8 entries dropped).
15    pub headers: BTreeMap<String, String>,
16}
17
18impl Message {
19    /// Decode the payload as JSON.
20    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, crate::LaserError> {
21        serde_json::from_slice(&self.payload)
22            .map_err(|error| crate::LaserError::Codec(error.to_string()))
23    }
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29
30    fn message(payload: &[u8]) -> Message {
31        Message {
32            payload: payload.to_vec(),
33            id: MessageId::new(0, 7),
34            headers: BTreeMap::new(),
35        }
36    }
37
38    #[test]
39    fn given_a_json_payload_when_decoding_then_should_return_the_value() {
40        let value: serde_json::Value = message(br#"{"n":1}"#).json().expect("json");
41        assert_eq!(value, serde_json::json!({"n": 1}));
42    }
43
44    #[test]
45    fn given_a_non_json_payload_when_decoding_then_should_return_a_codec_error() {
46        let error = message(b"\xff").json::<serde_json::Value>().unwrap_err();
47        assert!(matches!(error, crate::LaserError::Codec(_)));
48    }
49}