made-core 0.7.7

Domain core of MADE: entities, value objects, events, ports. No IO.
Documentation
use serde::{Deserialize, Deserializer, Serialize};

use crate::error::DomainError;

use super::{CeremonyId, ChildDepth, ChildDepthBudget, ChildGroupId, ChildPosition};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct CeremonyLineage {
    root_id: CeremonyId,
    parent_id: CeremonyId,
    group_id: ChildGroupId,
    position: ChildPosition,
    depth: ChildDepth,
    remaining_depth: ChildDepthBudget,
}

impl CeremonyLineage {
    pub fn new(
        root_id: CeremonyId,
        parent_id: CeremonyId,
        group_id: ChildGroupId,
        position: ChildPosition,
        depth: ChildDepth,
        remaining_depth: ChildDepthBudget,
    ) -> Result<Self, DomainError> {
        let lineage = Self {
            root_id,
            parent_id,
            group_id,
            position,
            depth,
            remaining_depth,
        };
        lineage.validate()?;
        Ok(lineage)
    }
    #[must_use]
    pub fn root_id(&self) -> &CeremonyId {
        &self.root_id
    }
    #[must_use]
    pub fn parent_id(&self) -> &CeremonyId {
        &self.parent_id
    }
    #[must_use]
    pub fn group_id(&self) -> &ChildGroupId {
        &self.group_id
    }
    #[must_use]
    pub const fn position(&self) -> ChildPosition {
        self.position
    }
    #[must_use]
    pub const fn depth(&self) -> ChildDepth {
        self.depth
    }
    #[must_use]
    pub const fn remaining_depth(&self) -> ChildDepthBudget {
        self.remaining_depth
    }

    pub fn validate(&self) -> Result<(), DomainError> {
        let root_is_parent = self.root_id == self.parent_id;
        if (self.depth == ChildDepth::FIRST) != root_is_parent {
            return Err(DomainError::InvalidDocument {
                reason: "child lineage root and parent are inconsistent with depth".to_owned(),
            });
        }
        if self.depth.get() + self.remaining_depth.get() > super::MaxChildDepth::SERVER_MAX.get() {
            return Err(DomainError::InvalidDocument {
                reason: "child lineage exceeds the server depth ceiling".to_owned(),
            });
        }
        Ok(())
    }
}

impl<'de> Deserialize<'de> for CeremonyLineage {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct UncheckedCeremonyLineage {
            root_id: CeremonyId,
            parent_id: CeremonyId,
            group_id: ChildGroupId,
            position: ChildPosition,
            depth: ChildDepth,
            remaining_depth: ChildDepthBudget,
        }

        let raw = UncheckedCeremonyLineage::deserialize(deserializer)?;
        Self::new(
            raw.root_id,
            raw.parent_id,
            raw.group_id,
            raw.position,
            raw.depth,
            raw.remaining_depth,
        )
        .map_err(serde::de::Error::custom)
    }
}