spin_factors/factor.rs
1use std::any::Any;
2
3use wasmtime::component::{Linker, ResourceTable};
4
5use crate::{prepare::FactorInstanceBuilder, App, Error, PrepareContext, RuntimeFactors};
6
7/// A contained (i.e., "factored") piece of runtime functionality.
8pub trait Factor: Any + Sized {
9 /// The particular runtime configuration relevant to this factor.
10 ///
11 /// Runtime configuration allows for user-provided customization of the
12 /// factor's behavior on a per-app basis.
13 type RuntimeConfig;
14
15 /// The application state of this factor.
16 ///
17 /// This state *may* be cached by the runtime across multiple requests.
18 type AppState: Sync;
19
20 /// The builder of instance state for this factor.
21 type InstanceBuilder: FactorInstanceBuilder;
22
23 /// Initializes this `Factor` for a runtime once at runtime startup.
24 ///
25 /// This will be called at most once, before any call to
26 /// [`Factor::prepare`]. `InitContext` provides access to a wasmtime
27 /// `Linker`, so this is where any bindgen `add_to_linker` calls go.
28 ///
29 /// The type parameter `T` here is the same as the [`wasmtime::Store`] type
30 /// parameter `T`, which will contain the [`RuntimeFactors::InstanceState`].
31 fn init<T: Send + 'static>(&mut self, mut ctx: InitContext<T, Self>) -> anyhow::Result<()> {
32 _ = &mut ctx;
33 Ok(())
34 }
35
36 /// Performs factor-specific validation and configuration for the given
37 /// [`App`].
38 ///
39 /// `ConfigureAppContext` gives access to:
40 /// - The `spin_app::App`
41 /// - This factors's `RuntimeConfig`
42 /// - The `AppState` for any factors configured before this one
43 ///
44 /// A runtime may - but is not required to - reuse the returned config
45 /// across multiple instances. Because this method may be called
46 /// per-instantiation, it should avoid any blocking operations that could
47 /// unnecessarily delay execution.
48 ///
49 /// This method may be called without any call to `init` or `prepare` in
50 /// cases where only validation is needed (e.g., `spin doctor`).
51 fn configure_app<T: RuntimeFactors>(
52 &self,
53 ctx: ConfigureAppContext<T, Self>,
54 ) -> anyhow::Result<Self::AppState>;
55
56 /// Creates a new `FactorInstanceBuilder`, which will later build
57 /// per-instance state for this factor.
58 ///
59 /// This method is given access to the app component being instantiated and
60 /// to any other factors' instance builders that have already been prepared.
61 /// As such, this is the primary place for inter-factor dependencies to be
62 /// used.
63 fn prepare<T: RuntimeFactors>(
64 &self,
65 ctx: PrepareContext<T, Self>,
66 ) -> anyhow::Result<Self::InstanceBuilder>;
67}
68
69/// The instance state of the given [`Factor`] `F`.
70pub type FactorInstanceState<F> =
71 <<F as Factor>::InstanceBuilder as FactorInstanceBuilder>::InstanceState;
72
73pub(crate) type GetDataFn<T, U> = fn(&mut T) -> &mut FactorInstanceState<U>;
74
75pub(crate) type GetDataWithTableFn<T, U> =
76 fn(&mut T) -> (&mut FactorInstanceState<U>, &mut ResourceTable);
77
78/// An InitContext is passed to [`Factor::init`], giving access to the global
79/// common [`wasmtime::component::Linker`].
80pub struct InitContext<'a, T, U: Factor> {
81 pub(crate) linker: &'a mut Linker<T>,
82 pub(crate) get_data: GetDataFn<T, U>,
83 pub(crate) get_data_with_table: GetDataWithTableFn<T, U>,
84}
85
86impl<'a, T, U: Factor> InitContext<'a, T, U> {
87 #[doc(hidden)]
88 pub fn new(
89 linker: &'a mut Linker<T>,
90 get_data: GetDataFn<T, U>,
91 get_data_with_table: GetDataWithTableFn<T, U>,
92 ) -> Self {
93 Self {
94 linker,
95 get_data,
96 get_data_with_table,
97 }
98 }
99
100 /// Returns a mutable reference to the [`wasmtime::component::Linker`].
101 pub fn linker(&mut self) -> &mut Linker<T> {
102 self.linker
103 }
104
105 /// Returns a function that can be used to get the instance state for this factor.
106 pub fn get_data_fn(&self) -> GetDataFn<T, U> {
107 self.get_data
108 }
109
110 /// Returns a function that can be used to get the instance state for this
111 /// factor along with the instance's [`ResourceTable`].
112 pub fn get_data_with_table_fn(&self) -> GetDataWithTableFn<T, U> {
113 self.get_data_with_table
114 }
115
116 /// Convenience method to link a binding to the linker.
117 pub fn link_bindings(
118 &mut self,
119 add_to_linker: impl Fn(
120 &mut Linker<T>,
121 fn(&mut T) -> &mut FactorInstanceState<U>,
122 ) -> anyhow::Result<()>,
123 ) -> anyhow::Result<()> {
124 add_to_linker(self.linker, self.get_data)
125 }
126}
127
128pub struct ConfigureAppContext<'a, T: RuntimeFactors, F: Factor> {
129 app: &'a App,
130 app_state: &'a T::AppState,
131 runtime_config: Option<F::RuntimeConfig>,
132}
133
134impl<'a, T: RuntimeFactors, F: Factor> ConfigureAppContext<'a, T, F> {
135 #[doc(hidden)]
136 pub fn new(
137 app: &'a App,
138 app_state: &'a T::AppState,
139 runtime_config: Option<F::RuntimeConfig>,
140 ) -> crate::Result<Self> {
141 Ok(Self {
142 app,
143 app_state,
144 runtime_config,
145 })
146 }
147
148 /// Get the [`App`] being configured.
149 pub fn app(&self) -> &'a App {
150 self.app
151 }
152
153 /// Get the app state related to the given factor.
154 pub fn app_state<U: Factor>(&self) -> crate::Result<&'a U::AppState> {
155 T::app_state::<U>(self.app_state).ok_or(Error::no_such_factor::<U>())
156 }
157
158 /// Get a reference to the runtime configuration for the given factor.
159 pub fn runtime_config(&self) -> Option<&F::RuntimeConfig> {
160 self.runtime_config.as_ref()
161 }
162
163 /// Take ownership of the runtime configuration for the given factor.
164 pub fn take_runtime_config(&mut self) -> Option<F::RuntimeConfig> {
165 self.runtime_config.take()
166 }
167}
168
169#[doc(hidden)]
170pub struct ConfiguredApp<T: RuntimeFactors> {
171 app: App,
172 app_state: T::AppState,
173}
174
175impl<T: RuntimeFactors> ConfiguredApp<T> {
176 #[doc(hidden)]
177 pub fn new(app: App, app_state: T::AppState) -> Self {
178 Self { app, app_state }
179 }
180
181 /// Get the configured [`App`].
182 pub fn app(&self) -> &App {
183 &self.app
184 }
185
186 /// Get the configured app's state related to the given factor.
187 pub fn app_state<U: Factor>(&self) -> crate::Result<&U::AppState> {
188 T::app_state::<U>(&self.app_state).ok_or(Error::no_such_factor::<U>())
189 }
190}