nizaam-core 1.0.1

Domain-agnostic Rust infrastructure core for building secure, observable, reliable, and extensible Nizaam engines.
Documentation
use nizaam_core::error::{
    CatalogError, ErrorCatalog, ErrorClass, ErrorCode, ErrorContext, ErrorDefinition, ErrorOwner,
    ErrorSystem, GlobalError, InvalidDefinition, ReportError, Severity,
};
use nizaam_core::prelude::*;

fn operation_context() -> OperationContext {
    OperationContext::new(Operation::new(
        OperationId::new("operation-3").unwrap(),
        CorrelationId::new("correlation-3").unwrap(),
    ))
}

fn definition() -> ErrorDefinition {
    ErrorDefinition::new(
        ErrorCode::new("CORE.CONTRACT.001").unwrap(),
        ErrorOwner::new("CORE").unwrap(),
        Version::new(1, 0, 0),
        ErrorClass::Contract,
        Severity::Error,
        "Contract rejected",
        Retryability::NonRetryable,
    )
    .unwrap()
}

#[test]
fn a_consumer_registers_and_reports_a_contextual_error() {
    let mut system = ErrorSystem::new();
    system.register(definition()).unwrap();
    let instance = system.instance();
    let cause = ErrorReference::new("CORE.TRANSPORT.001").unwrap();
    let context = ErrorContext::new(operation_context())
        .from_engine(EngineId::new("CORE").unwrap())
        .for_capability(CapabilityId::new("contract-validation").unwrap());

    let base = instance
        .report(&ErrorCode::new("CORE.CONTRACT.001").unwrap(), context)
        .unwrap();
    let event = instance
        .report_error(
            GlobalError {
                cause: Some(cause.clone()),
                ..base.error
            }
            .with_detail(
                nizaam_core::error::DiagnosticDetail::new("reason", "version mismatch").unwrap(),
            ),
        )
        .unwrap();

    assert_eq!(event.reference().as_str(), "CORE.CONTRACT.001");
    assert_eq!(
        event.error.context.operation.operation.id.as_str(),
        "operation-3"
    );
    assert_eq!(event.error.cause, Some(cause));
    assert_eq!(event.error.details[0].key, "reason");
}

#[test]
fn reporting_requires_registered_definitions() {
    let system = ErrorSystem::new();
    let result = system.instance().report(
        &ErrorCode::new("CORE.CONTRACT.404").unwrap(),
        ErrorContext::new(operation_context()),
    );

    assert_eq!(
        result,
        Err(ReportError::Validation(
            nizaam_core::error::ValidationError::UnregisteredDefinition
        ))
    );
}

#[test]
fn reporting_rejects_definition_metadata_mismatches() {
    let mut system = ErrorSystem::new();
    system.register(definition()).unwrap();
    let instance = system.instance();
    let base = instance
        .report(
            &ErrorCode::new("CORE.CONTRACT.001").unwrap(),
            ErrorContext::new(operation_context()),
        )
        .unwrap();

    let mismatched = GlobalError {
        severity: Severity::Critical,
        ..base.error
    };

    assert_eq!(
        instance.report_error(mismatched),
        Err(ReportError::Validation(
            nizaam_core::error::ValidationError::DefinitionMetadataMismatch
        ))
    );
}

#[test]
fn reporting_rejects_empty_diagnostic_fields() {
    let mut system = ErrorSystem::new();
    system.register(definition()).unwrap();
    let instance = system.instance();
    let base = instance
        .report(
            &ErrorCode::new("CORE.CONTRACT.001").unwrap(),
            ErrorContext::new(operation_context()),
        )
        .unwrap();

    let invalid_detail = nizaam_core::error::DiagnosticDetail {
        key: " ".to_owned(),
        value: "version mismatch".to_owned(),
    };

    assert_eq!(
        instance.report_error(base.error.with_detail(invalid_detail)),
        Err(ReportError::Validation(
            nizaam_core::error::ValidationError::EmptyDiagnosticField
        ))
    );
}

#[test]
fn catalog_starts_empty() {
    let catalog = ErrorCatalog::new();
    assert!(catalog.is_empty());
    assert_eq!(catalog.len(), 0);
}

#[test]
fn catalog_rejects_duplicate_codes() {
    let mut catalog = ErrorCatalog::new();
    let code = ErrorCode::new("CORE.CATALOG.001").unwrap();

    catalog
        .register(
            nizaam_core::error::ErrorDefinition::new(
                code.clone(),
                nizaam_core::error::ErrorOwner::new("CORE").unwrap(),
                Version::new(1, 0, 0),
                nizaam_core::error::ErrorClass::Contract,
                nizaam_core::error::Severity::Error,
                "Test error message".to_string(),
                nizaam_core::status::Retryability::NonRetryable,
            )
            .unwrap(),
        )
        .unwrap();

    let result = catalog.register(
        nizaam_core::error::ErrorDefinition::new(
            code,
            nizaam_core::error::ErrorOwner::new("CORE").unwrap(),
            Version::new(1, 0, 0),
            nizaam_core::error::ErrorClass::Contract,
            nizaam_core::error::Severity::Error,
            "Duplicate definition".to_string(),
            nizaam_core::status::Retryability::NonRetryable,
        )
        .unwrap(),
    );

    assert!(matches!(result, Err(CatalogError::DuplicateCode(_))));
}

#[test]
fn catalog_allows_multiple_codes() {
    let mut catalog = ErrorCatalog::new();

    let code1 = ErrorCode::new("CORE.MULTI.001").unwrap();
    let code2 = ErrorCode::new("CORE.MULTI.002").unwrap();
    let code3 = ErrorCode::new("CORE.MULTI.003").unwrap();

    catalog
        .register(
            nizaam_core::error::ErrorDefinition::new(
                code1,
                nizaam_core::error::ErrorOwner::new("CORE").unwrap(),
                Version::new(1, 0, 0),
                nizaam_core::error::ErrorClass::Contract,
                nizaam_core::error::Severity::Error,
                "Test".to_string(),
                nizaam_core::status::Retryability::NonRetryable,
            )
            .unwrap(),
        )
        .unwrap();
    catalog
        .register(
            nizaam_core::error::ErrorDefinition::new(
                code2,
                nizaam_core::error::ErrorOwner::new("CORE").unwrap(),
                Version::new(1, 0, 0),
                nizaam_core::error::ErrorClass::Contract,
                nizaam_core::error::Severity::Error,
                "Test".to_string(),
                nizaam_core::status::Retryability::NonRetryable,
            )
            .unwrap(),
        )
        .unwrap();
    catalog
        .register(
            nizaam_core::error::ErrorDefinition::new(
                code3,
                nizaam_core::error::ErrorOwner::new("CORE").unwrap(),
                Version::new(1, 0, 0),
                nizaam_core::error::ErrorClass::Contract,
                nizaam_core::error::Severity::Error,
                "Test".to_string(),
                nizaam_core::status::Retryability::NonRetryable,
            )
            .unwrap(),
        )
        .unwrap();

    assert_eq!(catalog.len(), 3);
}

#[test]
fn catalog_returns_none_for_unknown_code() {
    let catalog = ErrorCatalog::new();
    let unknown_code = ErrorCode::new("CORE.UNKNOWN.999").unwrap();
    assert!(catalog.get(&unknown_code).is_none());
}

#[test]
fn definition_rejects_empty_message() {
    let code = ErrorCode::new("CORE.EMPTY.001").unwrap();
    let owner = ErrorOwner::new("CORE").unwrap();

    let result = ErrorDefinition::new(
        code,
        owner,
        Version::new(1, 0, 0),
        ErrorClass::Contract,
        Severity::Error,
        "",
        Retryability::NonRetryable,
    );

    assert!(matches!(result, Err(InvalidDefinition::EmptyMessage)));
}

#[test]
fn definition_rejects_owner_namespace_mismatch() {
    let code = ErrorCode::new("CORE.MISMATCH.001").unwrap();
    let owner = ErrorOwner::new("TRANSPORT").unwrap();

    let result = ErrorDefinition::new(
        code,
        owner,
        Version::new(1, 0, 0),
        ErrorClass::Contract,
        Severity::Error,
        "Namespace mismatch",
        Retryability::NonRetryable,
    );

    assert!(matches!(
        result,
        Err(InvalidDefinition::OwnerNamespaceMismatch)
    ));
}