1use lnmp_envelope::LnmpEnvelope;
4
5use crate::error::{NetError, Result};
6use crate::kind::MessageKind;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone)]
42#[cfg_attr(
43 feature = "serde",
44 derive(Serialize, Deserialize),
45 serde(bound(deserialize = ""))
46)]
47pub struct NetMessage {
48 pub envelope: LnmpEnvelope,
50
51 pub kind: MessageKind,
53
54 pub priority: u8,
56
57 pub ttl_ms: u32,
59
60 pub class: Option<String>,
62}
63
64impl NetMessage {
65 pub fn new(envelope: LnmpEnvelope, kind: MessageKind) -> Self {
69 Self {
70 envelope,
71 kind,
72 priority: kind.default_priority(),
73 ttl_ms: kind.default_ttl_ms(),
74 class: None,
75 }
76 }
77
78 pub fn with_qos(envelope: LnmpEnvelope, kind: MessageKind, priority: u8, ttl_ms: u32) -> Self {
80 Self {
81 envelope,
82 kind,
83 priority,
84 ttl_ms,
85 class: None,
86 }
87 }
88
89 pub fn is_expired(&self, now_ms: u64) -> Result<bool> {
114 let timestamp = self
115 .envelope
116 .metadata
117 .timestamp
118 .ok_or(NetError::MissingTimestamp)?;
119
120 let age_ms = now_ms.saturating_sub(timestamp);
121 Ok(age_ms > self.ttl_ms as u64)
122 }
123
124 pub fn age_ms(&self, now_ms: u64) -> Option<u64> {
128 self.envelope
129 .metadata
130 .timestamp
131 .map(|ts| now_ms.saturating_sub(ts))
132 }
133
134 pub fn source(&self) -> Option<&str> {
136 self.envelope.metadata.source.as_deref()
137 }
138
139 pub fn trace_id(&self) -> Option<&str> {
141 self.envelope.metadata.trace_id.as_deref()
142 }
143
144 pub fn timestamp(&self) -> Option<u64> {
146 self.envelope.metadata.timestamp
147 }
148
149 pub fn record(&self) -> &lnmp_core::LnmpRecord {
151 &self.envelope.record
152 }
153
154 pub fn validate(&self) -> Result<()> {
156 self.envelope.validate()?;
157 Ok(())
158 }
159}
160
161pub struct NetMessageBuilder {
188 envelope: LnmpEnvelope,
189 kind: MessageKind,
190 priority: u8,
191 ttl_ms: u32,
192 class: Option<String>,
193}
194
195impl NetMessageBuilder {
196 pub fn new(envelope: LnmpEnvelope, kind: MessageKind) -> Self {
198 Self {
199 envelope,
200 kind,
201 priority: kind.default_priority(),
202 ttl_ms: kind.default_ttl_ms(),
203 class: None,
204 }
205 }
206
207 pub fn priority(mut self, priority: u8) -> Self {
209 self.priority = priority;
210 self
211 }
212
213 pub fn ttl_ms(mut self, ttl_ms: u32) -> Self {
215 self.ttl_ms = ttl_ms;
216 self
217 }
218
219 pub fn class(mut self, class: impl Into<String>) -> Self {
221 self.class = Some(class.into());
222 self
223 }
224
225 pub fn build(self) -> NetMessage {
227 NetMessage {
228 envelope: self.envelope,
229 kind: self.kind,
230 priority: self.priority,
231 ttl_ms: self.ttl_ms,
232 class: self.class,
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use lnmp_core::{LnmpField, LnmpRecord, LnmpValue};
241 use lnmp_envelope::EnvelopeBuilder;
242
243 fn sample_record() -> LnmpRecord {
244 let mut record = LnmpRecord::new();
245 record.add_field(LnmpField {
246 fid: 42,
247 value: LnmpValue::Int(100),
248 });
249 record
250 }
251
252 fn sample_envelope(timestamp: u64) -> LnmpEnvelope {
253 EnvelopeBuilder::new(sample_record())
254 .timestamp(timestamp)
255 .source("test-node")
256 .build()
257 }
258
259 #[test]
260 fn test_new_message_uses_kind_defaults() {
261 let envelope = sample_envelope(1000);
262 let msg = NetMessage::new(envelope, MessageKind::Alert);
263
264 assert_eq!(msg.kind, MessageKind::Alert);
265 assert_eq!(msg.priority, 255); assert_eq!(msg.ttl_ms, 1000); }
268
269 #[test]
270 fn test_is_expired_fresh_message() {
271 let envelope = sample_envelope(1000);
272 let msg = NetMessage::with_qos(envelope, MessageKind::Event, 100, 5000);
273
274 assert!(!msg.is_expired(5000).unwrap());
276 }
277
278 #[test]
279 fn test_is_expired_old_message() {
280 let envelope = sample_envelope(1000);
281 let msg = NetMessage::with_qos(envelope, MessageKind::Event, 100, 5000);
282
283 assert!(msg.is_expired(7000).unwrap());
285 }
286
287 #[test]
288 fn test_is_expired_missing_timestamp() {
289 let envelope = LnmpEnvelope::new(sample_record());
290 let msg = NetMessage::new(envelope, MessageKind::Event);
291
292 assert!(msg.is_expired(5000).is_err());
293 }
294
295 #[test]
296 fn test_age_ms() {
297 let envelope = sample_envelope(1000);
298 let msg = NetMessage::new(envelope, MessageKind::Event);
299
300 assert_eq!(msg.age_ms(6000), Some(5000));
301 }
302
303 #[test]
304 fn test_age_ms_missing_timestamp() {
305 let envelope = LnmpEnvelope::new(sample_record());
306 let msg = NetMessage::new(envelope, MessageKind::Event);
307
308 assert_eq!(msg.age_ms(5000), None);
309 }
310
311 #[test]
312 fn test_accessors() {
313 let envelope = EnvelopeBuilder::new(sample_record())
314 .timestamp(1000)
315 .source("node-alpha")
316 .trace_id("trace-123")
317 .build();
318
319 let msg = NetMessage::new(envelope, MessageKind::State);
320
321 assert_eq!(msg.source(), Some("node-alpha"));
322 assert_eq!(msg.trace_id(), Some("trace-123"));
323 assert_eq!(msg.timestamp(), Some(1000));
324 }
325
326 #[test]
327 fn test_builder_defaults() {
328 let envelope = sample_envelope(1000);
329 let msg = NetMessageBuilder::new(envelope, MessageKind::Command).build();
330
331 assert_eq!(msg.priority, 150); assert_eq!(msg.ttl_ms, 2000); assert_eq!(msg.class, None);
334 }
335
336 #[test]
337 fn test_builder_custom_values() {
338 let envelope = sample_envelope(1000);
339 let msg = NetMessageBuilder::new(envelope, MessageKind::Event)
340 .priority(200)
341 .ttl_ms(10000)
342 .class("health")
343 .build();
344
345 assert_eq!(msg.priority, 200);
346 assert_eq!(msg.ttl_ms, 10000);
347 assert_eq!(msg.class, Some("health".to_string()));
348 }
349
350 #[test]
351 fn test_validate() {
352 let envelope = sample_envelope(1000);
353 let msg = NetMessage::new(envelope, MessageKind::Event);
354
355 assert!(msg.validate().is_ok());
356 }
357}