feat(event-payload): serializable NATS event payload types
This commit is contained in:
@@ -33,6 +33,8 @@ axum = { version = "0.8", features = ["macros"] }
|
|||||||
tower-http = { version = "0.6", features = ["cors", "trace"] }
|
tower-http = { version = "0.6", features = ["cors", "trace"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
|
async-nats = "0.38"
|
||||||
|
async-stream = "0.3"
|
||||||
|
|
||||||
domain = { path = "crates/domain" }
|
domain = { path = "crates/domain" }
|
||||||
application = { path = "crates/application" }
|
application = { path = "crates/application" }
|
||||||
|
|||||||
@@ -2,3 +2,7 @@
|
|||||||
name = "event-payload"
|
name = "event-payload"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Serializable mirror of domain::events::DomainEvent.
|
||||||
|
/// All IDs are Strings (UUID hex) — no domain type dependencies.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", content = "data")]
|
||||||
|
pub enum EventPayload {
|
||||||
|
ThoughtCreated {
|
||||||
|
thought_id: String,
|
||||||
|
user_id: String,
|
||||||
|
in_reply_to_id: Option<String>,
|
||||||
|
},
|
||||||
|
ThoughtDeleted {
|
||||||
|
thought_id: String,
|
||||||
|
user_id: String,
|
||||||
|
},
|
||||||
|
ThoughtUpdated {
|
||||||
|
thought_id: String,
|
||||||
|
user_id: String,
|
||||||
|
},
|
||||||
|
LikeAdded {
|
||||||
|
like_id: String,
|
||||||
|
user_id: String,
|
||||||
|
thought_id: String,
|
||||||
|
},
|
||||||
|
LikeRemoved {
|
||||||
|
user_id: String,
|
||||||
|
thought_id: String,
|
||||||
|
},
|
||||||
|
BoostAdded {
|
||||||
|
boost_id: String,
|
||||||
|
user_id: String,
|
||||||
|
thought_id: String,
|
||||||
|
},
|
||||||
|
BoostRemoved {
|
||||||
|
user_id: String,
|
||||||
|
thought_id: String,
|
||||||
|
},
|
||||||
|
FollowRequested {
|
||||||
|
follower_id: String,
|
||||||
|
following_id: String,
|
||||||
|
},
|
||||||
|
FollowAccepted {
|
||||||
|
follower_id: String,
|
||||||
|
following_id: String,
|
||||||
|
},
|
||||||
|
FollowRejected {
|
||||||
|
follower_id: String,
|
||||||
|
following_id: String,
|
||||||
|
},
|
||||||
|
Unfollowed {
|
||||||
|
follower_id: String,
|
||||||
|
following_id: String,
|
||||||
|
},
|
||||||
|
UserBlocked {
|
||||||
|
blocker_id: String,
|
||||||
|
blocked_id: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventPayload {
|
||||||
|
/// Returns the NATS subject for this event.
|
||||||
|
pub fn subject(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::ThoughtCreated { .. } => "thoughts.created",
|
||||||
|
Self::ThoughtDeleted { .. } => "thoughts.deleted",
|
||||||
|
Self::ThoughtUpdated { .. } => "thoughts.updated",
|
||||||
|
Self::LikeAdded { .. } => "likes.added",
|
||||||
|
Self::LikeRemoved { .. } => "likes.removed",
|
||||||
|
Self::BoostAdded { .. } => "boosts.added",
|
||||||
|
Self::BoostRemoved { .. } => "boosts.removed",
|
||||||
|
Self::FollowRequested { .. } => "follows.requested",
|
||||||
|
Self::FollowAccepted { .. } => "follows.accepted",
|
||||||
|
Self::FollowRejected { .. } => "follows.rejected",
|
||||||
|
Self::Unfollowed { .. } => "follows.removed",
|
||||||
|
Self::UserBlocked { .. } => "users.blocked",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thought_created_roundtrip() {
|
||||||
|
let p = EventPayload::ThoughtCreated {
|
||||||
|
thought_id: "abc".into(),
|
||||||
|
user_id: "def".into(),
|
||||||
|
in_reply_to_id: None,
|
||||||
|
};
|
||||||
|
let json = serde_json::to_string(&p).unwrap();
|
||||||
|
let back: EventPayload = serde_json::from_str(&json).unwrap();
|
||||||
|
assert_eq!(back.subject(), "thoughts.created");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_subjects_are_unique() {
|
||||||
|
let samples: &[EventPayload] = &[
|
||||||
|
EventPayload::ThoughtCreated { thought_id: "a".into(), user_id: "b".into(), in_reply_to_id: None },
|
||||||
|
EventPayload::ThoughtDeleted { thought_id: "a".into(), user_id: "b".into() },
|
||||||
|
EventPayload::ThoughtUpdated { thought_id: "a".into(), user_id: "b".into() },
|
||||||
|
EventPayload::LikeAdded { like_id: "a".into(), user_id: "b".into(), thought_id: "c".into() },
|
||||||
|
EventPayload::LikeRemoved { user_id: "b".into(), thought_id: "c".into() },
|
||||||
|
EventPayload::BoostAdded { boost_id: "a".into(), user_id: "b".into(), thought_id: "c".into() },
|
||||||
|
EventPayload::BoostRemoved { user_id: "b".into(), thought_id: "c".into() },
|
||||||
|
EventPayload::FollowRequested { follower_id: "a".into(), following_id: "b".into() },
|
||||||
|
EventPayload::FollowAccepted { follower_id: "a".into(), following_id: "b".into() },
|
||||||
|
EventPayload::FollowRejected { follower_id: "a".into(), following_id: "b".into() },
|
||||||
|
EventPayload::Unfollowed { follower_id: "a".into(), following_id: "b".into() },
|
||||||
|
EventPayload::UserBlocked { blocker_id: "a".into(), blocked_id: "b".into() },
|
||||||
|
];
|
||||||
|
let mut subjects: Vec<&str> = samples.iter().map(|p| p.subject()).collect();
|
||||||
|
subjects.sort();
|
||||||
|
subjects.dedup();
|
||||||
|
assert_eq!(subjects.len(), samples.len(), "each event must have a unique subject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user