rust_supervisor/dashboard/
error.rs1use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Error)]
13#[error("{code} at {stage}: {message}")]
14pub struct DashboardError {
15 pub code: String,
17 pub stage: String,
19 pub target_id: Option<String>,
21 pub message: String,
23 pub retryable: bool,
25}
26
27impl DashboardError {
28 pub fn new(
42 code: impl Into<String>,
43 stage: impl Into<String>,
44 target_id: Option<String>,
45 message: impl Into<String>,
46 retryable: bool,
47 ) -> Self {
48 Self {
49 code: code.into(),
50 stage: stage.into(),
51 target_id,
52 message: message.into(),
53 retryable,
54 }
55 }
56
57 pub fn unsupported_method(method: impl AsRef<str>) -> Self {
67 Self::new(
68 "unsupported_method",
69 "protocol_parse",
70 None,
71 format!("unsupported dashboard IPC method {m}", m = method.as_ref()),
72 false,
73 )
74 }
75
76 pub fn validation(
88 stage: impl Into<String>,
89 target_id: Option<String>,
90 message: impl Into<String>,
91 ) -> Self {
92 Self::new("validation_failed", stage, target_id, message, false)
93 }
94
95 pub fn target_unavailable(
107 stage: impl Into<String>,
108 target_id: impl Into<String>,
109 message: impl Into<String>,
110 ) -> Self {
111 Self::new(
112 "target_unavailable",
113 stage,
114 Some(target_id.into()),
115 message,
116 true,
117 )
118 }
119
120 pub fn ipc_socket_owner_mismatch(message: impl Into<String>) -> Self {
126 Self::new(
127 "ipc_socket_owner_mismatch",
128 "ipc_bind",
129 None,
130 message,
131 false,
132 )
133 }
134
135 pub fn peer_cred_uid_mismatch(expected: u32, got: u32) -> Self {
137 Self::new(
138 "peer_cred_uid_mismatch",
139 "peer_credentials",
140 None,
141 format!("peer uid mismatch: expected {expected}, got {got}"),
142 false,
143 )
144 }
145
146 pub fn peer_cred_gid_not_allowed(gid: u32) -> Self {
148 Self::new(
149 "peer_cred_gid_not_allowed",
150 "peer_credentials",
151 None,
152 format!("peer gid {gid} is not in the allowed gid list"),
153 false,
154 )
155 }
156
157 pub fn peer_cred_pid_not_allowed(pid: u32) -> Self {
159 Self::new(
160 "peer_cred_pid_not_allowed",
161 "peer_credentials",
162 None,
163 format!("peer pid {pid} is not in the allowed pid list"),
164 false,
165 )
166 }
167
168 pub fn peer_cred_unavailable(message: impl Into<String>) -> Self {
170 Self::new(
171 "peer_cred_unavailable",
172 "peer_credentials",
173 None,
174 message,
175 false,
176 )
177 }
178
179 pub fn authz_denied(method: impl Into<String>) -> Self {
181 Self::new(
182 "authz_denied",
183 "authorization",
184 None,
185 format!("command {m} is not authorized", m = method.into()),
186 false,
187 )
188 }
189
190 pub fn authz_not_configured() -> Self {
192 Self::new(
193 "authz_not_configured",
194 "authorization",
195 None,
196 "command authorization is not configured",
197 false,
198 )
199 }
200
201 pub fn replay_detected(request_id: impl Into<String>) -> Self {
203 Self::new(
204 "replay_detected",
205 "replay_protection",
206 None,
207 format!("replay detected for request_id {r}", r = request_id.into()),
208 false,
209 )
210 }
211
212 pub fn request_too_large(actual: usize, max_bytes: usize) -> Self {
214 Self::new(
215 "request_too_large",
216 "size_limit",
217 None,
218 format!("request body {actual} bytes exceeds limit of {max_bytes} bytes"),
219 false,
220 )
221 }
222
223 pub fn rate_limit_exceeded() -> Self {
225 Self::new(
226 "rate_limit_exceeded",
227 "rate_limit",
228 None,
229 "rate limit exceeded",
230 false,
231 )
232 }
233
234 pub fn audit_write_failed(message: impl Into<String>) -> Self {
236 Self::new("audit_write_failed", "audit", None, message, false)
237 }
238
239 pub fn audit_queue_full() -> Self {
241 Self::new(
242 "audit_queue_full",
243 "audit",
244 None,
245 "audit defer queue is full",
246 false,
247 )
248 }
249
250 pub fn allowlist_denied(path: impl Into<String>) -> Self {
252 Self::new(
253 "allowlist_denied",
254 "allowlist",
255 None,
256 format!("external command not in allowlist: {p}", p = path.into()),
257 false,
258 )
259 }
260
261 pub fn allowlist_empty() -> Self {
263 Self::new(
264 "allowlist_empty",
265 "allowlist",
266 None,
267 "external command allowlist is empty — all external commands are denied",
268 false,
269 )
270 }
271}