1use simploxide_api_types::{
7 AChatItem, CIFile, CIMeta, ChatInfo, ChatItem, ChatRef, ChatType, Contact, FileTransferMeta,
8 GroupChatScope, GroupInfo, GroupMember, GroupRelay, RcvFileTransfer, SndFileTransfer, User,
9 UserContactRequest, UserInfo,
10};
11
12use std::num::NonZeroI64;
13
14macro_rules! typesafe_ids {
15 ($($name:ident),*) => {
16 $(
17 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18 #[repr(transparent)]
19 pub struct $name(NonZeroI64);
20
21 impl $name {
22 pub unsafe fn from_raw_unchecked(id: i64) -> Self {
31 unsafe {
32 Self(NonZeroI64::new_unchecked(id))
33 }
34 }
35
36 pub fn from_raw(id: i64) -> Self {
38 Self(NonZeroI64::try_from(id).unwrap())
39 }
40
41 pub fn raw(&self) -> i64 {
42 self.0.get()
43 }
44 }
45
46 impl TryFrom<i64> for $name {
47 type Error = Zero;
48
49 fn try_from(id: i64) -> Result<Self, Self::Error> {
50 let id = NonZeroI64::new(id).ok_or(Zero(stringify!($name)))?;
51 Ok(Self(id))
52 }
53 }
54
55 impl From<NonZeroI64> for $name {
56 fn from(id: NonZeroI64) -> Self {
57 Self(id)
58 }
59 }
60
61 impl ::std::fmt::Display for $name {
62 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> std::fmt::Result {
63 self.0.fmt(f)
64 }
65 }
66
67 impl ::std::str::FromStr for $name {
68 type Err = ::std::num::ParseIntError;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 Ok(Self(s.parse()?))
72 }
73 }
74 )*
75 }
76}
77
78typesafe_ids!(
79 UserId,
80 ContactId,
81 ContactRequestId,
82 GroupId,
83 FileId,
84 MessageId,
85 MemberId,
86 RelayId
87);
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
92pub enum ChatId {
93 Direct(ContactId),
94 Group {
95 id: GroupId,
96 scope: Option<MemberId>,
97 },
98 Local(UserId),
99}
100
101impl ChatId {
102 pub fn with_group_scope(id: GroupId, group_member_support_id: MemberId) -> Self {
104 Self::Group {
105 id,
106 scope: Some(group_member_support_id),
107 }
108 }
109
110 pub fn from_chat_ref(chat_ref: &ChatRef) -> Option<Self> {
112 match chat_ref.chat_type {
113 ChatType::Direct => Some(Self::Direct(unsafe {
114 ContactId::from_raw_unchecked(chat_ref.chat_id)
115 })),
116 ChatType::Group => Some(Self::Group {
117 id: unsafe { GroupId::from_raw_unchecked(chat_ref.chat_id) },
118 scope: chat_ref.chat_scope.as_ref().and_then(|scope| {
119 scope.member_support().and_then(|id| {
120 id.as_ref()
121 .copied()
122 .map(|id| unsafe { MemberId::from_raw_unchecked(id) })
123 })
124 }),
125 }),
126 ChatType::Local => Some(Self::Local(unsafe {
127 UserId::from_raw_unchecked(chat_ref.chat_id)
128 })),
129 _ => None,
130 }
131 }
132
133 pub fn from_chat_info(chat_info: &ChatInfo) -> Option<Self> {
135 match chat_info {
136 ChatInfo::Direct { contact, .. } => Some(Self::Direct(ContactId::from(contact))),
137 ChatInfo::Group {
138 group_info,
139 group_chat_scope,
140 ..
141 } => Some(Self::Group {
142 id: GroupId::from(group_info),
143 scope: group_chat_scope.as_ref().and_then(|scope| {
144 scope
145 .member_support()
146 .and_then(|member| member.as_ref().map(MemberId::from))
147 }),
148 }),
149 ChatInfo::Local { note_folder, .. } => Some(Self::Local(unsafe {
150 UserId::from_raw_unchecked(note_folder.user_id)
151 })),
152 _ => None,
153 }
154 }
155
156 pub fn into_chat_ref(self) -> ChatRef {
158 let (chat_type, chat_id, chat_scope) = match self {
159 Self::Direct(contact_id) => (ChatType::Direct, contact_id.raw(), None),
160 Self::Group {
161 id: group_id,
162 scope,
163 } => (
164 ChatType::Group,
165 group_id.raw(),
166 scope.map(|member_id| GroupChatScope::MemberSupport {
167 group_member_id: Some(member_id.raw()),
168 undocumented: Default::default(),
169 }),
170 ),
171 Self::Local(user_id) => (ChatType::Local, user_id.raw(), None),
172 };
173
174 ChatRef {
175 chat_type,
176 chat_id,
177 chat_scope,
178 undocumented: Default::default(),
179 }
180 }
181
182 pub fn is_direct(&self) -> bool {
183 matches!(self, Self::Direct(_))
184 }
185
186 pub fn is_group(&self) -> bool {
187 matches!(self, Self::Group { .. })
188 }
189
190 pub fn is_local(&self) -> bool {
191 matches!(self, Self::Local(_))
192 }
193}
194
195impl From<ContactId> for ChatId {
196 fn from(id: ContactId) -> Self {
197 Self::Direct(id)
198 }
199}
200
201impl From<GroupId> for ChatId {
202 fn from(id: GroupId) -> Self {
203 Self::Group { id, scope: None }
204 }
205}
206
207impl From<UserId> for ChatId {
208 fn from(id: UserId) -> Self {
209 Self::Local(id)
210 }
211}
212
213#[derive(Debug)]
214pub struct Zero(&'static str);
215
216impl std::fmt::Display for Zero {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 write!(f, "Got {} equal to zero", self.0)
219 }
220}
221
222impl std::error::Error for Zero {}
223
224macro_rules! impl_id_from_struct {
225 ($strct:ty as $id:ty, $val:ident, $conversion:expr) => {
226 impl From<$strct> for $id {
227 fn from($val: $strct) -> Self {
228 $conversion
229 }
230 }
231
232 impl<'a> From<&'a $strct> for $id {
233 fn from($val: &'a $strct) -> Self {
234 $conversion
235 }
236 }
237
238 impl<'a> From<&'a mut $strct> for $id {
239 fn from($val: &'a mut $strct) -> Self {
240 $conversion
241 }
242 }
243 };
244}
245
246impl_id_from_struct!(User as UserId, user, unsafe {
247 UserId::from_raw_unchecked(user.user_id)
248});
249impl_id_from_struct!(UserInfo as UserId, info, UserId::from(&info.user));
250
251impl_id_from_struct!(Contact as ContactId, contact, unsafe {
252 ContactId::from_raw_unchecked(contact.contact_id)
253});
254impl_id_from_struct!(Contact as ChatId, contact, ContactId::from(contact).into());
255
256impl_id_from_struct!(UserContactRequest as ContactRequestId, req, unsafe {
257 ContactRequestId::from_raw_unchecked(req.contact_request_id)
258});
259
260impl_id_from_struct!(GroupInfo as GroupId, group, unsafe {
261 GroupId::from_raw_unchecked(group.group_id)
262});
263impl_id_from_struct!(GroupInfo as ChatId, group, GroupId::from(group).into());
264
265impl_id_from_struct!(CIMeta as MessageId, meta, unsafe {
266 MessageId::from_raw_unchecked(meta.item_id)
267});
268impl_id_from_struct!(ChatItem as MessageId, item, MessageId::from(&item.meta));
269impl_id_from_struct!(AChatItem as MessageId, it, MessageId::from(&it.chat_item));
270
271impl_id_from_struct!(CIFile as FileId, file, unsafe {
272 FileId::from_raw_unchecked(file.file_id)
273});
274impl_id_from_struct!(RcvFileTransfer as FileId, ft, unsafe {
275 FileId::from_raw_unchecked(ft.file_id)
276});
277impl_id_from_struct!(FileTransferMeta as FileId, ft, unsafe {
278 FileId::from_raw_unchecked(ft.file_id)
279});
280impl_id_from_struct!(SndFileTransfer as FileId, ft, unsafe {
281 FileId::from_raw_unchecked(ft.file_id)
282});
283
284impl_id_from_struct!(GroupMember as MemberId, member, unsafe {
285 MemberId::from_raw_unchecked(member.group_member_id)
286});
287impl_id_from_struct!(GroupRelay as RelayId, relay, unsafe {
288 RelayId::from_raw_unchecked(relay.group_relay_id)
289});