Skip to main content

typeway_server/
mount.rs

1//! Builder-style API composition with `.mount()`.
2//!
3//! For APIs larger than 25 endpoints, or when you want to compose
4//! sub-APIs incrementally, the mount builder provides a flat,
5//! readable alternative to nested tuples.
6//!
7//! # Example
8//!
9//! ```ignore
10//! type UsersAPI = (GetEndpoint<UsersPath, Vec<User>>, PostEndpoint<UsersPath, CreateUser, User>);
11//! type OrdersAPI = (GetEndpoint<OrdersPath, Vec<Order>>,);
12//! type FullAPI = (UsersAPI, OrdersAPI);
13//!
14//! ServerBuilder::<FullAPI>::new()
15//!     .mount::<UsersAPI>((bind!(list_users), bind!(create_user)))
16//!     .mount::<OrdersAPI>((bind!(list_orders),))
17//!     .build()
18//!     .serve("0.0.0.0:3000".parse()?)
19//!     .await?;
20//! ```
21//!
22//! Each `.mount()` call registers a sub-API's handlers into the router.
23//! `.build()` requires all sub-APIs in the full API type to have been
24//! mounted — missing a mount is a compile error.
25
26use std::convert::Infallible;
27use std::marker::PhantomData;
28use std::sync::Arc;
29
30use typeway_core::effects::{AllProvided, ECons, ENil, Effect};
31use typeway_core::ApiSpec;
32
33use crate::body::BoxBody;
34use crate::router::{Router, RouterService};
35use crate::serves::Serves;
36
37// ---------------------------------------------------------------------------
38// Type-level mount tracking
39// ---------------------------------------------------------------------------
40
41/// Type-level empty list: no sub-APIs mounted yet.
42pub struct MNil;
43
44/// Type-level cons: sub-API `A` has been mounted, followed by `Tail`.
45pub struct MCons<A, Tail>(PhantomData<(A, Tail)>);
46
47/// Type-level proof that a sub-API has been mounted.
48///
49/// Uses the same index witness technique as the effects system.
50pub struct MHere;
51
52/// Type-level index: the sub-API is somewhere later in the list.
53pub struct MThere<T>(PhantomData<T>);
54
55/// Asserts that sub-API `A` is in the mounted list `M`.
56pub trait HasMount<A, Idx> {}
57
58impl<A, Tail> HasMount<A, MHere> for MCons<A, Tail> {}
59
60impl<A, Head, Tail, Idx> HasMount<A, MThere<Idx>> for MCons<Head, Tail> where Tail: HasMount<A, Idx> {}
61
62/// Asserts that ALL sub-APIs in `FullAPI` have been mounted.
63///
64/// Works like `AllProvided` for effects — recursively checks each
65/// element of the API tuple against the mounted list.
66#[diagnostic::on_unimplemented(
67    message = "not all sub-APIs have been mounted for `{Self}`",
68    label = "some sub-APIs are missing — add more .mount() calls",
69    note = "each sub-API in the API type must have a corresponding .mount() call"
70)]
71pub trait AllMounted<M, Idx> {}
72
73// Unit tuple — nothing to mount.
74impl<M> AllMounted<M, ()> for () {}
75
76// Tuples: each element must be present in the mounted list.
77macro_rules! impl_all_mounted_for_tuple {
78    ($($T:ident, $I:ident);+) => {
79        impl<Mounted, $($T: ApiSpec, $I,)+> AllMounted<Mounted, ($($I,)+)> for ($($T,)+)
80        where $(Mounted: HasMount<$T, $I>,)+ {}
81    };
82}
83
84impl_all_mounted_for_tuple!(A, IA);
85impl_all_mounted_for_tuple!(A, IA; B, IB);
86impl_all_mounted_for_tuple!(A, IA; B, IB; C, IC);
87impl_all_mounted_for_tuple!(A, IA; B, IB; C, IC; D, ID);
88impl_all_mounted_for_tuple!(A, IA; B, IB; C, IC; D, ID; E, IE);
89impl_all_mounted_for_tuple!(A, IA; B, IB; C, IC; D, ID; E, IE; F, IF);
90impl_all_mounted_for_tuple!(A, IA; B, IB; C, IC; D, ID; E, IE; F, IF; G, IG);
91impl_all_mounted_for_tuple!(A, IA; B, IB; C, IC; D, ID; E, IE; F, IF; G, IG; H, IH);
92impl_all_mounted_for_tuple!(A, IA; B, IB; C, IC; D, ID; E, IE; F, IF; G, IG; H, IH; I, II);
93impl_all_mounted_for_tuple!(A, IA; B, IB; C, IC; D, ID; E, IE; F, IF; G, IG; H, IH; I, II; J, IJ);
94
95// ---------------------------------------------------------------------------
96// ServerBuilder
97// ---------------------------------------------------------------------------
98
99/// A builder for composing large APIs from sub-APIs.
100///
101/// Each `.mount::<SubAPI>(handlers)` call registers a sub-API's handlers
102/// and records the mount at the type level. `.build()` only compiles
103/// when all sub-APIs in the full API type have been mounted.
104///
105/// # Example
106///
107/// ```ignore
108/// ServerBuilder::<FullAPI>::new()
109///     .mount::<UsersAPI>(users_handlers)
110///     .mount::<OrdersAPI>(orders_handlers)
111///     .build()
112///     .serve(addr)
113///     .await?;
114/// ```
115/// A builder for composing large APIs from sub-APIs with compile-time
116/// tracking of both mounted sub-APIs and provided middleware effects.
117///
118/// - `A`: the full API type
119/// - `Mounted`: type-level list of mounted sub-APIs (starts as `MNil`)
120/// - `Provided`: type-level list of provided effects (starts as `ENil`)
121pub struct ServerBuilder<A: ApiSpec, Mounted = MNil, Provided = ENil> {
122    router: Router,
123    _api: PhantomData<A>,
124    _mounted: PhantomData<Mounted>,
125    _provided: PhantomData<Provided>,
126}
127
128impl<A: ApiSpec> ServerBuilder<A, MNil, ENil> {
129    /// Create a new builder for the given API type.
130    pub fn new() -> Self {
131        ServerBuilder {
132            router: Router::new(),
133            _api: PhantomData,
134            _mounted: PhantomData,
135            _provided: PhantomData,
136        }
137    }
138}
139
140impl<A: ApiSpec, M, P> ServerBuilder<A, M, P> {
141    /// Mount a sub-API with its handler tuple.
142    ///
143    /// The type system tracks which sub-APIs have been mounted.
144    pub fn mount<Sub: ApiSpec, H: Serves<Sub>>(
145        mut self,
146        handlers: H,
147    ) -> ServerBuilder<A, MCons<Sub, M>, P> {
148        handlers.register(&mut self.router);
149        ServerBuilder {
150            router: self.router,
151            _api: PhantomData,
152            _mounted: PhantomData,
153            _provided: PhantomData,
154        }
155    }
156
157    /// Declare that a middleware effect has been provided.
158    ///
159    /// Pair with `.layer()` to apply the actual middleware. The type
160    /// system tracks which effects have been provided across all sub-APIs.
161    pub fn provide<E: Effect>(self) -> ServerBuilder<A, M, ECons<E, P>> {
162        ServerBuilder {
163            router: self.router,
164            _api: PhantomData,
165            _mounted: PhantomData,
166            _provided: PhantomData,
167        }
168    }
169
170    /// Apply a Tower middleware layer to the server.
171    ///
172    /// Typically paired with `.provide::<E>()` to discharge an effect
173    /// requirement. The layer wraps the entire router — applied once,
174    /// covering all mounted sub-APIs.
175    pub fn layer<L>(self, layer: L) -> LayeredServerBuilder<A, M, P, L::Service>
176    where
177        L: tower_layer::Layer<RouterService>,
178        L::Service: tower_service::Service<
179                http::Request<hyper::body::Incoming>,
180                Response = http::Response<BoxBody>,
181                Error = Infallible,
182            > + Clone
183            + Send
184            + 'static,
185        <L::Service as tower_service::Service<http::Request<hyper::body::Incoming>>>::Future:
186            Send + 'static,
187    {
188        let router = Arc::new(self.router);
189        let svc = RouterService::new(router.clone());
190        let layered = layer.layer(svc);
191        LayeredServerBuilder {
192            service: layered,
193            router,
194            _api: PhantomData,
195            _mounted: PhantomData,
196            _provided: PhantomData,
197        }
198    }
199
200    /// Set shared state accessible via `State<T>` extractors.
201    pub fn with_state<T: Clone + Send + Sync + 'static>(self, state: T) -> Self {
202        self.router.set_state_injector(Arc::new(move |ext| {
203            ext.insert(state.clone());
204        }));
205        self
206    }
207
208    /// Set the maximum request body size in bytes.
209    pub fn max_body_size(self, max: usize) -> Self {
210        self.router.set_max_body_size(max);
211        self
212    }
213
214    /// Finalize the server.
215    ///
216    /// Only compiles when:
217    /// - All sub-APIs in the full API type have been mounted
218    /// - All `Requires<E, _>` effects have been provided
219    pub fn build<MIdx, PIdx>(self) -> crate::server::Server<A>
220    where
221        A: AllMounted<M, MIdx>,
222        A: AllProvided<P, PIdx>,
223    {
224        crate::server::Server::from_router(Arc::new(self.router))
225    }
226}
227
228/// A [`ServerBuilder`] with Tower middleware layers applied.
229///
230/// Created by [`ServerBuilder::layer`]. Supports further `.provide()`,
231/// `.layer()`, and `.build()` calls.
232pub struct LayeredServerBuilder<A: ApiSpec, Mounted, Provided, S> {
233    service: S,
234    router: Arc<Router>,
235    _api: PhantomData<A>,
236    _mounted: PhantomData<Mounted>,
237    _provided: PhantomData<Provided>,
238}
239
240impl<A: ApiSpec, M, P, S> LayeredServerBuilder<A, M, P, S> {
241    /// Declare that a middleware effect has been provided.
242    pub fn provide<E: Effect>(self) -> LayeredServerBuilder<A, M, ECons<E, P>, S> {
243        LayeredServerBuilder {
244            service: self.service,
245            router: self.router,
246            _api: PhantomData,
247            _mounted: PhantomData,
248            _provided: PhantomData,
249        }
250    }
251}
252
253impl<A: ApiSpec, M, P, S> LayeredServerBuilder<A, M, P, S>
254where
255    S: tower_service::Service<
256            http::Request<hyper::body::Incoming>,
257            Response = http::Response<BoxBody>,
258            Error = Infallible,
259        > + Clone
260        + Send
261        + 'static,
262    S::Future: Send + 'static,
263{
264    /// Finalize the layered server.
265    ///
266    /// Only compiles when all sub-APIs are mounted and all effects provided.
267    pub fn build<MIdx, PIdx>(self) -> crate::server::LayeredServer<S>
268    where
269        A: AllMounted<M, MIdx>,
270        A: AllProvided<P, PIdx>,
271    {
272        crate::server::LayeredServer {
273            service: self.service,
274            router: self.router,
275        }
276    }
277
278    /// Apply another Tower middleware layer.
279    pub fn layer<L>(self, layer: L) -> LayeredServerBuilder<A, M, P, L::Service>
280    where
281        L: tower_layer::Layer<S>,
282        L::Service: tower_service::Service<
283                http::Request<hyper::body::Incoming>,
284                Response = http::Response<BoxBody>,
285                Error = Infallible,
286            > + Clone
287            + Send
288            + 'static,
289        <L::Service as tower_service::Service<http::Request<hyper::body::Incoming>>>::Future:
290            Send + 'static,
291    {
292        LayeredServerBuilder {
293            service: layer.layer(self.service),
294            router: self.router,
295            _api: PhantomData,
296            _mounted: PhantomData,
297            _provided: PhantomData,
298        }
299    }
300}
301
302impl<A: ApiSpec> Default for ServerBuilder<A, MNil, ENil> {
303    fn default() -> Self {
304        Self::new()
305    }
306}