@celestia-island/plana-types 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +38 -0
- package/bindings/FileAnchor.ts +3 -0
- package/bindings/engine.ts +277 -0
- package/bindings/enums.ts +31 -0
- package/bindings/httpTypes.ts +197 -0
- package/bindings/index.ts +47 -0
- package/bindings/mcp/aporia.ts +61 -0
- package/bindings/mcp/eleos.ts +18 -0
- package/bindings/mcp/epieikeia.ts +48 -0
- package/bindings/mcp/haplotes.ts +47 -0
- package/bindings/mcp/hubris.ts +41 -0
- package/bindings/mcp/index.ts +13 -0
- package/bindings/mcp/kalos.ts +47 -0
- package/bindings/mcp/neikos.ts +76 -0
- package/bindings/mcp/orexis.ts +64 -0
- package/bindings/mcp/philia.ts +57 -0
- package/bindings/mcp/polemos.ts +55 -0
- package/bindings/mcp/skemma.ts +58 -0
- package/bindings/mcp/skopeo.ts +56 -0
- package/bindings/mcp/webAutomation.ts +31 -0
- package/bindings/model.ts +240 -0
- package/bindings/package.json +16 -0
- package/bindings/region.ts +3 -0
- package/bindings/serde_json/JsonValue.ts +3 -0
- package/bindings/ws/agentLifecycle.ts +41 -0
- package/bindings/ws/auth.ts +15 -0
- package/bindings/ws/baseMessages.ts +7 -0
- package/bindings/ws/bridgeNetwork.ts +71 -0
- package/bindings/ws/core.ts +51 -0
- package/bindings/ws/fileBrowsing.ts +57 -0
- package/bindings/ws/handshake.ts +23 -0
- package/bindings/ws/industrial.ts +72 -0
- package/bindings/ws/knowledgeBase.ts +10 -0
- package/bindings/ws/layer2.ts +25 -0
- package/bindings/ws/llmProvider.ts +74 -0
- package/bindings/ws/logs.ts +11 -0
- package/bindings/ws/malkuth.ts +62 -0
- package/bindings/ws/noa.ts +17 -0
- package/bindings/ws/stateSync.ts +19 -0
- package/bindings/ws/systemUi.ts +5 -0
- package/bindings/ws/tasks.ts +6 -0
- package/bindings/ws/views.ts +163 -0
- package/bindings/ws/workspace.ts +11 -0
- package/bindings/ws/yolo.ts +32 -0
- package/examples/schema_dump.rs +51 -0
- package/package.json +6 -0
- package/pnpm-workspace.yaml +2 -0
- package/src/engine.rs +602 -0
- package/src/enums.rs +334 -0
- package/src/external_mcp.rs +132 -0
- package/src/http.rs +1077 -0
- package/src/identity.rs +160 -0
- package/src/lib.rs +1215 -0
- package/src/malkuth.rs +145 -0
- package/src/mcp/aporia.rs +272 -0
- package/src/mcp/eleos.rs +210 -0
- package/src/mcp/epieikeia.rs +194 -0
- package/src/mcp/haplotes.rs +310 -0
- package/src/mcp/hubris.rs +377 -0
- package/src/mcp/kalos.rs +251 -0
- package/src/mcp/mod.rs +23 -0
- package/src/mcp/neikos.rs +533 -0
- package/src/mcp/orexis.rs +493 -0
- package/src/mcp/philia.rs +267 -0
- package/src/mcp/polemos.rs +241 -0
- package/src/mcp/skemma.rs +442 -0
- package/src/mcp/skopeo.rs +282 -0
- package/src/mcp/web_automation.rs +122 -0
- package/src/model.rs +421 -0
- package/src/protocol/base_messages.rs +125 -0
- package/src/protocol/handshake.rs +364 -0
- package/src/protocol/jsonrpc.rs +888 -0
- package/src/protocol/mod.rs +10 -0
- package/src/rbac.rs +786 -0
- package/src/region.rs +362 -0
- package/src/tracing_helpers.rs +9 -0
- package/src/ws/agent/agent_lifecycle.rs +221 -0
- package/src/ws/agent/layer2.rs +126 -0
- package/src/ws/agent/mod.rs +9 -0
- package/src/ws/agent/state_sync.rs +111 -0
- package/src/ws/agent/tasks.rs +43 -0
- package/src/ws/agent/yolo.rs +161 -0
- package/src/ws/mod.rs +10 -0
- package/src/ws/services/auth.rs +99 -0
- package/src/ws/services/industrial.rs +647 -0
- package/src/ws/services/knowledge_base.rs +59 -0
- package/src/ws/services/llm_provider.rs +371 -0
- package/src/ws/services/mod.rs +7 -0
- package/src/ws/ui/bridge_network.rs +96 -0
- package/src/ws/ui/file_browsing.rs +88 -0
- package/src/ws/ui/logs.rs +55 -0
- package/src/ws/ui/mod.rs +11 -0
- package/src/ws/ui/noa.rs +105 -0
- package/src/ws/ui/system_ui.rs +27 -0
- package/src/ws/ui/views.rs +159 -0
- package/src/ws/ui/workspace.rs +73 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
//! WebSocket connection handshake & client-capability types.
|
|
2
|
+
|
|
3
|
+
use schemars::JsonSchema;
|
|
4
|
+
use serde::{Deserialize, Serialize};
|
|
5
|
+
use ts_rs::TS;
|
|
6
|
+
|
|
7
|
+
/// Handshake wire-protocol version. Bumped on incompatible changes to the
|
|
8
|
+
/// handshake payload itself. OLD clients that omit `protocol_version` still
|
|
9
|
+
/// deserialize via the serde default and are treated as v1.
|
|
10
|
+
pub const HANDSHAKE_VERSION: u32 = 1;
|
|
11
|
+
|
|
12
|
+
fn default_protocol_version() -> u32 {
|
|
13
|
+
HANDSHAKE_VERSION
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ═══════════════════════════════════════════════════════════════
|
|
17
|
+
// Connection / Handshake
|
|
18
|
+
// ═══════════════════════════════════════════════════════════════
|
|
19
|
+
|
|
20
|
+
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
|
21
|
+
#[ts(export, export_to = "ws/handshake.ts")]
|
|
22
|
+
pub struct HandshakeAckParams {
|
|
23
|
+
pub ok: bool,
|
|
24
|
+
#[serde(default)]
|
|
25
|
+
#[ts(optional)]
|
|
26
|
+
pub error: Option<String>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
|
30
|
+
#[ts(export, export_to = "ws/handshake.ts")]
|
|
31
|
+
pub struct ScepterIdentityParams {
|
|
32
|
+
#[ts(type = "string")]
|
|
33
|
+
pub device_id: uuid::Uuid,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
|
37
|
+
#[ts(export, export_to = "ws/handshake.ts")]
|
|
38
|
+
pub struct PingParams {
|
|
39
|
+
pub timestamp: u64,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════
|
|
43
|
+
// Client capability + handshake payload
|
|
44
|
+
//
|
|
45
|
+
// Mirrors `entelecheia/packages/shared/state_types/src/gateway/
|
|
46
|
+
// tui_types/message/types/mod.rs`. The webui declares capabilities
|
|
47
|
+
// in its `Sync.ConnectHandshake` so scepter's `client_node_registry`
|
|
48
|
+
// can route capability-scoped requests back to it (e.g. NOA
|
|
49
|
+
// handshakes are only sent to sessions that declared
|
|
50
|
+
// `ClientCapability::NoaWorkspace`).
|
|
51
|
+
// ═══════════════════════════════════════════════════════════════
|
|
52
|
+
|
|
53
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS, JsonSchema)]
|
|
54
|
+
#[ts(export, export_to = "ws/handshake.ts")]
|
|
55
|
+
#[serde(rename_all = "snake_case")]
|
|
56
|
+
pub enum ClientCapability {
|
|
57
|
+
FileRelay,
|
|
58
|
+
Terminal,
|
|
59
|
+
ScreenCapture,
|
|
60
|
+
NoaWorkspace,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
|
64
|
+
#[ts(export, export_to = "ws/handshake.ts")]
|
|
65
|
+
pub struct ClientNodeInfo {
|
|
66
|
+
pub hostname: String,
|
|
67
|
+
pub os: String,
|
|
68
|
+
#[serde(default)]
|
|
69
|
+
#[ts(optional)]
|
|
70
|
+
pub workspace_root: Option<String>,
|
|
71
|
+
#[serde(default)]
|
|
72
|
+
#[ts(optional)]
|
|
73
|
+
pub user_id: Option<String>,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
|
77
|
+
#[ts(export, export_to = "ws/handshake.ts")]
|
|
78
|
+
pub struct ConnectHandshakeParams {
|
|
79
|
+
/// Handshake wire-protocol version advertised by the client. Defaults to
|
|
80
|
+
/// [`HANDSHAKE_VERSION`] when absent (backward-compatible with old clients).
|
|
81
|
+
#[serde(default = "default_protocol_version")]
|
|
82
|
+
pub protocol_version: u32,
|
|
83
|
+
pub token: String,
|
|
84
|
+
#[serde(default)]
|
|
85
|
+
#[ts(optional)]
|
|
86
|
+
pub session_id: Option<String>,
|
|
87
|
+
#[serde(default)]
|
|
88
|
+
pub capabilities: Vec<ClientCapability>,
|
|
89
|
+
#[serde(default)]
|
|
90
|
+
#[ts(optional)]
|
|
91
|
+
pub node_info: Option<ClientNodeInfo>,
|
|
92
|
+
/// Stringified UUID — kept as a string on the wire so consumers
|
|
93
|
+
/// don't need a UUID parser to round-trip the JSON-RPC payload.
|
|
94
|
+
#[serde(default)]
|
|
95
|
+
#[ts(optional)]
|
|
96
|
+
pub workspace_id: Option<String>,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#[cfg(test)]
|
|
100
|
+
mod tests {
|
|
101
|
+
use super::*;
|
|
102
|
+
use serde_json::json;
|
|
103
|
+
|
|
104
|
+
// ── HandshakeAckParams ──────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
#[test]
|
|
107
|
+
fn handshake_ack_success_round_trip() {
|
|
108
|
+
let ack = HandshakeAckParams {
|
|
109
|
+
ok: true,
|
|
110
|
+
error: None,
|
|
111
|
+
};
|
|
112
|
+
let v = serde_json::to_value(&ack).unwrap();
|
|
113
|
+
assert_eq!(v["ok"], true);
|
|
114
|
+
// error is optional + #[ts(optional)] — serialized as null when None.
|
|
115
|
+
// (No skip_serializing_if, so null appears on the wire.)
|
|
116
|
+
assert_eq!(v["error"], serde_json::Value::Null);
|
|
117
|
+
let back: HandshakeAckParams = serde_json::from_value(v).unwrap();
|
|
118
|
+
assert!(back.ok);
|
|
119
|
+
assert!(back.error.is_none());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[test]
|
|
123
|
+
fn handshake_ack_failure_with_message() {
|
|
124
|
+
let ack = HandshakeAckParams {
|
|
125
|
+
ok: false,
|
|
126
|
+
error: Some("bad token".into()),
|
|
127
|
+
};
|
|
128
|
+
let v = serde_json::to_value(&ack).unwrap();
|
|
129
|
+
assert_eq!(v["ok"], false);
|
|
130
|
+
assert_eq!(v["error"], "bad token");
|
|
131
|
+
let back: HandshakeAckParams = serde_json::from_value(v).unwrap();
|
|
132
|
+
assert!(!back.ok);
|
|
133
|
+
assert_eq!(back.error.as_deref(), Some("bad token"));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[test]
|
|
137
|
+
fn handshake_ack_accepts_missing_error_field() {
|
|
138
|
+
// #[serde(default)] allows omitting error on deserialize.
|
|
139
|
+
let raw = json!({"ok": true});
|
|
140
|
+
let ack: HandshakeAckParams = serde_json::from_value(raw).unwrap();
|
|
141
|
+
assert!(ack.ok);
|
|
142
|
+
assert!(ack.error.is_none());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── ScepterIdentityParams ───────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
fn scepter_identity_uuid_serializes_as_string() {
|
|
149
|
+
let uid = uuid::Uuid::new_v4();
|
|
150
|
+
let p = ScepterIdentityParams { device_id: uid };
|
|
151
|
+
let v = serde_json::to_value(&p).unwrap();
|
|
152
|
+
// Uuid serializes as a JSON string (hyphenated).
|
|
153
|
+
assert_eq!(v["device_id"], uid.to_string());
|
|
154
|
+
let back: ScepterIdentityParams = serde_json::from_value(v).unwrap();
|
|
155
|
+
assert_eq!(back.device_id, uid);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── PingParams ──────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
#[test]
|
|
161
|
+
fn ping_params_round_trip() {
|
|
162
|
+
let p = PingParams {
|
|
163
|
+
timestamp: 1700000000,
|
|
164
|
+
};
|
|
165
|
+
let v = serde_json::to_value(&p).unwrap();
|
|
166
|
+
assert_eq!(v["timestamp"], 1700000000);
|
|
167
|
+
// No extra fields.
|
|
168
|
+
assert_eq!(v.as_object().unwrap().len(), 1);
|
|
169
|
+
let back: PingParams = serde_json::from_value(v).unwrap();
|
|
170
|
+
assert_eq!(back.timestamp, 1700000000);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#[test]
|
|
174
|
+
fn ping_params_zero_timestamp() {
|
|
175
|
+
let p = PingParams { timestamp: 0 };
|
|
176
|
+
let s = serde_json::to_string(&p).unwrap();
|
|
177
|
+
assert_eq!(s, r#"{"timestamp":0}"#);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── ClientCapability enum ───────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn client_capability_snake_case_serialization() {
|
|
184
|
+
let caps = vec![
|
|
185
|
+
ClientCapability::FileRelay,
|
|
186
|
+
ClientCapability::Terminal,
|
|
187
|
+
ClientCapability::ScreenCapture,
|
|
188
|
+
ClientCapability::NoaWorkspace,
|
|
189
|
+
];
|
|
190
|
+
let v = serde_json::to_value(&caps).unwrap();
|
|
191
|
+
assert_eq!(
|
|
192
|
+
v,
|
|
193
|
+
json!(["file_relay", "terminal", "screen_capture", "noa_workspace"])
|
|
194
|
+
);
|
|
195
|
+
let back: Vec<ClientCapability> = serde_json::from_value(v).unwrap();
|
|
196
|
+
assert_eq!(back, caps);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[test]
|
|
200
|
+
fn client_capability_round_trip_each_variant() {
|
|
201
|
+
for cap in [
|
|
202
|
+
ClientCapability::FileRelay,
|
|
203
|
+
ClientCapability::Terminal,
|
|
204
|
+
ClientCapability::ScreenCapture,
|
|
205
|
+
ClientCapability::NoaWorkspace,
|
|
206
|
+
] {
|
|
207
|
+
let s = serde_json::to_string(&cap).unwrap();
|
|
208
|
+
let back: ClientCapability = serde_json::from_str(&s).unwrap();
|
|
209
|
+
assert_eq!(back, cap);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#[test]
|
|
214
|
+
fn client_capability_rejects_unknown_variant() {
|
|
215
|
+
assert!(serde_json::from_str::<ClientCapability>("\"nonexistent\"").is_err());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── ClientNodeInfo ──────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
#[test]
|
|
221
|
+
fn client_node_info_full_round_trip() {
|
|
222
|
+
let info = ClientNodeInfo {
|
|
223
|
+
hostname: "gpu-box-01".into(),
|
|
224
|
+
os: "linux".into(),
|
|
225
|
+
workspace_root: Some("/home/user/proj".into()),
|
|
226
|
+
user_id: Some("u42".into()),
|
|
227
|
+
};
|
|
228
|
+
let v = serde_json::to_value(&info).unwrap();
|
|
229
|
+
assert_eq!(v["hostname"], "gpu-box-01");
|
|
230
|
+
assert_eq!(v["os"], "linux");
|
|
231
|
+
assert_eq!(v["workspace_root"], "/home/user/proj");
|
|
232
|
+
assert_eq!(v["user_id"], "u42");
|
|
233
|
+
let back: ClientNodeInfo = serde_json::from_value(v).unwrap();
|
|
234
|
+
assert_eq!(back.hostname, "gpu-box-01");
|
|
235
|
+
assert_eq!(back.workspace_root.as_deref(), Some("/home/user/proj"));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#[test]
|
|
239
|
+
fn client_node_info_optional_fields_serialize_as_null() {
|
|
240
|
+
let info = ClientNodeInfo {
|
|
241
|
+
hostname: "h".into(),
|
|
242
|
+
os: "linux".into(),
|
|
243
|
+
workspace_root: None,
|
|
244
|
+
user_id: None,
|
|
245
|
+
};
|
|
246
|
+
let v = serde_json::to_value(&info).unwrap();
|
|
247
|
+
// #[ts(optional)] without skip_serializing_if → null on wire.
|
|
248
|
+
assert_eq!(v["workspace_root"], serde_json::Value::Null);
|
|
249
|
+
assert_eq!(v["user_id"], serde_json::Value::Null);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[test]
|
|
253
|
+
fn client_node_info_accepts_missing_optional_fields() {
|
|
254
|
+
let raw = json!({"hostname": "h", "os": "win"});
|
|
255
|
+
let info: ClientNodeInfo = serde_json::from_value(raw).unwrap();
|
|
256
|
+
assert!(info.workspace_root.is_none());
|
|
257
|
+
assert!(info.user_id.is_none());
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── ConnectHandshakeParams ──────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
#[test]
|
|
263
|
+
fn connect_handshake_full_round_trip() {
|
|
264
|
+
let p = ConnectHandshakeParams {
|
|
265
|
+
protocol_version: 2,
|
|
266
|
+
token: "tok-abc".into(),
|
|
267
|
+
session_id: Some("sess-1".into()),
|
|
268
|
+
capabilities: vec![ClientCapability::Terminal, ClientCapability::NoaWorkspace],
|
|
269
|
+
node_info: Some(ClientNodeInfo {
|
|
270
|
+
hostname: "host".into(),
|
|
271
|
+
os: "linux".into(),
|
|
272
|
+
workspace_root: None,
|
|
273
|
+
user_id: None,
|
|
274
|
+
}),
|
|
275
|
+
workspace_id: Some("550e8400-e29b-41d4-a716-446655440000".into()),
|
|
276
|
+
};
|
|
277
|
+
let v = serde_json::to_value(&p).unwrap();
|
|
278
|
+
assert_eq!(v["protocol_version"], 2);
|
|
279
|
+
assert_eq!(v["token"], "tok-abc");
|
|
280
|
+
assert_eq!(v["session_id"], "sess-1");
|
|
281
|
+
assert_eq!(v["capabilities"][0], "terminal");
|
|
282
|
+
assert_eq!(v["capabilities"][1], "noa_workspace");
|
|
283
|
+
assert_eq!(v["node_info"]["hostname"], "host");
|
|
284
|
+
assert_eq!(v["workspace_id"], "550e8400-e29b-41d4-a716-446655440000");
|
|
285
|
+
let back: ConnectHandshakeParams = serde_json::from_value(v).unwrap();
|
|
286
|
+
assert_eq!(back.protocol_version, 2);
|
|
287
|
+
assert_eq!(back.token, "tok-abc");
|
|
288
|
+
assert_eq!(back.capabilities.len(), 2);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
#[test]
|
|
292
|
+
fn connect_handshake_defaults_protocol_version_when_absent() {
|
|
293
|
+
// Old clients that omit protocol_version must default to v1.
|
|
294
|
+
let raw = json!({"token": "tok"});
|
|
295
|
+
let p: ConnectHandshakeParams = serde_json::from_value(raw).unwrap();
|
|
296
|
+
assert_eq!(p.protocol_version, HANDSHAKE_VERSION);
|
|
297
|
+
assert_eq!(p.protocol_version, 1);
|
|
298
|
+
assert!(p.session_id.is_none());
|
|
299
|
+
assert!(p.capabilities.is_empty());
|
|
300
|
+
assert!(p.node_info.is_none());
|
|
301
|
+
assert!(p.workspace_id.is_none());
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[test]
|
|
305
|
+
fn connect_handshake_explicit_v1() {
|
|
306
|
+
let raw = json!({"protocol_version": 1, "token": "tok"});
|
|
307
|
+
let p: ConnectHandshakeParams = serde_json::from_value(raw).unwrap();
|
|
308
|
+
assert_eq!(p.protocol_version, 1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#[test]
|
|
312
|
+
fn connect_handshake_empty_capabilities() {
|
|
313
|
+
let raw = json!({"token": "tok", "capabilities": []});
|
|
314
|
+
let p: ConnectHandshakeParams = serde_json::from_value(raw).unwrap();
|
|
315
|
+
assert!(p.capabilities.is_empty());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#[test]
|
|
319
|
+
fn connect_handshake_capabilities_default_when_absent() {
|
|
320
|
+
let raw = json!({"token": "tok"});
|
|
321
|
+
let p: ConnectHandshakeParams = serde_json::from_value(raw).unwrap();
|
|
322
|
+
assert!(
|
|
323
|
+
p.capabilities.is_empty(),
|
|
324
|
+
"capabilities must default to empty vec"
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#[test]
|
|
329
|
+
fn connect_handshake_serialized_includes_protocol_version() {
|
|
330
|
+
let p = ConnectHandshakeParams {
|
|
331
|
+
protocol_version: 1,
|
|
332
|
+
token: "t".into(),
|
|
333
|
+
session_id: None,
|
|
334
|
+
capabilities: vec![],
|
|
335
|
+
node_info: None,
|
|
336
|
+
workspace_id: None,
|
|
337
|
+
};
|
|
338
|
+
let v = serde_json::to_value(&p).unwrap();
|
|
339
|
+
// protocol_version always appears (no skip_serializing_if).
|
|
340
|
+
assert!(v.get("protocol_version").is_some());
|
|
341
|
+
assert_eq!(v["protocol_version"], 1);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#[test]
|
|
345
|
+
fn connect_handshake_missing_token_rejected() {
|
|
346
|
+
let raw = json!({"protocol_version": 1});
|
|
347
|
+
assert!(serde_json::from_value::<ConnectHandshakeParams>(raw).is_err());
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[test]
|
|
351
|
+
fn connect_handshake_accepts_unknown_fields() {
|
|
352
|
+
// Forward-compat: new fields from a newer client should not break deserialization.
|
|
353
|
+
let raw = json!({"token": "t", "future_field": 42});
|
|
354
|
+
let p: ConnectHandshakeParams = serde_json::from_value(raw).unwrap();
|
|
355
|
+
assert_eq!(p.token, "t");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ── HANDSHAKE_VERSION constant ──────────────────────────────────
|
|
359
|
+
|
|
360
|
+
#[test]
|
|
361
|
+
fn handshake_version_is_one() {
|
|
362
|
+
assert_eq!(HANDSHAKE_VERSION, 1);
|
|
363
|
+
}
|
|
364
|
+
}
|