simploxide-client 0.10.2

SimpleX-Chat API client
Documentation
//! Type-safe wrappers for SimpleX Chat integer IDs and conversions from API structs.
//!
//!  ID types implement `From` for their corresponding API structs(and references to them), so you can pass a
//! `&Contact`, `GroupInfo`, `ChatItem`, etc. directly wherever a typed ID is expected.

use simploxide_api_types::{
    AChatItem, CIFile, CIMeta, ChatInfo, ChatItem, ChatRef, ChatType, Contact, FileTransferMeta,
    GroupChatScope, GroupInfo, GroupMember, GroupRelay, RcvFileTransfer, SndFileTransfer, User,
    UserContactRequest,
};

macro_rules! typesafe_ids {
    ($($name:ident),*) => {
        $(
            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
            #[repr(transparent)]
            pub struct $name(pub i64);

            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
);

/// Identifies a chat: direct contact, group (optionally scoped to a member support thread),
/// or local note-to-self.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ChatId {
    Direct(ContactId),
    Group {
        id: GroupId,
        scope: Option<MemberId>,
    },
    Local(UserId),
}

impl ChatId {
    /// Creates a [`ChatId::Group`] scoped to a member support thread.
    pub fn with_group_scope(id: GroupId, group_member_support_id: MemberId) -> Self {
        Self::Group {
            id,
            scope: Some(group_member_support_id),
        }
    }

    /// Converts a [`ChatRef`] from a SimpleX API response. Returns `None` for unrecognised chat types.
    pub fn from_chat_ref(chat_ref: &ChatRef) -> Option<Self> {
        match chat_ref.chat_type {
            ChatType::Direct => Some(Self::Direct(ContactId(chat_ref.chat_id))),
            ChatType::Group => Some(Self::Group {
                id: GroupId(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(MemberId))
                }),
            }),
            ChatType::Local => Some(Self::Local(UserId(chat_ref.chat_id))),
            _ => None,
        }
    }

    /// Converts a [`ChatInfo`] from a SimpleX API response. Returns `None` for unrecognised chat types.
    pub fn from_chat_info(chat_info: &ChatInfo) -> Option<Self> {
        match chat_info {
            ChatInfo::Direct { contact, .. } => Some(Self::Direct(ContactId(contact.contact_id))),
            ChatInfo::Group {
                group_info,
                group_chat_scope,
                ..
            } => Some(Self::Group {
                id: GroupId(group_info.group_id),
                scope: group_chat_scope.as_ref().and_then(|scope| {
                    scope
                        .member_support()
                        .and_then(|member| member.as_ref().map(|member| MemberId(member.group_id)))
                }),
            }),
            ChatInfo::Local { note_folder, .. } => Some(Self::Local(UserId(note_folder.user_id))),
            _ => None,
        }
    }

    /// Converts back into a [`ChatRef`] for use in raw API calls.
    pub fn into_chat_ref(self) -> ChatRef {
        let (chat_type, chat_id, chat_scope) = match self {
            Self::Direct(contact_id) => (ChatType::Direct, contact_id.0, None),
            Self::Group {
                id: group_id,
                scope,
            } => (
                ChatType::Group,
                group_id.0,
                scope.map(|member_id| GroupChatScope::MemberSupport {
                    group_member_id: Some(member_id.0),
                    undocumented: Default::default(),
                }),
            ),
            Self::Local(user_id) => (ChatType::Local, user_id.0, 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)
    }
}

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, UserId(user.user_id));

impl_id_from_struct!(Contact as ContactId, contact, ContactId(contact.contact_id));
impl_id_from_struct!(Contact as ChatId, contact, ContactId::from(contact).into());

impl_id_from_struct!(
    UserContactRequest as ContactRequestId,
    req,
    ContactRequestId(req.contact_request_id)
);

impl_id_from_struct!(GroupInfo as GroupId, group, GroupId(group.group_id));
impl_id_from_struct!(GroupInfo as ChatId, group, GroupId::from(group).into());

impl_id_from_struct!(CIMeta as MessageId, meta, MessageId(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, FileId(file.file_id));
impl_id_from_struct!(RcvFileTransfer as FileId, ft, FileId(ft.file_id));
impl_id_from_struct!(FileTransferMeta as FileId, ft, FileId(ft.file_id));
impl_id_from_struct!(SndFileTransfer as FileId, ft, FileId(ft.file_id));

impl_id_from_struct!(
    GroupMember as MemberId,
    member,
    MemberId(member.group_member_id)
);
impl_id_from_struct!(GroupRelay as RelayId, relay, RelayId(relay.group_relay_id));