Skip to main content

spin_http/
trigger.rs

1use serde::{Deserialize, Serialize};
2use spin_factor_outbound_http::wasi_2023_10_18::ProxyIndices as ProxyIndices2023_10_18;
3use spin_factor_outbound_http::wasi_2023_11_10::ProxyIndices as ProxyIndices2023_11_10;
4use wasmtime::component::InstancePre;
5use wasmtime_wasi::p2::bindings::CommandIndices;
6use wasmtime_wasi_http::handler::{HandlerState, ProxyHandler, ProxyPre};
7use wasmtime_wasi_http::p2::bindings::ProxyIndices;
8use wasmtime_wasi_http::p3::bindings::{ServiceIndices, ServicePre};
9
10#[derive(Clone, Debug, Default, Deserialize, Serialize)]
11#[serde(deny_unknown_fields)]
12pub struct Metadata {
13    // The based url
14    #[serde(default = "default_base")]
15    pub base: String,
16}
17
18pub fn default_base() -> String {
19    "/".into()
20}
21
22/// The type of http handler export used by a component.
23pub enum HandlerType<S: HandlerState> {
24    Spin,
25    Wagi(CommandIndices),
26    Wasi0_2(ProxyIndices),
27    Wasi0_3(ServiceIndices, ProxyHandler<S>),
28    Wasi2023_11_10(ProxyIndices2023_11_10),
29    Wasi2023_10_18(ProxyIndices2023_10_18),
30}
31
32/// The `incoming-handler` export for `wasi:http` version rc-2023-10-18
33const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-10-18";
34/// The `incoming-handler` export for `wasi:http` version rc-2023-11-10
35const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-11-10";
36/// The `incoming-handler` export prefix for all `wasi:http` 0.2 versions
37const WASI_HTTP_EXPORT_0_2_PREFIX: &str = "wasi:http/incoming-handler@0.2";
38/// The `handler` export `wasi:http` version 0.3.0-rc-2025-08-15
39const WASI_HTTP_EXPORT_0_3_0_RC_03_15: &str = "wasi:http/handler@0.3.0-rc-2026-03-15";
40/// The `inbound-http` export for `fermyon:spin`
41const SPIN_HTTP_EXPORT: &str = "fermyon:spin/inbound-http";
42
43impl<T, S: HandlerState<StoreData = T>> HandlerType<S> {
44    /// Determine the handler type from the exports of a component.
45    pub fn from_instance_pre(pre: &InstancePre<T>, handler_state: S) -> anyhow::Result<Self> {
46        let mut candidates = Vec::new();
47        if let Ok(indices) = ProxyIndices::new(pre) {
48            candidates.push(HandlerType::Wasi0_2(indices));
49        }
50        if let Ok(pre) = ServicePre::new(pre.clone()) {
51            candidates.push(HandlerType::Wasi0_3(
52                // We `.unwrap()` here because the `Ok(_)` result from
53                // `ServicePre::new` above proves that `pre` implements
54                // `wasi:http/handler@0.3.0-rc-2026-03-15`, so this can't fail.
55                ServiceIndices::new(pre.instance_pre()).unwrap(),
56                ProxyHandler::new(handler_state, ProxyPre::P3(pre)),
57            ));
58        }
59        if let Ok(indices) = ProxyIndices2023_10_18::new(pre) {
60            candidates.push(HandlerType::Wasi2023_10_18(indices));
61        }
62        if let Ok(indices) = ProxyIndices2023_11_10::new(pre) {
63            candidates.push(HandlerType::Wasi2023_11_10(indices));
64        }
65        if pre
66            .component()
67            .get_export_index(None, SPIN_HTTP_EXPORT)
68            .is_some()
69        {
70            candidates.push(HandlerType::Spin);
71        }
72
73        match candidates.len() {
74            0 => {
75                anyhow::bail!(
76                    "Expected component to export one of \
77                    `{WASI_HTTP_EXPORT_2023_10_18}`, \
78                    `{WASI_HTTP_EXPORT_2023_11_10}`, \
79                    `{WASI_HTTP_EXPORT_0_2_PREFIX}.*`, \
80                    `{WASI_HTTP_EXPORT_0_3_0_RC_03_15}`, \
81                     or `{SPIN_HTTP_EXPORT}` but it exported none of those. \
82                     This may mean the component handles a different trigger, or that its `wasi:http` export is newer then those supported by Spin. \
83                     If you're sure this is an HTTP module, check if a Spin upgrade is available: this may handle the newer version."
84                )
85            }
86            1 => Ok(candidates.pop().unwrap()),
87            _ => anyhow::bail!(
88                "component exports multiple different handlers but \
89                     it's expected to export only one"
90            ),
91        }
92    }
93}