icydb-core 0.219.1

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
//! Module: index::plan::error
//! Responsibility: carry index-planning errors plus boundary-observable signals.
//! Does not own: metrics emission, commit materialization, or executor behavior.
//! Boundary: index planning annotates outcomes; commit/executor boundaries observe them.

use crate::{
    db::commit::CommitSchemaFingerprint,
    error::{AcceptedConstraintFactContext, InternalError, MutationDiagnosticContext},
};
use std::rc::Rc;

///
/// IndexPlanError
///
/// Internal planning error wrapper that preserves the canonical `InternalError`
/// taxonomy while carrying any side-effect signal the caller must observe at
/// the commit/executor boundary.
///

pub(in crate::db) struct IndexPlanError {
    error: InternalError,
    unique_violation_entity_path: Option<Rc<str>>,
}

impl IndexPlanError {
    /// Build one ordinary index-planning error without boundary side effects.
    #[must_use]
    pub(in crate::db) const fn new(error: InternalError) -> Self {
        Self {
            error,
            unique_violation_entity_path: None,
        }
    }

    /// Build one accepted unique-constraint violation with catalog identity.
    #[must_use]
    pub(in crate::db) fn unique_violation(
        accepted_schema_fingerprint: CommitSchemaFingerprint,
        mutation: Option<MutationDiagnosticContext>,
        constraint_id: u32,
        entity_path: &str,
        entity_tag: u64,
    ) -> Self {
        Self {
            error: InternalError::mutation_constraint_violation(
                AcceptedConstraintFactContext::write_admission(
                    crate::db::schema::accepted_schema_cache_fingerprint_method_version(),
                    accepted_schema_fingerprint,
                    entity_tag,
                    constraint_id,
                    icydb_diagnostic_code::DiagnosticConstraintKind::Unique,
                    mutation,
                    None,
                ),
            ),
            unique_violation_entity_path: Some(entity_path.into()),
        }
    }

    /// Return the entity path for a unique-violation metric, when present.
    #[must_use]
    pub(in crate::db) fn unique_violation_entity_path(&self) -> Option<&str> {
        self.unique_violation_entity_path.as_deref()
    }

    /// Consume this wrapper into the canonical internal error.
    #[must_use]
    pub(in crate::db) fn into_internal_error(self) -> InternalError {
        self.error
    }
}

impl From<InternalError> for IndexPlanError {
    fn from(error: InternalError) -> Self {
        Self::new(error)
    }
}

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

    #[test]
    fn unique_violation_preserves_only_compact_accepted_identity() {
        let error = IndexPlanError::unique_violation(
            [0xAB; 16],
            Some(MutationDiagnosticContext::new(
                23,
                icydb_diagnostic_code::DiagnosticMutationOperation::Replace,
                4,
            )),
            17,
            "tests::User",
            23,
        )
        .into_internal_error();
        let facts = error.diagnostic_facts();
        assert!(facts.contains(&(icydb_diagnostic_code::DiagnosticFactTag::EntityTag, 23)));
        assert!(facts.contains(&(icydb_diagnostic_code::DiagnosticFactTag::ConstraintId, 17)));
        assert!(facts.contains(&(
            icydb_diagnostic_code::DiagnosticFactTag::ConstraintKind,
            icydb_diagnostic_code::DiagnosticConstraintKind::Unique.raw()
        )));
        assert!(facts.contains(&(
            icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
            icydb_diagnostic_code::DiagnosticMutationOperation::Replace.raw(),
        )));
        assert!(facts.contains(&(icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 4,)));
    }
}