use std::collections::BTreeSet;
use arkhe_kernel::abi::{EntityId, Tick, TypeCode};
use serde::{Deserialize, Serialize};
use crate::action::ActionCompute;
use crate::actor::ActorId;
use crate::brand::ShellId;
use crate::component::BoundedString;
use crate::context::{ActionContext, ActionError};
use crate::ArkheAction;
use crate::ArkheComponent;
use crate::arkhe_pure;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SpaceId(EntityId);
impl SpaceId {
#[inline]
#[must_use]
pub fn new(id: EntityId) -> Self {
Self(id)
}
#[inline]
#[must_use]
pub fn get(self) -> EntityId {
self.0
}
}
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum SpaceKind {
Flat = 0,
Tree = 1,
Graph = 2,
Hashtag = 3,
ActorFeed = 4,
Extension {
type_code: TypeCode,
} = 255,
}
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Visibility {
Public = 0,
RestrictedByRole = 1,
SubscribersOnly = 2,
PrivateInvite = 3,
Encrypted = 4,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheComponent)]
#[arkhe(type_code = 0x0003_0201, schema_version = 1)]
pub struct SpaceConfig {
pub schema_version: u16,
pub shell_id: ShellId,
pub slug: BoundedString<32>,
pub kind: SpaceKind,
pub visibility: Visibility,
pub creator: ActorId,
pub parent_space: Option<SpaceId>,
pub created_tick: Tick,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, ArkheComponent)]
#[arkhe(type_code = 0x0003_0202, schema_version = 1)]
pub struct ParentChainDepth {
pub schema_version: u16,
pub depth: u8,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheComponent)]
#[arkhe(type_code = 0x0003_0203, schema_version = 1)]
pub struct SpaceMembership {
pub schema_version: u16,
pub members: BTreeSet<ActorId>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheAction)]
#[arkhe(type_code = 0x0001_0201, schema_version = 1, band = 1)]
pub struct CreateSpace {
pub schema_version: u16,
pub config: SpaceConfig,
}
impl ActionCompute for CreateSpace {
#[arkhe_pure]
fn compute<'i>(&self, ctx: &mut ActionContext<'i>) -> Result<(), ActionError> {
ctx.ensure_actor_eligible(self.config.creator, ctx.tick())?;
let child_depth: u8 = match self.config.parent_space {
Some(parent_id) => {
let parent_depth = ctx
.read::<ParentChainDepth>(parent_id.get())?
.ok_or(ActionError::InvalidInput("parent space not found"))?;
let next = parent_depth.depth.saturating_add(1);
if next > MAX_SPACE_DEPTH {
return Err(ActionError::InvalidInput("space depth exceeded"));
}
next
}
None => 0,
};
let space_entity = ctx.spawn_entity_for::<SpaceConfig>()?;
ctx.set_component(space_entity, &self.config)?;
ctx.set_component(
space_entity,
&ParentChainDepth {
schema_version: 1,
depth: child_depth,
},
)?;
Ok(())
}
}
pub const MAX_SPACE_DEPTH: u8 = 64;
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::action::ArkheAction;
use crate::component::ArkheComponent;
fn ent(v: u64) -> EntityId {
EntityId::new(v).unwrap()
}
#[test]
fn space_config_serde_roundtrip_postcard() {
let cfg = SpaceConfig {
schema_version: 1,
shell_id: ShellId([0x01; 16]),
slug: BoundedString::<32>::new("general").unwrap(),
kind: SpaceKind::Tree,
visibility: Visibility::Public,
creator: ActorId::new(ent(42)),
parent_space: None,
created_tick: Tick(0),
};
let bytes = postcard::to_stdvec(&cfg).unwrap();
let back: SpaceConfig = postcard::from_bytes(&bytes).unwrap();
assert_eq!(cfg, back);
}
#[test]
fn space_membership_preserves_canonical_order() {
let mut set = BTreeSet::new();
set.insert(ActorId::new(ent(3)));
set.insert(ActorId::new(ent(1)));
set.insert(ActorId::new(ent(2)));
let m = SpaceMembership {
schema_version: 1,
members: set,
};
let serialized_once = postcard::to_stdvec(&m).unwrap();
let mut set2 = BTreeSet::new();
set2.insert(ActorId::new(ent(2)));
set2.insert(ActorId::new(ent(1)));
set2.insert(ActorId::new(ent(3)));
let m2 = SpaceMembership {
schema_version: 1,
members: set2,
};
assert_eq!(serialized_once, postcard::to_stdvec(&m2).unwrap());
}
#[test]
fn space_config_action_type_codes() {
assert_eq!(SpaceConfig::TYPE_CODE, 0x0003_0201);
assert_eq!(ParentChainDepth::TYPE_CODE, 0x0003_0202);
assert_eq!(SpaceMembership::TYPE_CODE, 0x0003_0203);
assert_eq!(CreateSpace::TYPE_CODE, 0x0001_0201);
assert_eq!(CreateSpace::BAND, 1);
}
#[test]
fn max_space_depth_is_sixty_four() {
assert_eq!(MAX_SPACE_DEPTH, 64);
}
}