made-core 0.1.3

Domain core of MADE: entities, value objects, events, ports. No IO.
Documentation
use std::collections::BTreeSet;

use serde::{Deserialize, Serialize};

use crate::error::DomainError;

use super::RoleId;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "role_ids", rename_all = "snake_case")]
pub enum CeremonyInterventionTarget {
    Table,
    Roles(BTreeSet<RoleId>),
}

impl CeremonyInterventionTarget {
    #[must_use]
    pub const fn table() -> Self {
        Self::Table
    }

    pub fn roles(role_ids: impl IntoIterator<Item = RoleId>) -> Result<Self, DomainError> {
        let role_ids = role_ids.into_iter().collect::<BTreeSet<_>>();
        if role_ids.is_empty() {
            return Err(DomainError::EmptyField {
                field: "ceremony_intervention.target_role_ids",
            });
        }
        Ok(Self::Roles(role_ids))
    }

    #[must_use]
    pub fn accepts(&self, role_id: &RoleId) -> bool {
        match self {
            Self::Table => true,
            Self::Roles(role_ids) => role_ids.contains(role_id),
        }
    }

    #[must_use]
    pub fn role_ids(&self) -> Option<&BTreeSet<RoleId>> {
        match self {
            Self::Table => None,
            Self::Roles(role_ids) => Some(role_ids),
        }
    }

    #[must_use]
    pub const fn is_table(&self) -> bool {
        matches!(self, Self::Table)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn table_accepts_every_role_and_scoped_target_does_not() {
        let engineer = RoleId::new("ENGINEER").unwrap();
        let observer = RoleId::new("OBSERVER").unwrap();

        assert!(CeremonyInterventionTarget::table().accepts(&engineer));
        let target = CeremonyInterventionTarget::roles([observer.clone()]).unwrap();
        assert!(target.accepts(&observer));
        assert!(!target.accepts(&engineer));
    }
}