1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use thiserror::Error;
6
7use crate::context::Context;
8use crate::effect::Disposable;
9use crate::error::ValidationError;
10
11#[derive(Debug, Error)]
12pub enum CordisError {
13 #[error("configuration error: {0}")]
14 Configuration(String),
15 #[error("fiber error: {0}")]
16 Fiber(String),
17 #[error("service not found: {0}")]
18 ServiceNotFound(String),
19 #[error("duplicate provider for '{name}' registered by '{owner}'")]
20 DuplicateProvider { name: String, owner: String },
21 #[error("invalid config: {0}")]
22 InvalidConfig(String),
23 #[error("invalid config: {0}")]
24 Validation(ValidationError),
25 #[error("fiber {fiber} stuck in transition for {waited_ms} ms")]
26 TransitionStuck { fiber: u64, waited_ms: u64 },
27 #[error("internal kernel error: {0}")]
28 Internal(String),
29 #[error("property '{name}' type mismatch: expected '{expected}'")]
30 PropertyTypeMismatch { name: String, expected: String },
31 #[error("property '{0}' is read-only")]
32 ReadOnlyProperty(String),
33}
34
35impl CordisError {
36 pub fn validation(issues: Vec<crate::error::ValidationIssue>) -> Self {
44 Self::Validation(ValidationError::new(issues))
45 }
46
47 pub fn validation_error(&self) -> Option<&ValidationError> {
53 match self {
54 Self::Validation(validation) => Some(validation),
55 _ => None,
56 }
57 }
58
59 pub fn message(&self) -> String {
65 self.to_string()
66 }
67}
68
69pub type ServiceInitFuture<'a> =
70 Pin<Box<dyn Future<Output = Result<Option<Box<dyn Disposable>>, CordisError>> + Send + 'a>>;
71
72pub trait Service: Send + Sync + 'static {
73 fn name(&self) -> &'static str {
74 std::any::type_name::<Self>()
75 }
76
77 fn init(&self, _ctx: &Arc<Context>) -> ServiceInitFuture<'_> {
78 Box::pin(async move { Ok(None) })
79 }
80
81 fn check(&self) -> bool {
103 true
104 }
105}
106
107#[cfg(test)]
108mod error_tests {
109 use super::CordisError;
110
111 #[test]
112 fn structured_variants_render_through_display_and_message() {
113 let cases: Vec<(CordisError, &str)> = vec![
114 (
115 CordisError::ServiceNotFound("ares_tools::Tools".into()),
116 "service not found: ares_tools::Tools",
117 ),
118 (
119 CordisError::DuplicateProvider {
120 name: "cordis::EventsService".into(),
121 owner: "context".into(),
122 },
123 "duplicate provider for 'cordis::EventsService' registered by 'context'",
124 ),
125 (
126 CordisError::InvalidConfig("missing url".into()),
127 "invalid config: missing url",
128 ),
129 (
130 CordisError::TransitionStuck {
131 fiber: 7,
132 waited_ms: 250,
133 },
134 "fiber 7 stuck in transition for 250 ms",
135 ),
136 (
137 CordisError::Internal("invariant violated".into()),
138 "internal kernel error: invariant violated",
139 ),
140 ];
141 for (err, expected) in cases {
142 assert_eq!(err.message(), expected);
143 match err {
145 CordisError::ServiceNotFound(name) => {
146 assert_eq!(name, "ares_tools::Tools");
147 }
148 CordisError::DuplicateProvider { name, owner } => {
149 assert_eq!(
150 (name.as_str(), owner.as_str()),
151 ("cordis::EventsService", "context")
152 );
153 }
154 CordisError::InvalidConfig(msg) => assert_eq!(msg, "missing url"),
155 CordisError::TransitionStuck { fiber, waited_ms } => {
156 assert_eq!((fiber, waited_ms), (7, 250));
157 }
158 CordisError::Internal(msg) => assert_eq!(msg, "invariant violated"),
159 other => panic!("unexpected variant: {other:?}"),
160 }
161 }
162 }
163
164 #[test]
165 fn legacy_catch_all_variants_are_unchanged() {
166 let config = CordisError::Configuration("still supported".into());
167 let fiber = CordisError::Fiber("still supported".into());
168 assert_eq!(config.message(), "configuration error: still supported");
169 assert_eq!(fiber.message(), "fiber error: still supported");
170 }
171
172 #[test]
175 fn duplicate_provider_display_keeps_asserted_phrase() {
176 let err = CordisError::DuplicateProvider {
177 name: "FooService".into(),
178 owner: "root".into(),
179 };
180 assert!(
181 err.to_string().contains("duplicate provider"),
182 "Display must keep the asserted phrase, got: {err}"
183 );
184 }
185}