Skip to main content

simploxide_client/
id.rs

1//! Type-safe wrappers for SimpleX Chat integer IDs and conversions from API structs.
2//!
3//!  ID types implement `From` for their corresponding API structs(and references to them), so you can pass a
4//! `&Contact`, `GroupInfo`, `ChatItem`, etc. directly wherever a typed ID is expected.
5
6use simploxide_api_types::{
7    AChatItem, CIFile, CIMeta, ChatInfo, ChatItem, ChatRef, ChatType, Contact, FileTransferMeta,
8    GroupChatScope, GroupInfo, GroupMember, GroupRelay, RcvFileTransfer, SndFileTransfer, User,
9    UserContactRequest,
10};
11
12macro_rules! typesafe_ids {
13    ($($name:ident),*) => {
14        $(
15            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16            #[repr(transparent)]
17            pub struct $name(pub i64);
18
19            impl ::std::fmt::Display for $name {
20                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> std::fmt::Result {
21                    self.0.fmt(f)
22                }
23            }
24
25            impl ::std::str::FromStr for $name {
26                type Err = ::std::num::ParseIntError;
27
28                fn from_str(s: &str) -> Result<Self, Self::Err> {
29                    Ok(Self(s.parse()?))
30                }
31            }
32        )*
33    }
34}
35
36typesafe_ids!(
37    UserId,
38    ContactId,
39    ContactRequestId,
40    GroupId,
41    FileId,
42    MessageId,
43    MemberId,
44    RelayId
45);
46
47/// Identifies a chat: direct contact, group (optionally scoped to a member support thread),
48/// or local note-to-self.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub enum ChatId {
51    Direct(ContactId),
52    Group {
53        id: GroupId,
54        scope: Option<MemberId>,
55    },
56    Local(UserId),
57}
58
59impl ChatId {
60    /// Creates a [`ChatId::Group`] scoped to a member support thread.
61    pub fn with_group_scope(id: GroupId, group_member_support_id: MemberId) -> Self {
62        Self::Group {
63            id,
64            scope: Some(group_member_support_id),
65        }
66    }
67
68    /// Converts a [`ChatRef`] from a SimpleX API response. Returns `None` for unrecognised chat types.
69    pub fn from_chat_ref(chat_ref: &ChatRef) -> Option<Self> {
70        match chat_ref.chat_type {
71            ChatType::Direct => Some(Self::Direct(ContactId(chat_ref.chat_id))),
72            ChatType::Group => Some(Self::Group {
73                id: GroupId(chat_ref.chat_id),
74                scope: chat_ref.chat_scope.as_ref().and_then(|scope| {
75                    scope
76                        .member_support()
77                        .and_then(|id| id.as_ref().copied().map(MemberId))
78                }),
79            }),
80            ChatType::Local => Some(Self::Local(UserId(chat_ref.chat_id))),
81            _ => None,
82        }
83    }
84
85    /// Converts a [`ChatInfo`] from a SimpleX API response. Returns `None` for unrecognised chat types.
86    pub fn from_chat_info(chat_info: &ChatInfo) -> Option<Self> {
87        match chat_info {
88            ChatInfo::Direct { contact, .. } => Some(Self::Direct(ContactId(contact.contact_id))),
89            ChatInfo::Group {
90                group_info,
91                group_chat_scope,
92                ..
93            } => Some(Self::Group {
94                id: GroupId(group_info.group_id),
95                scope: group_chat_scope.as_ref().and_then(|scope| {
96                    scope
97                        .member_support()
98                        .and_then(|member| member.as_ref().map(|member| MemberId(member.group_id)))
99                }),
100            }),
101            ChatInfo::Local { note_folder, .. } => Some(Self::Local(UserId(note_folder.user_id))),
102            _ => None,
103        }
104    }
105
106    /// Converts back into a [`ChatRef`] for use in raw API calls.
107    pub fn into_chat_ref(self) -> ChatRef {
108        let (chat_type, chat_id, chat_scope) = match self {
109            Self::Direct(contact_id) => (ChatType::Direct, contact_id.0, None),
110            Self::Group {
111                id: group_id,
112                scope,
113            } => (
114                ChatType::Group,
115                group_id.0,
116                scope.map(|member_id| GroupChatScope::MemberSupport {
117                    group_member_id: Some(member_id.0),
118                    undocumented: Default::default(),
119                }),
120            ),
121            Self::Local(user_id) => (ChatType::Local, user_id.0, None),
122        };
123
124        ChatRef {
125            chat_type,
126            chat_id,
127            chat_scope,
128            undocumented: Default::default(),
129        }
130    }
131
132    pub fn is_direct(&self) -> bool {
133        matches!(self, Self::Direct(_))
134    }
135
136    pub fn is_group(&self) -> bool {
137        matches!(self, Self::Group { .. })
138    }
139
140    pub fn is_local(&self) -> bool {
141        matches!(self, Self::Local(_))
142    }
143}
144
145impl From<ContactId> for ChatId {
146    fn from(id: ContactId) -> Self {
147        Self::Direct(id)
148    }
149}
150
151impl From<GroupId> for ChatId {
152    fn from(id: GroupId) -> Self {
153        Self::Group { id, scope: None }
154    }
155}
156
157impl From<UserId> for ChatId {
158    fn from(id: UserId) -> Self {
159        Self::Local(id)
160    }
161}
162
163macro_rules! impl_id_from_struct {
164    ($strct:ty as $id:ty, $val:ident, $conversion:expr) => {
165        impl From<$strct> for $id {
166            fn from($val: $strct) -> Self {
167                $conversion
168            }
169        }
170
171        impl<'a> From<&'a $strct> for $id {
172            fn from($val: &'a $strct) -> Self {
173                $conversion
174            }
175        }
176
177        impl<'a> From<&'a mut $strct> for $id {
178            fn from($val: &'a mut $strct) -> Self {
179                $conversion
180            }
181        }
182    };
183}
184
185impl_id_from_struct!(User as UserId, user, UserId(user.user_id));
186
187impl_id_from_struct!(Contact as ContactId, contact, ContactId(contact.contact_id));
188impl_id_from_struct!(Contact as ChatId, contact, ContactId::from(contact).into());
189
190impl_id_from_struct!(
191    UserContactRequest as ContactRequestId,
192    req,
193    ContactRequestId(req.contact_request_id)
194);
195
196impl_id_from_struct!(GroupInfo as GroupId, group, GroupId(group.group_id));
197impl_id_from_struct!(GroupInfo as ChatId, group, GroupId::from(group).into());
198
199impl_id_from_struct!(CIMeta as MessageId, meta, MessageId(meta.item_id));
200impl_id_from_struct!(ChatItem as MessageId, item, MessageId::from(&item.meta));
201impl_id_from_struct!(AChatItem as MessageId, it, MessageId::from(&it.chat_item));
202
203impl_id_from_struct!(CIFile as FileId, file, FileId(file.file_id));
204impl_id_from_struct!(RcvFileTransfer as FileId, ft, FileId(ft.file_id));
205impl_id_from_struct!(FileTransferMeta as FileId, ft, FileId(ft.file_id));
206impl_id_from_struct!(SndFileTransfer as FileId, ft, FileId(ft.file_id));
207
208impl_id_from_struct!(
209    GroupMember as MemberId,
210    member,
211    MemberId(member.group_member_id)
212);
213impl_id_from_struct!(GroupRelay as RelayId, relay, RelayId(relay.group_relay_id));