Skip to main content

co_didcomm/messages/headers/
decorators.rs

1//! Decorator Types.
2//!
3//! The decorator module provides implementations of some decorator data structures.
4//!
5//! Decorators represent additional metadata that adds semantic
6//! content relevant to messaging in general but not tied to a specific domain.
7//!
8//! Decorators represents additional metadata that add semantics
9//! content that are relevant to messing in general, but not tied to a specific domain.
10//! For more details, see Aries RFC
11//!
12
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16/// A `~thread` message decorator that provides request/reply
17/// and threading semantics according to Aries RFC 0008.
18#[derive(Default, Deserialize, Serialize, PartialEq, Eq, Debug, Clone)]
19pub struct Thread {
20    /// The ID of the message that serves as the thread start.
21    pub thid: String,
22
23    /// An optional parent `thid`.
24    ///
25    /// It's used when branching or nesting a new interaction off an
26    /// existing one.
27    pub pthid: String,
28
29    /// The index of the message in the sequence of all the messages
30    /// the current *sender* has contributed to in the thread.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub sender_order: Option<usize>,
33
34    /// A dictionary of sender_order/highest messages received on the thread.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub received_orders: Option<HashMap<String, usize>>,
37
38    /// Code to convey an action.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub goal_code: Option<String>,
41}
42
43impl Thread {
44    /// Creates implicit thread.
45    ///
46    /// # Example
47    ///
48    /// ```
49    /// use co_didcomm::Thread;
50    /// let message_id = "new-message";
51    /// let thread = Thread::implicit(&message_id);
52    /// assert_eq!(thread.thid, message_id.to_string())
53    /// ```
54    pub fn implicit(message_id: &str) -> Self {
55        Self {
56            thid: message_id.into(),
57            sender_order: Some(0),
58            ..Default::default()
59        }
60    }
61
62    /// Create implicit message reply thread.
63    ///
64    /// # Example
65    ///
66    /// ```
67    /// use co_didcomm::Thread;
68    /// let thid = "current-thread";
69    /// let thread = Thread::implicit_reply(&thid);
70    /// assert_eq!(thread.thid, thid);
71    /// ```
72    pub fn implicit_reply(thid: &str) -> Self {
73        Self {
74            thid: thid.into(),
75            ..Default::default()
76        }
77    }
78
79    /// Creates an effective implicit message reply thread.
80    pub fn effective_implicit_reply(thid: &str, sender: &str) -> Self {
81        let mut thr = Self::implicit_reply(thid);
82        thr.received_orders = Some(HashMap::from([(sender.into(), 0)]));
83        thr
84    }
85
86    /// Returns `true` if the thread is for an implicit reply message.
87    pub fn is_implicit_reply(&self, message_id: &str) -> bool {
88        if self.thid != message_id {
89            match self.received_orders {
90                Some(ref recv_orders) => recv_orders.values().all(|&x| x == 0),
91                None => true,
92            }
93        } else {
94            false
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use fake::faker::internet::en;
103    use fake::uuid::UUIDv4;
104    use fake::Fake;
105    use quickcheck::{Arbitrary, Gen};
106    use uuid::Uuid;
107
108    #[test]
109    fn default_thread_can_be_created() {
110        let thr = Thread::default();
111        assert_eq!(thr.thid, thr.thid);
112        assert_eq!(thr.thid, thr.pthid);
113        assert!(thr.sender_order.is_none());
114        assert!(thr.received_orders.is_none());
115        assert!(thr.goal_code.is_none());
116    }
117
118    #[derive(Clone, Debug)]
119    struct Id(String);
120
121    #[derive(Clone, Debug)]
122    struct Header {
123        id: String,
124        sender: String,
125    }
126
127    impl Arbitrary for Id {
128        fn arbitrary(_: &mut Gen) -> Self {
129            let s: Uuid = UUIDv4.fake();
130            Self(s.to_string())
131        }
132    }
133
134    impl Arbitrary for Header {
135        fn arbitrary(_: &mut Gen) -> Self {
136            let s: Uuid = UUIDv4.fake();
137            Self {
138                id: s.to_string(),
139                sender: en::Username().fake(),
140            }
141        }
142    }
143    #[quickcheck_macros::quickcheck]
144    fn create_implicit_thread(id: Id) -> bool {
145        let thread = Thread::implicit(&id.0);
146        thread.thid == id.0
147    }
148
149    #[quickcheck_macros::quickcheck]
150    fn create_effective_implicit_reply_thread(header: Header) -> bool {
151        let thread = Thread::effective_implicit_reply(&header.id, &header.sender);
152        thread.thid == header.id
153    }
154
155    #[quickcheck_macros::quickcheck]
156    fn implicit_thread_without_received_order_successfully_detected(id: Id) -> bool {
157        let thr = Thread::implicit_reply(&id.0);
158        !thr.is_implicit_reply(&id.0)
159    }
160
161    #[quickcheck_macros::quickcheck]
162    fn non_implicit_thread_with_received_order_successfully_detected(id: Id) -> bool {
163        let mut thr = Thread::implicit_reply(&id.0);
164        thr.received_orders = Some(HashMap::from([("test".into(), 1)]));
165        !thr.is_implicit_reply(&id.0)
166    }
167}