1use spn_core::BackendError;
4use std::path::PathBuf;
5use thiserror::Error;
6
7pub type Result<T> = std::result::Result<T, NativeError>;
9
10#[derive(Error, Debug)]
12pub enum NativeError {
13 #[error("HTTP request failed: {0}")]
15 Http(#[from] reqwest::Error),
16
17 #[error("I/O error: {0}")]
19 Io(#[from] std::io::Error),
20
21 #[error("Model not found: {repo}/{filename}")]
23 ModelNotFound {
24 repo: String,
26 filename: String,
28 },
29
30 #[error("Checksum mismatch for {path}: expected {expected}, got {actual}")]
32 ChecksumMismatch {
33 path: PathBuf,
35 expected: String,
37 actual: String,
39 },
40
41 #[error("Invalid model configuration: {0}")]
43 InvalidConfig(String),
44
45 #[error("Download interrupted: {0}")]
47 Interrupted(String),
48
49 #[error("Storage directory error: {0}")]
51 StorageDir(String),
52
53 #[error("JSON parse error: {0}")]
55 Json(#[from] serde_json::Error),
56
57 #[error("No model loaded")]
67 ModelNotLoaded,
68
69 #[allow(dead_code)] #[error("Inference failed: {0}")]
74 InferenceFailed(String),
75
76 #[allow(dead_code)] #[error("Unsupported architecture: {0}")]
81 UnsupportedArchitecture(String),
82
83 #[allow(dead_code)] #[error("Device error: {0}")]
88 DeviceError(String),
89
90 #[allow(dead_code)] #[error("Tokenizer error: {0}")]
95 TokenizerError(String),
96}
97
98impl From<NativeError> for BackendError {
99 fn from(err: NativeError) -> Self {
100 match err {
101 NativeError::Http(e) => BackendError::NetworkError(e.to_string()),
102 NativeError::Io(e) => BackendError::StorageError(e.to_string()),
103 NativeError::ModelNotFound { repo, filename } => {
104 BackendError::ModelNotFound(format!("{repo}/{filename}"))
105 }
106 NativeError::ChecksumMismatch {
107 expected, actual, ..
108 } => BackendError::ChecksumError { expected, actual },
109 NativeError::InvalidConfig(msg) => BackendError::InvalidConfig(msg),
110 NativeError::Interrupted(msg) => BackendError::DownloadError(msg),
111 NativeError::StorageDir(msg) => BackendError::StorageError(msg),
112 NativeError::Json(e) => BackendError::ParseError(e.to_string()),
113 NativeError::ModelNotLoaded => {
115 BackendError::BackendSpecific("No model loaded".to_string())
116 }
117 NativeError::InferenceFailed(msg) => BackendError::BackendSpecific(msg),
118 NativeError::UnsupportedArchitecture(arch) => {
119 BackendError::InvalidConfig(format!("Unsupported architecture: {arch}"))
120 }
121 NativeError::DeviceError(msg) => BackendError::BackendSpecific(msg),
122 NativeError::TokenizerError(msg) => BackendError::BackendSpecific(msg),
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_error_display() {
133 let err = NativeError::ModelNotFound {
134 repo: "test/repo".to_string(),
135 filename: "model.gguf".to_string(),
136 };
137 assert!(err.to_string().contains("test/repo"));
138 assert!(err.to_string().contains("model.gguf"));
139 }
140
141 #[test]
142 fn test_checksum_error() {
143 let err = NativeError::ChecksumMismatch {
144 path: PathBuf::from("/tmp/model.gguf"),
145 expected: "abc123".to_string(),
146 actual: "def456".to_string(),
147 };
148 assert!(err.to_string().contains("abc123"));
149 assert!(err.to_string().contains("def456"));
150 }
151
152 #[test]
153 fn test_into_backend_error() {
154 let err = NativeError::InvalidConfig("bad config".to_string());
155 let backend_err: BackendError = err.into();
156 assert!(matches!(backend_err, BackendError::InvalidConfig(_)));
157 }
158}