use bincode::{Decode, Encode};
use crate::infinitedb_core::{
address::{RevisionId, SpaceId},
branch::BranchId,
snapshot::SnapshotId,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SessionId(pub [u8; 16]);
impl SessionId {
pub fn new_random() -> Self {
let mut bytes = [0u8; 16];
getrandom::fill(&mut bytes).expect("getrandom failed");
Self(bytes)
}
pub fn as_hex(&self) -> String {
self.0.iter().map(|b| format!("{:02x}", b)).collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
pub enum AccessLevel {
ReadOnly,
ReadWrite,
Admin,
}
#[derive(Debug, Clone)]
pub struct SpaceGrant {
pub space: SpaceId,
pub level: AccessLevel,
}
#[derive(Debug)]
pub struct Session {
pub id: SessionId,
pub branch: BranchId,
pub pinned_snapshot: SnapshotId,
pub opened_at: RevisionId,
grants: Vec<SpaceGrant>,
}
impl Session {
pub fn new(
branch: BranchId,
pinned_snapshot: SnapshotId,
opened_at: RevisionId,
grants: Vec<SpaceGrant>,
) -> Self {
Self {
id: SessionId::new_random(),
branch,
pinned_snapshot,
opened_at,
grants,
}
}
pub fn access(&self, space: SpaceId) -> Option<AccessLevel> {
self.grants
.iter()
.find(|g| g.space == space)
.map(|g| g.level)
}
pub fn can_write(&self, space: SpaceId) -> bool {
matches!(
self.access(space),
Some(AccessLevel::ReadWrite) | Some(AccessLevel::Admin)
)
}
pub fn can_manage_branches(&self) -> bool {
self.grants.iter().any(|g| {
matches!(g.level, AccessLevel::ReadWrite | AccessLevel::Admin)
})
}
pub fn can_write_branch(&self, target_branch: BranchId) -> bool {
if self.branch == target_branch {
return true;
}
self.grants
.iter()
.any(|g| matches!(g.level, AccessLevel::Admin))
}
pub fn open_at_revision(
branch: BranchId,
pinned_snapshot: SnapshotId,
revision: u64,
grants: Vec<SpaceGrant>,
) -> Self {
Self::new(branch, pinned_snapshot, RevisionId(revision), grants)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::infinitedb_core::address::{RevisionId, SpaceId};
use crate::infinitedb_core::branch::BranchId;
use crate::infinitedb_core::snapshot::SnapshotId;
fn make_session(level: AccessLevel) -> Session {
Session::new(
BranchId(1),
SnapshotId(1),
RevisionId(0),
vec![SpaceGrant { space: SpaceId(1), level }],
)
}
#[test]
fn read_only_cannot_write() {
let s = make_session(AccessLevel::ReadOnly);
assert!(!s.can_write(SpaceId(1)));
}
#[test]
fn read_write_can_write() {
let s = make_session(AccessLevel::ReadWrite);
assert!(s.can_write(SpaceId(1)));
}
#[test]
fn no_grant_returns_none() {
let s = make_session(AccessLevel::ReadOnly);
assert!(s.access(SpaceId(99)).is_none());
}
}