1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! This module contains the definition of a [Message] type, which
//! can be used to describe some sort of domain value such as a [Domain Event][crate::event::Envelope],
//! a [Domain Command][crate::command::Envelope], and so on.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

/// Represents a piece of domain data that occurs in the system.
///
/// Each Message has a specific name to it, which should ideally be
/// unique within the domain you're operating in. Example: a Domain Event
/// that represents when an Order was created can have a `name()`: `"OrderWasCreated"`.
pub trait Message {
    /// Returns the domain name of the [Message].
    fn name(&self) -> &'static str;
}

/// Optional metadata to attach to an [Envelope] to provide additional context
/// to the [Message] carried out.
pub type Metadata = HashMap<String, String>;

/// Represents a [Message] packaged for persistance and/or processing by other
/// parts of the system.
///
/// It carries both the actual message (i.e. a payload) and some optional [Metadata].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Envelope<T>
where
    T: Message,
{
    /// The message payload.
    pub message: T,
    /// Optional metadata to provide additional context to the message.
    pub metadata: Metadata,
}

impl<T> Envelope<T>
where
    T: Message,
{
    /// Adds a new entry in the [Envelope]'s [Metadata].
    #[must_use]
    pub fn with_metadata(mut self, key: String, value: String) -> Self {
        self.metadata.insert(key, value);
        self
    }
}

impl<T> From<T> for Envelope<T>
where
    T: Message,
{
    fn from(message: T) -> Self {
        Envelope {
            message,
            metadata: Metadata::default(),
        }
    }
}

impl<T> PartialEq for Envelope<T>
where
    T: Message + PartialEq,
{
    fn eq(&self, other: &Envelope<T>) -> bool {
        self.message == other.message
    }
}

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

    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub(crate) struct StringMessage(pub(crate) &'static str);

    impl Message for StringMessage {
        fn name(&self) -> &'static str {
            "string_payload"
        }
    }

    #[test]
    fn message_with_metadata_does_not_affect_equality() {
        let message = Envelope {
            message: StringMessage("hello"),
            metadata: Metadata::default(),
        };

        let new_message = message
            .clone()
            .with_metadata("hello_world".into(), "test".into())
            .with_metadata("test_number".into(), 1.to_string());

        println!("Message: {message:?}");
        println!("New message: {new_message:?}");

        // Metadata does not affect equality of message.
        assert_eq!(message, new_message);
    }
}