ntex_service/lib.rs
1//! Asynchronous services, factories, middleware, and execution pipelines.
2//!
3//! The [`Service`] trait is the crate's central abstraction. A service
4//! asynchronously transforms a request into a response, while
5//! [`ServiceFactory`] constructs services and [`Pipeline`] manages readiness,
6//! calls, and shutdown.
7#![deny(clippy::pedantic)]
8#![allow(
9 clippy::cast_possible_truncation,
10 clippy::missing_fields_in_debug,
11 clippy::missing_errors_doc,
12 clippy::missing_panics_doc,
13 clippy::must_use_candidate,
14 clippy::type_complexity,
15 clippy::unused_async,
16 clippy::unused_async_trait_impl
17)]
18use std::rc::Rc;
19
20mod and_then;
21mod apply;
22mod bind;
23mod binding;
24pub mod boxed;
25pub mod cfg;
26mod chain;
27mod ctx;
28mod fn_ready;
29mod fn_service;
30mod fn_shutdown;
31mod macros;
32mod map;
33mod map_err;
34mod map_init_err;
35mod map_state;
36mod middleware;
37mod readiness;
38mod ready;
39pub mod state;
40mod then;
41mod util;
42
43pub mod pipeline;
44mod pl_factory;
45mod pl_inner;
46mod pl_state;
47
48pub use crate::apply::{apply_fn, apply_fn_factory};
49pub use crate::bind::BindCtx;
50pub use crate::binding::{ReadyBinding, ReadyBindingMiddleware};
51pub use crate::chain::{ServiceChain, ServiceChainFactory, factory, service};
52pub use crate::ctx::Ctx;
53pub use crate::fn_service::{fn_factory, fn_service, fn_service_st};
54pub use crate::map_state::{map_state, map_state_factory};
55pub use crate::middleware::{Identity, Middleware, Stack, apply, fn_layer};
56pub use crate::pipeline::Pipeline;
57pub use crate::readiness::{ReadyCallback, ReadyLink, ReadyNotifier};
58pub use crate::ready::{ready, ready_factory};
59pub use crate::state::{RequestState, State};
60
61#[allow(unused_variables)]
62/// An asynchronous operation from a request to a response.
63///
64/// A service receives requests and asynchronously produces responses.
65/// Conceptually, it is similar to:
66///
67/// ```rust,ignore
68/// async fn(Request) -> Result<Response, Error>
69/// ```
70///
71/// The request and pipeline-state types are generic parameters. The response
72/// and error types are associated types, allowing one service type to implement
73/// `Service` for multiple request types.
74///
75/// Methods take `&self`, so implementations that mutate internal state must use
76/// interior mutability such as `Cell`, `RefCell`, or a synchronization
77/// primitive when appropriate.
78///
79/// The same abstraction can represent client- and server-side operations.
80/// Services focus on transformation, making them straightforward to test and
81/// compose.
82///
83/// A service call requires a [`Ctx`] and therefore runs through a [`Pipeline`]
84/// or from another service. The pipeline coordinates readiness across a
85/// composed service chain before dispatching a request.
86///
87/// ```rust
88/// # use std::convert::Infallible;
89/// #
90/// # use ntex_service::{Service, Ctx};
91///
92/// struct MyService;
93///
94/// impl Service<(), u8> for MyService {
95/// type Res = u64;
96/// type Error = Infallible;
97///
98/// async fn call(&self, req: u8, ctx: Ctx<'_, Self>) -> Result<Self::Res, Self::Error> {
99/// Ok(req as u64)
100/// }
101/// }
102/// ```
103///
104/// Simple services do not need a manual trait implementation. The example
105/// above can be expressed with [`fn_service`]:
106///
107/// ```rust
108/// # use std::convert::Infallible;
109/// # use ntex_service::{Pipeline, fn_service};
110/// #
111/// # async fn run() -> Result<(), Infallible> {
112/// let service = fn_service(|req: u8| async move {
113/// Ok::<_, Infallible>(u64::from(req))
114/// });
115/// let pipeline = Pipeline::new((), service);
116///
117/// assert_eq!(pipeline.call(10).await?, 10);
118/// # Ok(())
119/// # }
120/// ```
121///
122/// # Framework guide
123///
124/// - [Service Model](https://docs.ntex.rs/1-service.html)
125/// - [Service Pipelines](https://docs.ntex.rs/2-pipeline.html)
126/// - [Service State](https://docs.ntex.rs/3-state.html)
127#[diagnostic::on_unimplemented(
128 message = "`{Self}` is not a `Service<{St}, {Req}>`",
129 label = "expected a service for `{Req}` requests with `{St}` state",
130 note = "the state type comes first: `impl Service<St, Req> for T`; use `()` for no state",
131 note = "check that `St` and `Req` match the pipeline or parent service exactly",
132 note = "wrap async functions with `fn_service` (`async |req| ..`) or `fn_service_st` (`async |st, req| ..`)"
133)]
134pub trait Service<St, Req> {
135 /// Response produced by the service.
136 type Res;
137
138 /// Error produced while checking readiness or processing a request.
139 type Error;
140
141 /// Processes a request and asynchronously returns the response.
142 ///
143 /// The enclosing pipeline checks readiness before invoking this method.
144 /// Implementations should not call their own `ready` method. A composed
145 /// service can use [`Ctx::call`] to call an inner service using the closest
146 /// upstream readiness cache. A service wrapped with [`ready()`] can be
147 /// called directly to check its own cache instead.
148 async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<Self::Res, Self::Error>;
149
150 #[inline]
151 /// Initializes the service within a [`Pipeline`].
152 ///
153 /// Called during pipeline construction or [`Ctx::create`], before runtime
154 /// operations on the new service.
155 ///
156 /// The default is suitable for leaves without notification sources.
157 /// Composites must bind every existing child, and services with changing
158 /// readiness must subscribe to all their sources here. Lazily created
159 /// children must be initialized through [`Ctx::create`], which binds them
160 /// explicitly before use.
161 /// Register reusable [`ReadyCallback`] handles with [`BindCtx::on_notify`]
162 /// to observe notifications reaching the service's scope.
163 ///
164 /// Binding is synchronous. Shared services can be bound to multiple parents;
165 /// implementations must support repeated binding.
166 /// [`forward!`] forwards to one or several service fields. Register [`Pipeline`] fields with [`Pipeline::bind_to`].
167 /// Ordinary readiness checks, calls, and shutdown do not add dependencies.
168 fn bind(&self, ctx: BindCtx<'_>) {}
169
170 #[inline]
171 /// Waits until the service is ready to process a request.
172 ///
173 /// If the service is at capacity, the returned future remains pending until
174 /// capacity becomes available.
175 ///
176 /// Pipeline readiness is coordinated across all services in a composed
177 /// chain. A request is dispatched only when the chain is ready.
178 ///
179 /// A [`Pipeline`] or [`ReadyBinding`] caches success until a subscribed
180 /// [`ReadyNotifier`] invalidates it; calls do not consume that success.
181 /// Subscribe in [`Self::bind`] and notify on every readiness-changing
182 /// transition, including consuming or releasing capacity and external
183 /// failures, before another caller can use stale cached readiness.
184 /// A task wake schedules a pending check but does not invalidate a cached
185 /// success. Services must not rely on this method being polled on every
186 /// call to drive background progress.
187 ///
188 /// Readiness checks and their wake sources run on the pipeline's thread.
189 /// This local-only requirement does not apply to task wakers supplied to
190 /// [`Self::call`] or [`Self::shutdown`].
191 ///
192 /// Readiness may inspect [`Ctx::st`], but it describes the shared gate, not
193 /// a reservation for one request or state value. State-taking pipeline
194 /// handles share the same cache; independent gates need separate pipelines
195 /// or service bindings.
196 async fn ready(&self, ctx: Ctx<'_, Self, St>) -> Result<(), Self::Error> {
197 Ok(())
198 }
199
200 #[inline]
201 /// Shuts down the service.
202 ///
203 /// Returns when the service has been properly shut down.
204 async fn shutdown(&self, ctx: Ctx<'_, Self, St>) {}
205
206 #[inline]
207 /// Maps this service's output to a different type, returning a new service.
208 ///
209 /// This is similar to `Option::map` or `Iterator::map`, changing the
210 /// output type of the underlying service.
211 ///
212 /// This function consumes the original service and returns a wrapped version,
213 /// following the pattern of standard library `map` methods.
214 fn map<F, Res>(self, f: F) -> ServiceChain<dev::Map<F, Self, Res>, St, Req>
215 where
216 Self: Sized,
217 F: Fn(Self::Res) -> Res,
218 {
219 service(dev::Map::new(f, self))
220 }
221
222 #[inline]
223 /// Maps this service's error to a different type, returning a new service.
224 ///
225 /// This is similar to `Result::map_err`, changing the error type of the
226 /// underlying service. It is useful, for example, to ensure multiple
227 /// services have the same error type.
228 ///
229 /// This function consumes the original service and returns a wrapped version.
230 fn map_err<F, E>(self, f: F) -> ServiceChain<dev::MapErr<F, Self, E>, St, Req>
231 where
232 Self: Sized,
233 F: Fn(Self::Error) -> E,
234 {
235 service(dev::MapErr::new(f, self))
236 }
237
238 #[inline]
239 /// Calls another service after this service completes successfully.
240 ///
241 /// The first service's response becomes the second service's request. If
242 /// the first service returns an error, the second service is not called.
243 fn and_then<Next, F>(self, f: F) -> ServiceChain<dev::AndThen<Self, Next>, St, Req>
244 where
245 Self: Sized,
246 Next: Service<St, Self::Res, Error = Self::Error>,
247 F: IntoService<Next, St, Self::Res>,
248 {
249 service(dev::AndThen::new(self, f.into_service()))
250 }
251
252 #[inline]
253 /// Wraps this service and its state in a [`Pipeline`].
254 fn pipeline(self, st: St) -> Pipeline<Req, Self::Res, Self::Error>
255 where
256 Self: Sized + 'static,
257 St: 'static,
258 Req: 'static,
259 {
260 Pipeline::new(st, self)
261 }
262}
263
264/// A factory for asynchronously creating [`Service`] values.
265///
266/// This is useful when new `Service`s must be produced dynamically. For example,
267/// a TCP server listener accepts new connections, constructs a new `Service` for
268/// each connection using the `ServiceFactory` trait, and uses that service to
269/// handle inbound requests.
270///
271/// `St` is the state type shared by the factory and its services.
272///
273/// Simple factories can often use [`fn_factory`] to reduce boilerplate.
274#[diagnostic::on_unimplemented(
275 message = "`{Self}` is not a `ServiceFactory<{St}, {Req}>`",
276 label = "expected a factory of services for `{Req}` requests with `{St}` state",
277 note = "the state type comes first: `impl ServiceFactory<St, Req> for T`; use `()` for no state",
278 note = "a `Service` is not a factory; use `fn_factory(async |st| Ok(service))` to create it per pipeline",
279 note = "async functions `async |req| ..` and `async |st, req| ..` convert into factories automatically"
280)]
281pub trait ServiceFactory<St, Req> {
282 /// Response produced by the created services.
283 type Res;
284
285 /// Error produced by the created services.
286 type Error;
287
288 /// The type of `Service` produced by this factory.
289 type Service: Service<St, Req, Res = Self::Res, Error = Self::Error>;
290
291 /// Error that can occur while constructing a service.
292 type InitError;
293
294 /// Asynchronously creates a service using the supplied state.
295 async fn create(&self, st: &St) -> Result<Self::Service, Self::InitError>;
296
297 #[inline]
298 /// Creates a service and wraps it with its state in a [`Pipeline`].
299 async fn pipeline(
300 &self,
301 st: St,
302 ) -> Result<Pipeline<Req, Self::Res, Self::Error>, Self::InitError>
303 where
304 Self: 'static,
305 St: 'static,
306 Req: 'static,
307 {
308 let svc = self.create(&st).await?;
309 Ok(Pipeline::new(st, svc))
310 }
311
312 #[inline]
313 /// Returns a factory whose services map responses to a different type.
314 fn map<F, Res>(self, f: F) -> ServiceChainFactory<dev::MapFactory<F, Self, Res>, St, Req>
315 where
316 Self: Sized,
317 F: Fn(Self::Res) -> Res + Clone,
318 {
319 factory(dev::MapFactory::new(f, self))
320 }
321
322 #[inline]
323 /// Returns a factory whose services map errors to a different type.
324 fn map_err<F, E>(self, f: F) -> ServiceChainFactory<dev::MapErrFactory<F, Self, E>, St, Req>
325 where
326 Self: Sized,
327 F: Fn(Self::Error) -> E + Clone,
328 {
329 factory(dev::MapErrFactory::new(f, self))
330 }
331
332 #[inline]
333 /// Maps this factory's initialization error to a different error,
334 /// returning a new service factory.
335 fn map_init_err<F, E>(self, f: F) -> ServiceChainFactory<dev::MapInitErr<F, Self, E>, St, Req>
336 where
337 Self: Sized,
338 F: Fn(Self::InitError) -> E + Clone,
339 {
340 factory(dev::MapInitErr::new(f, self))
341 }
342
343 /// Chains another factory after this factory's services.
344 ///
345 /// Each response from the first service becomes a request to the second
346 /// service. The second service is not called when the first returns an
347 /// error.
348 fn and_then<U, F>(self, f: F) -> ServiceChainFactory<dev::AndThenFactory<Self, U>, St, Req>
349 where
350 Self: Sized,
351 U: ServiceFactory<St, Self::Res, Error = Self::Error, InitError = Self::InitError>,
352 F: IntoServiceFactory<U, St, Self::Res>,
353 {
354 factory(dev::AndThenFactory::new(self, f.into_factory()))
355 }
356
357 /// Creates a boxed service factory.
358 fn boxed(self) -> boxed::BoxServiceFactory<St, Req, Self::Res, Self::Error, Self::InitError>
359 where
360 St: 'static,
361 Req: 'static,
362 Self: Sized + 'static,
363 {
364 boxed::factory(self)
365 }
366}
367
368impl<S, St, Req> Service<St, Req> for &S
369where
370 S: Service<St, Req>,
371{
372 type Res = S::Res;
373 type Error = S::Error;
374
375 fn bind(&self, ctx: BindCtx<'_>) {
376 ctx.bind(&**self);
377 }
378
379 #[inline]
380 async fn ready(&self, ctx: Ctx<'_, Self, St>) -> Result<(), S::Error> {
381 Service::ready(&**self, ctx.cast()).await
382 }
383
384 #[inline]
385 async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<S::Res, S::Error> {
386 Service::call(&**self, req, ctx.cast()).await
387 }
388
389 #[inline]
390 async fn shutdown(&self, ctx: Ctx<'_, Self, St>) {
391 Service::shutdown(&**self, ctx.cast()).await;
392 }
393}
394
395impl<S, St, Req> Service<St, Req> for Box<S>
396where
397 S: Service<St, Req>,
398{
399 type Res = S::Res;
400 type Error = S::Error;
401
402 fn bind(&self, ctx: BindCtx<'_>) {
403 ctx.bind(&**self);
404 }
405
406 #[inline]
407 async fn ready(&self, ctx: Ctx<'_, Self, St>) -> Result<(), S::Error> {
408 Service::ready(&**self, ctx.cast()).await
409 }
410
411 #[inline]
412 async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<S::Res, S::Error> {
413 Service::call(&**self, req, ctx.cast()).await
414 }
415
416 #[inline]
417 async fn shutdown(&self, ctx: Ctx<'_, Self, St>) {
418 Service::shutdown(&**self, ctx.cast()).await;
419 }
420}
421
422impl<S, St, Req> Service<St, Req> for Rc<S>
423where
424 S: Service<St, Req>,
425{
426 type Res = S::Res;
427 type Error = S::Error;
428
429 fn bind(&self, ctx: BindCtx<'_>) {
430 ctx.bind(&**self);
431 }
432
433 #[inline]
434 async fn ready(&self, ctx: Ctx<'_, Self, St>) -> Result<(), S::Error> {
435 Service::ready(&**self, ctx.cast()).await
436 }
437
438 #[inline]
439 async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<S::Res, S::Error> {
440 Service::call(&**self, req, ctx.cast()).await
441 }
442
443 #[inline]
444 async fn shutdown(&self, ctx: Ctx<'_, Self, St>) {
445 Service::shutdown(&**self, ctx.cast()).await;
446 }
447}
448
449impl<Sf, St, Req> ServiceFactory<St, Req> for Rc<Sf>
450where
451 Sf: ServiceFactory<St, Req>,
452{
453 type Res = Sf::Res;
454 type Error = Sf::Error;
455 type Service = Sf::Service;
456 type InitError = Sf::InitError;
457
458 async fn create(&self, st: &St) -> Result<Self::Service, Self::InitError> {
459 self.as_ref().create(st).await
460 }
461}
462
463/// A common interface for values that can call a service.
464pub trait ServiceCaller<Req, Res, Err> {
465 /// Waits for readiness, then calls the service.
466 async fn call_service(&self, req: Req) -> Result<Res, Err>;
467}
468
469/// Conversion into a [`Service`].
470#[diagnostic::on_unimplemented(
471 message = "`{Self}` cannot be converted into a `Service<{St}, {Req}>`",
472 label = "expected a service or an async function for `{Req}` requests",
473 note = "accepted values: a `Service<{St}, {Req}>`, `async |req: {Req}| -> Result<Res, Err>`, or `async |st: &{St}, req: {Req}| -> Result<Res, Err>`",
474 note = "async functions must return `Result`"
475)]
476pub trait IntoService<S, St, Req>
477where
478 S: Service<St, Req>,
479{
480 /// Converts this value into a service.
481 fn into_service(self) -> S;
482}
483
484/// Conversion into a [`ServiceFactory`].
485#[diagnostic::on_unimplemented(
486 message = "`{Self}` cannot be converted into a `ServiceFactory<{St}, {Req}>`",
487 label = "expected a service factory or an async function for `{Req}` requests",
488 note = "accepted values: a `ServiceFactory<{St}, {Req}>`, a `Clone` async function `async |req: {Req}| -> Result<Res, Err>` or `async |st: &{St}, req: {Req}| -> Result<Res, Err>`, or `async |st: &{St}| -> Result<impl Service, Err>`",
489 note = "a `Service` value is not a factory; wrap its construction with `fn_factory`"
490)]
491pub trait IntoServiceFactory<Sf, St, Req>
492where
493 Sf: ServiceFactory<St, Req>,
494{
495 /// Converts this value into a service factory.
496 fn into_factory(self) -> Sf;
497}
498
499impl<S, St, Req> IntoService<S, St, Req> for S
500where
501 S: Service<St, Req>,
502{
503 #[inline]
504 fn into_service(self) -> S {
505 self
506 }
507}
508
509impl<Sf, St, Req> IntoServiceFactory<Sf, St, Req> for Sf
510where
511 Sf: ServiceFactory<St, Req>,
512{
513 #[inline]
514 fn into_factory(self) -> Sf {
515 self
516 }
517}
518
519/// Combinator and helper types used by the public API.
520///
521/// Most services can be built through the crate-level functions and the
522/// [`Service`] and [`ServiceFactory`] methods without naming these types.
523/// This module exposes their concrete return types for type annotations,
524/// reusable wrappers, and custom composition.
525///
526/// - [`ServiceChain`] and [`ServiceChainFactory`] are builders returned by
527/// [`service()`] and [`factory()`].
528/// - [`dev::FnService`], [`dev::FnServiceSt`], and [`dev::FnFactory`] adapt
529/// functions into services and factories.
530/// - [`dev::AndThen`], [`dev::Then`], [`dev::Map`], and [`dev::MapErr`] compose
531/// or transform service results, with corresponding factory adapters.
532/// - [`dev::ApplyCtx`] provides state and inner-service calls to
533/// [`apply_fn`] middleware; [`dev::MapState`] substitutes the inner state.
534/// - [`dev::Ready`] and [`dev::ReadyFactory`] are the wrappers produced by
535/// [`ready()`] and [`ready_factory()`] for independent cached readiness.
536///
537/// # Framework guide
538///
539/// - [Service Pipelines](https://docs.ntex.rs/2-pipeline.html)
540/// - [Service State](https://docs.ntex.rs/3-state.html)
541#[doc(hidden)]
542pub mod __private {
543 pub use crate::readiness::Generation;
544 pub use crate::util::{join, ready_field, ready_join};
545}
546
547pub mod dev {
548 pub use crate::and_then::{AndThen, AndThenFactory};
549 pub use crate::apply::{Apply, ApplyCtx, ApplyFactory};
550 pub use crate::chain::{ServiceChain, ServiceChainFactory};
551 pub use crate::fn_ready::FnReadiness;
552 pub use crate::fn_service::{FnFactory, FnService, FnServiceSt, FnServiceStFactory};
553 pub use crate::fn_shutdown::FnShutdown;
554 pub use crate::map::{Map, MapFactory};
555 pub use crate::map_err::{MapErr, MapErrFactory};
556 pub use crate::map_init_err::MapInitErr;
557 pub use crate::map_state::{MapState, MapStateFactory};
558 pub use crate::middleware::{ApplyMiddleware, FnMiddleware};
559 pub use crate::ready::{Ready, ReadyFactory};
560 pub use crate::then::{Then, ThenFactory};
561}
562
563#[cfg(test)]
564#[path = "../tests/unit/lib.rs"]
565mod tests;