use simploxide_api_types::{
AChatItem, CIFile, CIMeta, ChatInfo, ChatItem, ChatRef, ChatType, Contact, FileTransferMeta,
GroupChatScope, GroupInfo, GroupMember, GroupRelay, RcvFileTransfer, SndFileTransfer, User,
UserContactRequest, UserInfo,
};
use std::num::NonZeroI64;
macro_rules! typesafe_ids {
($($name:ident),*) => {
$(
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct $name(NonZeroI64);
impl $name {
pub unsafe fn from_raw_unchecked(id: i64) -> Self {
unsafe {
Self(NonZeroI64::new_unchecked(id))
}
}
pub fn from_raw(id: i64) -> Self {
Self(NonZeroI64::try_from(id).unwrap())
}
pub fn raw(&self) -> i64 {
self.0.get()
}
}
impl TryFrom<i64> for $name {
type Error = Zero;
fn try_from(id: i64) -> Result<Self, Self::Error> {
let id = NonZeroI64::new(id).ok_or(Zero(stringify!($name)))?;
Ok(Self(id))
}
}
impl From<NonZeroI64> for $name {
fn from(id: NonZeroI64) -> Self {
Self(id)
}
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl ::std::str::FromStr for $name {
type Err = ::std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
)*
}
}
typesafe_ids!(
UserId,
ContactId,
ContactRequestId,
GroupId,
FileId,
MessageId,
MemberId,
RelayId
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ChatId {
Direct(ContactId),
Group {
id: GroupId,
scope: Option<MemberId>,
},
Local(UserId),
}
impl ChatId {
pub fn with_group_scope(id: GroupId, group_member_support_id: MemberId) -> Self {
Self::Group {
id,
scope: Some(group_member_support_id),
}
}
pub fn from_chat_ref(chat_ref: &ChatRef) -> Option<Self> {
match chat_ref.chat_type {
ChatType::Direct => Some(Self::Direct(unsafe {
ContactId::from_raw_unchecked(chat_ref.chat_id)
})),
ChatType::Group => Some(Self::Group {
id: unsafe { GroupId::from_raw_unchecked(chat_ref.chat_id) },
scope: chat_ref.chat_scope.as_ref().and_then(|scope| {
scope.member_support().and_then(|id| {
id.as_ref()
.copied()
.map(|id| unsafe { MemberId::from_raw_unchecked(id) })
})
}),
}),
ChatType::Local => Some(Self::Local(unsafe {
UserId::from_raw_unchecked(chat_ref.chat_id)
})),
_ => None,
}
}
pub fn from_chat_info(chat_info: &ChatInfo) -> Option<Self> {
match chat_info {
ChatInfo::Direct { contact, .. } => Some(Self::Direct(ContactId::from(contact))),
ChatInfo::Group {
group_info,
group_chat_scope,
..
} => Some(Self::Group {
id: GroupId::from(group_info),
scope: group_chat_scope.as_ref().and_then(|scope| {
scope
.member_support()
.and_then(|member| member.as_ref().map(MemberId::from))
}),
}),
ChatInfo::Local { note_folder, .. } => Some(Self::Local(unsafe {
UserId::from_raw_unchecked(note_folder.user_id)
})),
_ => None,
}
}
pub fn into_chat_ref(self) -> ChatRef {
let (chat_type, chat_id, chat_scope) = match self {
Self::Direct(contact_id) => (ChatType::Direct, contact_id.raw(), None),
Self::Group {
id: group_id,
scope,
} => (
ChatType::Group,
group_id.raw(),
scope.map(|member_id| GroupChatScope::MemberSupport {
group_member_id: Some(member_id.raw()),
undocumented: Default::default(),
}),
),
Self::Local(user_id) => (ChatType::Local, user_id.raw(), None),
};
ChatRef {
chat_type,
chat_id,
chat_scope,
undocumented: Default::default(),
}
}
pub fn is_direct(&self) -> bool {
matches!(self, Self::Direct(_))
}
pub fn is_group(&self) -> bool {
matches!(self, Self::Group { .. })
}
pub fn is_local(&self) -> bool {
matches!(self, Self::Local(_))
}
}
impl From<ContactId> for ChatId {
fn from(id: ContactId) -> Self {
Self::Direct(id)
}
}
impl From<GroupId> for ChatId {
fn from(id: GroupId) -> Self {
Self::Group { id, scope: None }
}
}
impl From<UserId> for ChatId {
fn from(id: UserId) -> Self {
Self::Local(id)
}
}
#[derive(Debug)]
pub struct Zero(&'static str);
impl std::fmt::Display for Zero {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Got {} equal to zero", self.0)
}
}
impl std::error::Error for Zero {}
macro_rules! impl_id_from_struct {
($strct:ty as $id:ty, $val:ident, $conversion:expr) => {
impl From<$strct> for $id {
fn from($val: $strct) -> Self {
$conversion
}
}
impl<'a> From<&'a $strct> for $id {
fn from($val: &'a $strct) -> Self {
$conversion
}
}
impl<'a> From<&'a mut $strct> for $id {
fn from($val: &'a mut $strct) -> Self {
$conversion
}
}
};
}
impl_id_from_struct!(User as UserId, user, unsafe {
UserId::from_raw_unchecked(user.user_id)
});
impl_id_from_struct!(UserInfo as UserId, info, UserId::from(&info.user));
impl_id_from_struct!(Contact as ContactId, contact, unsafe {
ContactId::from_raw_unchecked(contact.contact_id)
});
impl_id_from_struct!(Contact as ChatId, contact, ContactId::from(contact).into());
impl_id_from_struct!(UserContactRequest as ContactRequestId, req, unsafe {
ContactRequestId::from_raw_unchecked(req.contact_request_id)
});
impl_id_from_struct!(GroupInfo as GroupId, group, unsafe {
GroupId::from_raw_unchecked(group.group_id)
});
impl_id_from_struct!(GroupInfo as ChatId, group, GroupId::from(group).into());
impl_id_from_struct!(CIMeta as MessageId, meta, unsafe {
MessageId::from_raw_unchecked(meta.item_id)
});
impl_id_from_struct!(ChatItem as MessageId, item, MessageId::from(&item.meta));
impl_id_from_struct!(AChatItem as MessageId, it, MessageId::from(&it.chat_item));
impl_id_from_struct!(CIFile as FileId, file, unsafe {
FileId::from_raw_unchecked(file.file_id)
});
impl_id_from_struct!(RcvFileTransfer as FileId, ft, unsafe {
FileId::from_raw_unchecked(ft.file_id)
});
impl_id_from_struct!(FileTransferMeta as FileId, ft, unsafe {
FileId::from_raw_unchecked(ft.file_id)
});
impl_id_from_struct!(SndFileTransfer as FileId, ft, unsafe {
FileId::from_raw_unchecked(ft.file_id)
});
impl_id_from_struct!(GroupMember as MemberId, member, unsafe {
MemberId::from_raw_unchecked(member.group_member_id)
});
impl_id_from_struct!(GroupRelay as RelayId, relay, unsafe {
RelayId::from_raw_unchecked(relay.group_relay_id)
});