oxide_mesh/message.rs
1//! Wire protocol types.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7/// Stable peer identifier (human-chosen or auto-generated UUID).
8pub type PeerId = String;
9
10/// What a peer claims to be able to do. Free-form so federated-learning
11/// extensions can introduce new capability strings without a wire-format
12/// change.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct PeerCapability {
15 /// Capability slug (`"browser"`, `"mirror"`, `"compute"`, …).
16 pub name: String,
17 /// Optional semantic version.
18 #[serde(default)]
19 pub version: Option<String>,
20}
21
22/// One message on the mesh.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "kind", rename_all = "snake_case")]
25pub enum PeerMessage {
26 /// First message after a connection establishes — peers identify
27 /// themselves and advertise capabilities.
28 Hello {
29 /// Sender id.
30 from: PeerId,
31 /// Capability set.
32 capabilities: Vec<PeerCapability>,
33 },
34 /// Fanout publish to every peer subscribed to `topic`.
35 Broadcast {
36 /// Sender id.
37 from: PeerId,
38 /// Free-form topic.
39 topic: String,
40 /// Arbitrary JSON payload.
41 payload: serde_json::Value,
42 },
43 /// Point-to-point delivery.
44 Direct {
45 /// Sender.
46 from: PeerId,
47 /// Intended recipient.
48 to: PeerId,
49 /// Arbitrary JSON payload.
50 payload: serde_json::Value,
51 },
52 /// Distributed task assignment.
53 Task {
54 /// Sender (originator).
55 from: PeerId,
56 /// Task id (caller-chosen or auto).
57 task_id: String,
58 /// Task description.
59 spec: serde_json::Value,
60 /// Wall-clock time the task was published.
61 issued_at: DateTime<Utc>,
62 },
63 /// Result of a previously-issued [`PeerMessage::Task`].
64 Result {
65 /// Responder.
66 from: PeerId,
67 /// Same `task_id` as the originating Task.
68 task_id: String,
69 /// Result payload.
70 result: serde_json::Value,
71 /// Whether the task completed successfully.
72 ok: bool,
73 },
74}
75
76impl PeerMessage {
77 /// Sender id, useful for routing decisions.
78 pub fn sender(&self) -> &PeerId {
79 match self {
80 PeerMessage::Hello { from, .. }
81 | PeerMessage::Broadcast { from, .. }
82 | PeerMessage::Direct { from, .. }
83 | PeerMessage::Task { from, .. }
84 | PeerMessage::Result { from, .. } => from,
85 }
86 }
87
88 /// Convenience: build a broadcast message.
89 pub fn broadcast(
90 from: impl Into<PeerId>,
91 topic: impl Into<String>,
92 payload: serde_json::Value,
93 ) -> Self {
94 Self::Broadcast {
95 from: from.into(),
96 topic: topic.into(),
97 payload,
98 }
99 }
100
101 /// Convenience: build a direct message.
102 pub fn direct(
103 from: impl Into<PeerId>,
104 to: impl Into<PeerId>,
105 payload: serde_json::Value,
106 ) -> Self {
107 Self::Direct {
108 from: from.into(),
109 to: to.into(),
110 payload,
111 }
112 }
113
114 /// Convenience: build a task with an auto-generated id.
115 pub fn task(from: impl Into<PeerId>, spec: serde_json::Value) -> Self {
116 Self::Task {
117 from: from.into(),
118 task_id: Uuid::new_v4().to_string(),
119 spec,
120 issued_at: Utc::now(),
121 }
122 }
123}