Skip to main content

made_core/entities/
task_metadata.rs

1use serde::{Deserialize, Serialize};
2
3use crate::events::EventEnvelope;
4use crate::value_objects::{Attributes, CouncilContractId, EventId, OutputContractId};
5
6/// Causality and contract metadata that travels with a task.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
8#[serde(default)]
9pub struct TaskMetadata {
10    source_event_id: Option<EventId>,
11    causation_id: Option<EventId>,
12    correlation_id: Option<EventId>,
13    council_contract_id: Option<CouncilContractId>,
14    output_contract_id: Option<OutputContractId>,
15    execution_profile: Attributes,
16}
17
18impl TaskMetadata {
19    #[must_use]
20    pub fn new(
21        source_event_id: Option<EventId>,
22        causation_id: Option<EventId>,
23        correlation_id: Option<EventId>,
24        council_contract_id: Option<CouncilContractId>,
25        output_contract_id: Option<OutputContractId>,
26        execution_profile: Attributes,
27    ) -> Self {
28        Self {
29            source_event_id,
30            causation_id,
31            correlation_id,
32            council_contract_id,
33            output_contract_id,
34            execution_profile,
35        }
36    }
37
38    #[must_use]
39    pub fn from_trigger_envelope(envelope: &EventEnvelope) -> Self {
40        Self::default().with_trigger_envelope(envelope)
41    }
42
43    #[must_use]
44    pub fn with_trigger_envelope(mut self, envelope: &EventEnvelope) -> Self {
45        if self.source_event_id.is_none() {
46            self.source_event_id = Some(envelope.event_id().clone());
47        }
48        if self.causation_id.is_none() {
49            self.causation_id = envelope
50                .causation_id()
51                .cloned()
52                .or_else(|| Some(envelope.event_id().clone()));
53        }
54        if self.correlation_id.is_none() {
55            self.correlation_id = envelope
56                .correlation_id()
57                .cloned()
58                .or_else(|| Some(envelope.event_id().clone()));
59        }
60        self
61    }
62
63    #[must_use]
64    pub fn source_event_id(&self) -> Option<&EventId> {
65        self.source_event_id.as_ref()
66    }
67
68    #[must_use]
69    pub fn causation_id(&self) -> Option<&EventId> {
70        self.causation_id.as_ref()
71    }
72
73    #[must_use]
74    pub fn correlation_id(&self) -> Option<&EventId> {
75        self.correlation_id.as_ref()
76    }
77
78    #[must_use]
79    pub fn council_contract_id(&self) -> Option<&CouncilContractId> {
80        self.council_contract_id.as_ref()
81    }
82
83    #[must_use]
84    pub fn output_contract_id(&self) -> Option<&OutputContractId> {
85        self.output_contract_id.as_ref()
86    }
87
88    #[must_use]
89    pub fn execution_profile(&self) -> &Attributes {
90        &self.execution_profile
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use time::macros::datetime;
98
99    #[test]
100    fn derives_causality_from_trigger_envelope() {
101        let envelope = EventEnvelope::new_with_causation(
102            EventId::new("trigger-1").unwrap(),
103            datetime!(2026-04-15 12:00:00 UTC),
104            "pir",
105            Some(EventId::new("corr-1").unwrap()),
106            Some(EventId::new("cause-1").unwrap()),
107        )
108        .unwrap();
109
110        let metadata = TaskMetadata::from_trigger_envelope(&envelope);
111
112        assert_eq!(metadata.source_event_id().unwrap().as_str(), "trigger-1");
113        assert_eq!(metadata.causation_id().unwrap().as_str(), "cause-1");
114        assert_eq!(metadata.correlation_id().unwrap().as_str(), "corr-1");
115    }
116}