spin_factors/
factor.rs

1use std::any::Any;
2use std::marker::PhantomData;
3
4use wasmtime::component::{HasData, Linker, ResourceTable};
5
6use crate::{
7    prepare::FactorInstanceBuilder, App, AsInstanceState, Error, PrepareContext, RuntimeFactors,
8};
9
10/// A contained (i.e., "factored") piece of runtime functionality.
11pub trait Factor: Any + Sized {
12    /// The particular runtime configuration relevant to this factor.
13    ///
14    /// Runtime configuration allows for user-provided customization of the
15    /// factor's behavior on a per-app basis.
16    type RuntimeConfig;
17
18    /// The application state of this factor.
19    ///
20    /// This state *may* be cached by the runtime across multiple requests.
21    type AppState: Sync;
22
23    /// The builder of instance state for this factor.
24    type InstanceBuilder: FactorInstanceBuilder;
25
26    /// Initializes this `Factor` for a runtime once at runtime startup.
27    ///
28    /// This will be called at most once, before any call to
29    /// [`Factor::prepare`]. `InitContext` provides access to a wasmtime
30    /// `Linker`, so this is where any bindgen `add_to_linker` calls go.
31    fn init(&mut self, ctx: &mut impl InitContext<Self>) -> anyhow::Result<()> {
32        let _ = 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
73/// An InitContext is passed to [`Factor::init`], giving access to the global
74/// common [`wasmtime::component::Linker`].
75pub trait InitContext<F: Factor> {
76    /// The `T` in `Store<T>`.
77    type StoreData: Send + 'static;
78
79    /// Returns a mutable reference to the [`wasmtime::component::Linker`].
80    fn linker(&mut self) -> &mut Linker<Self::StoreData>;
81
82    /// Get the instance state for this factor from the store's state.
83    fn get_data(store: &mut Self::StoreData) -> &mut FactorInstanceState<F> {
84        Self::get_data_with_table(store).0
85    }
86
87    /// Get the instance state for this factor from the store's state, with the
88    /// resource table as well.
89    fn get_data_with_table(
90        store: &mut Self::StoreData,
91    ) -> (&mut FactorInstanceState<F>, &mut ResourceTable);
92
93    /// Convenience method to link a binding to the linker.
94    fn link_bindings(
95        &mut self,
96        add_to_linker: impl Fn(
97            &mut Linker<Self::StoreData>,
98            fn(&mut Self::StoreData) -> &mut FactorInstanceState<F>,
99        ) -> anyhow::Result<()>,
100    ) -> anyhow::Result<()> {
101        add_to_linker(self.linker(), Self::get_data)
102    }
103}
104
105/// Helper type to satisfy the `D: HasData` implementations needed by
106/// wasmtime's generated `add_to_linker` functions.
107pub struct FactorData<F>(F);
108
109impl<F: Factor> HasData for FactorData<F> {
110    type Data<'a> = &'a mut FactorInstanceState<F>;
111}
112
113// used in #[derive(RuntimeFactor)]
114#[doc(hidden)]
115pub struct FactorInitContext<'a, T: 'static, G> {
116    pub linker: &'a mut Linker<T>,
117    pub _marker: PhantomData<G>,
118}
119
120// used in #[derive(RuntimeFactor)]
121#[doc(hidden)]
122pub trait FactorField {
123    type State: crate::RuntimeFactorsInstanceState;
124    type Factor: Factor;
125
126    fn get(field: &mut Self::State)
127        -> (&mut FactorInstanceState<Self::Factor>, &mut ResourceTable);
128}
129
130impl<T, G> InitContext<G::Factor> for FactorInitContext<'_, T, G>
131where
132    G: FactorField,
133    T: AsInstanceState<G::State> + Send + 'static,
134{
135    type StoreData = T;
136
137    fn linker(&mut self) -> &mut Linker<Self::StoreData> {
138        self.linker
139    }
140
141    fn get_data_with_table(
142        store: &mut Self::StoreData,
143    ) -> (&mut FactorInstanceState<G::Factor>, &mut ResourceTable) {
144        G::get(store.as_instance_state())
145    }
146}
147
148pub struct ConfigureAppContext<'a, T: RuntimeFactors, F: Factor> {
149    app: &'a App,
150    app_state: &'a T::AppState,
151    runtime_config: Option<F::RuntimeConfig>,
152}
153
154impl<'a, T: RuntimeFactors, F: Factor> ConfigureAppContext<'a, T, F> {
155    #[doc(hidden)]
156    pub fn new(
157        app: &'a App,
158        app_state: &'a T::AppState,
159        runtime_config: Option<F::RuntimeConfig>,
160    ) -> crate::Result<Self> {
161        Ok(Self {
162            app,
163            app_state,
164            runtime_config,
165        })
166    }
167
168    /// Get the [`App`] being configured.
169    pub fn app(&self) -> &'a App {
170        self.app
171    }
172
173    /// Get the app state related to the given factor.
174    pub fn app_state<U: Factor>(&self) -> crate::Result<&'a U::AppState> {
175        T::app_state::<U>(self.app_state).ok_or(Error::no_such_factor::<U>())
176    }
177
178    /// Get a reference to the runtime configuration for the given factor.
179    pub fn runtime_config(&self) -> Option<&F::RuntimeConfig> {
180        self.runtime_config.as_ref()
181    }
182
183    /// Take ownership of the runtime configuration for the given factor.
184    pub fn take_runtime_config(&mut self) -> Option<F::RuntimeConfig> {
185        self.runtime_config.take()
186    }
187}
188
189#[doc(hidden)]
190pub struct ConfiguredApp<T: RuntimeFactors> {
191    app: App,
192    app_state: T::AppState,
193}
194
195impl<T: RuntimeFactors> ConfiguredApp<T> {
196    #[doc(hidden)]
197    pub fn new(app: App, app_state: T::AppState) -> Self {
198        Self { app, app_state }
199    }
200
201    /// Get the configured [`App`].
202    pub fn app(&self) -> &App {
203        &self.app
204    }
205
206    /// Get the configured app's state related to the given factor.
207    pub fn app_state<U: Factor>(&self) -> crate::Result<&U::AppState> {
208        T::app_state::<U>(&self.app_state).ok_or(Error::no_such_factor::<U>())
209    }
210}