Skip to content

Commit e5e1ea2

Browse files
committed
move spin-app and spin-core in from their temporary home
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
1 parent ff6c148 commit e5e1ea2

File tree

16 files changed

+1125
-9
lines changed

16 files changed

+1125
-9
lines changed

crates/http/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ wasmtime = "0.39.1"
4040
wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba", features = ["async"] }
4141

4242
# Temporary refactor deps
43-
spin-app = { path = "../../../lhc/crates/spin-app" }
44-
spin-core = { path = "../../../lhc/crates/spin-core" }
43+
spin-app = { path = "../spin-app" }
44+
spin-core = { path = "../spin-core" }
4545

4646
[dev-dependencies]
4747
criterion = { version = "0.3.5", features = ["async_tokio"] }

crates/loader/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ walkdir = "2.3.2"
3131
wasi-outbound-http = { path = "../outbound-http" }
3232

3333
# Temporary refactor deps
34-
spin-app = { path = "../../../lhc/crates/spin-app" }
34+
spin-app = { path = "../spin-app" }

crates/redis/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ wasmtime-wasi = "0.39.1"
2727
wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" }
2828

2929
# Temporary refactor deps
30-
spin-app = { path = "../../../lhc/crates/spin-app" }
31-
spin-core = { path = "../../../lhc/crates/spin-core" }
30+
spin-app = { path = "../spin-app" }
31+
spin-core = { path = "../spin-core" }
3232

3333
[dev-dependencies]
3434
spin-testing = { path = "../testing" }

crates/spin-app/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "spin-app"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
anyhow = "1.0"
8+
async-trait = "0.1"
9+
ouroboros = "0.15"
10+
serde = { version = "1.0", features = ["derive"] }
11+
serde_json = "1.0"
12+
spin-core = { path = "../spin-core" }
13+
thiserror = "1.0"

crates/spin-app/src/host_component.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use std::sync::Arc;
2+
3+
use spin_core::{EngineBuilder, HostComponent, HostComponentsData};
4+
5+
use crate::AppComponent;
6+
7+
pub trait DynamicHostComponent: HostComponent {
8+
fn update_data(&self, data: &mut Self::Data, component: &AppComponent) -> anyhow::Result<()>;
9+
}
10+
11+
impl<DHC: DynamicHostComponent> DynamicHostComponent for Arc<DHC> {
12+
fn update_data(&self, data: &mut Self::Data, component: &AppComponent) -> anyhow::Result<()> {
13+
(**self).update_data(data, component)
14+
}
15+
}
16+
17+
type DataUpdater =
18+
Box<dyn Fn(&mut HostComponentsData, &AppComponent) -> anyhow::Result<()> + Send + Sync>;
19+
20+
#[derive(Default)]
21+
pub struct DynamicHostComponents {
22+
data_updaters: Vec<DataUpdater>,
23+
}
24+
25+
impl DynamicHostComponents {
26+
pub fn add_dynamic_host_component<T: Send + Sync, DHC: DynamicHostComponent>(
27+
&mut self,
28+
engine_builder: &mut EngineBuilder<T>,
29+
host_component: DHC,
30+
) -> anyhow::Result<()> {
31+
let host_component = Arc::new(host_component);
32+
let handle = engine_builder.add_host_component(host_component.clone())?;
33+
self.data_updaters
34+
.push(Box::new(move |host_components_data, component| {
35+
let data = host_components_data.get_or_insert(handle);
36+
host_component.update_data(data, component)
37+
}));
38+
Ok(())
39+
}
40+
41+
pub fn update_data(
42+
&self,
43+
host_components_data: &mut HostComponentsData,
44+
component: &AppComponent,
45+
) -> anyhow::Result<()> {
46+
for data_updater in &self.data_updaters {
47+
data_updater(host_components_data, component)?;
48+
}
49+
Ok(())
50+
}
51+
}

crates/spin-app/src/lib.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
mod host_component;
2+
pub mod locked;
3+
pub mod values;
4+
5+
pub use async_trait::async_trait;
6+
pub use host_component::DynamicHostComponent;
7+
8+
use host_component::DynamicHostComponents;
9+
use locked::{ContentPath, LockedApp, LockedComponent, LockedComponentSource, LockedTrigger};
10+
use ouroboros::self_referencing;
11+
use serde::Deserialize;
12+
use spin_core::{wasmtime, Engine, EngineBuilder, StoreBuilder};
13+
14+
// TODO(lann): Should this migrate to spin-loader?
15+
#[async_trait]
16+
pub trait Loader {
17+
async fn load_app(&self, uri: &str) -> anyhow::Result<LockedApp>;
18+
19+
async fn load_module(
20+
&self,
21+
engine: &wasmtime::Engine,
22+
source: &LockedComponentSource,
23+
) -> anyhow::Result<spin_core::Module>;
24+
25+
async fn mount_files(
26+
&self,
27+
store_builder: &mut StoreBuilder,
28+
component: &AppComponent,
29+
) -> anyhow::Result<()>;
30+
}
31+
32+
pub struct AppLoader {
33+
inner: Box<dyn Loader + Send + Sync>,
34+
dynamic_host_components: DynamicHostComponents,
35+
}
36+
37+
impl AppLoader {
38+
pub fn new(loader: impl Loader + Send + Sync + 'static) -> Self {
39+
Self {
40+
inner: Box::new(loader),
41+
dynamic_host_components: Default::default(),
42+
}
43+
}
44+
45+
pub fn add_dynamic_host_component<T: Send + Sync, DHC: DynamicHostComponent>(
46+
&mut self,
47+
engine_builder: &mut EngineBuilder<T>,
48+
host_component: DHC,
49+
) -> anyhow::Result<()> {
50+
self.dynamic_host_components
51+
.add_dynamic_host_component(engine_builder, host_component)
52+
}
53+
54+
pub async fn load_app(&self, uri: String) -> Result<App> {
55+
let locked = self
56+
.inner
57+
.load_app(&uri)
58+
.await
59+
.map_err(Error::LoaderError)?;
60+
Ok(App {
61+
loader: self,
62+
uri,
63+
locked,
64+
})
65+
}
66+
67+
pub async fn load_owned_app(self, uri: String) -> Result<OwnedApp> {
68+
OwnedApp::try_new_async(self, |loader| Box::pin(loader.load_app(uri))).await
69+
}
70+
}
71+
72+
impl std::fmt::Debug for AppLoader {
73+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74+
f.debug_struct("AppLoader").finish()
75+
}
76+
}
77+
78+
#[self_referencing]
79+
#[derive(Debug)]
80+
pub struct OwnedApp {
81+
loader: AppLoader,
82+
#[borrows(loader)]
83+
#[covariant]
84+
app: App<'this>,
85+
}
86+
87+
impl std::ops::Deref for OwnedApp {
88+
type Target = App<'static>;
89+
90+
fn deref(&self) -> &Self::Target {
91+
unsafe {
92+
// We know that App's lifetime param is for AppLoader, which is owned by self here.
93+
std::mem::transmute::<&App, &App<'static>>(self.borrow_app())
94+
}
95+
}
96+
}
97+
98+
#[derive(Debug)]
99+
pub struct App<'a> {
100+
loader: &'a AppLoader,
101+
uri: String,
102+
locked: LockedApp,
103+
}
104+
105+
impl<'a> App<'a> {
106+
pub fn uri(&self) -> &str {
107+
&self.uri
108+
}
109+
110+
pub fn get_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Option<Result<T>> {
111+
self.locked
112+
.metadata
113+
.get(key)
114+
.map(|value| Ok(T::deserialize(value)?))
115+
}
116+
117+
pub fn components(&self) -> impl Iterator<Item = AppComponent> {
118+
self.locked
119+
.components
120+
.iter()
121+
.map(|locked| AppComponent { app: self, locked })
122+
}
123+
124+
pub fn get_component(&self, component_id: &str) -> Option<AppComponent> {
125+
self.components()
126+
.find(|component| component.locked.id == component_id)
127+
}
128+
129+
pub fn triggers(&self) -> impl Iterator<Item = AppTrigger> {
130+
self.locked
131+
.triggers
132+
.iter()
133+
.map(|locked| AppTrigger { app: self, locked })
134+
}
135+
136+
pub fn triggers_with_type(&'a self, trigger_type: &'a str) -> impl Iterator<Item = AppTrigger> {
137+
self.triggers()
138+
.filter(move |trigger| trigger.locked.trigger_type == trigger_type)
139+
}
140+
}
141+
142+
pub struct AppComponent<'a> {
143+
pub app: &'a App<'a>,
144+
locked: &'a LockedComponent,
145+
}
146+
147+
impl<'a> AppComponent<'a> {
148+
pub fn id(&self) -> &str {
149+
&self.locked.id
150+
}
151+
152+
pub fn source(&self) -> &LockedComponentSource {
153+
&self.locked.source
154+
}
155+
156+
pub fn files(&self) -> std::slice::Iter<ContentPath> {
157+
self.locked.files.iter()
158+
}
159+
160+
pub fn get_metadata<T: Deserialize<'a>>(&self, key: &str) -> Option<Result<T>> {
161+
self.locked
162+
.metadata
163+
.get(key)
164+
.map(|value| Ok(T::deserialize(value)?))
165+
}
166+
167+
pub async fn load_module<T: Send + Sync>(
168+
&self,
169+
engine: &Engine<T>,
170+
) -> Result<spin_core::Module> {
171+
self.app
172+
.loader
173+
.inner
174+
.load_module(engine.as_ref(), &self.locked.source)
175+
.await
176+
.map_err(Error::LoaderError)
177+
}
178+
179+
pub async fn apply_store_config(&self, builder: &mut StoreBuilder) -> Result<()> {
180+
builder.env(&self.locked.env).map_err(Error::CoreError)?;
181+
182+
let loader = self.app.loader;
183+
loader
184+
.inner
185+
.mount_files(builder, self)
186+
.await
187+
.map_err(Error::LoaderError)?;
188+
189+
loader
190+
.dynamic_host_components
191+
.update_data(builder.host_components_data(), self)
192+
.map_err(Error::HostComponentError)?;
193+
194+
Ok(())
195+
}
196+
}
197+
198+
pub struct AppTrigger<'a> {
199+
pub app: &'a App<'a>,
200+
locked: &'a LockedTrigger,
201+
}
202+
203+
impl<'a> AppTrigger<'a> {
204+
pub fn id(&self) -> &str {
205+
&self.locked.id
206+
}
207+
208+
pub fn trigger_type(&self) -> &str {
209+
&self.locked.trigger_type
210+
}
211+
212+
pub fn component(&self) -> Result<AppComponent<'a>> {
213+
let component_id = self.locked.trigger_config.get("component").ok_or_else(|| {
214+
Error::ManifestError(format!(
215+
"trigger {:?} missing 'component' config field",
216+
self.locked.id
217+
))
218+
})?;
219+
let component_id = component_id.as_str().ok_or_else(|| {
220+
Error::ManifestError(format!(
221+
"trigger {:?} 'component' field has unexpected value {:?}",
222+
self.locked.id, component_id
223+
))
224+
})?;
225+
self.app.get_component(component_id).ok_or_else(|| {
226+
Error::ManifestError(format!(
227+
"missing component {:?} configured for trigger {:?}",
228+
component_id, self.locked.id
229+
))
230+
})
231+
}
232+
233+
pub fn typed_config<Config: Deserialize<'a>>(&self) -> Result<Config> {
234+
Ok(Config::deserialize(&self.locked.trigger_config)?)
235+
}
236+
}
237+
238+
pub type Result<T> = std::result::Result<T, Error>;
239+
240+
#[derive(Debug, thiserror::Error)]
241+
pub enum Error {
242+
#[error("spin core error: {0}")]
243+
CoreError(anyhow::Error),
244+
#[error("host component error: {0}")]
245+
HostComponentError(anyhow::Error),
246+
#[error("loader error: {0}")]
247+
LoaderError(anyhow::Error),
248+
#[error("manifest error: {0}")]
249+
ManifestError(String),
250+
#[error("json error: {0}")]
251+
JsonError(#[from] serde_json::Error),
252+
}

0 commit comments

Comments
 (0)