eventuary-core 0.1.0

Core event model and async IO traits for eventuary
Documentation
use serde::Serialize;
use serde::de::DeserializeOwned;

use crate::error::Result;
use crate::event::Event;
use crate::payload::Payload;

pub trait PayloadCodec<P>: Clone + Send + Sync + 'static {
    fn encode(&self, payload: &P) -> Result<Payload>;
    fn decode(&self, payload: &Payload) -> Result<P>;
}

pub trait EventCodec<P>: Clone + Send + Sync + 'static {
    fn encode(&self, event: &Event<P>) -> Result<Event<Payload>>;
    fn decode(&self, event: Event<Payload>) -> Result<Event<P>>;
}

#[derive(Debug, Clone)]
pub struct PayloadEventCodec<C> {
    payload_codec: C,
}

impl<C> PayloadEventCodec<C> {
    pub fn new(payload_codec: C) -> Self {
        Self { payload_codec }
    }

    pub fn payload_codec(&self) -> &C {
        &self.payload_codec
    }
}

impl<P, C> EventCodec<P> for PayloadEventCodec<C>
where
    C: PayloadCodec<P>,
{
    fn encode(&self, event: &Event<P>) -> Result<Event<Payload>> {
        event.encode_payload(&self.payload_codec)
    }

    fn decode(&self, event: Event<Payload>) -> Result<Event<P>> {
        event.decode_payload(&self.payload_codec)
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct JsonPayloadCodec;

impl<P> PayloadCodec<P> for JsonPayloadCodec
where
    P: Serialize + DeserializeOwned,
{
    fn encode(&self, payload: &P) -> Result<Payload> {
        Payload::from_json(payload)
    }

    fn decode(&self, payload: &Payload) -> Result<P> {
        payload.to_json()
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct PayloadPassthroughCodec;

impl PayloadCodec<Payload> for PayloadPassthroughCodec {
    fn encode(&self, payload: &Payload) -> Result<Payload> {
        Ok(payload.clone())
    }

    fn decode(&self, payload: &Payload) -> Result<Payload> {
        Ok(payload.clone())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::error::Error;

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    struct UserUpdated {
        user_id: String,
        email: String,
    }

    #[test]
    fn json_codec_roundtrips_typed_payload() {
        let codec = JsonPayloadCodec;
        let typed = UserUpdated {
            user_id: "u-1".to_owned(),
            email: "a@example.com".to_owned(),
        };

        let payload = codec.encode(&typed).unwrap();
        assert_eq!(payload.content_type(), crate::ContentType::Json);

        let decoded: UserUpdated = codec.decode(&payload).unwrap();
        assert_eq!(decoded, typed);
    }

    #[test]
    fn json_codec_rejects_non_json_payload() {
        let codec = JsonPayloadCodec;
        let payload = Payload::from_string("not-json");
        let decoded: Result<serde_json::Value> = codec.decode(&payload);
        let err = decoded.unwrap_err();
        assert!(matches!(err, Error::InvalidPayload(_)));
    }

    #[test]
    fn payload_passthrough_clones_payload() {
        let codec = PayloadPassthroughCodec;
        let payload = Payload::from_string("hello");
        let encoded = codec.encode(&payload).unwrap();
        let decoded = codec.decode(&encoded).unwrap();
        assert_eq!(decoded.data(), b"hello");
        assert_eq!(decoded.content_type(), crate::ContentType::PlainText);
    }

    #[test]
    fn payload_event_codec_encodes_and_decodes_event() {
        let codec = PayloadEventCodec::new(JsonPayloadCodec);
        let event: Event<UserUpdated> = Event::create(
            "acme",
            "/users",
            "user.updated",
            "thing-1",
            UserUpdated {
                user_id: "u-1".to_owned(),
                email: "a@example.com".to_owned(),
            },
        )
        .unwrap();

        let id = event.id();
        let encoded = codec.encode(&event).unwrap();
        assert_eq!(encoded.id(), id);
        assert_eq!(encoded.payload().content_type(), crate::ContentType::Json);

        let decoded: Event<UserUpdated> = codec.decode(encoded).unwrap();
        assert_eq!(decoded.id(), id);
        assert_eq!(decoded.payload().email, "a@example.com");
    }
}