1use std::time::Duration;
4
5use ferrin_spec::JsonValue;
6use ferrin_spec::ToolName;
7
8type BoxError = Box<dyn std::error::Error + Send + Sync>;
9
10#[derive(Debug, thiserror::Error)]
16#[non_exhaustive]
17pub enum ToolError {
18 #[error("{message}")]
20 Message {
21 message: String,
23 #[source]
25 cause: Option<BoxError>,
26 },
27 #[error("tool returned error payload")]
29 Json {
30 value: JsonValue,
32 },
33 #[error("tool execution timed out after {0:?}")]
35 Timeout(Duration),
36 #[error("tool execution cancelled")]
38 Cancelled,
39}
40
41impl ToolError {
42 #[must_use]
44 pub fn message(message: impl Into<String>) -> Self {
45 Self::Message {
46 message: message.into(),
47 cause: None,
48 }
49 }
50
51 #[must_use]
53 pub fn json(value: JsonValue) -> Self {
54 Self::Json { value }
55 }
56
57 #[must_use]
59 pub fn from_error(error: impl std::error::Error + Send + Sync + 'static) -> Self {
60 Self::Message {
61 message: error.to_string(),
62 cause: Some(Box::new(error)),
63 }
64 }
65
66 #[must_use]
69 pub fn with_cause(self, cause: impl std::error::Error + Send + Sync + 'static) -> Self {
70 match self {
71 Self::Message { message, .. } => Self::Message {
72 message,
73 cause: Some(Box::new(cause)),
74 },
75 other => other,
76 }
77 }
78
79 #[must_use]
81 pub fn is_cancelled(&self) -> bool {
82 matches!(self, Self::Cancelled)
83 }
84}
85
86impl From<serde_json::Error> for ToolError {
87 fn from(error: serde_json::Error) -> Self {
88 Self::from_error(error)
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
94#[error("tool `{name}` is already defined")]
95pub struct DuplicateToolError {
96 pub name: ToolName,
98}