spin_factor_key_value/runtime_config/
spin.rs1use crate::{RuntimeConfig, StoreManager};
4use anyhow::Context as _;
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7use spin_factors::runtime_config::toml::GetTomlValue;
8use std::{collections::HashMap, sync::Arc};
9
10pub trait MakeKeyValueStore: 'static + Send + Sync {
12 const RUNTIME_CONFIG_TYPE: &'static str;
14 type RuntimeConfig: DeserializeOwned;
16 type StoreManager: StoreManager;
18
19 fn make_store(&self, runtime_config: Self::RuntimeConfig)
21 -> anyhow::Result<Self::StoreManager>;
22}
23
24type StoreFromToml =
26 Arc<dyn Fn(toml::Table) -> anyhow::Result<Arc<dyn StoreManager>> + Send + Sync>;
27
28fn store_from_toml_fn<T: MakeKeyValueStore>(provider_type: T) -> StoreFromToml {
30 Arc::new(move |table| {
31 let runtime_config: T::RuntimeConfig = table
32 .try_into()
33 .context("could not parse key-value runtime config")?;
34 let provider = provider_type
35 .make_store(runtime_config)
36 .context("could not make key-value store from runtime config")?;
37 Ok(Arc::new(provider))
38 })
39}
40
41#[derive(Default, Clone)]
47pub struct RuntimeConfigResolver {
48 store_types: HashMap<&'static str, StoreFromToml>,
51 defaults: HashMap<&'static str, StoreConfig>,
53}
54
55impl RuntimeConfigResolver {
56 pub fn new() -> Self {
58 <Self as Default>::default()
59 }
60
61 pub fn add_default_store<T>(
66 &mut self,
67 label: &'static str,
68 config: T::RuntimeConfig,
69 ) -> anyhow::Result<()>
70 where
71 T: MakeKeyValueStore,
72 T::RuntimeConfig: Serialize,
73 {
74 self.defaults.insert(
75 label,
76 StoreConfig::new(T::RUNTIME_CONFIG_TYPE.to_owned(), config)?,
77 );
78 Ok(())
79 }
80
81 pub fn register_store_type<T: MakeKeyValueStore>(
83 &mut self,
84 store_type: T,
85 ) -> anyhow::Result<()> {
86 if self
87 .store_types
88 .insert(T::RUNTIME_CONFIG_TYPE, store_from_toml_fn(store_type))
89 .is_some()
90 {
91 anyhow::bail!(
92 "duplicate key value store type {:?}",
93 T::RUNTIME_CONFIG_TYPE
94 );
95 }
96 Ok(())
97 }
98
99 pub fn resolve(&self, table: Option<&impl GetTomlValue>) -> anyhow::Result<RuntimeConfig> {
103 let mut runtime_config = self.resolve_from_toml(table)?.unwrap_or_default();
104
105 for (&label, config) in &self.defaults {
106 if !runtime_config.store_managers.contains_key(label) {
107 let store_manager = self
108 .store_manager_from_config(config.clone())
109 .with_context(|| {
110 format!("could not configure key-value store with label '{label}'")
111 })?;
112 runtime_config.add_store_manager(label.to_owned(), store_manager);
113 }
114 }
115 Ok(runtime_config)
116 }
117
118 fn resolve_from_toml(
119 &self,
120 table: Option<&impl GetTomlValue>,
121 ) -> anyhow::Result<Option<RuntimeConfig>> {
122 let Some(table) = table.and_then(|t| t.get("key_value_store")) else {
123 return Ok(None);
124 };
125 let table: HashMap<String, StoreConfig> = table.clone().try_into()?;
126
127 let mut runtime_config = RuntimeConfig::default();
128 for (label, config) in table {
129 let store_manager = self.store_manager_from_config(config).with_context(|| {
130 format!("could not configure key-value store with label '{label}'")
131 })?;
132 runtime_config.add_store_manager(label.clone(), store_manager);
133 }
134
135 Ok(Some(runtime_config))
136 }
137
138 fn store_manager_from_config(
143 &self,
144 config: StoreConfig,
145 ) -> anyhow::Result<Arc<dyn StoreManager>> {
146 let config_type = config.type_.as_str();
147 let maker = self.store_types.get(config_type).with_context(|| {
148 format!("the store type '{config_type}' was not registered with the config resolver")
149 })?;
150 maker(config.config)
151 }
152}
153
154#[derive(Deserialize, Clone)]
155pub struct StoreConfig {
156 #[serde(rename = "type")]
157 pub type_: String,
158 #[serde(flatten)]
159 pub config: toml::Table,
160}
161
162impl StoreConfig {
163 pub fn new<T>(type_: String, config: T) -> anyhow::Result<Self>
164 where
165 T: Serialize,
166 {
167 Ok(Self {
168 type_,
169 config: toml::value::Table::try_from(config)?,
170 })
171 }
172}