kcode-k1-groups-domain 0.1.0

K1 group domain vocabulary
Documentation
pub use kcode_k1_invites::UserId;
pub use kcode_k1_txn_ordering::TxId;

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct GroupId(TxId);

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum SentinelGroup {
    AllUsers,
    AllModels,
    LocalModels,
}

pub const ALL_USERS: GroupId = GroupId::new(TxId::from_bytes([
    255, 75, 49, 71, 82, 80, 0, 0, 0, 0, 0, 1,
]));
pub const ALL_MODELS: GroupId = GroupId::new(TxId::from_bytes([
    255, 75, 49, 71, 82, 80, 0, 0, 0, 0, 0, 2,
]));
pub const LOCAL_MODELS: GroupId = GroupId::new(TxId::from_bytes([
    255, 75, 49, 71, 82, 80, 0, 0, 0, 0, 0, 3,
]));

impl GroupId {
    pub const fn new(txid: TxId) -> Self {
        Self(txid)
    }

    pub const fn txid(self) -> TxId {
        self.0
    }

    pub const fn sentinel(self) -> Option<SentinelGroup> {
        match self {
            ALL_USERS => Some(SentinelGroup::AllUsers),
            ALL_MODELS => Some(SentinelGroup::AllModels),
            LOCAL_MODELS => Some(SentinelGroup::LocalModels),
            _ => None,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ModelId([u8; 32]);

impl ModelId {
    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
        Self(bytes)
    }

    pub const fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }

    pub const fn into_bytes(self) -> [u8; 32] {
        self.0
    }
}

#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct GroupName(String);

impl GroupName {
    pub fn new(value: String) -> Result<Self, String> {
        if !(1..=128).contains(&value.len()) {
            return Err("group name must be 1 through 128 UTF-8 bytes".to_owned());
        }
        if value.chars().any(char::is_control) {
            return Err("group name must not contain control characters".to_owned());
        }
        if !value.chars().any(|character| !character.is_whitespace()) {
            return Err("group name must contain a non-whitespace character".to_owned());
        }
        Ok(Self(value))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }

    pub fn into_string(self) -> String {
        self.0
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum GroupRole {
    User,
    Admin,
    Owner,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GroupUser {
    user_id: UserId,
    role: GroupRole,
}

impl GroupUser {
    pub fn user_id(&self) -> UserId {
        self.user_id
    }

    pub fn role(&self) -> GroupRole {
        self.role
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GroupRevision {
    group_id: GroupId,
    txid: TxId,
}

impl GroupRevision {
    pub fn group_id(&self) -> GroupId {
        self.group_id
    }

    pub fn txid(&self) -> TxId {
        self.txid
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Group {
    id: GroupId,
    name: GroupName,
    revision: GroupRevision,
    users: Vec<GroupUser>,
    models: Vec<ModelId>,
}

impl Group {
    pub fn id(&self) -> GroupId {
        self.id
    }

    pub fn name(&self) -> &GroupName {
        &self.name
    }

    pub fn revision(&self) -> &GroupRevision {
        &self.revision
    }

    pub fn users(&self) -> &[GroupUser] {
        &self.users
    }

    pub fn models(&self) -> &[ModelId] {
        &self.models
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GroupMemberships {
    revision: Option<TxId>,
    user_groups: Vec<GroupId>,
    model_groups: Vec<GroupId>,
    shared_groups: Vec<GroupId>,
}

impl GroupMemberships {
    pub fn revision(&self) -> Option<TxId> {
        self.revision
    }

    pub fn user_groups(&self) -> &[GroupId] {
        &self.user_groups
    }

    pub fn model_groups(&self) -> &[GroupId] {
        &self.model_groups
    }

    pub fn shared_groups(&self) -> &[GroupId] {
        &self.shared_groups
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GroupAction {
    Create {
        owner: UserId,
        name: GroupName,
    },
    Rename {
        group: GroupId,
        actor: UserId,
        name: GroupName,
    },
    SetUserRole {
        group: GroupId,
        actor: UserId,
        user: UserId,
        role: Option<GroupRole>,
    },
    SetModelMembership {
        group: GroupId,
        actor: UserId,
        model: ModelId,
        present: bool,
    },
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ApplyOutcome {
    Applied(GroupRevision),
    Unchanged(GroupRevision),
    Rejected(String),
}

pub mod projection_values {
    use super::{
        Group, GroupId, GroupMemberships, GroupName, GroupRevision, GroupRole, GroupUser, ModelId,
        TxId, UserId,
    };

    pub fn group_user(user_id: UserId, role: GroupRole) -> GroupUser {
        GroupUser { user_id, role }
    }

    pub fn group_revision(group_id: GroupId, txid: TxId) -> GroupRevision {
        GroupRevision { group_id, txid }
    }

    pub fn group(
        id: GroupId,
        name: GroupName,
        revision: GroupRevision,
        users: Vec<GroupUser>,
        models: Vec<ModelId>,
    ) -> Group {
        Group {
            id,
            name,
            revision,
            users,
            models,
        }
    }

    pub fn group_memberships(
        revision: Option<TxId>,
        user_groups: Vec<GroupId>,
        model_groups: Vec<GroupId>,
        shared_groups: Vec<GroupId>,
    ) -> GroupMemberships {
        GroupMemberships {
            revision,
            user_groups,
            model_groups,
            shared_groups,
        }
    }
}