Skip to main content

wire_framework/
wiring_layer.rs

1use std::fmt;
2
3use tokio::runtime;
4
5use crate::{FromContext, IntoContext, resource::ResourceId, service::ServiceContext};
6
7/// An envelope for the wiring layer function.
8/// Since `WiringLayer` has associated types, we cannot easily erase the types via `dyn WiringLayer`,
9/// so instead we preserve the layer type within the closure, and represent the actual wiring logic
10/// as a function of the service context instead.
11/// See [`WiringLayerExt`] trait for more context.
12#[allow(clippy::type_complexity)] // False positive, already a dedicated type.
13pub(crate) struct WireFn(
14    pub Box<dyn FnOnce(&runtime::Handle, &mut ServiceContext<'_>) -> Result<(), WiringError>>,
15);
16
17impl fmt::Debug for WireFn {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        f.debug_struct("WireFn").finish()
20    }
21}
22
23/// Wiring layer provides a way to customize the `Service` by
24/// adding new tasks or resources to it.
25///
26/// Structures that implement this trait are advised to specify in doc comments
27/// which resources they use or add, and the list of tasks they add.
28#[async_trait::async_trait]
29pub trait WiringLayer: 'static + Send + Sync {
30    type Input: FromContext;
31    type Output: IntoContext;
32
33    /// Identifier of the wiring layer.
34    fn layer_name(&self) -> &'static str;
35
36    /// Performs the wiring process, e.g. adds tasks and resources to the node.
37    /// This method will be called once during the node initialization.
38    async fn wire(self, input: Self::Input) -> Result<Self::Output, WiringError>;
39}
40
41pub(crate) trait WiringLayerExt: WiringLayer {
42    /// Hires the actual type of the wiring layer into the closure, so that rest of application
43    /// doesn't have to know it.
44    fn into_wire_fn(self) -> WireFn
45    where
46        Self: Sized,
47    {
48        WireFn(Box::new(move |rt, ctx| {
49            let input = Self::Input::from_context(ctx)?;
50            let output = rt.block_on(self.wire(input))?;
51            output.into_context(ctx)?;
52            Ok(())
53        }))
54    }
55}
56
57impl<T> WiringLayerExt for T where T: WiringLayer {}
58
59/// An error that can occur during the wiring phase.
60#[derive(thiserror::Error, Debug)]
61#[non_exhaustive]
62pub enum WiringError {
63    #[error("Layer attempted to add resource {name}, but it is already provided")]
64    ResourceAlreadyProvided { id: ResourceId, name: String },
65    #[error("Resource {name} is not provided")]
66    ResourceLacking { id: ResourceId, name: String },
67    #[error("Wiring layer has been incorrectly configured: {0}")]
68    Configuration(String),
69    #[error(transparent)]
70    Internal(#[from] eyre::Report),
71}
72
73impl WiringError {
74    /// Wraps the specified internal error.
75    pub fn internal(err: impl Into<eyre::Report>) -> Self {
76        Self::Internal(err.into())
77    }
78}