tari_service_framework/initializer.rs
1// Copyright 2019 The Tari Project
2//
3// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4// following conditions are met:
5//
6// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7// disclaimer.
8//
9// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10// following disclaimer in the documentation and/or other materials provided with the distribution.
11//
12// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13// products derived from this software without specific prior written permission.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23use crate::context::ServiceInitializerContext;
24use futures::{Future, FutureExt};
25use std::pin::Pin;
26
27pub type ServiceInitializationError = anyhow::Error;
28
29/// Implementors of this trait will initialize a service
30/// The `StackBuilder` builds impls of this trait.
31pub trait ServiceInitializer {
32 /// The future returned from the initialize function
33 type Future: Future<Output = Result<(), ServiceInitializationError>>;
34
35 /// Async initialization code for a service
36 fn initialize(&mut self, context: ServiceInitializerContext) -> Self::Future;
37
38 /// Create a boxed version of this ServiceInitializer.
39 fn boxed(self) -> BoxedServiceInitializer
40 where
41 Self: Sized + Send + 'static,
42 Self::Future: Send + 'static,
43 {
44 BoxedServiceInitializer::new(self)
45 }
46}
47
48/// Implementation of ServiceInitializer for any function matching the signature of `ServiceInitializer::initialize`
49/// This allows the following "short-hand" syntax to be used:
50///
51/// ```edition2018
52/// # use tari_service_framework::ServiceInitializerContext;
53/// # use tokio::runtime;
54/// let my_initializer = |executor: runtime::Handle, context: ServiceInitializerContext| {
55/// // initialization code
56/// futures::future::ready(Result::<_, ()>::Ok(()))
57/// };
58/// ```
59impl<TFunc, TFut> ServiceInitializer for TFunc
60where
61 TFunc: FnMut(ServiceInitializerContext) -> TFut,
62 TFut: Future<Output = Result<(), ServiceInitializationError>>,
63{
64 type Future = TFut;
65
66 fn initialize(&mut self, context: ServiceInitializerContext) -> Self::Future {
67 (self)(context)
68 }
69}
70
71/// An implementation of `ServiceInitializer` that wraps an `impl FnOnce`
72pub struct InitializerFn<TFunc>(Option<TFunc>);
73
74impl<TFunc> InitializerFn<TFunc> {
75 pub fn new(f: TFunc) -> Self {
76 Self(Some(f))
77 }
78}
79
80impl<TFunc, TFut> ServiceInitializer for InitializerFn<TFunc>
81where
82 TFunc: FnOnce(ServiceInitializerContext) -> TFut,
83 TFut: Future<Output = Result<(), ServiceInitializationError>>,
84{
85 type Future = TFut;
86
87 fn initialize(&mut self, context: ServiceInitializerContext) -> Self::Future {
88 let f = self.0.take().expect("initializer called more than once");
89 (f)(context)
90 }
91}
92
93//---------------------------------- Boxed Service Initializer --------------------------------------------//
94// The following code is essentially a substitute for async trait functions. Any initializer can
95// converted to the boxed form by using ServiceInitializer::boxed(). This is done for you when
96// using `StackBuilder::add_initializer`.
97
98/// A pinned, boxed form of the future resulting from a boxed ServiceInitializer
99type ServiceInitializationFuture = Pin<Box<dyn Future<Output = Result<(), ServiceInitializationError>> + Send>>;
100
101/// This trait mirrors the ServiceInitializer trait, with the exception
102/// of always returning a boxed future (aliased ServiceInitializationFuture type),
103/// therefore it does not need the `Future` associated type. This makes it
104/// possible to store a boxed dyn `AbstractServiceInitializer<TName, TExec>`.
105pub trait AbstractServiceInitializer {
106 fn initialize(&mut self, context: ServiceInitializerContext) -> ServiceInitializationFuture;
107}
108
109/// AbstractServiceInitializer impl for every T: ServiceInitializer.
110impl<T> AbstractServiceInitializer for T
111where
112 T: ServiceInitializer,
113 T::Future: Send + 'static,
114{
115 fn initialize(&mut self, context: ServiceInitializerContext) -> ServiceInitializationFuture {
116 let initialization = self.initialize(context);
117 initialization.boxed() as ServiceInitializationFuture
118 }
119}
120
121/// A concrete boxed version of a ServiceInitializer. This makes it possible
122/// to have a collection of ServiceInitializers which return various boxed future types.
123/// This type is used in StackBuilder's internal vec.
124pub struct BoxedServiceInitializer {
125 inner: Box<dyn AbstractServiceInitializer + Send + 'static>,
126}
127
128impl BoxedServiceInitializer {
129 pub(super) fn new<T>(initializer: T) -> Self
130 where
131 T: ServiceInitializer + Send + 'static,
132 T::Future: Send + 'static,
133 {
134 Self {
135 inner: Box::new(initializer),
136 }
137 }
138}
139
140impl ServiceInitializer for BoxedServiceInitializer {
141 type Future = ServiceInitializationFuture;
142
143 fn initialize(&mut self, context: ServiceInitializerContext) -> Self::Future {
144 self.inner.initialize(context)
145 }
146}