use super::ClientId;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use strum::EnumString;
use thiserror::Error;
use uuid::Uuid;
pub type SessionValue = Option<Value>;
pub type SessionId = Uuid;
pub type SessionPartyNumber = u16;
#[derive(Debug, Error)]
pub enum SessionError {
#[error("party number `{0}` is already occupied by another party")]
PartyNumberAlreadyOccupied(SessionPartyNumber),
#[error("client `{0}` is already signed up")]
ClientAlreadySignedUp(ClientId),
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
pub enum SessionKind {
#[serde(rename = "keygen")]
#[strum(serialize = "keygen")]
Keygen,
#[serde(rename = "sign")]
#[strum(serialize = "sign")]
Sign,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Session {
pub id: SessionId,
pub kind: SessionKind,
pub value: SessionValue,
#[serde(skip)]
pub party_signups: HashMap<SessionPartyNumber, ClientId>,
#[serde(skip)]
pub occupied_party_numbers: Vec<SessionPartyNumber>,
#[serde(skip)]
pub finished: HashSet<u16>,
}
impl Session {
pub fn new(id: Uuid, kind: SessionKind, value: SessionValue) -> Self {
Self {
id,
kind,
value,
party_signups: HashMap::new(),
occupied_party_numbers: Vec::new(),
finished: HashSet::new(),
}
}
#[cfg(feature = "server")]
pub fn signup(&mut self, client_id: ClientId) -> anyhow::Result<SessionPartyNumber> {
if self.is_client_in_session(&client_id) {
return Err(SessionError::ClientAlreadySignedUp(client_id).into());
}
let party_number = self.get_next_party_number();
self.add_party(client_id, party_number);
Ok(party_number)
}
#[cfg(feature = "server")]
pub fn login(
&mut self,
client_id: ClientId,
party_number: SessionPartyNumber,
) -> anyhow::Result<()> {
if self.is_client_in_session(&client_id) {
return Ok(()); }
if self.occupied_party_numbers.contains(&party_number) {
return Err(SessionError::PartyNumberAlreadyOccupied(party_number).into());
}
self.add_party(client_id, party_number);
Ok(())
}
#[cfg(feature = "server")]
fn add_party(&mut self, client_id: ClientId, party_number: SessionPartyNumber) {
self.occupied_party_numbers.push(party_number);
self.occupied_party_numbers.sort();
self.party_signups.insert(party_number, client_id);
}
#[cfg(feature = "server")]
pub fn get_party_number(&self, client_id: &ClientId) -> Option<SessionPartyNumber> {
self.party_signups
.iter()
.find(|(_, id)| id == &client_id)
.map(|(party, _)| *party)
}
#[cfg(feature = "server")]
pub fn is_client_in_session(&self, client_id: &ClientId) -> bool {
self.party_signups.values().any(|id| id == client_id)
}
#[cfg(feature = "server")]
pub fn get_client_id(&self, party_number: SessionPartyNumber) -> Option<ClientId> {
self.party_signups
.iter()
.find(|(&pn, _)| pn == party_number)
.map(|(_, id)| *id)
}
#[cfg(feature = "server")]
pub fn get_all_client_ids(&self) -> Vec<ClientId> {
self.party_signups.values().copied().collect()
}
#[cfg(feature = "server")]
pub fn get_number_of_clients(&self) -> usize {
self.party_signups.len()
}
#[cfg(feature = "server")]
fn get_next_party_number(&self) -> SessionPartyNumber {
for (i, party) in self.occupied_party_numbers.iter().enumerate() {
if (i + 1) != *party as usize {
return (i + 1) as SessionPartyNumber;
}
}
match self.occupied_party_numbers.last() {
Some(party) => party + 1,
None => 1,
}
}
}
impl Clone for Session {
fn clone(&self) -> Self {
Self {
id: self.id,
kind: self.kind,
value: self.value.clone(),
party_signups: HashMap::new(),
occupied_party_numbers: Vec::new(),
finished: HashSet::new(),
}
}
}