1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use thiserror::Error;
4
5#[derive(Debug, Error, Clone, Serialize, JsonSchema)]
7pub enum Error {
8 #[error("iii is not connected")]
9 NotConnected,
10 #[error("invocation timed out")]
11 Timeout,
12 #[error("runtime error: {0}")]
13 Runtime(String),
14 #[error("remote error ({code}): {message}")]
15 Remote {
16 code: String,
17 message: String,
18 stacktrace: Option<String>,
19 },
20 #[error("handler error: {0}")]
21 Handler(String),
22 #[error("serialization error: {0}")]
23 Serde(String),
24 #[error("websocket error: {0}")]
25 WebSocket(String),
26}
27
28impl From<serde_json::Error> for Error {
29 fn from(err: serde_json::Error) -> Self {
30 Error::Serde(err.to_string())
31 }
32}
33
34impl From<String> for Error {
35 fn from(msg: String) -> Self {
36 Error::Handler(msg)
37 }
38}
39
40impl From<&str> for Error {
41 fn from(msg: &str) -> Self {
42 Error::Handler(msg.to_string())
43 }
44}
45
46impl From<tokio_tungstenite::tungstenite::Error> for Error {
47 fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
48 Error::WebSocket(err.to_string())
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
58pub struct InvocationError {
59 pub code: String,
60 pub message: String,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub function_id: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub stacktrace: Option<String>,
65}
66
67impl std::fmt::Display for InvocationError {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 write!(f, "{}: {}", self.code, self.message)
70 }
71}
72
73impl std::error::Error for InvocationError {}
74
75impl Error {
76 pub fn invocation_error(&self) -> Option<InvocationError> {
79 match self {
80 Error::Remote {
81 code,
82 message,
83 stacktrace,
84 } => Some(InvocationError {
85 code: code.clone(),
86 message: message.clone(),
87 function_id: None,
88 stacktrace: stacktrace.clone(),
89 }),
90 _ => None,
91 }
92 }
93}
94
95#[cfg(test)]
96mod invocation_error_tests {
97 use super::*;
98
99 #[test]
100 fn remote_error_yields_invocation_error() {
101 let err = Error::Remote {
102 code: "FORBIDDEN".into(),
103 message: "nope".into(),
104 stacktrace: Some("trace".into()),
105 };
106 let inv = err.invocation_error().expect("remote -> invocation");
107 assert_eq!(inv.code, "FORBIDDEN");
108 assert_eq!(inv.message, "nope");
109 assert_eq!(inv.stacktrace.as_deref(), Some("trace"));
110 assert_eq!(inv.to_string(), "FORBIDDEN: nope");
111 }
112
113 #[test]
114 fn non_remote_error_yields_none() {
115 assert!(Error::Timeout.invocation_error().is_none());
116 }
117}