kcode-k1-web-code-service-errors 0.1.0

Own K1 WebCode service error classification
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../Documentation.md")]

use kcode_k1_objects::TxId;
use kcode_k1_web_code_runtime::RuntimeError;
use kcode_k1_web_code_session::SessionError;
use kcode_k1_web_coding::WebCodingError;
use kcode_k1_web_source_object::CaptureStateError;
use std::fmt::{Display, Formatter};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ServiceFailure {
    State,
    Authorization,
    WorkspaceDenied,
    FamilyExists,
    SourceUnavailable,
    VersionUsed,
    ForeignHandle,
    StaleSource,
    InvalidDocument,
    CheckFailed,
    SourcePreservation,
    PublicReleaseDenied,
    Publication,
    Dependency,
}

#[derive(Debug)]
pub struct ServiceError {
    failure: ServiceFailure,
    message: String,
    source_object: Option<TxId>,
    session_error: Option<SessionError>,
}

impl ServiceError {
    pub fn new(failure: ServiceFailure, message: impl Into<String>) -> Self {
        Self {
            failure,
            message: message.into(),
            source_object: None,
            session_error: None,
        }
    }

    pub const fn failure(&self) -> ServiceFailure {
        self.failure
    }

    pub const fn source_object(&self) -> Option<TxId> {
        self.source_object
    }

    pub fn with_source_object(mut self, object: Option<TxId>) -> Self {
        self.source_object = object;
        self
    }

    pub fn from_session(error: SessionError) -> Self {
        let failure = match error {
            SessionError::ForeignHandle => ServiceFailure::ForeignHandle,
            SessionError::StaleSource => ServiceFailure::StaleSource,
            SessionError::InvalidDocument(_) => ServiceFailure::InvalidDocument,
        };
        Self {
            failure,
            message: error.to_string(),
            source_object: None,
            session_error: Some(error),
        }
    }

    pub fn from_runtime(error: RuntimeError) -> Self {
        match error {
            RuntimeError::Coding(error) => Self::from_coding(error),
            RuntimeError::LockPoisoned(message) => Self::new(ServiceFailure::State, message),
        }
    }

    pub fn from_coding(error: WebCodingError) -> Self {
        let failure = match &error {
            WebCodingError::Authorization(_) => ServiceFailure::Authorization,
            WebCodingError::WorkspaceDenied => ServiceFailure::WorkspaceDenied,
            WebCodingError::CandidateUnavailable => ServiceFailure::SourceUnavailable,
            WebCodingError::CheckFailed(_) => ServiceFailure::CheckFailed,
            WebCodingError::SourcePreservation(_) => ServiceFailure::SourcePreservation,
            WebCodingError::PublicReleaseDenied => ServiceFailure::PublicReleaseDenied,
            WebCodingError::Projection(_) | WebCodingError::ProjectionAfterSubmit { .. } => {
                ServiceFailure::Publication
            }
            _ => ServiceFailure::Dependency,
        };
        Self::new(failure, error.to_string())
    }

    pub fn from_capture(error: CaptureStateError) -> Self {
        let message = match error {
            CaptureStateError::Poisoned => "source object capture mutex poisoned",
            CaptureStateError::Missing => "publication completed without a captured source object",
        };
        Self::new(ServiceFailure::State, message)
    }
}

impl Display for ServiceError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(&self.message)
    }
}

impl std::error::Error for ServiceError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.session_error.as_ref().map(|error| error as _)
    }
}

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

    #[test]
    fn session_errors_keep_classification_and_source() {
        let error = ServiceError::from_session(SessionError::StaleSource);
        assert_eq!(error.failure(), ServiceFailure::StaleSource);
        assert!(std::error::Error::source(&error).is_some());
        assert_eq!(error.to_string(), "stale source");
    }
}