Skip to main content

obeli_sk_wasm_workers/
lib.rs

1use concepts::{ComponentType, FunctionFqn, FunctionMetadata, StrVariant};
2use std::{error::Error, fmt::Debug, path::Path};
3use tracing::{debug, trace};
4use tracing_error::SpanTrace;
5use utils::wasm_tools::{self, DecodeError, ExIm, WasmComponent};
6
7pub mod activity;
8pub mod cancellation_driver;
9pub mod component_logger;
10pub mod cron;
11pub mod engines;
12pub mod epoch_ticker;
13pub mod http_hooks;
14pub mod http_request_policy;
15pub(crate) mod js_imports;
16pub(crate) mod js_worker_utils;
17pub mod log_db_forwarder;
18pub(crate) mod policy_builder;
19pub mod registry;
20pub mod std_output_stream;
21#[cfg(any(test, feature = "test"))]
22pub mod testing_fn_registry;
23pub mod webhook;
24pub mod workflow;
25
26#[derive(thiserror::Error, Debug)]
27pub enum WasmFileError {
28    #[error("cannot decode: {0}")]
29    DecodeError(
30        #[from]
31        #[source]
32        wasm_tools::DecodeError,
33    ),
34    #[error("linking error - {reason}, details: {err}")]
35    LinkingError {
36        reason: StrVariant,
37        #[source]
38        err: Box<dyn Error + Send + Sync>,
39        context: SpanTrace,
40    },
41}
42impl WasmFileError {
43    pub fn linking_error(
44        reason: impl Into<StrVariant>,
45        error: impl Into<Box<dyn Error + Send + Sync>>,
46    ) -> WasmFileError {
47        WasmFileError::LinkingError {
48            reason: reason.into(),
49            err: error.into(),
50            context: SpanTrace::capture(),
51        }
52    }
53}
54
55pub mod envvar {
56    #[derive(Clone, derive_more::Debug)]
57    pub struct EnvVar {
58        pub key: String,
59        #[debug(skip)]
60        pub val: String,
61    }
62}
63
64#[derive(derive_more::Debug, Clone)]
65pub struct RunnableComponent {
66    #[debug(skip)]
67    pub wasmtime_component: wasmtime::component::Component,
68    pub wasm_component: WasmComponent,
69}
70impl RunnableComponent {
71    pub fn new<P: AsRef<Path>>(
72        wasm_path: P,
73        engine: &wasmtime::Engine,
74        component_type: ComponentType,
75    ) -> Result<Self, DecodeError> {
76        let wasm_path = wasm_path.as_ref();
77        let wasm_component = WasmComponent::new(wasm_path, component_type)?;
78        trace!("Decoding using wasmtime");
79        let wasmtime_component = {
80            let stopwatch = std::time::Instant::now();
81            let wasmtime_component = wasmtime::component::Component::from_file(engine, wasm_path)
82                .map_err(|err| {
83                DecodeError::new_with_source(
84                    format!("cannot parse {wasm_path:?} using wasmtime"),
85                    err,
86                )
87            })?;
88            debug!("Parsed with wasmtime in {:?}", stopwatch.elapsed());
89            wasmtime_component
90        };
91        Ok(Self {
92            wasmtime_component,
93            wasm_component,
94        })
95    }
96
97    pub fn index_exported_functions(
98        wasmtime_component: &wasmtime::component::Component,
99        exim: &ExIm,
100    ) -> Result<
101        hashbrown::HashMap<FunctionFqn, wasmtime::component::ComponentExportIndex>,
102        DecodeError,
103    > {
104        let mut exported_ffqn_to_index = hashbrown::HashMap::new();
105        for FunctionMetadata { ffqn, .. } in exim.get_exports(false) {
106            let Some(ifc_export_index) = wasmtime_component.get_export_index(None, &*ffqn.ifc_fqn)
107            else {
108                return Err(DecodeError::new_without_source(format!(
109                    "cannot find exported interface {ffqn}"
110                )));
111            };
112            let Some(fn_export_index) =
113                wasmtime_component.get_export_index(Some(&ifc_export_index), &*ffqn.function_name)
114            else {
115                return Err(DecodeError::new_without_source(format!(
116                    "cannot find exported function {ffqn}"
117                )));
118            };
119            exported_ffqn_to_index.insert(ffqn.clone(), fn_export_index);
120        }
121        Ok(exported_ffqn_to_index)
122    }
123}
124
125#[cfg(test)]
126pub(crate) mod tests {
127
128    mod populate_codegen_cache {
129        use crate::{
130            activity::activity_worker::test::compile_activity,
131            workflow::workflow_worker::test::compile_workflow,
132        };
133
134        #[rstest::rstest(wasm_path => [
135            test_programs_fibo_activity_builder::TEST_PROGRAMS_FIBO_ACTIVITY,
136            test_programs_http_get_activity_builder::TEST_PROGRAMS_HTTP_GET_ACTIVITY,
137            test_programs_sleep_activity_builder::TEST_PROGRAMS_SLEEP_ACTIVITY,
138            activity_js_runtime_builder::ACTIVITY_JS_RUNTIME,
139            ])]
140        #[tokio::test]
141        async fn activity(wasm_path: &str) {
142            compile_activity(wasm_path).await;
143        }
144
145        #[rstest::rstest(wasm_path => [
146            test_programs_fibo_workflow_builder::TEST_PROGRAMS_FIBO_WORKFLOW,
147            test_programs_http_get_workflow_builder::TEST_PROGRAMS_HTTP_GET_WORKFLOW,
148            test_programs_sleep_workflow_builder::TEST_PROGRAMS_SLEEP_WORKFLOW,
149            workflow_js_runtime_builder::WORKFLOW_JS_RUNTIME,
150            ])]
151        #[tokio::test]
152        async fn workflow(wasm_path: &str) {
153            compile_workflow(wasm_path).await;
154        }
155
156        #[rstest::rstest(wasm_path => [
157            test_programs_fibo_webhook_builder::TEST_PROGRAMS_FIBO_WEBHOOK,
158            webhook_js_runtime_builder::WEBHOOK_JS_RUNTIME,
159            ])]
160        #[test]
161        fn webhook(wasm_path: &str) {
162            crate::webhook::webhook_trigger::tests::compile_webhook(wasm_path);
163        }
164    }
165}