1use crate::prelude::*;
2use bitcoin::hashes::sha256::Hash as Sha256Hash;
3use bitcoin::hashes::Hash;
4use bitcoin::key::Secp256k1;
5use bitcoin::secp256k1::Message as BitcoinMessage;
6use nostr_sdk::prelude::*;
7#[cfg(feature = "sqlx")]
8use sqlx::FromRow;
9#[cfg(feature = "sqlx")]
10use sqlx_crud::SqlxCrud;
11
12use std::fmt;
13use uuid::Uuid;
14
15#[derive(Debug, Deserialize, Serialize, Clone)]
17pub struct Peer {
18 pub pubkey: String,
19 pub reputation: Option<UserInfo>,
20}
21
22impl Peer {
23 pub fn new(pubkey: String, reputation: Option<UserInfo>) -> Self {
24 Self { pubkey, reputation }
25 }
26
27 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
28 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
29 }
30
31 pub fn as_json(&self) -> Result<String, ServiceError> {
32 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
33 }
34}
35
36#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
38#[serde(rename_all = "kebab-case")]
39pub enum Action {
40 NewOrder,
41 TakeSell,
42 TakeBuy,
43 PayInvoice,
44 FiatSent,
45 FiatSentOk,
46 Release,
47 Released,
48 Cancel,
49 Canceled,
50 CooperativeCancelInitiatedByYou,
51 CooperativeCancelInitiatedByPeer,
52 DisputeInitiatedByYou,
53 DisputeInitiatedByPeer,
54 CooperativeCancelAccepted,
55 BuyerInvoiceAccepted,
56 PurchaseCompleted,
57 HoldInvoicePaymentAccepted,
58 HoldInvoicePaymentSettled,
59 HoldInvoicePaymentCanceled,
60 WaitingSellerToPay,
61 WaitingBuyerInvoice,
62 AddInvoice,
63 BuyerTookOrder,
64 Rate,
65 RateUser,
66 RateReceived,
67 CantDo,
68 Dispute,
69 AdminCancel,
70 AdminCanceled,
71 AdminSettle,
72 AdminSettled,
73 AdminAddSolver,
74 AdminTakeDispute,
75 AdminTookDispute,
76 PaymentFailed,
77 InvoiceUpdated,
78 SendDm,
79 TradePubkey,
80 RestoreSession,
81 Orders,
82}
83
84impl fmt::Display for Action {
85 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86 write!(f, "{self:?}")
87 }
88}
89
90#[derive(Debug, Clone, Deserialize, Serialize)]
92#[serde(rename_all = "kebab-case")]
93pub enum Message {
94 Order(MessageKind),
95 Dispute(MessageKind),
96 CantDo(MessageKind),
97 Rate(MessageKind),
98 Dm(MessageKind),
99 Restore(MessageKind),
100}
101
102impl Message {
103 pub fn new_order(
105 id: Option<Uuid>,
106 request_id: Option<u64>,
107 trade_index: Option<i64>,
108 action: Action,
109 payload: Option<Payload>,
110 ) -> Self {
111 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
112 Self::Order(kind)
113 }
114
115 pub fn new_dispute(
117 id: Option<Uuid>,
118 request_id: Option<u64>,
119 trade_index: Option<i64>,
120 action: Action,
121 payload: Option<Payload>,
122 ) -> Self {
123 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
124
125 Self::Dispute(kind)
126 }
127
128 pub fn new_restore(payload: Option<Payload>) -> Self {
129 let kind = MessageKind::new(None, None, None, Action::RestoreSession, payload);
130 Self::Restore(kind)
131 }
132
133 pub fn cant_do(id: Option<Uuid>, request_id: Option<u64>, payload: Option<Payload>) -> Self {
135 let kind = MessageKind::new(id, request_id, None, Action::CantDo, payload);
136
137 Self::CantDo(kind)
138 }
139
140 pub fn new_dm(
142 id: Option<Uuid>,
143 request_id: Option<u64>,
144 action: Action,
145 payload: Option<Payload>,
146 ) -> Self {
147 let kind = MessageKind::new(id, request_id, None, action, payload);
148
149 Self::Dm(kind)
150 }
151
152 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
154 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
155 }
156
157 pub fn as_json(&self) -> Result<String, ServiceError> {
159 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
160 }
161
162 pub fn get_inner_message_kind(&self) -> &MessageKind {
164 match self {
165 Message::Dispute(k)
166 | Message::Order(k)
167 | Message::CantDo(k)
168 | Message::Rate(k)
169 | Message::Dm(k)
170 | Message::Restore(k) => k,
171 }
172 }
173
174 pub fn inner_action(&self) -> Option<Action> {
176 match self {
177 Message::Dispute(a)
178 | Message::Order(a)
179 | Message::CantDo(a)
180 | Message::Rate(a)
181 | Message::Dm(a)
182 | Message::Restore(a) => Some(a.get_action()),
183 }
184 }
185
186 pub fn verify(&self) -> bool {
188 match self {
189 Message::Order(m)
190 | Message::Dispute(m)
191 | Message::CantDo(m)
192 | Message::Rate(m)
193 | Message::Dm(m)
194 | Message::Restore(m) => m.verify(),
195 }
196 }
197
198 pub fn sign(message: String, keys: &Keys) -> Signature {
199 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
200 let hash = hash.to_byte_array();
201 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
202
203 keys.sign_schnorr(&message)
204 }
205
206 pub fn verify_signature(message: String, pubkey: PublicKey, sig: Signature) -> bool {
207 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
209 let hash = hash.to_byte_array();
210 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
211
212 let secp = Secp256k1::verification_only();
214 if let Ok(xonlykey) = pubkey.xonly() {
216 xonlykey.verify(&secp, &message, &sig).is_ok()
217 } else {
218 false
219 }
220 }
221}
222
223#[derive(Debug, Clone, Deserialize, Serialize)]
225pub struct MessageKind {
226 pub version: u8,
228 pub request_id: Option<u64>,
230 pub trade_index: Option<i64>,
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub id: Option<Uuid>,
235 pub action: Action,
237 pub payload: Option<Payload>,
239}
240
241type Amount = i64;
242
243#[derive(Debug, Deserialize, Serialize, Clone)]
245pub struct PaymentFailedInfo {
246 pub payment_attempts: u32,
248 pub payment_retries_interval: u32,
250}
251
252#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
255#[derive(Debug, Deserialize, Serialize, Clone)]
256pub struct RestoredOrderHelper {
257 pub id: Uuid,
258 pub status: String,
259 pub master_buyer_pubkey: Option<String>,
260 pub master_seller_pubkey: Option<String>,
261 pub trade_index_buyer: Option<i64>,
262 pub trade_index_seller: Option<i64>,
263}
264
265#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
269#[derive(Debug, Deserialize, Serialize, Clone)]
270pub struct RestoredDisputeHelper {
271 pub dispute_id: Uuid,
272 pub order_id: Uuid,
273 pub dispute_status: String,
274 pub master_buyer_pubkey: Option<String>,
275 pub master_seller_pubkey: Option<String>,
276 pub trade_index_buyer: Option<i64>,
277 pub trade_index_seller: Option<i64>,
278}
279
280#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
282#[derive(Debug, Deserialize, Serialize, Clone)]
283pub struct RestoredOrdersInfo {
284 pub order_id: Uuid,
286 pub trade_index: i64,
288 pub status: String,
290}
291
292#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
294#[derive(Debug, Deserialize, Serialize, Clone)]
295pub struct RestoredDisputesInfo {
296 pub dispute_id: Uuid,
298 pub order_id: Uuid,
300 pub trade_index: i64,
302 pub status: String,
304}
305
306#[derive(Debug, Deserialize, Serialize, Clone, Default)]
308pub struct RestoreSessionInfo {
309 #[serde(rename = "orders")]
311 pub restore_orders: Vec<RestoredOrdersInfo>,
312 #[serde(rename = "disputes")]
314 pub restore_disputes: Vec<RestoredDisputesInfo>,
315}
316
317#[derive(Debug, Deserialize, Serialize, Clone)]
319#[serde(rename_all = "snake_case")]
320pub enum Payload {
321 Order(SmallOrder),
323 PaymentRequest(Option<SmallOrder>, String, Option<Amount>),
325 TextMessage(String),
327 Peer(Peer),
329 RatingUser(u8),
331 Amount(Amount),
333 Dispute(Uuid, Option<SolverDisputeInfo>),
335 CantDo(Option<CantDoReason>),
337 NextTrade(String, u32),
341 PaymentFailed(PaymentFailedInfo),
343 RestoreData(RestoreSessionInfo),
345 Ids(Vec<Uuid>),
347 Orders(Vec<SmallOrder>),
349}
350
351#[allow(dead_code)]
352impl MessageKind {
353 pub fn new(
355 id: Option<Uuid>,
356 request_id: Option<u64>,
357 trade_index: Option<i64>,
358 action: Action,
359 payload: Option<Payload>,
360 ) -> Self {
361 Self {
362 version: PROTOCOL_VER,
363 request_id,
364 trade_index,
365 id,
366 action,
367 payload,
368 }
369 }
370 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
372 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
373 }
374 pub fn as_json(&self) -> Result<String, ServiceError> {
376 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
377 }
378
379 pub fn get_action(&self) -> Action {
381 self.action.clone()
382 }
383
384 pub fn get_next_trade_key(&self) -> Result<Option<(String, u32)>, ServiceError> {
386 match &self.payload {
387 Some(Payload::NextTrade(key, index)) => Ok(Some((key.to_string(), *index))),
388 None => Ok(None),
389 _ => Err(ServiceError::InvalidPayload),
390 }
391 }
392
393 pub fn get_rating(&self) -> Result<u8, ServiceError> {
394 if let Some(Payload::RatingUser(v)) = self.payload.to_owned() {
395 if !(MIN_RATING..=MAX_RATING).contains(&v) {
396 return Err(ServiceError::InvalidRatingValue);
397 }
398 Ok(v)
399 } else {
400 Err(ServiceError::InvalidRating)
401 }
402 }
403
404 pub fn verify(&self) -> bool {
406 match &self.action {
407 Action::NewOrder => matches!(&self.payload, Some(Payload::Order(_))),
408 Action::PayInvoice | Action::AddInvoice => {
409 if self.id.is_none() {
410 return false;
411 }
412 matches!(&self.payload, Some(Payload::PaymentRequest(_, _, _)))
413 }
414 Action::TakeSell
415 | Action::TakeBuy
416 | Action::FiatSent
417 | Action::FiatSentOk
418 | Action::Release
419 | Action::Released
420 | Action::Dispute
421 | Action::AdminCancel
422 | Action::AdminCanceled
423 | Action::AdminSettle
424 | Action::AdminSettled
425 | Action::Rate
426 | Action::RateReceived
427 | Action::AdminTakeDispute
428 | Action::AdminTookDispute
429 | Action::DisputeInitiatedByYou
430 | Action::DisputeInitiatedByPeer
431 | Action::WaitingBuyerInvoice
432 | Action::PurchaseCompleted
433 | Action::HoldInvoicePaymentAccepted
434 | Action::HoldInvoicePaymentSettled
435 | Action::HoldInvoicePaymentCanceled
436 | Action::WaitingSellerToPay
437 | Action::BuyerTookOrder
438 | Action::BuyerInvoiceAccepted
439 | Action::CooperativeCancelInitiatedByYou
440 | Action::CooperativeCancelInitiatedByPeer
441 | Action::CooperativeCancelAccepted
442 | Action::Cancel
443 | Action::InvoiceUpdated
444 | Action::AdminAddSolver
445 | Action::SendDm
446 | Action::TradePubkey
447 | Action::Canceled => {
448 if self.id.is_none() {
449 return false;
450 }
451 true
452 }
453 Action::PaymentFailed => {
454 if self.id.is_none() {
455 return false;
456 }
457 matches!(&self.payload, Some(Payload::PaymentFailed(_)))
458 }
459 Action::RateUser => {
460 matches!(&self.payload, Some(Payload::RatingUser(_)))
461 }
462 Action::CantDo => {
463 matches!(&self.payload, Some(Payload::CantDo(_)))
464 }
465 Action::RestoreSession => {
466 if self.id.is_some() || self.request_id.is_some() || self.trade_index.is_some() {
467 return false;
468 }
469 matches!(&self.payload, None | Some(Payload::RestoreData(_)))
470 }
471 Action::Orders => {
472 matches!(
473 &self.payload,
474 Some(Payload::Ids(_)) | Some(Payload::Orders(_))
475 )
476 }
477 }
478 }
479
480 pub fn get_order(&self) -> Option<&SmallOrder> {
481 if self.action != Action::NewOrder {
482 return None;
483 }
484 match &self.payload {
485 Some(Payload::Order(o)) => Some(o),
486 _ => None,
487 }
488 }
489
490 pub fn get_payment_request(&self) -> Option<String> {
491 if self.action != Action::TakeSell
492 && self.action != Action::AddInvoice
493 && self.action != Action::NewOrder
494 {
495 return None;
496 }
497 match &self.payload {
498 Some(Payload::PaymentRequest(_, pr, _)) => Some(pr.to_owned()),
499 Some(Payload::Order(ord)) => ord.buyer_invoice.to_owned(),
500 _ => None,
501 }
502 }
503
504 pub fn get_amount(&self) -> Option<Amount> {
505 if self.action != Action::TakeSell && self.action != Action::TakeBuy {
506 return None;
507 }
508 match &self.payload {
509 Some(Payload::PaymentRequest(_, _, amount)) => *amount,
510 Some(Payload::Amount(amount)) => Some(*amount),
511 _ => None,
512 }
513 }
514
515 pub fn get_payload(&self) -> Option<&Payload> {
516 self.payload.as_ref()
517 }
518
519 pub fn has_trade_index(&self) -> (bool, i64) {
520 if let Some(index) = self.trade_index {
521 return (true, index);
522 }
523 (false, 0)
524 }
525
526 pub fn trade_index(&self) -> i64 {
527 if let Some(index) = self.trade_index {
528 return index;
529 }
530 0
531 }
532}
533
534#[cfg(test)]
535mod test {
536 use crate::message::{Action, Message, MessageKind, Payload, Peer};
537 use crate::user::UserInfo;
538 use nostr_sdk::Keys;
539 use uuid::uuid;
540
541 #[test]
542 fn test_peer_with_reputation() {
543 let reputation = UserInfo {
545 rating: 4.5,
546 reviews: 10,
547 operating_days: 30,
548 };
549 let peer = Peer::new(
550 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
551 Some(reputation.clone()),
552 );
553
554 assert_eq!(
556 peer.pubkey,
557 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
558 );
559 assert!(peer.reputation.is_some());
560 let peer_reputation = peer.reputation.clone().unwrap();
561 assert_eq!(peer_reputation.rating, 4.5);
562 assert_eq!(peer_reputation.reviews, 10);
563 assert_eq!(peer_reputation.operating_days, 30);
564
565 let json = peer.as_json().unwrap();
567 let deserialized_peer = Peer::from_json(&json).unwrap();
568 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
569 assert!(deserialized_peer.reputation.is_some());
570 let deserialized_reputation = deserialized_peer.reputation.unwrap();
571 assert_eq!(deserialized_reputation.rating, 4.5);
572 assert_eq!(deserialized_reputation.reviews, 10);
573 assert_eq!(deserialized_reputation.operating_days, 30);
574 }
575
576 #[test]
577 fn test_peer_without_reputation() {
578 let peer = Peer::new(
580 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
581 None,
582 );
583
584 assert_eq!(
586 peer.pubkey,
587 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
588 );
589 assert!(peer.reputation.is_none());
590
591 let json = peer.as_json().unwrap();
593 let deserialized_peer = Peer::from_json(&json).unwrap();
594 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
595 assert!(deserialized_peer.reputation.is_none());
596 }
597
598 #[test]
599 fn test_peer_in_message() {
600 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
601
602 let reputation = UserInfo {
604 rating: 4.5,
605 reviews: 10,
606 operating_days: 30,
607 };
608 let peer_with_reputation = Peer::new(
609 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
610 Some(reputation),
611 );
612 let payload_with_reputation = Payload::Peer(peer_with_reputation);
613 let message_with_reputation = Message::Order(MessageKind::new(
614 Some(uuid),
615 Some(1),
616 Some(2),
617 Action::FiatSentOk,
618 Some(payload_with_reputation),
619 ));
620
621 assert!(message_with_reputation.verify());
623 let message_json = message_with_reputation.as_json().unwrap();
624 let deserialized_message = Message::from_json(&message_json).unwrap();
625 assert!(deserialized_message.verify());
626
627 let peer_without_reputation = Peer::new(
629 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
630 None,
631 );
632 let payload_without_reputation = Payload::Peer(peer_without_reputation);
633 let message_without_reputation = Message::Order(MessageKind::new(
634 Some(uuid),
635 Some(1),
636 Some(2),
637 Action::FiatSentOk,
638 Some(payload_without_reputation),
639 ));
640
641 assert!(message_without_reputation.verify());
643 let message_json = message_without_reputation.as_json().unwrap();
644 let deserialized_message = Message::from_json(&message_json).unwrap();
645 assert!(deserialized_message.verify());
646 }
647
648 #[test]
649 fn test_payment_failed_payload() {
650 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
651
652 let payment_failed_info = crate::message::PaymentFailedInfo {
654 payment_attempts: 3,
655 payment_retries_interval: 60,
656 };
657
658 let payload = Payload::PaymentFailed(payment_failed_info);
659 let message = Message::Order(MessageKind::new(
660 Some(uuid),
661 Some(1),
662 Some(2),
663 Action::PaymentFailed,
664 Some(payload),
665 ));
666
667 assert!(message.verify());
669
670 let message_json = message.as_json().unwrap();
672
673 let deserialized_message = Message::from_json(&message_json).unwrap();
675 assert!(deserialized_message.verify());
676
677 if let Message::Order(kind) = deserialized_message {
679 if let Some(Payload::PaymentFailed(info)) = kind.payload {
680 assert_eq!(info.payment_attempts, 3);
681 assert_eq!(info.payment_retries_interval, 60);
682 } else {
683 panic!("Expected PaymentFailed payload");
684 }
685 } else {
686 panic!("Expected Order message");
687 }
688 }
689
690 #[test]
691 fn test_message_payload_signature() {
692 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
693 let peer = Peer::new(
694 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
695 None, );
697 let payload = Payload::Peer(peer);
698 let test_message = Message::Order(MessageKind::new(
699 Some(uuid),
700 Some(1),
701 Some(2),
702 Action::FiatSentOk,
703 Some(payload),
704 ));
705 assert!(test_message.verify());
706 let test_message_json = test_message.as_json().unwrap();
707 let trade_keys =
709 Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
710 .unwrap();
711 let sig = Message::sign(test_message_json.clone(), &trade_keys);
712
713 assert!(Message::verify_signature(
714 test_message_json,
715 trade_keys.public_key(),
716 sig
717 ));
718 }
719
720 #[test]
721 fn test_restore_session_message() {
722 let restore_request_message = Message::Restore(MessageKind::new(
724 None,
725 None,
726 None,
727 Action::RestoreSession,
728 None,
729 ));
730
731 assert!(restore_request_message.verify());
733 assert_eq!(
734 restore_request_message.inner_action(),
735 Some(Action::RestoreSession)
736 );
737
738 let message_json = restore_request_message.as_json().unwrap();
740 let deserialized_message = Message::from_json(&message_json).unwrap();
741 assert!(deserialized_message.verify());
742 assert_eq!(
743 deserialized_message.inner_action(),
744 Some(Action::RestoreSession)
745 );
746
747 let restored_orders = vec![
749 crate::message::RestoredOrdersInfo {
750 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
751 trade_index: 1,
752 status: "active".to_string(),
753 },
754 crate::message::RestoredOrdersInfo {
755 order_id: uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24"),
756 trade_index: 2,
757 status: "success".to_string(),
758 },
759 ];
760
761 let restored_disputes = vec![crate::message::RestoredDisputesInfo {
762 dispute_id: uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25"),
763 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
764 trade_index: 1,
765 status: "initiated".to_string(),
766 }];
767
768 let restore_session_info = crate::message::RestoreSessionInfo {
769 restore_orders: restored_orders.clone(),
770 restore_disputes: restored_disputes.clone(),
771 };
772
773 let restore_data_payload = Payload::RestoreData(restore_session_info);
774 let restore_data_message = Message::Restore(MessageKind::new(
775 None,
776 None,
777 None,
778 Action::RestoreSession,
779 Some(restore_data_payload),
780 ));
781
782 assert!(restore_data_message.verify());
784 assert_eq!(
785 restore_data_message.inner_action(),
786 Some(Action::RestoreSession)
787 );
788
789 let message_json = restore_data_message.as_json().unwrap();
791 let deserialized_message = Message::from_json(&message_json).unwrap();
792 assert!(deserialized_message.verify());
793 assert_eq!(
794 deserialized_message.inner_action(),
795 Some(Action::RestoreSession)
796 );
797
798 if let Message::Restore(kind) = deserialized_message {
800 if let Some(Payload::RestoreData(info)) = kind.payload {
801 assert_eq!(info.restore_orders.len(), 2);
802 assert_eq!(info.restore_disputes.len(), 1);
803
804 assert_eq!(
806 info.restore_orders[0].order_id,
807 uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")
808 );
809 assert_eq!(info.restore_orders[0].trade_index, 1);
810 assert_eq!(info.restore_orders[0].status, "active");
811
812 assert_eq!(
814 info.restore_orders[1].order_id,
815 uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24")
816 );
817 assert_eq!(info.restore_orders[1].trade_index, 2);
818 assert_eq!(info.restore_orders[1].status, "success");
819
820 assert_eq!(
822 info.restore_disputes[0].dispute_id,
823 uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25")
824 );
825 assert_eq!(
826 info.restore_disputes[0].order_id,
827 uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")
828 );
829 assert_eq!(info.restore_disputes[0].trade_index, 1);
830 assert_eq!(info.restore_disputes[0].status, "initiated");
831 } else {
832 panic!("Expected RestoreData payload");
833 }
834 } else {
835 panic!("Expected Restore message");
836 }
837 }
838
839 #[test]
840 fn test_restore_session_message_validation() {
841 let restore_request_message = Message::Restore(MessageKind::new(
843 None,
844 None,
845 None,
846 Action::RestoreSession,
847 None, ));
849
850 assert!(restore_request_message.verify());
852
853 let wrong_payload = Payload::TextMessage("wrong payload".to_string());
855 let wrong_message = Message::Restore(MessageKind::new(
856 None,
857 None,
858 None,
859 Action::RestoreSession,
860 Some(wrong_payload),
861 ));
862
863 assert!(!wrong_message.verify());
865
866 let with_id = Message::Restore(MessageKind::new(
868 Some(uuid!("00000000-0000-0000-0000-000000000001")),
869 None,
870 None,
871 Action::RestoreSession,
872 None,
873 ));
874 assert!(!with_id.verify());
875
876 let with_request_id = Message::Restore(MessageKind::new(
878 None,
879 Some(42),
880 None,
881 Action::RestoreSession,
882 None,
883 ));
884 assert!(!with_request_id.verify());
885
886 let with_trade_index = Message::Restore(MessageKind::new(
888 None,
889 None,
890 Some(7),
891 Action::RestoreSession,
892 None,
893 ));
894 assert!(!with_trade_index.verify());
895 }
896
897 #[test]
898 fn test_restore_session_message_constructor() {
899 let restore_request_message = Message::new_restore(None);
901
902 assert!(matches!(restore_request_message, Message::Restore(_)));
903 assert!(restore_request_message.verify());
904 assert_eq!(
905 restore_request_message.inner_action(),
906 Some(Action::RestoreSession)
907 );
908
909 let restore_session_info = crate::message::RestoreSessionInfo {
911 restore_orders: vec![],
912 restore_disputes: vec![],
913 };
914 let restore_data_message =
915 Message::new_restore(Some(Payload::RestoreData(restore_session_info)));
916
917 assert!(matches!(restore_data_message, Message::Restore(_)));
918 assert!(restore_data_message.verify());
919 assert_eq!(
920 restore_data_message.inner_action(),
921 Some(Action::RestoreSession)
922 );
923 }
924}