use std::collections::BTreeSet;
use std::fmt;
use std::str::FromStr;
use nostr::{EventId, PublicKey, RelayUrl, Timestamp};
use openmls::group::GroupId;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::error::GroupError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum GroupState {
Active,
Inactive,
Pending,
}
impl fmt::Display for GroupState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl GroupState {
pub fn as_str(&self) -> &str {
match self {
Self::Active => "active",
Self::Inactive => "inactive",
Self::Pending => "pending",
}
}
}
impl FromStr for GroupState {
type Err = GroupError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"active" => Ok(Self::Active),
"inactive" => Ok(Self::Inactive),
"pending" => Ok(Self::Pending),
_ => Err(GroupError::InvalidParameters(format!(
"Invalid group state: {}",
s
))),
}
}
}
impl Serialize for GroupState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for GroupState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Group {
pub mls_group_id: GroupId,
pub nostr_group_id: [u8; 32],
pub name: String,
pub description: String,
pub image_url: Option<String>,
pub image_key: Option<Vec<u8>>,
pub image_nonce: Option<Vec<u8>>,
pub admin_pubkeys: BTreeSet<PublicKey>,
pub last_message_id: Option<EventId>,
pub last_message_at: Option<Timestamp>,
pub epoch: u64,
pub state: GroupState,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct GroupRelay {
pub relay_url: RelayUrl,
pub mls_group_id: GroupId,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct GroupExporterSecret {
pub mls_group_id: GroupId,
pub epoch: u64,
pub secret: [u8; 32],
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_group_state_from_str() {
assert_eq!(GroupState::from_str("active").unwrap(), GroupState::Active);
assert_eq!(
GroupState::from_str("inactive").unwrap(),
GroupState::Inactive
);
let err = GroupState::from_str("invalid").unwrap_err();
match err {
GroupError::InvalidParameters(msg) => {
assert!(msg.contains("Invalid group state: invalid"));
}
_ => panic!("Expected InvalidParameters error"),
}
}
#[test]
fn test_group_state_to_string() {
assert_eq!(GroupState::Active.to_string(), "active");
assert_eq!(GroupState::Inactive.to_string(), "inactive");
}
#[test]
fn test_group_state_serialization() {
let active = GroupState::Active;
let serialized = serde_json::to_string(&active).unwrap();
assert_eq!(serialized, r#""active""#);
let inactive = GroupState::Inactive;
let serialized = serde_json::to_string(&inactive).unwrap();
assert_eq!(serialized, r#""inactive""#);
}
#[test]
fn test_group_state_deserialization() {
let active: GroupState = serde_json::from_str(r#""active""#).unwrap();
assert_eq!(active, GroupState::Active);
let inactive: GroupState = serde_json::from_str(r#""inactive""#).unwrap();
assert_eq!(inactive, GroupState::Inactive);
}
#[test]
fn test_group_serialization() {
let group = Group {
mls_group_id: GroupId::from_slice(&[1, 2, 3]),
nostr_group_id: [0u8; 32],
name: "Test Group".to_string(),
description: "Test Description".to_string(),
image_url: None,
image_key: None,
image_nonce: None,
admin_pubkeys: BTreeSet::new(),
last_message_id: None,
last_message_at: None,
epoch: 0,
state: GroupState::Active,
};
let serialized = serde_json::to_value(&group).unwrap();
assert_eq!(serialized["mls_group_id"]["value"]["vec"], json!([1, 2, 3]));
assert_eq!(
serialized["nostr_group_id"],
json!([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0
])
);
assert_eq!(serialized["name"], json!("Test Group"));
assert_eq!(serialized["description"], json!("Test Description"));
assert_eq!(serialized["state"], json!("active"));
}
#[test]
fn test_group_exporter_secret_serialization() {
let secret = GroupExporterSecret {
mls_group_id: GroupId::from_slice(&[1, 2, 3]),
epoch: 42,
secret: [0u8; 32],
};
let serialized = serde_json::to_value(&secret).unwrap();
assert_eq!(serialized["mls_group_id"]["value"]["vec"], json!([1, 2, 3]));
assert_eq!(serialized["epoch"], json!(42));
assert_eq!(
serialized["secret"],
json!([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0
])
);
let deserialized: GroupExporterSecret = serde_json::from_value(serialized).unwrap();
assert_eq!(deserialized.epoch, 42);
assert_eq!(deserialized.secret, [0u8; 32]);
}
#[test]
fn test_group_relay_serialization() {
let relay = GroupRelay {
relay_url: RelayUrl::from_str("wss://relay.example.com").unwrap(),
mls_group_id: GroupId::from_slice(&[1, 2, 3]),
};
let serialized = serde_json::to_value(&relay).unwrap();
assert_eq!(serialized["relay_url"], json!("wss://relay.example.com"));
assert_eq!(serialized["mls_group_id"]["value"]["vec"], json!([1, 2, 3]));
let deserialized: GroupRelay = serde_json::from_value(serialized).unwrap();
assert_eq!(
deserialized.relay_url.to_string(),
"wss://relay.example.com"
);
}
}