Skip to main content

hexeract_core/
envelope.rs

1use crate::command::Command;
2use crate::ids::{CorrelationId, MessageId};
3use crate::query::Query;
4
5/// Metadata carried alongside every dispatch.
6///
7/// The envelope exposes the message type name and identifiers without
8/// requiring the middleware to know the concrete `Command` or `Query` type.
9#[derive(Debug, Clone)]
10pub struct MessageEnvelope {
11    type_name: &'static str,
12    message_id: MessageId,
13    correlation_id: CorrelationId,
14}
15
16impl MessageEnvelope {
17    /// Builds an envelope for a [`Command`] dispatch.
18    #[must_use]
19    pub fn for_command<C: Command>(message_id: MessageId, correlation_id: CorrelationId) -> Self {
20        Self {
21            type_name: std::any::type_name::<C>(),
22            message_id,
23            correlation_id,
24        }
25    }
26
27    /// Builds an envelope for a [`Query`] dispatch.
28    #[must_use]
29    pub fn for_query<Q: Query>(message_id: MessageId, correlation_id: CorrelationId) -> Self {
30        Self {
31            type_name: std::any::type_name::<Q>(),
32            message_id,
33            correlation_id,
34        }
35    }
36
37    /// The fully-qualified type name of the dispatched message.
38    #[must_use]
39    pub fn type_name(&self) -> &'static str {
40        self.type_name
41    }
42
43    /// The unique identifier of this dispatch.
44    #[must_use]
45    pub fn message_id(&self) -> MessageId {
46        self.message_id
47    }
48
49    /// The correlation identifier linking this dispatch to its causal chain.
50    #[must_use]
51    pub fn correlation_id(&self) -> CorrelationId {
52        self.correlation_id
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    struct Ping;
61    impl Command for Ping {
62        type Output = ();
63    }
64
65    struct Pong;
66    impl Query for Pong {
67        type Output = ();
68    }
69
70    #[test]
71    fn for_command_records_command_type_name() {
72        let env = MessageEnvelope::for_command::<Ping>(MessageId::new(), CorrelationId::new());
73        assert!(env.type_name().ends_with("::Ping"));
74    }
75
76    #[test]
77    fn for_query_records_query_type_name() {
78        let env = MessageEnvelope::for_query::<Pong>(MessageId::new(), CorrelationId::new());
79        assert!(env.type_name().ends_with("::Pong"));
80    }
81
82    #[test]
83    fn envelope_preserves_identifiers() {
84        let msg = MessageId::new();
85        let corr = CorrelationId::new();
86        let env = MessageEnvelope::for_command::<Ping>(msg, corr);
87        assert_eq!(env.message_id(), msg);
88        assert_eq!(env.correlation_id(), corr);
89    }
90
91    #[test]
92    fn envelope_is_clone() {
93        let env = MessageEnvelope::for_command::<Ping>(MessageId::new(), CorrelationId::new());
94        let cloned = env.clone();
95        assert_eq!(env.message_id(), cloned.message_id());
96        assert_eq!(env.type_name(), cloned.type_name());
97    }
98}