Skip to main content

toolkit_zero/socket/
server.rs

1
2//! Typed, fluent HTTP server construction.
3//!
4//! This module provides a builder-oriented API for declaring HTTP routes and
5//! serving them with [`warp`] under the hood. The entry point for every route
6//! is [`ServerMechanism`], which pairs an HTTP method with a URL path and
7//! supports incremental enrichment — attaching a JSON body expectation, URL
8//! query parameter deserialisation, or shared state — before being finalised
9//! into a [`SocketType`] route handle via `onconnect`.
10//!
11//! Completed routes are registered on a [`Server`] and served with a single
12//! `.await`. Graceful shutdown is available via
13//! [`Server::serve_with_graceful_shutdown`] and [`Server::serve_from_listener`].
14//!
15//! # Builder chains at a glance
16//!
17//! | Chain | Handler receives |
18//! |---|---|
19//! | `ServerMechanism::method(path).onconnect(f)` | nothing |
20//! | `.json::<T>().onconnect(f)` | `T: DeserializeOwned` |
21//! | `.query::<T>().onconnect(f)` | `T: DeserializeOwned` |
22//! | `.encryption::<T>(key).onconnect(f)` | `T: bincode::Decode<()>` (VEIL-decrypted body) |
23//! | `.encrypted_query::<T>(key).onconnect(f)` | `T: bincode::Decode<()>` (VEIL-decrypted query) |
24//! | `.state(s).onconnect(f)` | `S: Clone + Send + Sync` |
25//! | `.state(s).json::<T>().onconnect(f)` | `(S, T)` |
26//! | `.state(s).query::<T>().onconnect(f)` | `(S, T)` |
27//! | `.state(s).encryption::<T>(key).onconnect(f)` | `(S, T)` — VEIL-decrypted body |
28//! | `.state(s).encrypted_query::<T>(key).onconnect(f)` | `(S, T)` — VEIL-decrypted query |
29//!
30//! For blocking handlers (not recommended in production) every finaliser also
31//! has an unsafe `onconnect_sync` counterpart.
32//!
33//! # `#[mechanism]` attribute macro
34//!
35//! As an alternative to spelling out the builder chain by hand, the
36//! [`mechanism`] attribute macro collapses the entire
37//! `server.mechanism(ServerMechanism::method(path) … .onconnect(handler))` call
38//! into a single decorated `async fn`:
39//!
40//! ```rust,no_run
41//! # use toolkit_zero::socket::server::{Server, mechanism, reply, Status};
42//! # use serde::Deserialize;
43//! # #[derive(Deserialize)] struct NewItem { name: String }
44//! # #[derive(serde::Serialize)] struct Item { id: u32, name: String }
45//! # let mut server = Server::default();
46//! // Fluent form:
47//! // server.mechanism(
48//! //     ServerMechanism::post("/items")
49//! //         .json::<NewItem>()
50//! //         .onconnect(|body: NewItem| async move {
51//! //             reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
52//! //         })
53//! // );
54//!
55//! // Equivalent attribute form:
56//! #[mechanism(server, POST, "/items", json)]
57//! async fn create(body: NewItem) {
58//!     reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
59//! }
60//! ```
61//!
62//! See the [`mechanism`] item for the full syntax reference.
63//!
64//! # Response helpers
65//!
66//! Use the [`reply!`] macro as the most concise way to build a response:
67//!
68//! ```rust,no_run
69//! # use toolkit_zero::socket::server::*;
70//! # use serde::Serialize;
71//! # #[derive(Serialize)] struct Item { id: u32 }
72//! # let item = Item { id: 1 };
73//! // 200 OK, empty body
74//! // reply!()
75//!
76//! // 200 OK, JSON body
77//! // reply!(json => item)
78//!
79//! // 201 Created, JSON body
80//! // reply!(json => item, status => Status::Created)
81//! ```
82
83use std::{future::Future, net::SocketAddr};
84use serde::{de::DeserializeOwned, Serialize};
85use warp::{Filter, Rejection, Reply, filters::BoxedFilter};
86
87pub use super::SerializationKey;
88pub use toolkit_zero_macros::mechanism;
89
90/// A fully assembled, type-erased HTTP route ready to be registered on a [`Server`].
91///
92/// This is the final product of every builder chain. Pass it to [`Server::mechanism`] to mount it.
93pub type SocketType = BoxedFilter<(warp::reply::Response, )>;
94
95#[derive(Clone, Copy, Debug)]
96enum HttpMethod {
97    Get,
98    Post,
99    Put,
100    Delete,
101    Patch,
102    Head,
103    Options,
104}
105
106impl HttpMethod {
107    fn filter(&self) -> BoxedFilter<()> {
108        match self {
109            HttpMethod::Get     => warp::get().boxed(),
110            HttpMethod::Post    => warp::post().boxed(),
111            HttpMethod::Put     => warp::put().boxed(),
112            HttpMethod::Delete  => warp::delete().boxed(),
113            HttpMethod::Patch   => warp::patch().boxed(),
114            HttpMethod::Head    => warp::head().boxed(),
115            HttpMethod::Options => warp::options().boxed(),
116        }
117    }
118}
119
120
121fn path_filter(path: &str) -> BoxedFilter<()> {
122    log::trace!("Building path filter for: '{}'", path);
123    let segs: Vec<&'static str> = path
124        .trim_matches('/')
125        .split('/')
126        .filter(|s| !s.is_empty())
127        .map(|s| -> &'static str { Box::leak(s.to_owned().into_boxed_str()) })
128        .collect();
129
130    if segs.is_empty() {
131        return warp::path::end().boxed();
132    }
133
134    // Start with the first segment, then and-chain the rest.
135    let mut f: BoxedFilter<()> = warp::path(segs[0]).boxed();
136    for seg in &segs[1..] {
137        f = f.and(warp::path(*seg)).boxed();
138    }
139    f.and(warp::path::end()).boxed()
140}
141
142/// Entry point for building an HTTP route.
143///
144/// Pairs an HTTP method with a URL path and acts as the root of a fluent builder chain.
145/// Optionally attach shared state, a JSON body expectation, or URL query parameter
146/// deserialisation — then finalise with [`onconnect`](ServerMechanism::onconnect) (async) or
147/// [`onconnect_sync`](ServerMechanism::onconnect_sync) (sync) to produce a [`SocketType`]
148/// ready to be mounted on a [`Server`].
149///
150/// # Example
151/// ```rust,no_run
152/// # use serde::{Deserialize, Serialize};
153/// # use std::sync::{Arc, Mutex};
154/// # #[derive(Deserialize, Serialize)] struct Item { id: u32, name: String }
155/// # #[derive(Deserialize)] struct CreateItem { name: String }
156/// # #[derive(Deserialize)] struct SearchQuery { q: String }
157///
158/// // Plain GET — no body, no state
159/// let health = ServerMechanism::get("/health")
160///     .onconnect(|| async { reply!() });
161///
162/// // POST — JSON body deserialised into `CreateItem`
163/// let create = ServerMechanism::post("/items")
164///     .json::<CreateItem>()
165///     .onconnect(|body| async move {
166///         let item = Item { id: 1, name: body.name };
167///         reply!(json => item, status => Status::Created)
168///     });
169///
170/// // GET — URL query params deserialised into `SearchQuery`
171/// let search = ServerMechanism::get("/search")
172///     .query::<SearchQuery>()
173///     .onconnect(|params| async move {
174///         let _q = params.q;
175///         reply!()
176///     });
177///
178/// // GET — shared counter state injected on every request
179/// let counter: Arc<Mutex<u64>> = Arc::new(Mutex::new(0));
180/// let count_route = ServerMechanism::get("/count")
181///     .state(counter.clone())
182///     .onconnect(|state| async move {
183///         let n = *state.lock().unwrap();
184///         reply!(json => n)
185///     });
186/// ```
187pub struct ServerMechanism {
188    method: HttpMethod,
189    path: String,
190}
191
192impl ServerMechanism {
193
194    fn instance(method: HttpMethod, path: impl Into<String>) -> Self {
195        let path = path.into();
196        log::debug!("Creating {:?} route at '{}'", method, path);
197        Self { method, path }
198    }
199
200    /// Creates a route matching HTTP `GET` requests at `path`.
201    pub fn get(path: impl Into<String>) -> Self { Self::instance(HttpMethod::Get, path) }
202
203    /// Creates a route matching HTTP `POST` requests at `path`.
204    pub fn post(path: impl Into<String>) -> Self { Self::instance(HttpMethod::Post, path) }
205
206    /// Creates a route matching HTTP `PUT` requests at `path`.
207    pub fn put(path: impl Into<String>) -> Self { Self::instance(HttpMethod::Put, path) }
208
209    /// Creates a route matching HTTP `DELETE` requests at `path`.
210    pub fn delete(path: impl Into<String>) -> Self { Self::instance(HttpMethod::Delete, path) }
211
212    /// Creates a route matching HTTP `PATCH` requests at `path`.
213    pub fn patch(path: impl Into<String>) -> Self { Self::instance(HttpMethod::Patch, path) }
214
215    /// Creates a route matching HTTP `HEAD` requests at `path`.
216    pub fn head(path: impl Into<String>) -> Self { Self::instance(HttpMethod::Head, path) }
217
218    /// Creates a route matching HTTP `OPTIONS` requests at `path`.
219    pub fn options(path: impl Into<String>) -> Self { Self::instance(HttpMethod::Options, path) }
220
221    /// Attaches shared state `S` to this route, transitioning to [`StatefulSocketBuilder`].
222    ///
223    /// A fresh clone of `S` is injected into the handler on every request.  For mutable
224    /// shared state, wrap the inner value in `Arc<Mutex<_>>` or `Arc<RwLock<_>>` before
225    /// passing it here — only the outer `Arc` is cloned per request; the inner data stays
226    /// shared across all requests.
227    ///
228    /// `S` must be `Clone + Send + Sync + 'static`.
229    ///
230    /// From [`StatefulSocketBuilder`] you can further add a JSON body (`.json`), query
231    /// parameters (`.query`), or encryption (`.encryption` / `.encrypted_query`) before
232    /// finalising with `onconnect`.
233    ///
234    /// # Example
235    /// ```rust,no_run
236    /// use std::sync::{Arc, Mutex};
237    ///
238    /// let db: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
239    ///
240    /// let route = ServerMechanism::get("/list")
241    ///     .state(db.clone())
242    ///     .onconnect(|state| async move {
243    ///         let items = state.lock().unwrap().clone();
244    ///         reply!(json => items)
245    ///     });
246    /// ```
247    pub fn state<S: Clone + Send + Sync + 'static>(self, state: S) -> StatefulSocketBuilder<S> {
248        log::trace!("Attaching state to {:?} route at '{}'", self.method, self.path);
249        StatefulSocketBuilder { base: self, state }
250    }
251
252    /// Declares that this route expects a JSON-encoded request body, transitioning to
253    /// [`JsonSocketBuilder`].
254    ///
255    /// On each incoming request the body is parsed as `Content-Type: application/json`
256    /// and deserialised into `T`.  If the body is absent, malformed, or fails to
257    /// deserialise, the request is rejected before the handler is ever called.
258    /// When you subsequently call [`onconnect`](JsonSocketBuilder::onconnect), the handler
259    /// receives a fully-deserialised, ready-to-use `T`.
260    ///
261    /// `T` must implement `serde::de::DeserializeOwned`.
262    ///
263    /// # Example
264    /// ```rust,no_run
265    /// # use serde::Deserialize;
266    /// # #[derive(Deserialize)] struct Payload { value: i32 }
267    ///
268    /// let route = ServerMechanism::post("/submit")
269    ///     .json::<Payload>()
270    ///     .onconnect(|body| async move {
271    ///         reply!(json => body.value)
272    ///     });
273    /// ```
274    pub fn json<T: DeserializeOwned + Send>(self) -> JsonSocketBuilder<T> {
275        log::trace!("Attaching JSON body expectation to {:?} route at '{}'", self.method, self.path);
276        JsonSocketBuilder { base: self, _phantom: std::marker::PhantomData }
277    }
278
279    /// Declares that this route extracts its input from URL query parameters, transitioning
280    /// to [`QuerySocketBuilder`].
281    ///
282    /// On each incoming request the query string (`?field=value&...`) is deserialised into
283    /// `T`.  A missing or malformed query string is rejected before the handler is called.
284    /// When you subsequently call [`onconnect`](QuerySocketBuilder::onconnect), the handler
285    /// receives a fully-deserialised, ready-to-use `T`.
286    ///
287    /// `T` must implement `serde::de::DeserializeOwned`.
288    ///
289    /// # Example
290    /// ```rust,no_run
291    /// # use serde::Deserialize;
292    /// # #[derive(Deserialize)] struct Filter { page: u32, per_page: u32 }
293    ///
294    /// let route = ServerMechanism::get("/items")
295    ///     .query::<Filter>()
296    ///     .onconnect(|filter| async move {
297    ///         let _ = (filter.page, filter.per_page);
298    ///         reply!()
299    ///     });
300    /// ```
301    pub fn query<T: DeserializeOwned + Send>(self) -> QuerySocketBuilder<T> {
302        log::trace!("Attaching query parameter expectation to {:?} route at '{}'", self.method, self.path);
303        QuerySocketBuilder { base: self, _phantom: std::marker::PhantomData }
304    }
305
306    /// Declares that this route expects a VEIL-encrypted request body, transitioning to
307    /// [`EncryptedBodyBuilder`].
308    ///
309    /// On each incoming request the raw body bytes are decrypted using the provided
310    /// [`SerializationKey`] before the handler is called.  If the key does not match or
311    /// the body is corrupt, the route responds with `403 Forbidden` and the handler is
312    /// never invoked — meaning the `T` your handler receives is always a legitimate,
313    /// trusted, fully-decrypted value.
314    ///
315    /// Use `SerializationKey::Default` when both client and server share the built-in key,
316    /// or `SerializationKey::Value("your-key")` for a custom shared secret.
317    /// For plain-JSON routes (no encryption) use [`.json::<T>()`](ServerMechanism::json)
318    /// instead.
319    ///
320    /// `T` must implement `bincode::Decode<()>`.
321    ///
322    /// # Example
323    /// ```rust,no_run
324    /// # use bincode::{Encode, Decode};
325    /// # #[derive(Encode, Decode)] struct Payload { value: i32 }
326    ///
327    /// let route = ServerMechanism::post("/submit")
328    ///     .encryption::<Payload>(SerializationKey::Default)
329    ///     .onconnect(|body| async move {
330    ///         // `body` is already decrypted and deserialised — use it directly.
331    ///         reply!(json => body.value)
332    ///     });
333    /// ```
334    pub fn encryption<T>(self, key: SerializationKey) -> EncryptedBodyBuilder<T> {
335        log::trace!("Attaching encrypted body to {:?} route at '{}'", self.method, self.path);
336        EncryptedBodyBuilder { base: self, key, _phantom: std::marker::PhantomData }
337    }
338
339    /// Declares that this route expects VEIL-encrypted URL query parameters, transitioning
340    /// to [`EncryptedQueryBuilder`].
341    ///
342    /// The client must send a single `?data=<base64url>` query parameter whose value is
343    /// the URL-safe base64 encoding of the VEIL-encrypted struct bytes.  On each request
344    /// the server base64-decodes then decrypts the payload using the provided
345    /// [`SerializationKey`].  If the `data` parameter is missing, the encoding is invalid,
346    /// the key does not match, or the bytes are corrupt, the route responds with
347    /// `403 Forbidden` and the handler is never invoked — meaning the `T` your handler
348    /// receives is always a legitimate, trusted, fully-decrypted value.
349    ///
350    /// Use `SerializationKey::Default` or `SerializationKey::Value("your-key")`.  For
351    /// plain query-string routes (no encryption) use
352    /// [`.query::<T>()`](ServerMechanism::query) instead.
353    ///
354    /// `T` must implement `bincode::Decode<()>`.
355    pub fn encrypted_query<T>(self, key: SerializationKey) -> EncryptedQueryBuilder<T> {
356        log::trace!("Attaching encrypted query to {:?} route at '{}'", self.method, self.path);
357        EncryptedQueryBuilder { base: self, key, _phantom: std::marker::PhantomData }
358    }
359
360    /// Finalises this route with an async handler that receives no arguments.
361    ///
362    /// Neither a request body nor query parameters are read.  The handler runs on every
363    /// matching request and must return `Result<impl Reply, Rejection>`.  Use the
364    /// [`reply!`] macro or the standalone reply helpers ([`reply_with_json`],
365    /// [`reply_with_status`], etc.) to construct a response.
366    ///
367    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
368    ///
369    /// # Example
370    /// ```rust,no_run
371    /// # use serde::Serialize;
372    /// # #[derive(Serialize)] struct Pong { ok: bool }
373    ///
374    /// let route = ServerMechanism::get("/ping")
375    ///     .onconnect(|| async {
376    ///         reply!(json => Pong { ok: true })
377    ///     });
378    /// ```
379    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
380    where
381        F: Fn() -> Fut + Clone + Send + Sync + 'static,
382        Fut: Future<Output = Result<Re, Rejection>> + Send,
383        Re: Reply + Send,
384    {
385        log::debug!("Finalising async {:?} route at '{}' (no args)", self.method, self.path);
386        let m = self.method.filter();
387        let p = path_filter(&self.path);
388        m.and(p)
389            .and_then(handler)
390            .map(|r: Re| r.into_response())
391            .boxed()
392    }
393
394    /// Finalises this route with a **synchronous** handler that receives no arguments.
395    ///
396    /// Behaviour and contract are identical to the async variant — neither a body nor
397    /// query parameters are read — but the closure may block.  Each request is dispatched
398    /// to the blocking thread pool, so the handler must complete quickly to avoid starving
399    /// other requests.
400    ///
401    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
402    ///
403    /// # Safety
404    ///
405    /// Every incoming request spawns an independent task on Tokio's blocking thread pool.
406    /// The pool caps the number of live OS threads (default 512), but the **queue of waiting
407    /// tasks is unbounded** — under a traffic surge, tasks accumulate without limit, consuming
408    /// unbounded memory and causing severe latency spikes or OOM crashes before any queued task
409    /// gets a chance to run. Additionally, any panic inside the handler is silently converted
410    /// into a `Rejection`, masking runtime errors. Callers must ensure the handler completes
411    /// quickly and that adequate backpressure or rate limiting is applied externally.
412    pub unsafe fn onconnect_sync<F, Re>(self, handler: F) -> SocketType
413    where
414        F: Fn() -> Result<Re, Rejection> + Clone + Send + Sync + 'static,
415        Re: Reply + Send + 'static,
416    {
417        log::warn!("Registering sync handler on {:?} '{}' — ensure rate-limiting is applied externally", self.method, self.path);
418        let m = self.method.filter();
419        let p = path_filter(&self.path);
420        m.and(p)
421            .and_then(move || {
422                let handler = handler.clone();
423                async move {
424                    tokio::task::spawn_blocking(move || handler())
425                        .await
426                        .unwrap_or_else(|_| {
427                            log::warn!("Sync handler panicked; converting to Rejection");
428                            Err(warp::reject())
429                        })
430                }
431            })
432            .map(|r: Re| r.into_response())
433            .boxed()
434    }
435}
436
437
438/// Route builder that expects and deserialises a JSON request body of type `T`.
439///
440/// Obtained from [`ServerMechanism::json`]. Optionally attach shared state via
441/// [`state`](JsonSocketBuilder::state), or finalise immediately with
442/// [`onconnect`](JsonSocketBuilder::onconnect) /
443/// [`onconnect_sync`](JsonSocketBuilder::onconnect_sync).
444pub struct JsonSocketBuilder<T> {
445    base: ServerMechanism,
446    _phantom: std::marker::PhantomData<T>,
447}
448
449impl<T: DeserializeOwned + Send + 'static> JsonSocketBuilder<T> {
450
451    /// Finalises this route with an async handler that receives the deserialised JSON body.
452    ///
453    /// Before the handler is called, the incoming request body is parsed as
454    /// `Content-Type: application/json` and deserialised into `T`.  The handler receives
455    /// a ready-to-use `T` — no manual parsing is needed.  If the body is absent or cannot
456    /// be decoded the request is rejected before the handler is ever invoked.
457    ///
458    /// The handler must return `Result<impl Reply, Rejection>`.
459    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
460    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
461    where 
462        F: Fn(T) -> Fut + Clone + Send + Sync + 'static,
463        Fut: Future<Output = Result<Re, Rejection>> + Send,
464        Re: Reply + Send,
465    {
466        log::debug!("Finalising async {:?} route at '{}' (JSON body)", self.base.method, self.base.path);
467        let m = self.base.method.filter();
468        let p = path_filter(&self.base.path);
469        m.and(p)
470            .and(warp::body::json::<T>())
471            .and_then(handler)
472            .map(|r: Re| r.into_response())
473            .boxed()
474    }
475
476    /// Finalises this route with a **synchronous** handler that receives the deserialised
477    /// JSON body.
478    ///
479    /// The body is decoded into `T` before the handler is dispatched, identical to the
480    /// async variant.  The closure may block but must complete quickly to avoid exhausting
481    /// the blocking thread pool.
482    ///
483    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
484    ///
485    /// # Safety
486    ///
487    /// Every incoming request spawns an independent task on Tokio's blocking thread pool.
488    /// The pool caps the number of live OS threads (default 512), but the **queue of waiting
489    /// tasks is unbounded** — under a traffic surge, tasks accumulate without limit, consuming
490    /// unbounded memory and causing severe latency spikes or OOM crashes before any queued task
491    /// gets a chance to run. Additionally, any panic inside the handler is silently converted
492    /// into a `Rejection`, masking runtime errors. Callers must ensure the handler completes
493    /// quickly and that adequate backpressure or rate limiting is applied externally.
494    pub unsafe fn onconnect_sync<F, Re>(self, handler: F) -> SocketType
495    where
496        F: Fn(T) -> Result<Re, Rejection> + Clone + Send + Sync + 'static,
497        Re: Reply + Send + 'static,
498    {
499        log::warn!("Registering sync handler on {:?} '{}' (JSON body) — ensure rate-limiting is applied externally", self.base.method, self.base.path);
500        let m = self.base.method.filter();
501        let p = path_filter(&self.base.path);
502        m.and(p)
503            .and(warp::body::json::<T>())
504            .and_then(move |body: T| {
505                let handler = handler.clone();
506                async move {
507                    tokio::task::spawn_blocking(move || handler(body))
508                        .await
509                        .unwrap_or_else(|_| {
510                            log::warn!("Sync handler (JSON body) panicked; converting to Rejection");
511                            Err(warp::reject())
512                        })
513                }
514            })
515            .map(|r: Re| r.into_response())
516            .boxed()
517    }
518
519    /// Attaches shared state `S`, transitioning to [`StatefulJsonSocketBuilder`].
520    ///
521    /// Alternative ordering to `.state(s).json::<T>()` — both produce the same route.
522    /// A fresh clone of `S` is injected alongside the JSON-decoded `T` on every request.
523    /// The handler will receive `(state: S, body: T)`.
524    ///
525    /// `S` must be `Clone + Send + Sync + 'static`.
526    pub fn state<S: Clone + Send + Sync + 'static>(self, state: S) -> StatefulJsonSocketBuilder<T, S> {
527        StatefulJsonSocketBuilder { base: self.base, state, _phantom: std::marker::PhantomData }
528    }
529
530}
531
532/// Route builder that expects and deserialises URL query parameters of type `T`.
533///
534/// Obtained from [`ServerMechanism::query`]. Optionally attach shared state via
535/// [`state`](QuerySocketBuilder::state), or finalise immediately with
536/// [`onconnect`](QuerySocketBuilder::onconnect) /
537/// [`onconnect_sync`](QuerySocketBuilder::onconnect_sync).
538pub struct QuerySocketBuilder<T> {
539    base: ServerMechanism,
540    _phantom: std::marker::PhantomData<T>,
541}
542
543impl<T: DeserializeOwned + Send + 'static> QuerySocketBuilder<T> {
544    /// Finalises this route with an async handler that receives the deserialised query
545    /// parameters.
546    ///
547    /// Before the handler is called, the URL query string (`?field=value&...`) is parsed
548    /// and deserialised into `T`.  The handler receives a ready-to-use `T` — no manual
549    /// parsing is needed.  A missing or malformed query string is rejected before the
550    /// handler is ever invoked.
551    ///
552    /// The handler must return `Result<impl Reply, Rejection>`.
553    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
554    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
555    where
556        F: Fn(T) -> Fut + Clone + Send + Sync + 'static,
557        Fut: Future<Output = Result<Re, Rejection>> + Send,
558        Re: Reply + Send,
559    {
560        log::debug!("Finalising async {:?} route at '{}' (query params)", self.base.method, self.base.path);
561        let m = self.base.method.filter();
562        let p = path_filter(&self.base.path);
563        m.and(p)
564            .and(warp::filters::query::query::<T>())
565            .and_then(handler)
566            .map(|r: Re| r.into_response())
567            .boxed()
568    }
569
570    /// Finalises this route with a **synchronous** handler that receives the deserialised
571    /// query parameters.
572    ///
573    /// The query string is decoded into `T` before the handler is dispatched, identical to
574    /// the async variant.  The closure may block but must complete quickly.
575    ///
576    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
577    ///
578    /// # Safety
579    ///
580    /// Every incoming request spawns an independent task on Tokio's blocking thread pool.
581    /// The pool caps the number of live OS threads (default 512), but the **queue of waiting
582    /// tasks is unbounded** — under a traffic surge, tasks accumulate without limit, consuming
583    /// unbounded memory and causing severe latency spikes or OOM crashes before any queued task
584    /// gets a chance to run. Additionally, any panic inside the handler is silently converted
585    /// into a `Rejection`, masking runtime errors. Callers must ensure the handler completes
586    /// quickly and that adequate backpressure or rate limiting is applied externally.
587    pub unsafe fn onconnect_sync<F, Re>(self, handler: F) -> SocketType
588    where
589        F: Fn(T) -> Result<Re, Rejection> + Clone + Send + Sync + 'static,
590        Re: Reply + Send + 'static,
591    {
592        log::warn!("Registering sync handler on {:?} '{}' (query params) — ensure rate-limiting is applied externally", self.base.method, self.base.path);
593        let m = self.base.method.filter();
594        let p = path_filter(&self.base.path);
595        m.and(p)
596            .and(warp::filters::query::query::<T>())
597            .and_then(move |query: T| {
598                let handler = handler.clone();
599                async move {
600                    tokio::task::spawn_blocking(move || handler(query))
601                        .await
602                        .unwrap_or_else(|_| {
603                            log::warn!("Sync handler (query params) panicked; converting to Rejection");
604                            Err(warp::reject())
605                        })
606                }
607            })
608            .map(|r: Re| r.into_response())
609            .boxed()
610    }
611
612    /// Attaches shared state `S`, transitioning to [`StatefulQuerySocketBuilder`].
613    ///
614    /// Alternative ordering to `.state(s).query::<T>()` — both produce the same route.
615    /// A fresh clone of `S` is injected alongside the query-decoded `T` on every request.
616    /// The handler will receive `(state: S, query: T)`.
617    ///
618    /// `S` must be `Clone + Send + Sync + 'static`.
619    pub fn state<S: Clone + Send + Sync + 'static>(self, state: S) -> StatefulQuerySocketBuilder<T, S> {
620        StatefulQuerySocketBuilder { base: self.base, state, _phantom: std::marker::PhantomData }
621    }
622
623}
624
625
626/// Route builder that carries shared state `S` with no body or query expectation.
627///
628/// Obtained from [`ServerMechanism::state`]. `S` must be `Clone + Send + Sync + 'static`.
629/// For mutable shared state, wrap it in `Arc<Mutex<_>>` or `Arc<RwLock<_>>` before passing
630/// it here. Finalise with [`onconnect`](StatefulSocketBuilder::onconnect) /
631/// [`onconnect_sync`](StatefulSocketBuilder::onconnect_sync).
632pub struct StatefulSocketBuilder<S> {
633    base: ServerMechanism,
634    state: S,
635}
636
637
638impl<S: Clone + Send + Sync + 'static> StatefulSocketBuilder<S> {
639    /// Adds a JSON body expectation, transitioning to [`StatefulJsonSocketBuilder`].
640    ///
641    /// Alternative ordering to `.json::<T>().state(s)` — both produce the same route.
642    /// On each request the incoming JSON body is deserialised into `T` and a fresh clone
643    /// of `S` is prepared; both are handed to the handler together as `(state: S, body: T)`.
644    ///
645    /// Allows `.state(s).json::<T>()` as an alternative ordering to `.json::<T>().state(s)`.
646    pub fn json<T: DeserializeOwned + Send>(self) -> StatefulJsonSocketBuilder<T, S> {
647        log::trace!("Attaching JSON body expectation (after state) to {:?} route at '{}'", self.base.method, self.base.path);
648        StatefulJsonSocketBuilder { base: self.base, state: self.state, _phantom: std::marker::PhantomData }
649    }
650
651    /// Adds a query parameter expectation, transitioning to [`StatefulQuerySocketBuilder`].
652    ///
653    /// Alternative ordering to `.query::<T>().state(s)` — both produce the same route.
654    /// On each request the URL query string is deserialised into `T` and a fresh clone
655    /// of `S` is prepared; both are handed to the handler together as `(state: S, query: T)`.
656    ///
657    /// Allows `.state(s).query::<T>()` as an alternative ordering to `.query::<T>().state(s)`.
658    pub fn query<T: DeserializeOwned + Send>(self) -> StatefulQuerySocketBuilder<T, S> {
659        log::trace!("Attaching query param expectation (after state) to {:?} route at '{}'", self.base.method, self.base.path);
660        StatefulQuerySocketBuilder { base: self.base, state: self.state, _phantom: std::marker::PhantomData }
661    }
662
663    /// Adds an encrypted body expectation, transitioning to [`StatefulEncryptedBodyBuilder`].
664    ///
665    /// Alternative ordering to `.encryption::<T>(key).state(s)` — both produce the same
666    /// route.  On each request the raw body bytes are VEIL-decrypted using `key` before
667    /// the handler runs; a wrong key or corrupt body returns `403 Forbidden` without
668    /// invoking the handler.  The handler will receive `(state: S, body: T)` where `T` is
669    /// always a trusted, fully-decrypted value.
670    ///
671    /// Allows `.state(s).encryption::<T>(key)` as an alternative ordering to
672    /// `.encryption::<T>(key).state(s)`.
673    pub fn encryption<T>(self, key: SerializationKey) -> StatefulEncryptedBodyBuilder<T, S> {
674        log::trace!("Attaching encrypted body (after state) to {:?} route at '{}'", self.base.method, self.base.path);
675        StatefulEncryptedBodyBuilder { base: self.base, key, state: self.state, _phantom: std::marker::PhantomData }
676    }
677
678    /// Adds an encrypted query expectation, transitioning to [`StatefulEncryptedQueryBuilder`].
679    ///
680    /// Alternative ordering to `.encrypted_query::<T>(key).state(s)` — both produce the
681    /// same route.  On each request the `?data=<base64url>` query parameter is
682    /// base64-decoded then VEIL-decrypted using `key`; a missing, malformed, or
683    /// undecryptable payload returns `403 Forbidden` without invoking the handler.  The
684    /// handler will receive `(state: S, query: T)` where `T` is always a trusted,
685    /// fully-decrypted value.
686    ///
687    /// Allows `.state(s).encrypted_query::<T>(key)` as an alternative ordering to
688    /// `.encrypted_query::<T>(key).state(s)`.
689    pub fn encrypted_query<T>(self, key: SerializationKey) -> StatefulEncryptedQueryBuilder<T, S> {
690        log::trace!("Attaching encrypted query (after state) to {:?} route at '{}'", self.base.method, self.base.path);
691        StatefulEncryptedQueryBuilder { base: self.base, key, state: self.state, _phantom: std::marker::PhantomData }
692    }
693
694    /// Finalises this route with an async handler that receives only the shared state.
695    ///
696    /// On each request a fresh clone of `S` is injected into the handler.  No request body
697    /// or query parameters are read.  The handler must return `Result<impl Reply, Rejection>`.
698    ///
699    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
700    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
701    where
702        F: Fn(S) -> Fut + Clone + Send + Sync + 'static,
703        Fut: Future<Output = Result<Re, Rejection>> + Send,
704        Re: Reply + Send,
705    {
706        log::debug!("Finalising async {:?} route at '{}' (state)", self.base.method, self.base.path);
707        let m = self.base.method.filter();
708        let p = path_filter(&self.base.path);
709        let state = self.state;
710        let state_filter = warp::any().map(move || state.clone());
711        m.and(p)
712            .and(state_filter)
713            .and_then(handler)
714            .map(|r: Re| r.into_response())
715            .boxed()
716    }
717
718    /// Finalises this route with a **synchronous** handler that receives only the shared
719    /// state.
720    ///
721    /// On each request a fresh clone of `S` is passed to the handler.  If `S` wraps a mutex
722    /// or lock, contention across concurrent requests can stall the thread pool — ensure the
723    /// lock is held only briefly.  The closure may block but must complete quickly.
724    ///
725    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
726    ///
727    /// # Safety
728    ///
729    /// Every incoming request spawns an independent task on Tokio's blocking thread pool.
730    /// The pool caps the number of live OS threads (default 512), but the **queue of waiting
731    /// tasks is unbounded** — under a traffic surge, tasks accumulate without limit, consuming
732    /// unbounded memory and causing severe latency spikes or OOM crashes before any queued task
733    /// gets a chance to run. When the handler acquires a lock on `S` (e.g. `Arc<Mutex<_>>`),
734    /// concurrent blocking tasks contending on the same lock can stall indefinitely, causing the
735    /// thread pool queue to grow without bound and compounding the exhaustion risk. Additionally,
736    /// any panic inside the handler is silently converted into a `Rejection`, masking runtime
737    /// errors. Callers must ensure the handler completes quickly, that lock contention on `S`
738    /// cannot produce indefinite stalls, and that adequate backpressure or rate limiting is
739    /// applied externally.
740    pub unsafe fn onconnect_sync<F, Re>(self, handler: F) -> SocketType
741    where
742        F: Fn(S) -> Result<Re, Rejection> + Clone + Send + Sync + 'static,
743        Re: Reply + Send + 'static,
744    {
745        log::warn!("Registering sync handler on {:?} '{}' (state) — ensure rate-limiting and lock-free state are in place", self.base.method, self.base.path);
746        let m = self.base.method.filter();
747        let p = path_filter(&self.base.path);
748        let state = self.state;
749        let state_filter = warp::any().map(move || state.clone());
750        m.and(p)
751            .and(state_filter)
752            .and_then(move |s: S| {
753                let handler = handler.clone();
754                async move {
755                    tokio::task::spawn_blocking(move || handler(s))
756                        .await
757                        .unwrap_or_else(|_| {
758                            log::warn!("Sync handler (state) panicked; converting to Rejection");
759                            Err(warp::reject())
760                        })
761                }
762            })
763            .map(|r: Re| r.into_response())
764            .boxed()
765    }
766}
767
768
769/// Route builder that carries shared state `S` and expects a JSON body of type `T`.
770///
771/// Obtained from [`JsonSocketBuilder::state`]. `S` must be `Clone + Send + Sync + 'static`.
772/// `T` must implement `serde::de::DeserializeOwned`. Finalise with
773/// [`onconnect`](StatefulJsonSocketBuilder::onconnect) /
774/// [`onconnect_sync`](StatefulJsonSocketBuilder::onconnect_sync).
775pub struct StatefulJsonSocketBuilder<T, S> {
776    base: ServerMechanism,
777    state: S,
778    _phantom: std::marker::PhantomData<T>,
779}
780
781impl<T: DeserializeOwned + Send + 'static, S: Clone + Send + Sync + 'static>
782    StatefulJsonSocketBuilder<T, S>
783{
784    /// Finalises this route with an async handler that receives `(state: S, body: T)`.
785    ///
786    /// On each request the incoming JSON body is deserialised into `T` and a fresh clone
787    /// of `S` is prepared — both are handed to the handler together.  The handler is only
788    /// called when the body can be decoded; a missing or malformed body is rejected first.
789    ///
790    /// The handler must return `Result<impl Reply, Rejection>`.
791    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
792    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
793    where
794        F: Fn(S, T) -> Fut + Clone + Send + Sync + 'static,
795        Fut: Future<Output = Result<Re, Rejection>> + Send,
796        Re: Reply + Send,
797    {
798        log::debug!("Finalising async {:?} route at '{}' (state + JSON body)", self.base.method, self.base.path);
799        let m = self.base.method.filter();
800        let p = path_filter(&self.base.path);
801        let state = self.state;
802        let state_filter = warp::any().map(move || state.clone());
803        m.and(p)
804            .and(state_filter)
805            .and(warp::body::json::<T>())
806            .and_then(handler)
807            .map(|r: Re| r.into_response())
808            .boxed()
809    }
810
811    /// Finalises this route with a **synchronous** handler that receives
812    /// `(state: S, body: T)`.
813    ///
814    /// The body is decoded into `T` and a clone of `S` is prepared before the blocking
815    /// handler is dispatched.  If `S` wraps a lock, keep it held only briefly.  See
816    /// [`ServerMechanism::onconnect_sync`] for the full thread-pool safety notes.
817    ///
818    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
819    ///
820    /// # Safety
821    ///
822    /// Every incoming request spawns an independent task on Tokio's blocking thread pool.
823    /// The pool caps the number of live OS threads (default 512), but the **queue of waiting
824    /// tasks is unbounded** — under a traffic surge, tasks accumulate without limit, consuming
825    /// unbounded memory and causing severe latency spikes or OOM crashes before any queued task
826    /// gets a chance to run. When the handler acquires a lock on `S` (e.g. `Arc<Mutex<_>>`),
827    /// concurrent blocking tasks contending on the same lock can stall indefinitely, causing the
828    /// thread pool queue to grow without bound and compounding the exhaustion risk. Additionally,
829    /// any panic inside the handler is silently converted into a `Rejection`, masking runtime
830    /// errors. Callers must ensure the handler completes quickly, that lock contention on `S`
831    /// cannot produce indefinite stalls, and that adequate backpressure or rate limiting is
832    /// applied externally.
833    pub unsafe fn onconnect_sync<F, Re>(self, handler: F) -> SocketType
834    where
835        F: Fn(S, T) -> Result<Re, Rejection> + Clone + Send + Sync + 'static,
836        Re: Reply + Send + 'static,
837    {
838        log::warn!("Registering sync handler on {:?} '{}' (state + JSON body) — ensure rate-limiting and lock-free state are in place", self.base.method, self.base.path);
839        let m = self.base.method.filter();
840        let p = path_filter(&self.base.path);
841        let state = self.state;
842        let state_filter = warp::any().map(move || state.clone());
843        m.and(p)
844            .and(state_filter)
845            .and(warp::body::json::<T>())
846            .and_then(move |s: S, body: T| {
847                let handler = handler.clone();
848                async move {
849                    tokio::task::spawn_blocking(move || handler(s, body))
850                        .await
851                        .unwrap_or_else(|_| {
852                            log::warn!("Sync handler (state + JSON body) panicked; converting to Rejection");
853                            Err(warp::reject())
854                        })
855                }
856            })
857            .map(|r: Re| r.into_response())
858            .boxed()
859    }
860}
861
862/// Route builder that carries shared state `S` and expects URL query parameters of type `T`.
863///
864/// Obtained from [`QuerySocketBuilder::state`]. `S` must be `Clone + Send + Sync + 'static`.
865/// `T` must implement `serde::de::DeserializeOwned`. Finalise with
866/// [`onconnect`](StatefulQuerySocketBuilder::onconnect) /
867/// [`onconnect_sync`](StatefulQuerySocketBuilder::onconnect_sync).
868pub struct StatefulQuerySocketBuilder<T, S> {
869    base: ServerMechanism,
870    state: S,
871    _phantom: std::marker::PhantomData<T>,
872}
873
874impl<T: DeserializeOwned + Send + 'static, S: Clone + Send + Sync + 'static>
875    StatefulQuerySocketBuilder<T, S>
876{
877    /// Finalises this route with an async handler that receives `(state: S, query: T)`.
878    ///
879    /// On each request the URL query string is deserialised into `T` and a fresh clone of
880    /// `S` is prepared — both are handed to the handler together.  A missing or malformed
881    /// query string is rejected before the handler is ever invoked.
882    ///
883    /// The handler must return `Result<impl Reply, Rejection>`.
884    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
885    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
886    where
887        F: Fn(S, T) -> Fut + Clone + Send + Sync + 'static,
888        Fut: Future<Output = Result<Re, Rejection>> + Send,
889        Re: Reply + Send,
890    {
891        log::debug!("Finalising async {:?} route at '{}' (state + query params)", self.base.method, self.base.path);
892        let m = self.base.method.filter();
893        let p = path_filter(&self.base.path);
894        let state = self.state;
895        let state_filter = warp::any().map(move || state.clone());
896        m.and(p)
897            .and(state_filter)
898            .and(warp::filters::query::query::<T>())
899            .and_then(handler)
900            .map(|r: Re| r.into_response())
901            .boxed()
902    }
903
904    /// Finalises this route with a **synchronous** handler that receives
905    /// `(state: S, query: T)`.
906    ///
907    /// The query string is decoded into `T` and a clone of `S` is prepared before the
908    /// blocking handler is dispatched.  If `S` wraps a lock, keep it held only briefly.
909    /// See [`ServerMechanism::onconnect_sync`] for the full thread-pool safety notes.
910    ///
911    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
912    ///
913    /// # Safety
914    ///
915    /// Every incoming request spawns an independent task on Tokio's blocking thread pool.
916    /// The pool caps the number of live OS threads (default 512), but the **queue of waiting
917    /// tasks is unbounded** — under a traffic surge, tasks accumulate without limit, consuming
918    /// unbounded memory and causing severe latency spikes or OOM crashes before any queued task
919    /// gets a chance to run. When the handler acquires a lock on `S` (e.g. `Arc<Mutex<_>>`),
920    /// concurrent blocking tasks contending on the same lock can stall indefinitely, causing the
921    /// thread pool queue to grow without bound and compounding the exhaustion risk. Additionally,
922    /// any panic inside the handler is silently converted into a `Rejection`, masking runtime
923    /// errors. Callers must ensure the handler completes quickly, that lock contention on `S`
924    /// cannot produce indefinite stalls, and that adequate backpressure or rate limiting is
925    /// applied externally.
926    pub unsafe fn onconnect_sync<F, Re>(self, handler: F) -> SocketType
927    where
928        F: Fn(S, T) -> Result<Re, Rejection> + Clone + Send + Sync + 'static,
929        Re: Reply + Send + 'static,
930    {
931        log::warn!("Registering sync handler on {:?} '{}' (state + query params) — ensure rate-limiting and lock-free state are in place", self.base.method, self.base.path);
932        let m = self.base.method.filter();
933        let p = path_filter(&self.base.path);
934        let state = self.state;
935        let state_filter = warp::any().map(move || state.clone());
936        m.and(p)
937            .and(state_filter)
938            .and(warp::filters::query::query::<T>())
939            .and_then(move |s: S, query: T| {
940                let handler = handler.clone();
941                async move {
942                    tokio::task::spawn_blocking(move || handler(s, query))
943                        .await
944                        .unwrap_or_else(|_| {
945                            log::warn!("Sync handler (state + query params) panicked; converting to Rejection");
946                            Err(warp::reject())
947                        })
948                }
949            })
950            .map(|r: Re| r.into_response())
951            .boxed()
952    }
953}
954
955// ─── EncryptedBodyBuilder ────────────────────────────────────────────────────
956
957/// Route builder that expects a VEIL-encrypted request body of type `T`.
958///
959/// Obtained from [`ServerMechanism::encryption`].  On each matching request the raw
960/// body bytes are decrypted using the [`SerializationKey`] supplied there.  If
961/// decryption fails for any reason (wrong key, mismatched secret, corrupted payload)
962/// the route immediately returns `403 Forbidden` — the handler is never invoked.
963///
964/// Optionally attach shared state via [`state`](EncryptedBodyBuilder::state) before
965/// finalising with [`onconnect`](EncryptedBodyBuilder::onconnect) /
966/// [`onconnect_sync`](EncryptedBodyBuilder::onconnect_sync).
967pub struct EncryptedBodyBuilder<T> {
968    base: ServerMechanism,
969    key: SerializationKey,
970    _phantom: std::marker::PhantomData<T>,
971}
972
973impl<T> EncryptedBodyBuilder<T>
974where
975    T: bincode::Decode<()> + Send + 'static,
976{
977    /// Finalises this route with an async handler that receives the decrypted body as `T`.
978    ///
979    /// On each request the raw body bytes are VEIL-decrypted using the [`SerializationKey`]
980    /// configured on this builder.  The handler is only invoked when decryption succeeds —
981    /// a wrong key, mismatched secret, or corrupted body causes the route to return
982    /// `403 Forbidden` without ever reaching the handler.  The `T` the closure receives is
983    /// therefore always a trusted, fully-decrypted value ready to use.
984    ///
985    /// The handler must return `Result<impl Reply, Rejection>`.
986    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
987    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
988    where
989        F: Fn(T) -> Fut + Clone + Send + Sync + 'static,
990        Fut: Future<Output = Result<Re, Rejection>> + Send,
991        Re: Reply + Send,
992    {
993        log::debug!("Finalising async {:?} route at '{}' (encrypted body)", self.base.method, self.base.path);
994        let m = self.base.method.filter();
995        let p = path_filter(&self.base.path);
996        let key = self.key;
997        m.and(p)
998            .and(warp::body::bytes())
999            .and_then(move |raw: bytes::Bytes| {
1000                let key = key.clone();
1001                let handler = handler.clone();
1002                async move {
1003                    let value: T = decode_body(&raw, &key)?;
1004                    handler(value).await.map(|r| r.into_response())
1005                }
1006            })
1007            .boxed()
1008    }
1009
1010    /// Finalises this route with a **synchronous** handler that receives the decrypted
1011    /// body as `T`.
1012    ///
1013    /// Decryption happens before the handler is dispatched to the thread pool: if the key
1014    /// is wrong or the body is corrupt the request is rejected with `403 Forbidden` and
1015    /// the thread pool is not touched at all.  The `T` handed to the closure is always a
1016    /// trusted, fully-decrypted value.  The closure may block but must complete quickly.
1017    ///
1018    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
1019    ///
1020    /// # Safety
1021    /// See [`ServerMechanism::onconnect_sync`] for the thread-pool safety notes.
1022    pub unsafe fn onconnect_sync<F, Re>(self, handler: F) -> SocketType
1023    where
1024        F: Fn(T) -> Result<Re, Rejection> + Clone + Send + Sync + 'static,
1025        Re: Reply + Send + 'static,
1026    {
1027        log::warn!("Registering sync handler on {:?} '{}' (encrypted body) — ensure rate-limiting is applied externally", self.base.method, self.base.path);
1028        let m = self.base.method.filter();
1029        let p = path_filter(&self.base.path);
1030        let key = self.key;
1031        m.and(p)
1032            .and(warp::body::bytes())
1033            .and_then(move |raw: bytes::Bytes| {
1034                let key = key.clone();
1035                let handler = handler.clone();
1036                async move {
1037                    let value: T = decode_body(&raw, &key)?;
1038                    tokio::task::spawn_blocking(move || handler(value))
1039                        .await
1040                        .unwrap_or_else(|_| {
1041                            log::warn!("Sync encrypted handler panicked; converting to Rejection");
1042                            Err(warp::reject())
1043                        })
1044                        .map(|r| r.into_response())
1045                }
1046            })
1047            .boxed()
1048    }
1049
1050    /// Attaches shared state `S`, transitioning to [`StatefulEncryptedBodyBuilder`].
1051    ///
1052    /// A fresh clone of `S` is injected alongside the decrypted `T` on every request.
1053    /// The handler will receive `(state: S, body: T)`.
1054    ///
1055    /// `S` must be `Clone + Send + Sync + 'static`.
1056    pub fn state<S: Clone + Send + Sync + 'static>(self, state: S) -> StatefulEncryptedBodyBuilder<T, S> {
1057        StatefulEncryptedBodyBuilder { base: self.base, key: self.key, state, _phantom: std::marker::PhantomData }
1058    }
1059}
1060
1061// ─── EncryptedQueryBuilder ────────────────────────────────────────────────────
1062
1063/// Route builder that expects VEIL-encrypted URL query parameters of type `T`.
1064///
1065/// Obtained from [`ServerMechanism::encrypted_query`].  The client must send a single
1066/// `?data=<base64url>` query parameter whose value is the URL-safe base64 encoding of
1067/// the VEIL-encrypted struct bytes.  On each matching request the server base64-decodes
1068/// then decrypts the value using the configured [`SerializationKey`].  If any step fails
1069/// (missing `data` parameter, invalid base64, wrong key, corrupt bytes) the route
1070/// returns `403 Forbidden` — the handler is never invoked.
1071///
1072/// Optionally attach shared state via [`state`](EncryptedQueryBuilder::state) before
1073/// finalising with [`onconnect`](EncryptedQueryBuilder::onconnect).
1074pub struct EncryptedQueryBuilder<T> {
1075    base: ServerMechanism,
1076    key: SerializationKey,
1077    _phantom: std::marker::PhantomData<T>,
1078}
1079
1080impl<T> EncryptedQueryBuilder<T>
1081where
1082    T: bincode::Decode<()> + Send + 'static,
1083{
1084    /// Finalises this route with an async handler that receives the decrypted query
1085    /// parameters as `T`.
1086    ///
1087    /// On each request the `?data=<base64url>` query parameter is base64-decoded then
1088    /// VEIL-decrypted using the [`SerializationKey`] on this builder.  The handler is only
1089    /// invoked when every step succeeds — a missing `data` parameter, invalid base64,
1090    /// wrong key, or corrupt payload causes the route to return `403 Forbidden` without
1091    /// ever reaching the handler.  The `T` the closure receives is therefore always a
1092    /// trusted, fully-decrypted value ready to use.
1093    ///
1094    /// The handler must return `Result<impl Reply, Rejection>`.
1095    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
1096    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
1097    where
1098        F: Fn(T) -> Fut + Clone + Send + Sync + 'static,
1099        Fut: Future<Output = Result<Re, Rejection>> + Send,
1100        Re: Reply + Send,
1101    {
1102        log::debug!("Finalising async {:?} route at '{}' (encrypted query)", self.base.method, self.base.path);
1103        let m = self.base.method.filter();
1104        let p = path_filter(&self.base.path);
1105        let key = self.key;
1106        m.and(p)
1107            .and(warp::filters::query::raw())
1108            .and_then(move |raw_query: String| {
1109                let key = key.clone();
1110                let handler = handler.clone();
1111                async move {
1112                    let value: T = decode_query(&raw_query, &key)?;
1113                    handler(value).await.map(|r| r.into_response())
1114                }
1115            })
1116            .boxed()
1117    }
1118
1119    /// Attaches shared state `S`, transitioning to [`StatefulEncryptedQueryBuilder`].
1120    ///
1121    /// A fresh clone of `S` is injected alongside the decrypted `T` on every request.
1122    /// The handler will receive `(state: S, query: T)`.
1123    ///
1124    /// `S` must be `Clone + Send + Sync + 'static`.
1125    pub fn state<S: Clone + Send + Sync + 'static>(self, state: S) -> StatefulEncryptedQueryBuilder<T, S> {
1126        StatefulEncryptedQueryBuilder { base: self.base, key: self.key, state, _phantom: std::marker::PhantomData }
1127    }
1128}
1129
1130// ─── StatefulEncryptedBodyBuilder ────────────────────────────────────────────
1131
1132/// Route builder carrying shared state `S` and a VEIL-encrypted request body of type `T`.
1133///
1134/// Obtained from [`EncryptedBodyBuilder::state`] or [`StatefulSocketBuilder::encryption`].
1135/// On each matching request the body is VEIL-decrypted and a fresh clone of `S` is
1136/// prepared before the handler is called.  A wrong key or corrupt body returns
1137/// `403 Forbidden` without ever invoking the handler.
1138///
1139/// Finalise with [`onconnect`](StatefulEncryptedBodyBuilder::onconnect).
1140pub struct StatefulEncryptedBodyBuilder<T, S> {
1141    base: ServerMechanism,
1142    key: SerializationKey,
1143    state: S,
1144    _phantom: std::marker::PhantomData<T>,
1145}
1146
1147impl<T, S> StatefulEncryptedBodyBuilder<T, S>
1148where
1149    T: bincode::Decode<()> + Send + 'static,
1150    S: Clone + Send + Sync + 'static,
1151{
1152    /// Finalises this route with an async handler that receives `(state: S, body: T)`.
1153    ///
1154    /// On each request the raw body bytes are VEIL-decrypted using the configured
1155    /// [`SerializationKey`] and a fresh clone of `S` is prepared — both are handed to the
1156    /// handler together.  If decryption fails (wrong key or corrupt body) the route returns
1157    /// `403 Forbidden` and the handler is never invoked.  The `T` the closure receives is
1158    /// always a trusted, fully-decrypted value ready to use.
1159    ///
1160    /// The handler must return `Result<impl Reply, Rejection>`.
1161    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
1162    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
1163    where
1164        F: Fn(S, T) -> Fut + Clone + Send + Sync + 'static,
1165        Fut: Future<Output = Result<Re, Rejection>> + Send,
1166        Re: Reply + Send,
1167    {
1168        log::debug!("Finalising async {:?} route at '{}' (state + encrypted body)", self.base.method, self.base.path);
1169        let m = self.base.method.filter();
1170        let p = path_filter(&self.base.path);
1171        let key = self.key;
1172        let state = self.state;
1173        let state_filter = warp::any().map(move || state.clone());
1174        m.and(p)
1175            .and(state_filter)
1176            .and(warp::body::bytes())
1177            .and_then(move |s: S, raw: bytes::Bytes| {
1178                let key = key.clone();
1179                let handler = handler.clone();
1180                async move {
1181                    let value: T = decode_body(&raw, &key)?;
1182                    handler(s, value).await.map(|r| r.into_response())
1183                }
1184            })
1185            .boxed()
1186    }
1187}
1188
1189// ─── StatefulEncryptedQueryBuilder ───────────────────────────────────────────
1190
1191/// Route builder carrying shared state `S` and VEIL-encrypted query parameters of
1192/// type `T`.
1193///
1194/// Obtained from [`EncryptedQueryBuilder::state`] or
1195/// [`StatefulSocketBuilder::encrypted_query`].  On each matching request the
1196/// `?data=<base64url>` parameter is base64-decoded then VEIL-decrypted and a fresh
1197/// clone of `S` is prepared before the handler is called.  Any decode or decryption
1198/// failure returns `403 Forbidden` without ever invoking the handler.
1199///
1200/// Finalise with [`onconnect`](StatefulEncryptedQueryBuilder::onconnect).
1201pub struct StatefulEncryptedQueryBuilder<T, S> {
1202    base: ServerMechanism,
1203    key: SerializationKey,
1204    state: S,
1205    _phantom: std::marker::PhantomData<T>,
1206}
1207
1208impl<T, S> StatefulEncryptedQueryBuilder<T, S>
1209where
1210    T: bincode::Decode<()> + Send + 'static,
1211    S: Clone + Send + Sync + 'static,
1212{
1213    /// Finalises this route with an async handler that receives `(state: S, query: T)`.
1214    ///
1215    /// On each request the `?data=<base64url>` query parameter is base64-decoded then
1216    /// VEIL-decrypted using the configured [`SerializationKey`] and a fresh clone of `S`
1217    /// is prepared — both are handed to the handler together.  If any step fails (missing
1218    /// parameter, bad encoding, wrong key, or corrupt data) the route returns
1219    /// `403 Forbidden` and the handler is never invoked.  The `T` the closure receives is
1220    /// always a trusted, fully-decrypted value ready to use.
1221    ///
1222    /// The handler must return `Result<impl Reply, Rejection>`.
1223    /// Returns a [`SocketType`] ready to be passed to [`Server::mechanism`].
1224    pub fn onconnect<F, Fut, Re>(self, handler: F) -> SocketType
1225    where
1226        F: Fn(S, T) -> Fut + Clone + Send + Sync + 'static,
1227        Fut: Future<Output = Result<Re, Rejection>> + Send,
1228        Re: Reply + Send,
1229    {
1230        log::debug!("Finalising async {:?} route at '{}' (state + encrypted query)", self.base.method, self.base.path);
1231        let m = self.base.method.filter();
1232        let p = path_filter(&self.base.path);
1233        let key = self.key;
1234        let state = self.state;
1235        let state_filter = warp::any().map(move || state.clone());
1236        m.and(p)
1237            .and(state_filter)
1238            .and(warp::filters::query::raw())
1239            .and_then(move |s: S, raw_query: String| {
1240                let key = key.clone();
1241                let handler = handler.clone();
1242                async move {
1243                    let value: T = decode_query(&raw_query, &key)?;
1244                    handler(s, value).await.map(|r| r.into_response())
1245                }
1246            })
1247            .boxed()
1248    }
1249}
1250
1251// ─── Internal decode helpers ─────────────────────────────────────────────────
1252
1253/// Decode a VEIL-sealed request body into `T`, returning `403 Forbidden` on any failure.
1254fn decode_body<T: bincode::Decode<()>>(raw: &bytes::Bytes, key: &SerializationKey) -> Result<T, Rejection> {
1255    crate::serialization::open(raw, key.veil_key()).map_err(|e| {
1256        log::debug!("VEIL open failed (key mismatch or corrupt body): {e}");
1257        forbidden()
1258    })
1259}
1260
1261/// Decode a VEIL-sealed query parameter (`?data=<base64url>`) into `T`,
1262/// returning `403 Forbidden` on any failure.
1263fn decode_query<T: bincode::Decode<()>>(raw_query: &str, key: &SerializationKey) -> Result<T, Rejection> {
1264    // Extract the `data` parameter value from the raw query string.
1265    let data_value = serde_urlencoded::from_str::<std::collections::HashMap<String, String>>(raw_query)
1266        .ok()
1267        .and_then(|mut m| m.remove("data"));
1268
1269    let b64 = data_value.ok_or_else(|| {
1270        log::debug!("Encrypted query missing `data` parameter");
1271        forbidden()
1272    })?;
1273
1274    let bytes = base64::engine::Engine::decode(
1275        &base64::engine::general_purpose::URL_SAFE_NO_PAD,
1276        b64.trim_end_matches('='),
1277    )
1278    .map_err(|e| {
1279        log::debug!("base64url decode failed: {e}");
1280        forbidden()
1281    })?;
1282
1283    crate::serialization::open(&bytes, key.veil_key()).map_err(|e| {
1284        log::debug!("VEIL open (query) failed: {e}");
1285        forbidden()
1286    })
1287}
1288
1289/// Builds a `403 Forbidden` rejection response.
1290fn forbidden() -> Rejection {
1291    // Warp doesn't expose a built-in 403 rejection, so we use a custom one.
1292    warp::reject::custom(ForbiddenError)
1293}
1294
1295#[derive(Debug)]
1296struct ForbiddenError;
1297
1298impl warp::reject::Reject for ForbiddenError {}
1299
1300/// A collection of common HTTP status codes used with the reply helpers.
1301///
1302/// Covers 2xx success, 3xx redirect, 4xx client error, and 5xx server error ranges.
1303/// Converts into `warp::http::StatusCode` via [`From`] and is accepted by
1304/// [`reply_with_status`] and [`reply_with_status_and_json`]. Also usable directly in the
1305/// [`reply!`] macro via the `status => Status::X` argument.
1306#[derive(Clone, Copy, Debug)]
1307pub enum Status {
1308    // 2xx
1309    Ok,
1310    Created,
1311    Accepted,
1312    NoContent,
1313    // 3xx
1314    MovedPermanently,
1315    Found,
1316    NotModified,
1317    TemporaryRedirect,
1318    PermanentRedirect,
1319    // 4xx
1320    BadRequest,
1321    Unauthorized,
1322    Forbidden,
1323    NotFound,
1324    MethodNotAllowed,
1325    Conflict,
1326    Gone,
1327    UnprocessableEntity,
1328    TooManyRequests,
1329    // 5xx
1330    InternalServerError,
1331    NotImplemented,
1332    BadGateway,
1333    ServiceUnavailable,
1334    GatewayTimeout,
1335}
1336
1337impl From<Status> for warp::http::StatusCode {
1338    fn from(s: Status) -> Self {
1339        match s {
1340            Status::Ok                  => warp::http::StatusCode::OK,
1341            Status::Created             => warp::http::StatusCode::CREATED,
1342            Status::Accepted            => warp::http::StatusCode::ACCEPTED,
1343            Status::NoContent           => warp::http::StatusCode::NO_CONTENT,
1344            Status::MovedPermanently    => warp::http::StatusCode::MOVED_PERMANENTLY,
1345            Status::Found               => warp::http::StatusCode::FOUND,
1346            Status::NotModified         => warp::http::StatusCode::NOT_MODIFIED,
1347            Status::TemporaryRedirect   => warp::http::StatusCode::TEMPORARY_REDIRECT,
1348            Status::PermanentRedirect   => warp::http::StatusCode::PERMANENT_REDIRECT,
1349            Status::BadRequest          => warp::http::StatusCode::BAD_REQUEST,
1350            Status::Unauthorized        => warp::http::StatusCode::UNAUTHORIZED,
1351            Status::Forbidden           => warp::http::StatusCode::FORBIDDEN,
1352            Status::NotFound            => warp::http::StatusCode::NOT_FOUND,
1353            Status::MethodNotAllowed    => warp::http::StatusCode::METHOD_NOT_ALLOWED,
1354            Status::Conflict            => warp::http::StatusCode::CONFLICT,
1355            Status::Gone                => warp::http::StatusCode::GONE,
1356            Status::UnprocessableEntity => warp::http::StatusCode::UNPROCESSABLE_ENTITY,
1357            Status::TooManyRequests     => warp::http::StatusCode::TOO_MANY_REQUESTS,
1358            Status::InternalServerError => warp::http::StatusCode::INTERNAL_SERVER_ERROR,
1359            Status::NotImplemented      => warp::http::StatusCode::NOT_IMPLEMENTED,
1360            Status::BadGateway          => warp::http::StatusCode::BAD_GATEWAY,
1361            Status::ServiceUnavailable  => warp::http::StatusCode::SERVICE_UNAVAILABLE,
1362            Status::GatewayTimeout      => warp::http::StatusCode::GATEWAY_TIMEOUT,
1363        }
1364    }
1365}
1366
1367/// The HTTP server that owns and dispatches a collection of [`SocketType`] routes.
1368///
1369/// Build routes through the [`ServerMechanism`] builder chain, register each with
1370/// [`mechanism`](Server::mechanism), then start the server with [`serve`](Server::serve).
1371///
1372/// # Example
1373/// ```rust,no_run
1374/// # use serde::Serialize;
1375/// # #[derive(Serialize)] struct Pong { ok: bool }
1376///
1377/// let mut server = Server::default();
1378///
1379/// server
1380///     .mechanism(
1381///         ServerMechanism::get("/ping")
1382///             .onconnect(|| async { reply!(json => Pong { ok: true }) })
1383///     )
1384///     .mechanism(
1385///         ServerMechanism::delete("/session")
1386///             .onconnect(|| async { reply!(message => warp::reply(), status => Status::NoContent) })
1387///     );
1388///
1389/// // Blocks forever — call only to actually run the server:
1390/// // server.serve(([0, 0, 0, 0], 8080)).await;
1391/// ```
1392///
1393/// # Caution
1394/// Calling [`serve`](Server::serve) with no routes registered will **panic**.
1395pub struct Server {
1396    mechanisms: Vec<SocketType>,
1397}
1398
1399impl Default for Server {
1400    fn default() -> Self {
1401        Self::new()
1402    }
1403}
1404
1405impl Server {
1406    fn new() -> Self {
1407        Self { mechanisms: Vec::new() }
1408    }
1409
1410    /// Registers a [`SocketType`] route on this server.
1411    ///
1412    /// Routes are evaluated in registration order. Returns `&mut Self` for method chaining.
1413    pub fn mechanism(&mut self, mech: SocketType) -> &mut Self {
1414        self.mechanisms.push(mech);
1415        log::debug!("Route registered (total: {})", self.mechanisms.len());
1416        self
1417    }
1418
1419    /// Binds to `addr` and starts serving all registered routes.
1420    ///
1421    /// This is an infinite async loop — it never returns under normal operation.
1422    ///
1423    /// # Panics
1424    /// Panics immediately if no routes have been registered via [`mechanism`](Server::mechanism).
1425    pub async fn serve(self, addr: impl Into<SocketAddr>) {
1426        let addr = addr.into();
1427        log::info!("Server binding to {}", addr);
1428        let mut iter = self.mechanisms.into_iter();
1429        let first = iter.next().unwrap_or_else(|| {
1430            log::trace!("No mechanisms are defined on the server, this will result in a panic!");
1431            log::error!("The server contains no mechanisms to follow through");
1432            panic!();
1433        });
1434
1435        let combined = iter.fold(first.boxed(), |acc, next| {
1436            acc.or(next).unify().boxed()
1437        });
1438
1439        log::info!("Server running on {}", addr);
1440        warp::serve(combined).run(addr).await;
1441    }
1442
1443    /// Binds to `addr`, serves all registered routes, and shuts down gracefully when
1444    /// `shutdown` resolves.
1445    ///
1446    /// Equivalent to calling [`serve_from_listener`](Server::serve_from_listener) with an
1447    /// address instead of a pre-bound listener.
1448    ///
1449    /// # Example
1450    ///
1451    /// ```rust,no_run
1452    /// use tokio::sync::oneshot;
1453    ///
1454    /// # async fn run() {
1455    /// let (tx, rx) = oneshot::channel::<()>();
1456    /// // ... build server and mount routes ...
1457    /// // trigger shutdown later by calling tx.send(())
1458    /// server.serve_with_graceful_shutdown(([127, 0, 0, 1], 8080), async move {
1459    ///     rx.await.ok();
1460    /// }).await;
1461    /// # }
1462    /// ```
1463    pub async fn serve_with_graceful_shutdown(
1464        self,
1465        addr: impl Into<std::net::SocketAddr>,
1466        shutdown: impl std::future::Future<Output = ()> + Send + 'static,
1467    ) {
1468        let addr = addr.into();
1469        log::info!("Server binding to {} (graceful shutdown enabled)", addr);
1470        let mut iter = self.mechanisms.into_iter();
1471        let first = iter.next().unwrap_or_else(|| {
1472            log::error!("The server contains no mechanisms to follow through");
1473            panic!("serve_with_graceful_shutdown called with no routes registered");
1474        });
1475        let combined = iter.fold(first.boxed(), |acc, next| acc.or(next).unify().boxed());
1476        log::info!("Server running on {} (graceful shutdown enabled)", addr);
1477        warp::serve(combined)
1478            .bind(addr)
1479            .await
1480            .graceful(shutdown)
1481            .run()
1482            .await;
1483    }
1484
1485    /// Serves all registered routes from an already-bound `listener`, shutting down gracefully
1486    /// when `shutdown` resolves.
1487    ///
1488    /// Use this when port `0` is passed to `TcpListener::bind` and you need to know the actual
1489    /// OS-assigned port before the server starts (e.g. to open a browser to the correct URL).
1490    ///
1491    /// # Example
1492    ///
1493    /// ```rust,no_run
1494    /// use tokio::net::TcpListener;
1495    /// use tokio::sync::oneshot;
1496    ///
1497    /// # async fn run() -> std::io::Result<()> {
1498    /// let listener = TcpListener::bind("127.0.0.1:0").await?;
1499    /// let port = listener.local_addr()?.port();
1500    /// println!("Will listen on port {port}");
1501    ///
1502    /// let (tx, rx) = oneshot::channel::<()>();
1503    /// // ... build server and mount routes ...
1504    /// server.serve_from_listener(listener, async move { rx.await.ok(); }).await;
1505    /// # Ok(())
1506    /// # }
1507    /// ```
1508    pub async fn serve_from_listener(
1509        self,
1510        listener: tokio::net::TcpListener,
1511        shutdown: impl std::future::Future<Output = ()> + Send + 'static,
1512    ) {
1513        log::info!("Server running on {} (graceful shutdown enabled)",
1514            listener.local_addr().map(|a| a.to_string()).unwrap_or_else(|_| "?".into()));
1515        let mut iter = self.mechanisms.into_iter();
1516        let first = iter.next().unwrap_or_else(|| {
1517            log::error!("The server contains no mechanisms to follow through");
1518            panic!("serve_from_listener called with no routes registered");
1519        });
1520        let combined = iter.fold(first.boxed(), |acc, next| acc.or(next).unify().boxed());
1521        warp::serve(combined)
1522            .incoming(listener)
1523            .graceful(shutdown)
1524            .run()
1525            .await;
1526    }
1527}
1528
1529/// Wraps `reply` with the given HTTP `status` code and returns it as a warp result.
1530///
1531/// Use when an endpoint needs to respond with a specific status alongside a plain message.
1532/// Pairs with the [`reply!`] macro form `reply!(message => ..., status => ...)`.
1533pub fn reply_with_status(status: Status, reply: impl Reply) -> Result<impl Reply, Rejection> {
1534    Ok::<_, Rejection>(warp::reply::with_status(reply, status.into()))
1535}
1536
1537/// Returns an empty `200 OK` warp reply result.
1538///
1539/// Useful for endpoints that need only to acknowledge a request with no body.
1540/// Equivalent to `reply!()`.
1541pub fn reply() -> Result<impl Reply, Rejection> {
1542    Ok::<_, Rejection>(warp::reply())
1543}
1544
1545/// Serialises `json` as a JSON body and returns it as a `200 OK` warp reply result.
1546///
1547/// `T` must implement `serde::Serialize`. Equivalent to `reply!(json => ...)`.
1548pub fn reply_with_json<T: Serialize>(json: &T) -> Result<impl Reply + use<T>, Rejection> {
1549    Ok::<_, Rejection>(warp::reply::json(json))
1550}
1551
1552/// Serialises `json` as a JSON body, attaches the given HTTP `status`, and returns a warp result.
1553///
1554/// `T` must implement `serde::Serialize`. Equivalent to `reply!(json => ..., status => ...)`.
1555pub fn reply_with_status_and_json<T: Serialize>(status: Status, json: &T) -> Result<impl Reply + use<T>, Rejection> {
1556    Ok::<_, Rejection>(warp::reply::with_status(warp::reply::json(json), status.into()))
1557}
1558
1559/// Seals `value` with `key` and returns it as an `application/octet-stream` response (`200 OK`).
1560///
1561/// `T` must implement `bincode::Encode`.
1562/// Equivalent to `reply!(sealed => value, key => key)`.
1563pub fn reply_sealed<T: bincode::Encode>(
1564    value: &T,
1565    key: SerializationKey,
1566) -> Result<warp::reply::Response, Rejection> {
1567    sealed_response(value, key, None)
1568}
1569
1570/// Seals `value` with `key`, attaches the given HTTP `status`, and returns it as a warp result.
1571///
1572/// Equivalent to `reply!(sealed => value, key => key, status => Status::X)`.
1573pub fn reply_sealed_with_status<T: bincode::Encode>(
1574    value: &T,
1575    key: SerializationKey,
1576    status: Status,
1577) -> Result<warp::reply::Response, Rejection> {
1578    sealed_response(value, key, Some(status))
1579}
1580
1581fn sealed_response<T: bincode::Encode>(
1582    value: &T,
1583    key: SerializationKey,
1584    status: Option<Status>,
1585) -> Result<warp::reply::Response, Rejection> {
1586    use warp::http::StatusCode;
1587    use warp::Reply;
1588    let code: StatusCode = status.map(Into::into).unwrap_or(StatusCode::OK);
1589    let sealed = crate::serialization::seal(value, key.veil_key()).map_err(|_| warp::reject())?;
1590    Ok(warp::reply::with_status(sealed, code).into_response())
1591}
1592
1593/// Convenience macro for constructing warp reply results inside route handlers.
1594///
1595/// | Syntax | Equivalent | Description |
1596/// |---|---|---|
1597/// | `reply!()` | [`reply()`] | Empty `200 OK` response. |
1598/// | `reply!(message => expr, status => Status::X)` | [`reply_with_status`] | Plain reply with a status code. |
1599/// | `reply!(json => expr)` | [`reply_with_json`] | JSON body with `200 OK`. |
1600/// | `reply!(json => expr, status => Status::X)` | [`reply_with_status_and_json`] | JSON body with a status code. |
1601/// | `reply!(sealed => expr, key => key)` | [`reply_sealed`] | VEIL-sealed (or JSON for `Plain`) body, `200 OK`. |
1602/// | `reply!(sealed => expr, key => key, status => Status::X)` | [`reply_sealed_with_status`] | VEIL-sealed (or JSON for `Plain`) body with status. |
1603///
1604/// # Example
1605/// ```rust,no_run
1606/// # use serde::{Deserialize, Serialize};
1607/// # #[derive(Deserialize, Serialize)] struct Item { id: u32, name: String }
1608///
1609/// // Empty 200 OK
1610/// let ping = ServerMechanism::get("/ping")
1611///     .onconnect(|| async { reply!() });
1612///
1613/// // Plain reply with a custom status
1614/// let gone = ServerMechanism::delete("/v1")
1615///     .onconnect(|| async {
1616///         reply!(message => warp::reply::html("endpoint deprecated"), status => Status::Gone)
1617///     });
1618///
1619/// // JSON body, 200 OK
1620/// let list = ServerMechanism::get("/items")
1621///     .onconnect(|| async {
1622///         let items: Vec<Item> = vec![];
1623///         reply!(json => items)
1624///     });
1625///
1626/// // JSON body with a custom status
1627/// let create = ServerMechanism::post("/items")
1628///     .json::<Item>()
1629///     .onconnect(|item| async move {
1630///         reply!(json => item, status => Status::Created)
1631///     });
1632/// ```
1633#[macro_export]
1634macro_rules! reply {
1635    () => {{
1636        $crate::socket::server::reply()
1637    }};
1638
1639    (message => $message: expr, status => $status: expr) => {{
1640        $crate::socket::server::reply_with_status($status, $message)
1641    }};
1642
1643    (json => $json: expr) => {{
1644        $crate::socket::server::reply_with_json(&$json)
1645    }};
1646
1647    (json => $json: expr, status => $status: expr) => {{
1648        $crate::socket::server::reply_with_status_and_json($status, &$json)
1649    }};
1650
1651    (sealed => $val: expr, key => $key: expr) => {{
1652        $crate::socket::server::reply_sealed(&$val, $key)
1653    }};
1654
1655    (sealed => $val: expr, key => $key: expr, status => $status: expr) => {{
1656        $crate::socket::server::reply_sealed_with_status(&$val, $key, $status)
1657    }};
1658}
1659