1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::path::PathBuf;
4
5#[derive(Debug)]
6pub enum FurnaceError {
7 Model(ModelError),
8
9 Api(ApiError),
10
11 Nerve(crate::nerve::BuildError),
14}
15
16impl FurnaceError {
17 pub fn is_client_error(&self) -> bool {
20 matches!(
21 self,
22 Self::Model(
23 ModelError::InputValidation { .. }
24 | ModelError::EmptyBatch
25 | ModelError::BatchTooLarge { .. }
26 | ModelError::InvalidDataType(_)
27 | ModelError::InvalidInput(_)
28 | ModelError::InvalidArgument { .. }
29 )
30 )
31 }
32}
33
34impl fmt::Display for FurnaceError {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 Self::Model(e) => fmt::Display::fmt(e, f),
39 Self::Api(e) => fmt::Display::fmt(e, f),
40 Self::Nerve(e) => fmt::Display::fmt(e, f),
41 }
42 }
43}
44
45impl std::error::Error for FurnaceError {
46 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
47 match self {
48 Self::Model(e) => e.source(),
49 Self::Api(e) => e.source(),
50 Self::Nerve(e) => e.source(),
51 }
52 }
53}
54
55impl From<ModelError> for FurnaceError {
56 fn from(e: ModelError) -> Self {
57 Self::Model(e)
58 }
59}
60
61impl From<ApiError> for FurnaceError {
62 fn from(e: ApiError) -> Self {
63 Self::Api(e)
64 }
65}
66
67impl From<crate::nerve::BuildError> for FurnaceError {
68 fn from(e: crate::nerve::BuildError) -> Self {
69 Self::Nerve(e)
70 }
71}
72
73#[derive(Debug)]
74pub enum ModelError {
75 LoadFailed {
76 path: PathBuf,
77 source: Box<dyn std::error::Error + Send + Sync>,
78 },
79
80 InvalidFormat(String),
81
82 FileNotFound(PathBuf),
83
84 InferenceFailed(String),
85
86 InputValidation {
88 batch: usize,
90 item: Vec<usize>,
92 got: usize,
94 },
95
96 EmptyBatch,
98
99 BatchTooLarge {
101 size: usize,
102 max: usize,
103 },
104
105 InvalidDataType(String),
106
107 InvalidInput(String),
109
110 InvalidArgument {
111 arg: String,
112 value: String,
113 reason: String,
114 },
115}
116
117impl fmt::Display for ModelError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 match self {
120 Self::LoadFailed { path, source } => {
121 write!(f, "Failed to load model from {}: {source}", path.display())
122 }
123 Self::InvalidFormat(m) => write!(f, "Invalid model format: {m}"),
124 Self::FileNotFound(path) => write!(f, "Model file not found: {}", path.display()),
125 Self::InferenceFailed(m) => write!(f, "Inference failed: {m}"),
126 Self::InputValidation { batch, item, got } => write!(
127 f,
128 "expected {} values (batch {batch} × {item:?}), got {got}",
129 batch * item.iter().product::<usize>()
130 ),
131 Self::EmptyBatch => {
132 f.write_str("a request needs at least one item, got an empty batch")
133 }
134 Self::BatchTooLarge { size, max } => write!(
135 f,
136 "batch size {size} exceeds the limit of {max} items per request"
137 ),
138 Self::InvalidDataType(m) => write!(f, "Invalid input data type: {m}"),
139 Self::InvalidInput(m) => write!(f, "Invalid input: {m}"),
140 Self::InvalidArgument { arg, value, reason } => {
141 write!(f, "Invalid argument '{arg}': {value} - {reason}")
142 }
143 }
144 }
145}
146
147impl std::error::Error for ModelError {
148 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
149 match self {
150 Self::LoadFailed { source, .. } => Some(&**source),
151 _ => None,
152 }
153 }
154}
155
156#[derive(Debug)]
157pub enum ApiError {
158 ServerStartup(String),
159}
160
161impl fmt::Display for ApiError {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 Self::ServerStartup(m) => write!(f, "Server startup failed: {m}"),
165 }
166 }
167}
168
169impl std::error::Error for ApiError {}
170
171pub type Result<T> = std::result::Result<T, FurnaceError>;
172
173#[derive(Debug, Serialize, Deserialize)]
175pub struct ErrorResponse {
176 pub error: String,
177 pub code: String,
178 pub details: Option<serde_json::Value>,
179 pub timestamp: String,
180}
181
182impl ErrorResponse {
183 pub fn new(error: String, code: String, details: Option<serde_json::Value>) -> Self {
184 Self {
185 error,
186 code,
187 details,
188 timestamp: crate::time::rfc3339_now(),
189 }
190 }
191}