1use serde::Serialize;
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
8#[serde(rename_all = "snake_case")]
9pub enum ErrorKind {
10 Processing,
12 Usage,
14 InputMissing,
16 Unsupported,
18 ModelNotCached,
20 ModelDownload,
22 Plugin,
24 Cancelled,
26 Verification,
28 Network,
30 Internal,
32}
33
34impl ErrorKind {
35 pub fn exit_code(self) -> i32 {
36 match self {
37 ErrorKind::Processing => 1,
38 ErrorKind::Usage => 2,
39 ErrorKind::InputMissing => 3,
40 ErrorKind::Unsupported => 4,
41 ErrorKind::ModelNotCached => 5,
42 ErrorKind::ModelDownload => 6,
43 ErrorKind::Plugin => 7,
44 ErrorKind::Cancelled => 8,
45 ErrorKind::Verification => 9,
46 ErrorKind::Network => 10,
47 ErrorKind::Internal => 70,
48 }
49 }
50
51 pub fn all() -> &'static [ErrorKind] {
52 &[
53 ErrorKind::Processing,
54 ErrorKind::Usage,
55 ErrorKind::InputMissing,
56 ErrorKind::Unsupported,
57 ErrorKind::ModelNotCached,
58 ErrorKind::ModelDownload,
59 ErrorKind::Plugin,
60 ErrorKind::Cancelled,
61 ErrorKind::Verification,
62 ErrorKind::Network,
63 ErrorKind::Internal,
64 ]
65 }
66
67 pub fn describe(self) -> &'static str {
68 match self {
69 ErrorKind::Processing => "Processing failed",
70 ErrorKind::Usage => "Usage or parameter error",
71 ErrorKind::InputMissing => "Input missing or unreadable",
72 ErrorKind::Unsupported => "Unsupported format or codec",
73 ErrorKind::ModelNotCached => "Model not cached and downloads disabled",
74 ErrorKind::ModelDownload => "Model download or verification failed",
75 ErrorKind::Plugin => "Plugin crashed, timed out, or was not found",
76 ErrorKind::Cancelled => "Cancelled",
77 ErrorKind::Verification => "Output verification failed (checksum, clipping guard, duration mismatch)",
78 ErrorKind::Network => "Network error",
79 ErrorKind::Internal => "Internal error (bug)",
80 }
81 }
82}
83
84#[derive(Debug, Clone, Serialize)]
87pub struct Error {
88 #[serde(skip)]
89 pub kind: ErrorKind,
90 pub code: String,
91 pub message: String,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub hint: Option<String>,
94 pub exit_code: i32,
95}
96
97impl Error {
98 pub fn new(kind: ErrorKind, code: impl Into<String>, message: impl Into<String>) -> Self {
99 Error { kind, code: code.into(), message: message.into(), hint: None, exit_code: kind.exit_code() }
100 }
101
102 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
103 self.hint = Some(hint.into());
104 self
105 }
106
107 pub fn usage(message: impl Into<String>) -> Self {
108 Error::new(ErrorKind::Usage, "invalid_argument", message)
109 }
110
111 pub fn processing(message: impl Into<String>) -> Self {
112 Error::new(ErrorKind::Processing, "processing_failed", message)
113 }
114
115 pub fn input(message: impl Into<String>) -> Self {
116 Error::new(ErrorKind::InputMissing, "input_unreadable", message)
117 }
118
119 pub fn unsupported(message: impl Into<String>) -> Self {
120 Error::new(ErrorKind::Unsupported, "unsupported_format", message)
121 }
122
123 pub fn internal(message: impl Into<String>) -> Self {
124 Error::new(ErrorKind::Internal, "internal_error", message).with_hint(
125 "Please report this at https://github.com/djynnius/ferraus/issues with the command and its sidecar",
126 )
127 }
128
129 pub fn verification(message: impl Into<String>) -> Self {
130 Error::new(ErrorKind::Verification, "verification_failed", message)
131 }
132
133 pub fn plugin(message: impl Into<String>) -> Self {
134 Error::new(ErrorKind::Plugin, "plugin_failed", message)
135 }
136
137 pub fn cancelled(message: impl Into<String>) -> Self {
138 Error::new(ErrorKind::Cancelled, "cancelled", message)
139 }
140}
141
142impl fmt::Display for Error {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 write!(f, "{}", self.message)?;
145 if let Some(h) = &self.hint {
146 write!(f, " (hint: {h})")?;
147 }
148 Ok(())
149 }
150}
151
152impl std::error::Error for Error {}
153
154impl From<std::io::Error> for Error {
155 fn from(e: std::io::Error) -> Self {
156 match e.kind() {
157 std::io::ErrorKind::NotFound => Error::new(ErrorKind::InputMissing, "not_found", e.to_string()),
158 std::io::ErrorKind::PermissionDenied => {
159 Error::new(ErrorKind::InputMissing, "permission_denied", e.to_string())
160 }
161 _ => Error::new(ErrorKind::Processing, "io_error", e.to_string()),
162 }
163 }
164}
165
166impl From<serde_json::Error> for Error {
167 fn from(e: serde_json::Error) -> Self {
168 Error::usage(format!("invalid JSON: {e}"))
169 }
170}
171
172pub type Result<T, E = Error> = std::result::Result<T, E>;