#![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");
}
}