kcode_k1_web_code_service_errors/
lib.rs1#![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}