1use std::fmt;
8
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12use crate::collective::Collective;
13use crate::experience::Experience;
14use crate::insight::DerivedInsight;
15use crate::relation::ExperienceRelation;
16use crate::types::{CollectiveId, ExperienceId, InsightId, RelationId, Timestamp};
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub struct InstanceId(pub Uuid);
38
39impl InstanceId {
40 #[inline]
42 pub fn new() -> Self {
43 Self(Uuid::now_v7())
44 }
45
46 #[inline]
49 pub fn nil() -> Self {
50 Self(Uuid::nil())
51 }
52
53 #[inline]
55 pub fn as_bytes(&self) -> &[u8; 16] {
56 self.0.as_bytes()
57 }
58
59 #[inline]
61 pub fn from_bytes(bytes: [u8; 16]) -> Self {
62 Self(Uuid::from_bytes(bytes))
63 }
64}
65
66impl Default for InstanceId {
67 fn default() -> Self {
71 Self::nil()
72 }
73}
74
75impl fmt::Display for InstanceId {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "{}", self.0)
78 }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
91pub struct SyncCursor {
92 pub instance_id: InstanceId,
94
95 pub last_sequence: u64,
97}
98
99impl SyncCursor {
100 pub fn new(instance_id: InstanceId) -> Self {
102 Self {
103 instance_id,
104 last_sequence: 0,
105 }
106 }
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
115#[repr(u8)]
116pub enum SyncEntityType {
117 Experience = 0,
119 Relation = 1,
121 Insight = 2,
123 Collective = 3,
125}
126
127#[derive(Clone, Debug, Default, Serialize, Deserialize)]
137pub struct SerializableExperienceUpdate {
138 pub importance: Option<f32>,
140
141 pub confidence: Option<f32>,
143
144 pub domain: Option<Vec<String>>,
146
147 pub related_files: Option<Vec<String>>,
149
150 pub archived: Option<bool>,
152}
153
154impl From<crate::experience::ExperienceUpdate> for SerializableExperienceUpdate {
155 fn from(update: crate::experience::ExperienceUpdate) -> Self {
156 Self {
157 importance: update.importance,
158 confidence: update.confidence,
159 domain: update.domain,
160 related_files: update.related_files,
161 archived: update.archived,
162 }
163 }
164}
165
166impl From<SerializableExperienceUpdate> for crate::experience::ExperienceUpdate {
167 fn from(update: SerializableExperienceUpdate) -> Self {
168 Self {
169 importance: update.importance,
170 confidence: update.confidence,
171 domain: update.domain,
172 related_files: update.related_files,
173 archived: update.archived,
174 }
175 }
176}
177
178#[derive(Clone, Debug, Serialize, Deserialize)]
188pub enum SyncPayload {
189 ExperienceCreated(Experience),
191
192 ExperienceUpdated {
194 id: ExperienceId,
196 update: SerializableExperienceUpdate,
198 timestamp: Timestamp,
200 },
201
202 ExperienceArchived {
204 id: ExperienceId,
206 timestamp: Timestamp,
208 },
209
210 ExperienceDeleted {
212 id: ExperienceId,
214 timestamp: Timestamp,
216 },
217
218 RelationCreated(ExperienceRelation),
220
221 RelationDeleted {
223 id: RelationId,
225 timestamp: Timestamp,
227 },
228
229 InsightCreated(DerivedInsight),
231
232 InsightDeleted {
234 id: InsightId,
236 timestamp: Timestamp,
238 },
239
240 CollectiveCreated(Collective),
242}
243
244#[derive(Clone, Debug, Serialize, Deserialize)]
253pub struct SyncChange {
254 pub sequence: u64,
256
257 pub source_instance: InstanceId,
259
260 pub collective_id: CollectiveId,
262
263 pub entity_type: SyncEntityType,
265
266 pub payload: SyncPayload,
268
269 pub timestamp: Timestamp,
271}
272
273#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
279pub enum SyncStatus {
280 Idle,
282 Syncing,
284 Error(String),
286 Disconnected,
288}
289
290#[derive(Clone, Debug, Serialize, Deserialize)]
296pub struct HandshakeRequest {
297 pub instance_id: InstanceId,
299 pub protocol_version: u32,
301 pub capabilities: Vec<String>,
303}
304
305#[derive(Clone, Debug, Serialize, Deserialize)]
307pub struct HandshakeResponse {
308 pub instance_id: InstanceId,
310 pub protocol_version: u32,
312 pub accepted: bool,
314 pub reason: Option<String>,
316}
317
318#[derive(Clone, Debug, Serialize, Deserialize)]
324pub struct PullRequest {
325 pub cursor: SyncCursor,
327 pub batch_size: usize,
329 pub collectives: Option<Vec<CollectiveId>>,
331}
332
333#[derive(Clone, Debug, Serialize, Deserialize)]
335pub struct PullResponse {
336 pub changes: Vec<SyncChange>,
338 pub has_more: bool,
340 pub new_cursor: SyncCursor,
342}
343
344#[derive(Clone, Debug, Serialize, Deserialize)]
350pub struct PushResponse {
351 pub accepted: usize,
353 pub rejected: usize,
355 pub new_cursor: SyncCursor,
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_instance_id_new_is_unique() {
365 let a = InstanceId::new();
366 let b = InstanceId::new();
367 assert_ne!(a, b);
368 }
369
370 #[test]
371 fn test_instance_id_nil() {
372 let id = InstanceId::nil();
373 assert_eq!(id, InstanceId::default());
374 assert_eq!(id.0, Uuid::nil());
375 }
376
377 #[test]
378 fn test_instance_id_bytes_roundtrip() {
379 let id = InstanceId::new();
380 let bytes = *id.as_bytes();
381 let restored = InstanceId::from_bytes(bytes);
382 assert_eq!(id, restored);
383 }
384
385 #[test]
386 fn test_instance_id_display() {
387 let id = InstanceId::nil();
388 assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000000");
389 }
390
391 #[test]
392 fn test_instance_id_bincode_roundtrip() {
393 let id = InstanceId::new();
394 let bytes = bincode::serialize(&id).unwrap();
395 let restored: InstanceId = bincode::deserialize(&bytes).unwrap();
396 assert_eq!(id, restored);
397 }
398
399 #[test]
400 fn test_sync_cursor_new() {
401 let id = InstanceId::new();
402 let cursor = SyncCursor::new(id);
403 assert_eq!(cursor.instance_id, id);
404 assert_eq!(cursor.last_sequence, 0);
405 }
406
407 #[test]
408 fn test_sync_cursor_bincode_roundtrip() {
409 let cursor = SyncCursor {
410 instance_id: InstanceId::new(),
411 last_sequence: 42,
412 };
413 let bytes = bincode::serialize(&cursor).unwrap();
414 let restored: SyncCursor = bincode::deserialize(&bytes).unwrap();
415 assert_eq!(cursor, restored);
416 }
417
418 #[test]
419 fn test_sync_entity_type_repr() {
420 assert_eq!(SyncEntityType::Experience as u8, 0);
421 assert_eq!(SyncEntityType::Relation as u8, 1);
422 assert_eq!(SyncEntityType::Insight as u8, 2);
423 assert_eq!(SyncEntityType::Collective as u8, 3);
424 }
425
426 #[test]
427 fn test_serializable_experience_update_from_conversion() {
428 let update = crate::experience::ExperienceUpdate {
429 importance: Some(0.9),
430 confidence: None,
431 domain: Some(vec!["rust".to_string()]),
432 related_files: None,
433 archived: Some(false),
434 };
435 let serializable: SerializableExperienceUpdate = update.into();
436 assert_eq!(serializable.importance, Some(0.9));
437 assert_eq!(serializable.confidence, None);
438 assert_eq!(serializable.domain, Some(vec!["rust".to_string()]));
439 assert_eq!(serializable.archived, Some(false));
440 }
441
442 #[test]
443 fn test_serializable_experience_update_into_conversion() {
444 let serializable = SerializableExperienceUpdate {
445 importance: Some(0.5),
446 confidence: Some(0.8),
447 domain: None,
448 related_files: Some(vec!["main.rs".to_string()]),
449 archived: None,
450 };
451 let update: crate::experience::ExperienceUpdate = serializable.into();
452 assert_eq!(update.importance, Some(0.5));
453 assert_eq!(update.confidence, Some(0.8));
454 assert_eq!(update.related_files, Some(vec!["main.rs".to_string()]));
455 }
456
457 #[test]
458 fn test_serializable_experience_update_bincode_roundtrip() {
459 let update = SerializableExperienceUpdate {
460 importance: Some(0.7),
461 confidence: Some(0.9),
462 domain: Some(vec!["test".to_string()]),
463 related_files: None,
464 archived: Some(true),
465 };
466 let bytes = bincode::serialize(&update).unwrap();
467 let restored: SerializableExperienceUpdate = bincode::deserialize(&bytes).unwrap();
468 assert_eq!(update.importance, restored.importance);
469 assert_eq!(update.confidence, restored.confidence);
470 assert_eq!(update.domain, restored.domain);
471 assert_eq!(update.archived, restored.archived);
472 }
473
474 #[test]
475 fn test_sync_status_equality() {
476 assert_eq!(SyncStatus::Idle, SyncStatus::Idle);
477 assert_eq!(SyncStatus::Error("x".into()), SyncStatus::Error("x".into()));
478 assert_ne!(SyncStatus::Idle, SyncStatus::Syncing);
479 }
480
481 #[test]
482 fn test_handshake_request_bincode_roundtrip() {
483 let req = HandshakeRequest {
484 instance_id: InstanceId::new(),
485 protocol_version: 1,
486 capabilities: vec!["push".to_string(), "pull".to_string()],
487 };
488 let bytes = bincode::serialize(&req).unwrap();
489 let restored: HandshakeRequest = bincode::deserialize(&bytes).unwrap();
490 assert_eq!(req.instance_id, restored.instance_id);
491 assert_eq!(req.protocol_version, restored.protocol_version);
492 assert_eq!(req.capabilities, restored.capabilities);
493 }
494
495 #[test]
496 fn test_pull_request_bincode_roundtrip() {
497 let req = PullRequest {
498 cursor: SyncCursor::new(InstanceId::new()),
499 batch_size: 500,
500 collectives: Some(vec![CollectiveId::new()]),
501 };
502 let bytes = bincode::serialize(&req).unwrap();
503 let restored: PullRequest = bincode::deserialize(&bytes).unwrap();
504 assert_eq!(req.cursor, restored.cursor);
505 assert_eq!(req.batch_size, restored.batch_size);
506 }
507
508 #[test]
509 fn test_push_response_bincode_roundtrip() {
510 let resp = PushResponse {
511 accepted: 10,
512 rejected: 2,
513 new_cursor: SyncCursor {
514 instance_id: InstanceId::new(),
515 last_sequence: 100,
516 },
517 };
518 let bytes = bincode::serialize(&resp).unwrap();
519 let restored: PushResponse = bincode::deserialize(&bytes).unwrap();
520 assert_eq!(resp.accepted, restored.accepted);
521 assert_eq!(resp.rejected, restored.rejected);
522 assert_eq!(resp.new_cursor, restored.new_cursor);
523 }
524}