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 raw(&self) -> i64 {
106 match self {
107 Self::Direct(id) => id.raw(),
108 Self::Group { id, scope: _ } => id.raw(),
109 Self::Local(id) => id.raw(),
110 }
111 }
112
113 pub fn with_group_scope(id: GroupId, group_member_support_id: MemberId) -> Self {
115 Self::Group {
116 id,
117 scope: Some(group_member_support_id),
118 }
119 }
120
121 pub fn from_chat_ref(chat_ref: &ChatRef) -> Option<Self> {
123 match chat_ref.chat_type {
124 ChatType::Direct => Some(Self::Direct(unsafe {
125 ContactId::from_raw_unchecked(chat_ref.chat_id)
126 })),
127 ChatType::Group => Some(Self::Group {
128 id: unsafe { GroupId::from_raw_unchecked(chat_ref.chat_id) },
129 scope: chat_ref.chat_scope.as_ref().and_then(|scope| {
130 scope.member_support().and_then(|id| {
131 id.as_ref()
132 .copied()
133 .map(|id| unsafe { MemberId::from_raw_unchecked(id) })
134 })
135 }),
136 }),
137 ChatType::Local => Some(Self::Local(unsafe {
138 UserId::from_raw_unchecked(chat_ref.chat_id)
139 })),
140 _ => None,
141 }
142 }
143
144 pub fn from_chat_info(chat_info: &ChatInfo) -> Option<Self> {
146 match chat_info {
147 ChatInfo::Direct { contact, .. } => Some(Self::Direct(ContactId::from(contact))),
148 ChatInfo::Group {
149 group_info,
150 group_chat_scope,
151 ..
152 } => Some(Self::Group {
153 id: GroupId::from(group_info),
154 scope: group_chat_scope.as_ref().and_then(|scope| {
155 scope
156 .member_support()
157 .and_then(|member| member.as_ref().map(MemberId::from))
158 }),
159 }),
160 ChatInfo::Local { note_folder, .. } => Some(Self::Local(unsafe {
161 UserId::from_raw_unchecked(note_folder.user_id)
162 })),
163 _ => None,
164 }
165 }
166
167 pub fn into_chat_ref(self) -> ChatRef {
169 let (chat_type, chat_id, chat_scope) = match self {
170 Self::Direct(contact_id) => (ChatType::Direct, contact_id.raw(), None),
171 Self::Group {
172 id: group_id,
173 scope,
174 } => (
175 ChatType::Group,
176 group_id.raw(),
177 scope.map(|member_id| GroupChatScope::MemberSupport {
178 group_member_id: Some(member_id.raw()),
179 undocumented: Default::default(),
180 }),
181 ),
182 Self::Local(user_id) => (ChatType::Local, user_id.raw(), None),
183 };
184
185 ChatRef {
186 chat_type,
187 chat_id,
188 chat_scope,
189 undocumented: Default::default(),
190 }
191 }
192
193 pub fn direct(&self) -> Option<ContactId> {
194 if let Self::Direct(contact) = self {
195 Some(*contact)
196 } else {
197 None
198 }
199 }
200
201 pub fn group(&self) -> Option<(GroupId, Option<MemberId>)> {
202 if let Self::Group { id, scope } = self {
203 Some((*id, *scope))
204 } else {
205 None
206 }
207 }
208
209 pub fn local(&self) -> Option<UserId> {
210 if let Self::Local(id) = self {
211 Some(*id)
212 } else {
213 None
214 }
215 }
216
217 pub fn is_direct(&self) -> bool {
218 self.direct().is_some()
219 }
220
221 pub fn is_group(&self) -> bool {
222 self.group().is_some()
223 }
224
225 pub fn is_local(&self) -> bool {
226 self.local().is_some()
227 }
228}
229
230impl From<ContactId> for ChatId {
231 fn from(id: ContactId) -> Self {
232 Self::Direct(id)
233 }
234}
235
236impl From<GroupId> for ChatId {
237 fn from(id: GroupId) -> Self {
238 Self::Group { id, scope: None }
239 }
240}
241
242impl From<UserId> for ChatId {
243 fn from(id: UserId) -> Self {
244 Self::Local(id)
245 }
246}
247
248#[derive(Debug)]
249pub struct Zero(&'static str);
250
251impl std::fmt::Display for Zero {
252 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253 write!(f, "Got {} equal to zero", self.0)
254 }
255}
256
257impl std::error::Error for Zero {}
258
259macro_rules! impl_id_from_struct {
260 ($strct:ty as $id:ty, $val:ident, $conversion:expr) => {
261 impl From<$strct> for $id {
262 fn from($val: $strct) -> Self {
263 $conversion
264 }
265 }
266
267 impl<'a> From<&'a $strct> for $id {
268 fn from($val: &'a $strct) -> Self {
269 $conversion
270 }
271 }
272
273 impl<'a> From<&'a mut $strct> for $id {
274 fn from($val: &'a mut $strct) -> Self {
275 $conversion
276 }
277 }
278 };
279}
280
281impl_id_from_struct!(User as UserId, user, unsafe {
282 UserId::from_raw_unchecked(user.user_id)
283});
284impl_id_from_struct!(UserInfo as UserId, info, UserId::from(&info.user));
285
286impl_id_from_struct!(Contact as ContactId, contact, unsafe {
287 ContactId::from_raw_unchecked(contact.contact_id)
288});
289impl_id_from_struct!(Contact as ChatId, contact, ContactId::from(contact).into());
290
291impl_id_from_struct!(UserContactRequest as ContactRequestId, req, unsafe {
292 ContactRequestId::from_raw_unchecked(req.contact_request_id)
293});
294
295impl_id_from_struct!(GroupInfo as GroupId, group, unsafe {
296 GroupId::from_raw_unchecked(group.group_id)
297});
298impl_id_from_struct!(GroupInfo as ChatId, group, GroupId::from(group).into());
299
300impl_id_from_struct!(CIMeta as MessageId, meta, unsafe {
301 MessageId::from_raw_unchecked(meta.item_id)
302});
303impl_id_from_struct!(ChatItem as MessageId, item, MessageId::from(&item.meta));
304impl_id_from_struct!(AChatItem as MessageId, it, MessageId::from(&it.chat_item));
305
306impl_id_from_struct!(CIFile as FileId, file, unsafe {
307 FileId::from_raw_unchecked(file.file_id)
308});
309impl_id_from_struct!(RcvFileTransfer as FileId, ft, unsafe {
310 FileId::from_raw_unchecked(ft.file_id)
311});
312impl_id_from_struct!(FileTransferMeta as FileId, ft, unsafe {
313 FileId::from_raw_unchecked(ft.file_id)
314});
315impl_id_from_struct!(SndFileTransfer as FileId, ft, unsafe {
316 FileId::from_raw_unchecked(ft.file_id)
317});
318
319impl_id_from_struct!(GroupMember as MemberId, member, unsafe {
320 MemberId::from_raw_unchecked(member.group_member_id)
321});
322impl_id_from_struct!(GroupRelay as RelayId, relay, unsafe {
323 RelayId::from_raw_unchecked(relay.group_relay_id)
324});