Skip to main content

kcode_k1_web_code_service_errors/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../Documentation.md")]
3
4use kcode_k1_objects::TxId;
5use kcode_k1_web_code_runtime::RuntimeError;
6use kcode_k1_web_code_session::SessionError;
7use kcode_k1_web_coding::WebCodingError;
8use kcode_k1_web_source_object::CaptureStateError;
9use std::fmt::{Display, Formatter};
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum ServiceFailure {
13    State,
14    Authorization,
15    WorkspaceDenied,
16    FamilyExists,
17    SourceUnavailable,
18    VersionUsed,
19    ForeignHandle,
20    StaleSource,
21    InvalidDocument,
22    CheckFailed,
23    SourcePreservation,
24    PublicReleaseDenied,
25    Publication,
26    Dependency,
27}
28
29#[derive(Debug)]
30pub struct ServiceError {
31    failure: ServiceFailure,
32    message: String,
33    source_object: Option<TxId>,
34    session_error: Option<SessionError>,
35}
36
37impl ServiceError {
38    pub fn new(failure: ServiceFailure, message: impl Into<String>) -> Self {
39        Self {
40            failure,
41            message: message.into(),
42            source_object: None,
43            session_error: None,
44        }
45    }
46
47    pub const fn failure(&self) -> ServiceFailure {
48        self.failure
49    }
50
51    pub const fn source_object(&self) -> Option<TxId> {
52        self.source_object
53    }
54
55    pub fn with_source_object(mut self, object: Option<TxId>) -> Self {
56        self.source_object = object;
57        self
58    }
59
60    pub fn from_session(error: SessionError) -> Self {
61        let failure = match error {
62            SessionError::ForeignHandle => ServiceFailure::ForeignHandle,
63            SessionError::StaleSource => ServiceFailure::StaleSource,
64            SessionError::InvalidDocument(_) => ServiceFailure::InvalidDocument,
65        };
66        Self {
67            failure,
68            message: error.to_string(),
69            source_object: None,
70            session_error: Some(error),
71        }
72    }
73
74    pub fn from_runtime(error: RuntimeError) -> Self {
75        match error {
76            RuntimeError::Coding(error) => Self::from_coding(error),
77            RuntimeError::LockPoisoned(message) => Self::new(ServiceFailure::State, message),
78        }
79    }
80
81    pub fn from_coding(error: WebCodingError) -> Self {
82        let failure = match &error {
83            WebCodingError::Authorization(_) => ServiceFailure::Authorization,
84            WebCodingError::WorkspaceDenied => ServiceFailure::WorkspaceDenied,
85            WebCodingError::CandidateUnavailable => ServiceFailure::SourceUnavailable,
86            WebCodingError::CheckFailed(_) => ServiceFailure::CheckFailed,
87            WebCodingError::SourcePreservation(_) => ServiceFailure::SourcePreservation,
88            WebCodingError::PublicReleaseDenied => ServiceFailure::PublicReleaseDenied,
89            WebCodingError::Projection(_) | WebCodingError::ProjectionAfterSubmit { .. } => {
90                ServiceFailure::Publication
91            }
92            _ => ServiceFailure::Dependency,
93        };
94        Self::new(failure, error.to_string())
95    }
96
97    pub fn from_capture(error: CaptureStateError) -> Self {
98        let message = match error {
99            CaptureStateError::Poisoned => "source object capture mutex poisoned",
100            CaptureStateError::Missing => "publication completed without a captured source object",
101        };
102        Self::new(ServiceFailure::State, message)
103    }
104}
105
106impl Display for ServiceError {
107    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
108        formatter.write_str(&self.message)
109    }
110}
111
112impl std::error::Error for ServiceError {
113    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
114        self.session_error.as_ref().map(|error| error as _)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn session_errors_keep_classification_and_source() {
124        let error = ServiceError::from_session(SessionError::StaleSource);
125        assert_eq!(error.failure(), ServiceFailure::StaleSource);
126        assert!(std::error::Error::source(&error).is_some());
127        assert_eq!(error.to_string(), "stale source");
128    }
129}