Skip to main content

appcore_core/
envelope.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: envelope.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/05/29 20:47:35 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/07/23 23:50:45 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! Transport-neutral command/event envelope contracts.
12//! These are passed to command handlers and returned in command results.
13
14use crate::error::RuntimeResult;
15use crate::ids::{validate_identifier, AppId, CommandName, EventName, NodeId};
16use crate::trace::TraceContext;
17
18/// Immutable command envelope.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct CommandEnvelope {
21    /// Stable command name.
22    pub command_name: CommandName,
23    /// Unique command identity.
24    pub command_id: String,
25    /// Application issuing the command.
26    pub app_id: AppId,
27    /// Node issuing the command.
28    pub node_id: NodeId,
29    /// Issue timestamp in Unix milliseconds.
30    pub issued_at_ms: u64,
31    /// Optional key used to deduplicate mutating commands.
32    pub idempotency_key: Option<String>,
33    /// Opaque application-owned payload.
34    pub payload: Vec<u8>,
35    /// Optional distributed trace context.
36    pub trace: Option<TraceContext>,
37}
38
39impl CommandEnvelope {
40    /// Creates and validates a command envelope.
41    pub fn new(
42        command_name: CommandName,
43        command_id: String,
44        app_id: AppId,
45        node_id: NodeId,
46        issued_at_ms: u64,
47        idempotency_key: Option<String>,
48        payload: Vec<u8>,
49    ) -> RuntimeResult<Self> {
50        validate_identifier("CommandId", &command_id)?;
51        if let Some(key) = &idempotency_key {
52            validate_identifier("IdempotencyKey", key)?;
53        }
54        command_name.validate()?;
55        app_id.validate()?;
56        node_id.validate()?;
57
58        Ok(Self {
59            command_name,
60            command_id,
61            app_id,
62            node_id,
63            issued_at_ms,
64            idempotency_key,
65            payload,
66            trace: None,
67        })
68    }
69
70    /// Attaches distributed trace context.
71    pub fn with_trace(mut self, trace: TraceContext) -> Self {
72        self.trace = Some(trace);
73        self
74    }
75
76    /// Returns the command name.
77    pub fn command_name(&self) -> &CommandName {
78        &self.command_name
79    }
80
81    /// Returns opaque command payload bytes.
82    pub fn payload(&self) -> &[u8] {
83        &self.payload
84    }
85}
86
87/// Immutable event envelope.
88#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
89pub struct EventEnvelope {
90    /// Stable event name.
91    pub event_name: EventName,
92    /// Unique event identity.
93    pub event_id: String,
94    /// Application that emitted the event.
95    pub app_id: AppId,
96    /// Node that emitted the event.
97    pub node_id: NodeId,
98    /// Occurrence timestamp in Unix milliseconds.
99    pub occurred_at_ms: u64,
100    /// Opaque application-owned payload.
101    pub payload: Vec<u8>,
102    /// Optional distributed trace context.
103    pub trace: Option<TraceContext>,
104}
105
106impl EventEnvelope {
107    /// Creates and validates an event envelope.
108    pub fn new(
109        event_name: EventName,
110        event_id: String,
111        app_id: AppId,
112        node_id: NodeId,
113        occurred_at_ms: u64,
114        payload: Vec<u8>,
115    ) -> RuntimeResult<Self> {
116        validate_identifier("EventId", &event_id)?;
117        event_name.validate()?;
118        app_id.validate()?;
119        node_id.validate()?;
120
121        Ok(Self {
122            event_name,
123            event_id,
124            app_id,
125            node_id,
126            occurred_at_ms,
127            payload,
128            trace: None,
129        })
130    }
131
132    /// Attaches distributed trace context.
133    pub fn with_trace(mut self, trace: TraceContext) -> Self {
134        self.trace = Some(trace);
135        self
136    }
137
138    /// Returns the event name.
139    pub fn event_name(&self) -> &EventName {
140        &self.event_name
141    }
142
143    /// Returns opaque event payload bytes.
144    pub fn payload(&self) -> &[u8] {
145        &self.payload
146    }
147}
148
149#[cfg(test)]
150#[path = "envelope_tests.rs"]
151mod tests;