openmls_sqlx_storage 0.2.0

A storage provider for OpenMLS using SQLx.
Documentation
use openmls_traits::storage::{CURRENT_VERSION, Entity};
use sqlx::{
    Database, Decode, Encode, Sqlite, encode::IsNull, error::BoxDynError, sqlite::SqliteTypeInfo,
};

#[derive(Debug, Clone, Copy)]
pub(super) enum GroupDataType {
    JoinGroupConfig,
    Tree,
    InterimTranscriptHash,
    Context,
    ConfirmationTag,
    GroupState,
    MessageSecrets,
    ResumptionPskStore,
    OwnLeafIndex,
    UseRatchetTreeExtension,
    GroupEpochSecrets,
    #[cfg(feature = "extensions-draft-08")]
    ApplicationExportTree,
}

impl GroupDataType {
    fn to_str(self) -> &'static str {
        match self {
            GroupDataType::JoinGroupConfig => "join_group_config",
            GroupDataType::Tree => "tree",
            GroupDataType::InterimTranscriptHash => "interim_transcript_hash",
            GroupDataType::Context => "context",
            GroupDataType::ConfirmationTag => "confirmation_tag",
            GroupDataType::GroupState => "group_state",
            GroupDataType::MessageSecrets => "message_secrets",
            GroupDataType::ResumptionPskStore => "resumption_psk_store",
            GroupDataType::OwnLeafIndex => "own_leaf_index",
            GroupDataType::UseRatchetTreeExtension => "use_ratchet_tree_extension",
            GroupDataType::GroupEpochSecrets => "group_epoch_secrets",
            #[cfg(feature = "extensions-draft-08")]
            GroupDataType::ApplicationExportTree => "application_export_tree",
        }
    }

    fn from_str(s: &str) -> Option<Self> {
        match s {
            "join_group_config" => Some(GroupDataType::JoinGroupConfig),
            "tree" => Some(GroupDataType::Tree),
            "interim_transcript_hash" => Some(GroupDataType::InterimTranscriptHash),
            "context" => Some(GroupDataType::Context),
            "confirmation_tag" => Some(GroupDataType::ConfirmationTag),
            "group_state" => Some(GroupDataType::GroupState),
            "message_secrets" => Some(GroupDataType::MessageSecrets),
            "resumption_psk_store" => Some(GroupDataType::ResumptionPskStore),
            "own_leaf_index" => Some(GroupDataType::OwnLeafIndex),
            "use_ratchet_tree_extension" => Some(GroupDataType::UseRatchetTreeExtension),
            "group_epoch_secrets" => Some(GroupDataType::GroupEpochSecrets),
            #[cfg(feature = "extensions-draft-08")]
            "application_export_tree" => Some(GroupDataType::ApplicationExportTree),
            _ => None,
        }
    }
}

impl sqlx::Type<Sqlite> for GroupDataType {
    fn type_info() -> SqliteTypeInfo {
        <String as sqlx::Type<Sqlite>>::type_info()
    }
}

impl<'q> Encode<'q, Sqlite> for GroupDataType {
    fn encode_by_ref(
        &self,
        buf: &mut <Sqlite as Database>::ArgumentBuffer<'q>,
    ) -> Result<IsNull, BoxDynError> {
        Encode::<Sqlite>::encode(self.to_str(), buf)
    }
}

#[derive(Debug, thiserror::Error)]
#[error("invalid group data type: {value}")]
struct InvalidGroupDataTypeError {
    value: String,
}

impl<'r> Decode<'r, Sqlite> for GroupDataType {
    fn decode(value: <Sqlite as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
        let value: &str = Decode::<Sqlite>::decode(value)?;
        Self::from_str(value).ok_or_else(|| {
            InvalidGroupDataTypeError {
                value: value.to_string(),
            }
            .into()
        })
    }
}

pub(crate) struct StorableGroupData<GroupData: Entity<CURRENT_VERSION>>(pub GroupData);

pub(super) struct StorableGroupDataRef<'a, GroupData: Entity<CURRENT_VERSION>>(pub &'a GroupData);