1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[non_exhaustive]
13#[serde(rename_all = "snake_case")]
14pub enum CoreErrorCode {
15 InvalidId,
16 InvalidTick,
17 InvalidSequence,
18 ChannelClosed,
19 ChannelFull,
20 ChannelEmpty,
21 Unsupported,
22 Internal,
23}
24
25#[derive(Debug, Clone, Error, Serialize, Deserialize)]
34#[non_exhaustive]
35#[error("{code:?}: {message}")]
36pub struct CoreError {
37 code: CoreErrorCode,
38 message: String,
39}
40
41impl CoreError {
42 pub fn new(code: CoreErrorCode, message: impl Into<String>) -> Self {
43 Self {
44 code,
45 message: message.into(),
46 }
47 }
48
49 pub fn code(&self) -> CoreErrorCode {
50 self.code
51 }
52
53 pub fn message(&self) -> &str {
54 &self.message
55 }
56
57 pub fn with_context(self, context: impl Into<String>) -> Self {
59 let combined = format!("{}: {}", context.into(), self.message);
60 Self {
61 code: self.code,
62 message: combined,
63 }
64 }
65}
66
67pub type CoreResult<T> = Result<T, CoreError>;
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn serde_round_trip() {
76 let err = CoreError::new(CoreErrorCode::InvalidId, "bad id");
77 let json = serde_json::to_string(&err).expect("serialize");
78 let back: CoreError = serde_json::from_str(&json).expect("deserialize");
79 assert_eq!(back.code(), CoreErrorCode::InvalidId);
80 assert_eq!(back.message(), "bad id");
81 }
82
83 #[test]
84 fn display_includes_code() {
85 let err = CoreError::new(CoreErrorCode::ChannelFull, "full");
86 let rendered = err.to_string();
87 assert!(rendered.contains("ChannelFull"));
88 assert!(rendered.contains("full"));
89 }
90}