Skip to main content

coil_runtime/wasm/host/
prepare.rs

1use super::*;
2
3impl WasmHost {
4    pub fn prepare_page_invocation(
5        &self,
6        execution: &RequestExecution,
7    ) -> Result<Option<InvocationPlan>, WasmModelError> {
8        let method = http_method_to_wasm(execution.method);
9        let input = InvocationInput::Page(PageInvocation::new(
10            invocation_surface_path(execution),
11            method,
12        )?);
13        let context = self.request_context(execution, input)?;
14        self.registry.prepare_page_invocation(
15            invocation_surface_path(execution).as_str(),
16            method,
17            context,
18        )
19    }
20
21    pub fn begin_page_invocation(
22        &self,
23        execution: &RequestExecution,
24    ) -> Result<Option<WasmExecutionSession>, WasmModelError> {
25        Ok(self
26            .prepare_page_invocation(execution)?
27            .map(|plan| plan.begin_execution_with_executor(self.host_service_executor.clone())))
28    }
29
30    pub fn prepare_api_invocation(
31        &self,
32        execution: &RequestExecution,
33    ) -> Result<Option<InvocationPlan>, WasmModelError> {
34        let method = http_method_to_wasm(execution.method);
35        let input = InvocationInput::Api(ApiInvocation::new(
36            invocation_surface_path(execution),
37            method,
38        )?);
39        let context = self.request_context(execution, input)?;
40        self.registry.prepare_api_invocation(
41            invocation_surface_path(execution).as_str(),
42            method,
43            context,
44        )
45    }
46
47    pub fn begin_api_invocation(
48        &self,
49        execution: &RequestExecution,
50    ) -> Result<Option<WasmExecutionSession>, WasmModelError> {
51        Ok(self
52            .prepare_api_invocation(execution)?
53            .map(|plan| plan.begin_execution_with_executor(self.host_service_executor.clone())))
54    }
55
56    pub fn prepare_leased_job_invocation(
57        &self,
58        lease: &JobLease,
59    ) -> Result<Option<InvocationPlan>, WasmModelError> {
60        let trace_id = format!("job:{}", lease.record.spec.job_id.as_str());
61        let principal = ExtensionPrincipal::service_account("runtime.jobs");
62        let attempts = lease.record.attempts.saturating_add(1);
63        let job_name = lease.record.spec.job_name.as_str();
64
65        if let Some(declared_job) = job_name.strip_prefix("event-handler:") {
66            return self.prepare_job_invocation(declared_job, attempts, trace_id, principal);
67        }
68
69        match self
70            .registered_jobs
71            .iter()
72            .find(|definition| definition.contract.name == job_name)
73            .map(|definition| definition.contract.trigger)
74        {
75            Some(JobTriggerKind::Scheduled) => {
76                self.prepare_scheduled_job_invocation(job_name, trace_id, principal)
77            }
78            _ => self.prepare_job_invocation(job_name, attempts, trace_id, principal),
79        }
80    }
81
82    pub fn begin_leased_job_invocation(
83        &self,
84        lease: &JobLease,
85    ) -> Result<Option<WasmExecutionSession>, WasmModelError> {
86        Ok(self
87            .prepare_leased_job_invocation(lease)?
88            .map(|plan| plan.begin_execution_with_executor(self.host_service_executor.clone())))
89    }
90
91    pub fn prepare_job_invocation(
92        &self,
93        job_name: &str,
94        attempt: u32,
95        trace_id: impl Into<String>,
96        principal: ExtensionPrincipal,
97    ) -> Result<Option<InvocationPlan>, WasmModelError> {
98        let input = InvocationInput::Job(JobInvocation::new(job_name.to_string(), attempt)?);
99        let context = self.async_context(trace_id.into(), principal, input)?;
100        self.registry.prepare_job_invocation(job_name, context)
101    }
102
103    pub fn begin_job_invocation(
104        &self,
105        job_name: &str,
106        attempt: u32,
107        trace_id: impl Into<String>,
108        principal: ExtensionPrincipal,
109    ) -> Result<Option<WasmExecutionSession>, WasmModelError> {
110        Ok(self
111            .prepare_job_invocation(job_name, attempt, trace_id, principal)?
112            .map(|plan| plan.begin_execution_with_executor(self.host_service_executor.clone())))
113    }
114
115    pub fn prepare_scheduled_job_invocation(
116        &self,
117        job_name: &str,
118        trace_id: impl Into<String>,
119        principal: ExtensionPrincipal,
120    ) -> Result<Option<InvocationPlan>, WasmModelError> {
121        let input =
122            InvocationInput::ScheduledJob(ScheduledJobInvocation::new(job_name.to_string())?);
123        let context = self.async_context(trace_id.into(), principal, input)?;
124        self.registry
125            .prepare_scheduled_job_invocation(job_name, context)
126    }
127
128    pub fn begin_scheduled_job_invocation(
129        &self,
130        job_name: &str,
131        trace_id: impl Into<String>,
132        principal: ExtensionPrincipal,
133    ) -> Result<Option<WasmExecutionSession>, WasmModelError> {
134        Ok(self
135            .prepare_scheduled_job_invocation(job_name, trace_id, principal)?
136            .map(|plan| plan.begin_execution_with_executor(self.host_service_executor.clone())))
137    }
138
139    pub fn prepare_webhook_invocation(
140        &self,
141        source: &str,
142        event: &str,
143        verified: bool,
144        replay_protected: bool,
145        trace_id: impl Into<String>,
146        principal: ExtensionPrincipal,
147    ) -> Result<Option<InvocationPlan>, WasmModelError> {
148        let input = InvocationInput::Webhook(WebhookInvocation::new(
149            source.to_string(),
150            event.to_string(),
151            verified,
152            replay_protected,
153        )?);
154        let context = self.async_context(trace_id.into(), principal, input)?;
155        let prepared = self
156            .registry
157            .prepare_webhook_invocation(source, event, context.clone());
158        if let Err(error) = &prepared {
159            let status = match error {
160                WasmModelError::UnverifiedWebhook { .. } => {
161                    Some(services::WebhookObservationStatus::VerificationFailed)
162                }
163                WasmModelError::ReplayUnsafeWebhook { .. } => {
164                    Some(services::WebhookObservationStatus::ReplayRejected)
165                }
166                _ => None,
167            };
168            if let Some(status) = status {
169                let _ = self.host_services.record_webhook_observation(
170                    source,
171                    event,
172                    status,
173                    &context,
174                    Some(error.to_string()),
175                );
176            }
177        }
178        prepared
179    }
180
181    pub fn begin_webhook_invocation(
182        &self,
183        source: &str,
184        event: &str,
185        verified: bool,
186        replay_protected: bool,
187        trace_id: impl Into<String>,
188        principal: ExtensionPrincipal,
189    ) -> Result<Option<WasmExecutionSession>, WasmModelError> {
190        Ok(self
191            .prepare_webhook_invocation(
192                source,
193                event,
194                verified,
195                replay_protected,
196                trace_id,
197                principal,
198            )?
199            .map(|plan| plan.begin_execution_with_executor(self.host_service_executor.clone())))
200    }
201
202    pub fn prepare_admin_widget_invocations(
203        &self,
204        slot: &str,
205        execution: &RequestExecution,
206    ) -> Result<Vec<InvocationPlan>, WasmModelError> {
207        let input = InvocationInput::AdminWidget(AdminWidgetInvocation::new(slot.to_string())?);
208        let context = self.request_context(execution, input)?;
209        self.registry
210            .prepare_admin_widget_invocations(slot, context)
211    }
212
213    pub fn begin_admin_widget_invocations(
214        &self,
215        slot: &str,
216        execution: &RequestExecution,
217    ) -> Result<Vec<WasmExecutionSession>, WasmModelError> {
218        Ok(self
219            .prepare_admin_widget_invocations(slot, execution)?
220            .into_iter()
221            .map(|plan| plan.begin_execution_with_executor(self.host_service_executor.clone()))
222            .collect())
223    }
224
225    pub fn prepare_render_hook_invocations(
226        &self,
227        slot: &str,
228        execution: &RequestExecution,
229    ) -> Result<Vec<InvocationPlan>, WasmModelError> {
230        let input = InvocationInput::RenderHook(RenderHookInvocation::new(slot.to_string())?);
231        let context = self.request_context(execution, input)?;
232        self.registry.prepare_render_hook_invocations(slot, context)
233    }
234
235    pub fn begin_render_hook_invocations(
236        &self,
237        slot: &str,
238        execution: &RequestExecution,
239    ) -> Result<Vec<WasmExecutionSession>, WasmModelError> {
240        Ok(self
241            .prepare_render_hook_invocations(slot, execution)?
242            .into_iter()
243            .map(|plan| plan.begin_execution_with_executor(self.host_service_executor.clone()))
244            .collect())
245    }
246}