|
| 1 | +mod store; |
| 2 | + |
| 3 | +use std::{ |
| 4 | + collections::{HashMap, HashSet}, |
| 5 | + sync::Arc, |
| 6 | +}; |
| 7 | + |
| 8 | +use anyhow::{bail, ensure}; |
| 9 | +use serde::Deserialize; |
| 10 | +use spin_factors::{ |
| 11 | + anyhow::{self, Context}, |
| 12 | + ConfigureAppContext, Factor, FactorInstanceBuilder, FactorRuntimeConfig, InitContext, |
| 13 | + InstanceBuilders, PrepareContext, RuntimeFactors, |
| 14 | +}; |
| 15 | +use spin_key_value::{ |
| 16 | + CachingStoreManager, DelegatingStoreManager, KeyValueDispatch, StoreManager, |
| 17 | + KEY_VALUE_STORES_KEY, |
| 18 | +}; |
| 19 | +use store::{store_from_toml_fn, StoreFromToml}; |
| 20 | + |
| 21 | +pub use store::MakeKeyValueStore; |
| 22 | + |
| 23 | +#[derive(Default)] |
| 24 | +pub struct KeyValueFactor { |
| 25 | + store_types: HashMap<&'static str, StoreFromToml>, |
| 26 | +} |
| 27 | + |
| 28 | +impl KeyValueFactor { |
| 29 | + pub fn add_store_type<T: MakeKeyValueStore>(&mut self, store_type: T) -> anyhow::Result<()> { |
| 30 | + if self |
| 31 | + .store_types |
| 32 | + .insert(T::RUNTIME_CONFIG_TYPE, store_from_toml_fn(store_type)) |
| 33 | + .is_some() |
| 34 | + { |
| 35 | + bail!( |
| 36 | + "duplicate key value store type {:?}", |
| 37 | + T::RUNTIME_CONFIG_TYPE |
| 38 | + ); |
| 39 | + } |
| 40 | + Ok(()) |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 44 | +impl Factor for KeyValueFactor { |
| 45 | + type RuntimeConfig = RuntimeConfig; |
| 46 | + type AppState = AppState; |
| 47 | + type InstanceBuilder = InstanceBuilder; |
| 48 | + |
| 49 | + fn init<Factors: RuntimeFactors>( |
| 50 | + &mut self, |
| 51 | + mut ctx: InitContext<Factors, Self>, |
| 52 | + ) -> anyhow::Result<()> { |
| 53 | + ctx.link_bindings(spin_world::v1::key_value::add_to_linker)?; |
| 54 | + ctx.link_bindings(spin_world::v2::key_value::add_to_linker)?; |
| 55 | + Ok(()) |
| 56 | + } |
| 57 | + |
| 58 | + fn configure_app<T: RuntimeFactors>( |
| 59 | + &self, |
| 60 | + mut ctx: ConfigureAppContext<T, Self>, |
| 61 | + ) -> anyhow::Result<Self::AppState> { |
| 62 | + // Build StoreManager from runtime config |
| 63 | + let mut stores = HashMap::new(); |
| 64 | + if let Some(runtime_config) = ctx.take_runtime_config() { |
| 65 | + for (label, StoreConfig { type_, config }) in runtime_config.store_configs { |
| 66 | + let store_maker = self |
| 67 | + .store_types |
| 68 | + .get(type_.as_str()) |
| 69 | + .with_context(|| format!("unknown key value store type {type_:?}"))?; |
| 70 | + let store = store_maker(config)?; |
| 71 | + stores.insert(label, store); |
| 72 | + } |
| 73 | + } |
| 74 | + let delegating_manager = DelegatingStoreManager::new(stores); |
| 75 | + let caching_manager = CachingStoreManager::new(delegating_manager); |
| 76 | + let store_manager = Arc::new(caching_manager); |
| 77 | + |
| 78 | + // Build component -> allowed stores map |
| 79 | + let mut component_allowed_stores = HashMap::new(); |
| 80 | + for component in ctx.app().components() { |
| 81 | + let component_id = component.id().to_string(); |
| 82 | + let key_value_stores = component |
| 83 | + .get_metadata(KEY_VALUE_STORES_KEY)? |
| 84 | + .unwrap_or_default() |
| 85 | + .into_iter() |
| 86 | + .collect::<HashSet<_>>(); |
| 87 | + for label in &key_value_stores { |
| 88 | + // TODO: port nicer errors from KeyValueComponent (via error type?) |
| 89 | + ensure!( |
| 90 | + store_manager.is_defined(label), |
| 91 | + "unknown key_value_stores label {label:?} for component {component_id:?}" |
| 92 | + ); |
| 93 | + } |
| 94 | + component_allowed_stores.insert(component_id, key_value_stores); |
| 95 | + // TODO: warn (?) on unused store? |
| 96 | + } |
| 97 | + |
| 98 | + Ok(AppState { |
| 99 | + store_manager, |
| 100 | + component_allowed_stores, |
| 101 | + }) |
| 102 | + } |
| 103 | + |
| 104 | + fn prepare<T: RuntimeFactors>( |
| 105 | + ctx: PrepareContext<Self>, |
| 106 | + _builders: &mut InstanceBuilders<T>, |
| 107 | + ) -> anyhow::Result<InstanceBuilder> { |
| 108 | + let app_state = ctx.app_state(); |
| 109 | + let allowed_stores = app_state |
| 110 | + .component_allowed_stores |
| 111 | + .get(ctx.app_component().id()) |
| 112 | + .expect("component should be in component_stores") |
| 113 | + .clone(); |
| 114 | + Ok(InstanceBuilder { |
| 115 | + store_manager: app_state.store_manager.clone(), |
| 116 | + allowed_stores, |
| 117 | + }) |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +#[derive(Deserialize)] |
| 122 | +#[serde(transparent)] |
| 123 | +pub struct RuntimeConfig { |
| 124 | + store_configs: HashMap<String, StoreConfig>, |
| 125 | +} |
| 126 | + |
| 127 | +impl FactorRuntimeConfig for RuntimeConfig { |
| 128 | + const KEY: &'static str = "key_value_store"; |
| 129 | +} |
| 130 | + |
| 131 | +#[derive(Deserialize)] |
| 132 | +struct StoreConfig { |
| 133 | + #[serde(rename = "type")] |
| 134 | + type_: String, |
| 135 | + #[serde(flatten)] |
| 136 | + config: toml::Table, |
| 137 | +} |
| 138 | + |
| 139 | +type AppStoreManager = CachingStoreManager<DelegatingStoreManager>; |
| 140 | + |
| 141 | +pub struct AppState { |
| 142 | + store_manager: Arc<AppStoreManager>, |
| 143 | + component_allowed_stores: HashMap<String, HashSet<String>>, |
| 144 | +} |
| 145 | + |
| 146 | +pub struct InstanceBuilder { |
| 147 | + store_manager: Arc<AppStoreManager>, |
| 148 | + allowed_stores: HashSet<String>, |
| 149 | +} |
| 150 | + |
| 151 | +impl FactorInstanceBuilder for InstanceBuilder { |
| 152 | + type InstanceState = KeyValueDispatch; |
| 153 | + |
| 154 | + fn build(self) -> anyhow::Result<Self::InstanceState> { |
| 155 | + let Self { |
| 156 | + store_manager, |
| 157 | + allowed_stores, |
| 158 | + } = self; |
| 159 | + let mut dispatch = KeyValueDispatch::new_with_capacity(u32::MAX); |
| 160 | + dispatch.init(allowed_stores, store_manager); |
| 161 | + Ok(dispatch) |
| 162 | + } |
| 163 | +} |
0 commit comments