tii/
functional_traits.rs

1//! Defines traits for handler and filter functions.
2
3use crate::RequestContext;
4use crate::Response;
5use crate::TiiResult;
6use crate::{ConnectionStream, MimeType, RequestBody, ResponseContext, TiiError, UserError};
7use crate::{WebsocketReceiver, WebsocketSender};
8use std::any::Any;
9use std::fmt::{Debug, Formatter};
10use std::marker::PhantomData;
11use std::sync::Mutex;
12use std::thread;
13use std::thread::JoinHandle;
14
15/// Represents an opaque join handle
16pub struct ThreadAdapterJoinHandle(Box<dyn FnOnce() -> thread::Result<()> + Send>);
17
18impl ThreadAdapterJoinHandle {
19  /// Constructor
20  pub fn new(inner: Box<dyn FnOnce() -> thread::Result<()> + Send>) -> Self {
21    ThreadAdapterJoinHandle(inner)
22  }
23
24  /// Calls the join fn
25  pub fn join(self) -> thread::Result<()> {
26    self.0()
27  }
28}
29
30impl Debug for ThreadAdapterJoinHandle {
31  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32    f.write_str("ThreadAdapterJoinHandle")
33  }
34}
35
36impl Default for ThreadAdapterJoinHandle {
37  fn default() -> Self {
38    Self(Box::new(|| Ok(())))
39  }
40}
41
42/// Trait that represents a user implemented opaque thread starting/pooling mechanism.
43pub trait ThreadAdapter: Send + Sync + Debug {
44  /// Spawns executes the given task immediately in the thread. like "thread::spawn".
45  fn spawn(&self, task: Box<dyn FnOnce() + Send>) -> TiiResult<ThreadAdapterJoinHandle>;
46}
47
48#[allow(dead_code)] //This is not used in all feature combinations.
49#[derive(Debug)]
50pub(crate) struct DefaultThreadAdapter;
51impl ThreadAdapter for DefaultThreadAdapter {
52  fn spawn(&self, task: Box<dyn FnOnce() + Send>) -> TiiResult<ThreadAdapterJoinHandle> {
53    let hdl: JoinHandle<()> = thread::Builder::new().spawn(task)?;
54    Ok(ThreadAdapterJoinHandle::new(Box::new(move || hdl.join())))
55  }
56}
57
58/// Represents a function able to handle a WebSocket handshake and consequent data frames.
59pub trait WebsocketEndpoint: Send + Sync {
60  /// serve the web socket request.
61  fn serve(
62    &self,
63    request: &RequestContext,
64    receiver: WebsocketReceiver,
65    sender: WebsocketSender,
66  ) -> TiiResult<()>;
67}
68
69trait IntoWebsocketEndpointResponse {
70  fn into(self) -> TiiResult<()>;
71}
72
73impl IntoWebsocketEndpointResponse for TiiResult<()> {
74  fn into(self) -> TiiResult<()> {
75    self
76  }
77}
78
79impl IntoWebsocketEndpointResponse for () {
80  fn into(self) -> TiiResult<()> {
81    Ok(())
82  }
83}
84
85impl<F, R> WebsocketEndpoint for F
86where
87  R: IntoWebsocketEndpointResponse,
88  F: Fn(&RequestContext, WebsocketReceiver, WebsocketSender) -> R + Send + Sync,
89{
90  fn serve(
91    &self,
92    request: &RequestContext,
93    receiver: WebsocketReceiver,
94    sender: WebsocketSender,
95  ) -> TiiResult<()> {
96    self(request, receiver, sender).into()
97  }
98}
99
100/// Represents a function able to handle a request.
101/// It is passed the request and must return a response.
102///
103/// ## Example
104/// The most basic request handler would be as follows:
105/// ```
106/// use tii::MimeType;
107///
108/// fn handler(_: tii::RequestContext) -> tii::Response {
109///     tii::Response::ok("Success", MimeType::TextPlain)
110/// }
111/// ```
112pub trait HttpEndpoint: Send + Sync {
113  /// Serve an ordinary http request.
114  fn serve(&self, request: &RequestContext) -> TiiResult<Response>;
115
116  /// This fn is called before the post-routing filters have been called to parse the entity.
117  /// If the endpoint does not receive structured data then this fn should return Ok(None)
118  fn parse_entity(
119    &self,
120    mime: &MimeType,
121    request: &RequestBody,
122  ) -> TiiResult<Option<Box<dyn Any + Send + Sync>>>;
123}
124
125impl<F, R> HttpEndpoint for F
126where
127  R: Into<TiiResult<Response>>,
128  F: Fn(&RequestContext) -> R + Send + Sync,
129{
130  fn serve(&self, request: &RequestContext) -> TiiResult<Response> {
131    if request.get_request_entity().is_some() {
132      return Err(TiiError::UserError(UserError::BadFilterOrBadEndpointCausedEntityTypeMismatch));
133    }
134
135    self(request).into()
136  }
137  fn parse_entity(
138    &self,
139    _: &MimeType,
140    _: &RequestBody,
141  ) -> TiiResult<Option<Box<dyn Any + Send + Sync>>> {
142    // This type of endpoint does not receive structured data.
143    Ok(None)
144  }
145}
146
147/// Trait for De-Serializing request entities
148pub trait EntityDeserializer<T: Any + Send + Sync> {
149  /// Deserialize a RequestBody to T (or fail with an error)
150  /// If the deserializer errors then the error handler is invoked next with the returned error.
151  fn deserialize(&self, mime: &MimeType, body: &RequestBody) -> TiiResult<T>;
152}
153
154impl<F, T> EntityDeserializer<T> for F
155where
156  T: Any + Send + Sync,
157  F: Fn(&MimeType, &RequestBody) -> TiiResult<T>,
158{
159  fn deserialize(&self, mime: &MimeType, body: &RequestBody) -> TiiResult<T> {
160    self(mime, body)
161  }
162}
163
164pub(crate) struct EntityHttpEndpoint<T, F, R, D>
165where
166  T: Any + Send + Sync,
167  R: Into<TiiResult<Response>> + Send,
168  F: Fn(&RequestContext, &T) -> R + Send + Sync,
169  D: EntityDeserializer<T> + Send + Sync,
170{
171  pub(crate) endpoint: F,
172  pub(crate) deserializer: D,
173  pub(crate) _p1: PhantomData<T>,
174  //TODO this is completely retarded, but the compiler is satisfied with this, the main problem is that
175  //The HTTP endpoint needs to be Send+sync but the Response is only Send.
176  //Obviously this struct does not contain a response object, but the compiler thinks that PhantomData<Response> is also not sync, thus this entire struct is not sync.
177  //The only sane fix for this would be to either make a custom PhantomData that is Sent+Sync but that requires unsafe or use a external crate that does the same.
178  //Or we could just unsafe impl Sync on this struct directly. (No...)
179  //We could also check if its worth it to make Response itself Sync.
180  pub(crate) _p2: Mutex<PhantomData<R>>,
181}
182
183impl<T, F, R, D> HttpEndpoint for EntityHttpEndpoint<T, F, R, D>
184where
185  T: Any + Send + Sync,
186  R: Into<TiiResult<Response>> + Send,
187  F: Fn(&RequestContext, &T) -> R + Send + Sync,
188  D: EntityDeserializer<T> + Send + Sync,
189{
190  fn serve(&self, request: &RequestContext) -> TiiResult<Response> {
191    let Some(entity) = request.get_request_entity() else {
192      return Err(TiiError::UserError(UserError::BadFilterOrBadEndpointCausedEntityTypeMismatch));
193    };
194
195    let Some(entity) = entity.downcast_ref::<T>() else {
196      return Err(TiiError::UserError(UserError::BadFilterOrBadEndpointCausedEntityTypeMismatch));
197    };
198
199    (self.endpoint)(request, entity).into()
200  }
201
202  fn parse_entity(
203    &self,
204    mime: &MimeType,
205    request: &RequestBody,
206  ) -> TiiResult<Option<Box<dyn Any + Send + Sync>>> {
207    let result: T = self.deserializer.deserialize(mime, request)?;
208    Ok(Some(Box::new(result) as Box<dyn Any + Send + Sync>))
209  }
210}
211
212/// Trait for a "filter" that decide if a router is responsible for handling a request.
213/// Intended use is to do matching on things like base path, Host HTTP Header,
214/// some other magic header.
215pub trait RouterFilter: Send + Sync {
216  /// true -> the router should handle this one,
217  /// false -> the router should not handle this one,
218  //TODO make it impossible for this shit to read the body.
219  fn filter(&self, request: &RequestContext) -> TiiResult<bool>;
220}
221
222impl<F: Fn(&RequestContext) -> TiiResult<bool> + Send + Sync> RouterFilter for F {
223  fn filter(&self, request: &RequestContext) -> TiiResult<bool> {
224    self(request)
225  }
226}
227
228trait IntoRequestFilterResult {
229  fn into(self) -> TiiResult<Option<Response>>;
230}
231
232impl IntoRequestFilterResult for Option<Response> {
233  fn into(self) -> TiiResult<Option<Response>> {
234    Ok(self)
235  }
236}
237
238impl IntoRequestFilterResult for TiiResult<Option<Response>> {
239  fn into(self) -> TiiResult<Option<Response>> {
240    self
241  }
242}
243impl IntoRequestFilterResult for () {
244  fn into(self) -> TiiResult<Option<Response>> {
245    Ok(None)
246  }
247}
248
249impl IntoRequestFilterResult for TiiResult<()> {
250  fn into(self) -> TiiResult<Option<Response>> {
251    self.map(|_| None)
252  }
253}
254
255/// Trait for a filter that may alter a request before its brought to an endpoint.
256/// It's also capable of aborting a request so that it's not processed further.
257/// Use cases: (Non-Exhaustive)
258/// - Authentication/Authorization
259/// - Transforming of the request entity. (I.e. transform json)
260/// - Logging of the request
261/// - "Rough" estimation of the time it takes for the endpoint to process things.
262pub trait RequestFilter: Send + Sync {
263  /// Called with the request context before the endpoint is called.
264  /// Ok(None) -> proceed.
265  /// Ok(Some) -> abort request with given response.
266  /// Err -> Call error handler and proceed (endpoint won't be called)
267  fn filter(&self, request: &mut RequestContext) -> TiiResult<Option<Response>>;
268}
269
270impl<F, R> RequestFilter for F
271where
272  R: IntoRequestFilterResult,
273  F: Fn(&mut RequestContext) -> R + Send + Sync,
274{
275  fn filter(&self, request: &mut RequestContext) -> TiiResult<Option<Response>> {
276    self(request).into()
277  }
278}
279
280/// Trait for a filter that may alter a Response after an endpoint has been called or a filter has aborted the request.
281/// Use cases: (Non-Exhaustive)
282/// - Adding Cors headers
283/// - Adding Various other headers
284/// - Logging of the response
285/// - "Rough" estimation of the time it takes for the endpoint to process things.
286pub trait ResponseFilter: Send + Sync {
287  /// Called with the request context adn response after the endpoint or error handler is called.
288  /// Ok(...) -> proceed.
289  /// Err -> Call error handler and proceed. (You cannot create a loop, a Response filter will only be called exactly once per RequestContext)
290  fn filter(&self, request: &mut ResponseContext<'_>) -> TiiResult<()>;
291}
292
293impl<F, R> ResponseFilter for F
294where
295  R: Into<TiiResult<()>>,
296  F: Fn(&mut ResponseContext<'_>) -> R + Send + Sync,
297{
298  fn filter(&self, request: &mut ResponseContext<'_>) -> TiiResult<()> {
299    self(request).into()
300  }
301}
302
303/// A router may respond to a web-socket request with a http response or indicate that the socket has been handled with a protocol switch
304/// Or it may indicate that it hasn't handled the socket and signal that the next router should do it.
305/// This enum represents those 3 states
306#[derive(Debug)]
307pub enum RouterWebSocketServingResponse {
308  /// Handled with protocol switch to WS
309  HandledWithProtocolSwitch,
310  /// Handled using HTTP protocol
311  HandledWithoutProtocolSwitch(Response),
312  /// Not handled, next router should do it.
313  NotHandled,
314}
315
316/// Trait for a router.
317pub trait Router: Debug + Send + Sync {
318  /// Handle an ordinary http request
319  /// Ok(Some) -> request was handled
320  /// Ok(None) -> request was not handled and should be handled by the next router
321  /// Err -> abort
322  ///
323  /// Note: If the request body is read then returning Ok(None) will most likely result in unintended behavior in the next Router.
324  fn serve(&self, request: &mut RequestContext) -> TiiResult<Option<Response>>;
325
326  /// Handle a web socket request.
327  /// Ok(true) -> request was handled
328  /// Ok(false) -> request should not be handled by this router
329  /// Err -> abort
330  ///
331  /// Note: If the stream is read or written to then returning Ok(false) will most likely result in unintended behavior in the next Router.
332  fn serve_websocket(
333    &self,
334    stream: &dyn ConnectionStream,
335    request: &mut RequestContext,
336  ) -> TiiResult<RouterWebSocketServingResponse>;
337}