@zap-js/server 0.0.2 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +18 -14
- package/package.json +4 -9
- package/src/bin/zap.rs +0 -154
- package/src/config.rs +0 -253
- package/src/connection_pool.rs +0 -404
- package/src/error.rs +0 -380
- package/src/handler.rs +0 -89
- package/src/ipc.js +0 -10
- package/src/ipc.rs +0 -499
- package/src/lib.rs +0 -433
- package/src/metrics.rs +0 -264
- package/src/proxy.rs +0 -436
- package/src/reliability.rs +0 -917
- package/src/request.rs +0 -60
- package/src/request_id.rs +0 -97
- package/src/response.rs +0 -182
- package/src/rpc.js +0 -14
- package/src/server.rs +0 -597
- package/src/static.rs +0 -572
- package/src/types.js +0 -21
- package/src/utils.rs +0 -18
- package/src/websocket.rs +0 -429
package/src/error.rs
DELETED
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
//! Comprehensive error handling with proper context and recovery
|
|
2
|
-
//!
|
|
3
|
-
//! Type-safe error handling throughout the application with proper
|
|
4
|
-
//! error propagation and context preservation.
|
|
5
|
-
//!
|
|
6
|
-
//! ## Error Structure
|
|
7
|
-
//! Each error has:
|
|
8
|
-
//! - A machine-readable **code** (e.g., "HANDLER_ERROR")
|
|
9
|
-
//! - A human-readable **message**
|
|
10
|
-
//! - An HTTP **status code**
|
|
11
|
-
//! - A unique **digest** for log correlation
|
|
12
|
-
//! - Optional **details** for additional context
|
|
13
|
-
|
|
14
|
-
use serde::{Deserialize, Serialize};
|
|
15
|
-
use std::io;
|
|
16
|
-
use thiserror::Error;
|
|
17
|
-
use uuid::Uuid;
|
|
18
|
-
|
|
19
|
-
/// Zap error type covering all possible failure modes
|
|
20
|
-
#[derive(Debug, Error)]
|
|
21
|
-
pub enum ZapError {
|
|
22
|
-
/// HTTP server errors
|
|
23
|
-
#[error("HTTP error: {message}")]
|
|
24
|
-
Http {
|
|
25
|
-
message: String,
|
|
26
|
-
#[source]
|
|
27
|
-
source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
/// Route not found (404)
|
|
31
|
-
#[error("Route not found: {path}")]
|
|
32
|
-
RouteNotFound { path: String },
|
|
33
|
-
|
|
34
|
-
/// Handler execution errors
|
|
35
|
-
#[error("Handler error: {message}")]
|
|
36
|
-
Handler {
|
|
37
|
-
message: String,
|
|
38
|
-
handler_id: Option<String>,
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
/// IPC/Socket errors
|
|
42
|
-
#[error("IPC error: {message}")]
|
|
43
|
-
Ipc { message: String },
|
|
44
|
-
|
|
45
|
-
/// Configuration errors
|
|
46
|
-
#[error("Configuration error: {message}")]
|
|
47
|
-
Config { message: String },
|
|
48
|
-
|
|
49
|
-
/// I/O errors
|
|
50
|
-
#[error("I/O error: {0}")]
|
|
51
|
-
Io(#[from] io::Error),
|
|
52
|
-
|
|
53
|
-
/// Serialization errors
|
|
54
|
-
#[error("Serialization error: {0}")]
|
|
55
|
-
Serialization(#[from] serde_json::Error),
|
|
56
|
-
|
|
57
|
-
/// Validation errors (400)
|
|
58
|
-
#[error("Validation error: {message}")]
|
|
59
|
-
Validation {
|
|
60
|
-
message: String,
|
|
61
|
-
field: Option<String>,
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
/// Authentication required (401)
|
|
65
|
-
#[error("Authentication required: {message}")]
|
|
66
|
-
Unauthorized { message: String },
|
|
67
|
-
|
|
68
|
-
/// Access forbidden (403)
|
|
69
|
-
#[error("Access forbidden: {message}")]
|
|
70
|
-
Forbidden { message: String },
|
|
71
|
-
|
|
72
|
-
/// Timeout errors (408/504)
|
|
73
|
-
#[error("Timeout: {message}")]
|
|
74
|
-
Timeout { message: String, timeout_ms: u64 },
|
|
75
|
-
|
|
76
|
-
/// Rate limit exceeded (429)
|
|
77
|
-
#[error("Rate limit exceeded")]
|
|
78
|
-
RateLimited { retry_after_secs: u64 },
|
|
79
|
-
|
|
80
|
-
/// Invalid state
|
|
81
|
-
#[error("Invalid state: {0}")]
|
|
82
|
-
InvalidState(String),
|
|
83
|
-
|
|
84
|
-
/// Internal error (500)
|
|
85
|
-
#[error("Internal error: {0}")]
|
|
86
|
-
Internal(String),
|
|
87
|
-
|
|
88
|
-
/// WebSocket errors
|
|
89
|
-
#[error("WebSocket error: {message}")]
|
|
90
|
-
WebSocket { message: String },
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
impl ZapError {
|
|
94
|
-
/// Get the machine-readable error code
|
|
95
|
-
pub fn code(&self) -> &'static str {
|
|
96
|
-
match self {
|
|
97
|
-
ZapError::Http { .. } => "HTTP_ERROR",
|
|
98
|
-
ZapError::RouteNotFound { .. } => "ROUTE_NOT_FOUND",
|
|
99
|
-
ZapError::Handler { .. } => "HANDLER_ERROR",
|
|
100
|
-
ZapError::Ipc { .. } => "IPC_ERROR",
|
|
101
|
-
ZapError::Config { .. } => "CONFIG_ERROR",
|
|
102
|
-
ZapError::Io(_) => "IO_ERROR",
|
|
103
|
-
ZapError::Serialization(_) => "SERIALIZATION_ERROR",
|
|
104
|
-
ZapError::Validation { .. } => "VALIDATION_ERROR",
|
|
105
|
-
ZapError::Unauthorized { .. } => "UNAUTHORIZED",
|
|
106
|
-
ZapError::Forbidden { .. } => "FORBIDDEN",
|
|
107
|
-
ZapError::Timeout { .. } => "TIMEOUT",
|
|
108
|
-
ZapError::RateLimited { .. } => "RATE_LIMITED",
|
|
109
|
-
ZapError::InvalidState(_) => "INVALID_STATE",
|
|
110
|
-
ZapError::Internal(_) => "INTERNAL_ERROR",
|
|
111
|
-
ZapError::WebSocket { .. } => "WEBSOCKET_ERROR",
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/// Get the appropriate HTTP status code
|
|
116
|
-
pub fn status_code(&self) -> u16 {
|
|
117
|
-
match self {
|
|
118
|
-
ZapError::Http { .. } => 500,
|
|
119
|
-
ZapError::RouteNotFound { .. } => 404,
|
|
120
|
-
ZapError::Handler { .. } => 500,
|
|
121
|
-
ZapError::Ipc { .. } => 502,
|
|
122
|
-
ZapError::Config { .. } => 500,
|
|
123
|
-
ZapError::Io(_) => 500,
|
|
124
|
-
ZapError::Serialization(_) => 400,
|
|
125
|
-
ZapError::Validation { .. } => 400,
|
|
126
|
-
ZapError::Unauthorized { .. } => 401,
|
|
127
|
-
ZapError::Forbidden { .. } => 403,
|
|
128
|
-
ZapError::Timeout { .. } => 504,
|
|
129
|
-
ZapError::RateLimited { .. } => 429,
|
|
130
|
-
ZapError::InvalidState(_) => 500,
|
|
131
|
-
ZapError::Internal(_) => 500,
|
|
132
|
-
ZapError::WebSocket { .. } => 500,
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/// Convert to a structured error response
|
|
137
|
-
pub fn to_error_response(&self) -> ErrorResponse {
|
|
138
|
-
let digest = Uuid::new_v4().to_string();
|
|
139
|
-
|
|
140
|
-
ErrorResponse {
|
|
141
|
-
error: true,
|
|
142
|
-
code: self.code().to_string(),
|
|
143
|
-
message: self.to_string(),
|
|
144
|
-
status: self.status_code(),
|
|
145
|
-
digest,
|
|
146
|
-
details: self.details(),
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/// Get additional error-specific details
|
|
151
|
-
fn details(&self) -> Option<serde_json::Value> {
|
|
152
|
-
match self {
|
|
153
|
-
ZapError::Validation { field, .. } => {
|
|
154
|
-
field.as_ref().map(|f| serde_json::json!({ "field": f }))
|
|
155
|
-
}
|
|
156
|
-
ZapError::RateLimited { retry_after_secs } => {
|
|
157
|
-
Some(serde_json::json!({ "retryAfter": retry_after_secs }))
|
|
158
|
-
}
|
|
159
|
-
ZapError::Timeout { timeout_ms, .. } => {
|
|
160
|
-
Some(serde_json::json!({ "timeoutMs": timeout_ms }))
|
|
161
|
-
}
|
|
162
|
-
ZapError::RouteNotFound { path } => Some(serde_json::json!({ "path": path })),
|
|
163
|
-
ZapError::Handler { handler_id, .. } => {
|
|
164
|
-
handler_id.as_ref().map(|id| serde_json::json!({ "handlerId": id }))
|
|
165
|
-
}
|
|
166
|
-
_ => None,
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Convenience constructors
|
|
171
|
-
|
|
172
|
-
/// Create an HTTP error
|
|
173
|
-
pub fn http(message: impl Into<String>) -> Self {
|
|
174
|
-
ZapError::Http {
|
|
175
|
-
message: message.into(),
|
|
176
|
-
source: None,
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/// Create a route not found error
|
|
181
|
-
pub fn route_not_found(path: impl Into<String>) -> Self {
|
|
182
|
-
ZapError::RouteNotFound { path: path.into() }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/// Create a handler error
|
|
186
|
-
pub fn handler(message: impl Into<String>) -> Self {
|
|
187
|
-
ZapError::Handler {
|
|
188
|
-
message: message.into(),
|
|
189
|
-
handler_id: None,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/// Create a handler error with handler ID
|
|
194
|
-
pub fn handler_with_id(message: impl Into<String>, handler_id: impl Into<String>) -> Self {
|
|
195
|
-
ZapError::Handler {
|
|
196
|
-
message: message.into(),
|
|
197
|
-
handler_id: Some(handler_id.into()),
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/// Create an IPC error
|
|
202
|
-
pub fn ipc(message: impl Into<String>) -> Self {
|
|
203
|
-
ZapError::Ipc {
|
|
204
|
-
message: message.into(),
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/// Create a config error
|
|
209
|
-
pub fn config(message: impl Into<String>) -> Self {
|
|
210
|
-
ZapError::Config {
|
|
211
|
-
message: message.into(),
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/// Create a validation error
|
|
216
|
-
pub fn validation(message: impl Into<String>) -> Self {
|
|
217
|
-
ZapError::Validation {
|
|
218
|
-
message: message.into(),
|
|
219
|
-
field: None,
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/// Create a validation error with field name
|
|
224
|
-
pub fn validation_field(message: impl Into<String>, field: impl Into<String>) -> Self {
|
|
225
|
-
ZapError::Validation {
|
|
226
|
-
message: message.into(),
|
|
227
|
-
field: Some(field.into()),
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/// Create an unauthorized error
|
|
232
|
-
pub fn unauthorized(message: impl Into<String>) -> Self {
|
|
233
|
-
ZapError::Unauthorized {
|
|
234
|
-
message: message.into(),
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/// Create a forbidden error
|
|
239
|
-
pub fn forbidden(message: impl Into<String>) -> Self {
|
|
240
|
-
ZapError::Forbidden {
|
|
241
|
-
message: message.into(),
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/// Create a timeout error
|
|
246
|
-
pub fn timeout(message: impl Into<String>, timeout_ms: u64) -> Self {
|
|
247
|
-
ZapError::Timeout {
|
|
248
|
-
message: message.into(),
|
|
249
|
-
timeout_ms,
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/// Create a rate limited error
|
|
254
|
-
pub fn rate_limited(retry_after_secs: u64) -> Self {
|
|
255
|
-
ZapError::RateLimited { retry_after_secs }
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/// Create a WebSocket error
|
|
259
|
-
pub fn websocket(message: impl Into<String>) -> Self {
|
|
260
|
-
ZapError::WebSocket {
|
|
261
|
-
message: message.into(),
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
impl From<String> for ZapError {
|
|
267
|
-
fn from(msg: String) -> Self {
|
|
268
|
-
Self::Internal(msg)
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
impl From<&str> for ZapError {
|
|
273
|
-
fn from(msg: &str) -> Self {
|
|
274
|
-
Self::Internal(msg.to_string())
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/// Structured error response for JSON serialization
|
|
279
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
280
|
-
pub struct ErrorResponse {
|
|
281
|
-
/// Always true for error responses
|
|
282
|
-
pub error: bool,
|
|
283
|
-
|
|
284
|
-
/// Machine-readable error code (e.g., "HANDLER_ERROR")
|
|
285
|
-
pub code: String,
|
|
286
|
-
|
|
287
|
-
/// Human-readable error message
|
|
288
|
-
pub message: String,
|
|
289
|
-
|
|
290
|
-
/// HTTP status code
|
|
291
|
-
pub status: u16,
|
|
292
|
-
|
|
293
|
-
/// Unique error identifier for log correlation
|
|
294
|
-
pub digest: String,
|
|
295
|
-
|
|
296
|
-
/// Additional error-specific details
|
|
297
|
-
#[serde(skip_serializing_if = "Option::is_none")]
|
|
298
|
-
pub details: Option<serde_json::Value>,
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
impl ErrorResponse {
|
|
302
|
-
/// Create a new error response
|
|
303
|
-
pub fn new(code: impl Into<String>, message: impl Into<String>, status: u16) -> Self {
|
|
304
|
-
Self {
|
|
305
|
-
error: true,
|
|
306
|
-
code: code.into(),
|
|
307
|
-
message: message.into(),
|
|
308
|
-
status,
|
|
309
|
-
digest: Uuid::new_v4().to_string(),
|
|
310
|
-
details: None,
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/// Add details to the error response
|
|
315
|
-
pub fn with_details(mut self, details: serde_json::Value) -> Self {
|
|
316
|
-
self.details = Some(details);
|
|
317
|
-
self
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/// Convert to JSON string
|
|
321
|
-
pub fn to_json(&self) -> String {
|
|
322
|
-
serde_json::to_string(self).unwrap_or_else(|_| {
|
|
323
|
-
format!(
|
|
324
|
-
r#"{{"error":true,"code":"{}","message":"{}","status":{},"digest":"{}"}}"#,
|
|
325
|
-
self.code, self.message, self.status, self.digest
|
|
326
|
-
)
|
|
327
|
-
})
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/// Convenient Result type for Zap operations
|
|
332
|
-
pub type ZapResult<T> = Result<T, ZapError>;
|
|
333
|
-
|
|
334
|
-
#[cfg(test)]
|
|
335
|
-
mod tests {
|
|
336
|
-
use super::*;
|
|
337
|
-
|
|
338
|
-
#[test]
|
|
339
|
-
fn test_error_codes() {
|
|
340
|
-
assert_eq!(ZapError::route_not_found("/test").code(), "ROUTE_NOT_FOUND");
|
|
341
|
-
assert_eq!(ZapError::handler("test").code(), "HANDLER_ERROR");
|
|
342
|
-
assert_eq!(ZapError::validation("test").code(), "VALIDATION_ERROR");
|
|
343
|
-
assert_eq!(ZapError::rate_limited(60).code(), "RATE_LIMITED");
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
#[test]
|
|
347
|
-
fn test_status_codes() {
|
|
348
|
-
assert_eq!(ZapError::route_not_found("/test").status_code(), 404);
|
|
349
|
-
assert_eq!(ZapError::validation("test").status_code(), 400);
|
|
350
|
-
assert_eq!(ZapError::unauthorized("test").status_code(), 401);
|
|
351
|
-
assert_eq!(ZapError::forbidden("test").status_code(), 403);
|
|
352
|
-
assert_eq!(ZapError::rate_limited(60).status_code(), 429);
|
|
353
|
-
assert_eq!(ZapError::timeout("test", 5000).status_code(), 504);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
#[test]
|
|
357
|
-
fn test_error_response() {
|
|
358
|
-
let error = ZapError::validation_field("Invalid email", "email");
|
|
359
|
-
let response = error.to_error_response();
|
|
360
|
-
|
|
361
|
-
assert!(response.error);
|
|
362
|
-
assert_eq!(response.code, "VALIDATION_ERROR");
|
|
363
|
-
assert_eq!(response.status, 400);
|
|
364
|
-
assert!(!response.digest.is_empty());
|
|
365
|
-
assert!(response.details.is_some());
|
|
366
|
-
|
|
367
|
-
let details = response.details.unwrap();
|
|
368
|
-
assert_eq!(details["field"], "email");
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
#[test]
|
|
372
|
-
fn test_error_response_json() {
|
|
373
|
-
let response = ErrorResponse::new("TEST_ERROR", "Test message", 500);
|
|
374
|
-
let json = response.to_json();
|
|
375
|
-
|
|
376
|
-
assert!(json.contains("TEST_ERROR"));
|
|
377
|
-
assert!(json.contains("Test message"));
|
|
378
|
-
assert!(json.contains("500"));
|
|
379
|
-
}
|
|
380
|
-
}
|
package/src/handler.rs
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
//! Handler traits and implementations for ZapServer
|
|
2
|
-
|
|
3
|
-
use std::future::Future;
|
|
4
|
-
use std::pin::Pin;
|
|
5
|
-
|
|
6
|
-
use crate::error::ZapError;
|
|
7
|
-
use crate::response::ZapResponse;
|
|
8
|
-
use zap_core::Request;
|
|
9
|
-
use crate::request::RequestData;
|
|
10
|
-
|
|
11
|
-
/// Handler trait for request processing
|
|
12
|
-
pub trait Handler {
|
|
13
|
-
/// Handle the request and return a response
|
|
14
|
-
fn handle<'a>(
|
|
15
|
-
&'a self,
|
|
16
|
-
req: Request<'a>,
|
|
17
|
-
) -> Pin<Box<dyn Future<Output = Result<ZapResponse, ZapError>> + Send + 'a>>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/// Implement Handler for simple closures that return strings
|
|
21
|
-
impl<F> Handler for F
|
|
22
|
-
where
|
|
23
|
-
F: Fn() -> &'static str + Send + Sync,
|
|
24
|
-
{
|
|
25
|
-
fn handle<'a>(
|
|
26
|
-
&'a self,
|
|
27
|
-
_req: Request<'a>,
|
|
28
|
-
) -> Pin<Box<dyn Future<Output = Result<ZapResponse, ZapError>> + Send + 'a>> {
|
|
29
|
-
let response = self();
|
|
30
|
-
Box::pin(async move { Ok(ZapResponse::Text(response.to_string())) })
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/// Simple handler that returns a ZapResponse
|
|
35
|
-
pub struct SimpleHandler<F> {
|
|
36
|
-
func: F,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
impl<F> SimpleHandler<F> {
|
|
40
|
-
pub fn new(func: F) -> Self {
|
|
41
|
-
Self { func }
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
impl<F> Handler for SimpleHandler<F>
|
|
46
|
-
where
|
|
47
|
-
F: Fn() -> String + Send + Sync,
|
|
48
|
-
{
|
|
49
|
-
fn handle<'a>(
|
|
50
|
-
&'a self,
|
|
51
|
-
_req: Request<'a>,
|
|
52
|
-
) -> Pin<Box<dyn Future<Output = Result<ZapResponse, ZapError>> + Send + 'a>> {
|
|
53
|
-
let response = (self.func)();
|
|
54
|
-
Box::pin(async move { Ok(ZapResponse::Text(response)) })
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/// Async handler wrapper
|
|
59
|
-
pub struct AsyncHandler<F> {
|
|
60
|
-
func: F,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
impl<F> AsyncHandler<F> {
|
|
64
|
-
pub fn new(func: F) -> Self {
|
|
65
|
-
Self { func }
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
impl<F, Fut> Handler for AsyncHandler<F>
|
|
70
|
-
where
|
|
71
|
-
F: Fn(RequestData) -> Fut + Send + Sync,
|
|
72
|
-
Fut: Future<Output = ZapResponse> + Send,
|
|
73
|
-
{
|
|
74
|
-
fn handle<'a>(
|
|
75
|
-
&'a self,
|
|
76
|
-
req: Request<'a>,
|
|
77
|
-
) -> Pin<Box<dyn Future<Output = Result<ZapResponse, ZapError>> + Send + 'a>> {
|
|
78
|
-
// Extract request data that can be moved
|
|
79
|
-
let req_data = RequestData::from_request(&req);
|
|
80
|
-
|
|
81
|
-
Box::pin(async move {
|
|
82
|
-
let response = (self.func)(req_data).await;
|
|
83
|
-
Ok(response)
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/// Type alias for boxed async handlers
|
|
89
|
-
pub type BoxedHandler = Box<dyn Handler + Send + Sync>;
|