Skip to main content

a3s_boot/pipeline/
filter.rs

1use super::ExecutionContext;
2use crate::{
3    BootError, BootErrorKind, BootResponse, BoxFuture, Result, TransportContext, TransportReply,
4    WebSocketContext, WebSocketMessage,
5};
6
7/// Maps route/pipeline errors to HTTP responses.
8pub trait ExceptionFilter: Send + Sync + 'static {
9    fn catch(
10        &self,
11        context: ExecutionContext,
12        error: BootError,
13    ) -> BoxFuture<'static, Result<Option<BootResponse>>>;
14}
15
16/// Handled transport exception response.
17#[derive(Debug, Clone, PartialEq)]
18pub struct TransportExceptionResponse {
19    reply: Option<TransportReply>,
20}
21
22impl TransportExceptionResponse {
23    pub fn reply(reply: TransportReply) -> Self {
24        Self { reply: Some(reply) }
25    }
26
27    pub fn empty() -> Self {
28        Self { reply: None }
29    }
30
31    pub fn into_reply(self) -> Option<TransportReply> {
32        self.reply
33    }
34}
35
36impl From<TransportReply> for TransportExceptionResponse {
37    fn from(reply: TransportReply) -> Self {
38        Self::reply(reply)
39    }
40}
41
42/// Maps transport pipeline errors to protocol replies.
43pub trait TransportExceptionFilter: Send + Sync + 'static {
44    fn catch(
45        &self,
46        context: TransportContext,
47        error: BootError,
48    ) -> BoxFuture<'static, Result<Option<TransportExceptionResponse>>>;
49}
50
51impl<F, Fut> TransportExceptionFilter for F
52where
53    F: Fn(TransportContext, BootError) -> Fut + Send + Sync + 'static,
54    Fut: std::future::Future<Output = Result<Option<TransportExceptionResponse>>> + Send + 'static,
55{
56    fn catch(
57        &self,
58        context: TransportContext,
59        error: BootError,
60    ) -> BoxFuture<'static, Result<Option<TransportExceptionResponse>>> {
61        Box::pin(self(context, error))
62    }
63}
64
65/// Handled WebSocket exception response.
66#[derive(Debug, Clone, PartialEq)]
67pub struct WebSocketExceptionResponse {
68    message: Option<WebSocketMessage>,
69}
70
71impl WebSocketExceptionResponse {
72    pub fn message(message: WebSocketMessage) -> Self {
73        Self {
74            message: Some(message),
75        }
76    }
77
78    pub fn empty() -> Self {
79        Self { message: None }
80    }
81
82    pub fn into_message(self) -> Option<WebSocketMessage> {
83        self.message
84    }
85}
86
87impl From<WebSocketMessage> for WebSocketExceptionResponse {
88    fn from(message: WebSocketMessage) -> Self {
89        Self::message(message)
90    }
91}
92
93/// Maps WebSocket gateway errors to outbound messages.
94pub trait WebSocketExceptionFilter: Send + Sync + 'static {
95    fn catch(
96        &self,
97        context: WebSocketContext,
98        error: BootError,
99    ) -> BoxFuture<'static, Result<Option<WebSocketExceptionResponse>>>;
100}
101
102impl<F, Fut> WebSocketExceptionFilter for F
103where
104    F: Fn(WebSocketContext, BootError) -> Fut + Send + Sync + 'static,
105    Fut: std::future::Future<Output = Result<Option<WebSocketExceptionResponse>>> + Send + 'static,
106{
107    fn catch(
108        &self,
109        context: WebSocketContext,
110        error: BootError,
111    ) -> BoxFuture<'static, Result<Option<WebSocketExceptionResponse>>> {
112        Box::pin(self(context, error))
113    }
114}
115
116impl<F, Fut> ExceptionFilter for F
117where
118    F: Fn(ExecutionContext, BootError) -> Fut + Send + Sync + 'static,
119    Fut: std::future::Future<Output = Result<Option<BootResponse>>> + Send + 'static,
120{
121    fn catch(
122        &self,
123        context: ExecutionContext,
124        error: BootError,
125    ) -> BoxFuture<'static, Result<Option<BootResponse>>> {
126        Box::pin(self(context, error))
127    }
128}
129
130/// Exception filter wrapper that only handles selected [`BootErrorKind`] values.
131pub struct CatchFilter<F> {
132    kinds: Vec<BootErrorKind>,
133    filter: F,
134}
135
136impl<F> CatchFilter<F> {
137    pub fn new<I>(kinds: I, filter: F) -> Self
138    where
139        I: IntoIterator<Item = BootErrorKind>,
140    {
141        Self {
142            kinds: kinds.into_iter().collect(),
143            filter,
144        }
145    }
146
147    pub fn kinds(&self) -> &[BootErrorKind] {
148        &self.kinds
149    }
150
151    pub fn into_inner(self) -> F {
152        self.filter
153    }
154}
155
156impl<F> ExceptionFilter for CatchFilter<F>
157where
158    F: ExceptionFilter,
159{
160    fn catch(
161        &self,
162        context: ExecutionContext,
163        error: BootError,
164    ) -> BoxFuture<'static, Result<Option<BootResponse>>> {
165        if self.kinds.is_empty() || self.kinds.contains(&error.kind()) {
166            return self.filter.catch(context, error);
167        }
168
169        Box::pin(async { Ok(None) })
170    }
171}
172
173impl<F> TransportExceptionFilter for CatchFilter<F>
174where
175    F: TransportExceptionFilter,
176{
177    fn catch(
178        &self,
179        context: TransportContext,
180        error: BootError,
181    ) -> BoxFuture<'static, Result<Option<TransportExceptionResponse>>> {
182        if self.kinds.is_empty() || self.kinds.contains(&error.kind()) {
183            return self.filter.catch(context, error);
184        }
185
186        Box::pin(async { Ok(None) })
187    }
188}
189
190impl<F> WebSocketExceptionFilter for CatchFilter<F>
191where
192    F: WebSocketExceptionFilter,
193{
194    fn catch(
195        &self,
196        context: WebSocketContext,
197        error: BootError,
198    ) -> BoxFuture<'static, Result<Option<WebSocketExceptionResponse>>> {
199        if self.kinds.is_empty() || self.kinds.contains(&error.kind()) {
200            return self.filter.catch(context, error);
201        }
202
203        Box::pin(async { Ok(None) })
204    }
205}
206
207/// Build a Nest-style catch filter for selected [`BootErrorKind`] values.
208pub fn catch_errors<I, F>(kinds: I, filter: F) -> CatchFilter<F>
209where
210    I: IntoIterator<Item = BootErrorKind>,
211{
212    CatchFilter::new(kinds, filter)
213}