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::bindings::ProxyIndices;
7use wasmtime_wasi_http::handler::{HandlerState, ProxyHandler, ProxyPre};
8use wasmtime_wasi_http::p3::bindings::ProxyPre as P3ProxyPre;
9
10use crate::config::HttpExecutorType;
11
12#[derive(Clone, Debug, Default, Deserialize, Serialize)]
13#[serde(deny_unknown_fields)]
14pub struct Metadata {
15    // The based url
16    #[serde(default = "default_base")]
17    pub base: String,
18}
19
20pub fn default_base() -> String {
21    "/".into()
22}
23
24/// The type of http handler export used by a component.
25pub enum HandlerType<S: HandlerState> {
26    Spin,
27    Wagi(CommandIndices),
28    Wasi0_2(ProxyIndices),
29    Wasi0_3(ProxyHandler<S>),
30    Wasi2023_11_10(ProxyIndices2023_11_10),
31    Wasi2023_10_18(ProxyIndices2023_10_18),
32}
33
34/// The `incoming-handler` export for `wasi:http` version rc-2023-10-18
35const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-10-18";
36/// The `incoming-handler` export for `wasi:http` version rc-2023-11-10
37const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-11-10";
38/// The `incoming-handler` export prefix for all `wasi:http` 0.2 versions
39const WASI_HTTP_EXPORT_0_2_PREFIX: &str = "wasi:http/incoming-handler@0.2";
40/// The `handler` export `wasi:http` version 0.3.0-rc-2025-08-15
41const WASI_HTTP_EXPORT_0_3_0_RC_2025_09_16: &str = "wasi:http/handler@0.3.0-rc-2025-09-16";
42/// The `inbound-http` export for `fermyon:spin`
43const SPIN_HTTP_EXPORT: &str = "fermyon:spin/inbound-http";
44
45impl<T, S: HandlerState<StoreData = T>> HandlerType<S> {
46    /// Determine the handler type from the exports of a component.
47    pub fn from_instance_pre(pre: &InstancePre<T>, handler_state: S) -> anyhow::Result<Self> {
48        let mut candidates = Vec::new();
49        if let Ok(indices) = ProxyIndices::new(pre) {
50            candidates.push(HandlerType::Wasi0_2(indices));
51        }
52        if let Ok(pre) = P3ProxyPre::new(pre.clone()) {
53            candidates.push(HandlerType::Wasi0_3(ProxyHandler::new(
54                handler_state,
55                ProxyPre::P3(pre),
56            )));
57        }
58        if let Ok(indices) = ProxyIndices2023_10_18::new(pre) {
59            candidates.push(HandlerType::Wasi2023_10_18(indices));
60        }
61        if let Ok(indices) = ProxyIndices2023_11_10::new(pre) {
62            candidates.push(HandlerType::Wasi2023_11_10(indices));
63        }
64        if pre
65            .component()
66            .get_export_index(None, SPIN_HTTP_EXPORT)
67            .is_some()
68        {
69            candidates.push(HandlerType::Spin);
70        }
71
72        match candidates.len() {
73            0 => {
74                anyhow::bail!(
75                    "Expected component to export one of \
76                    `{WASI_HTTP_EXPORT_2023_10_18}`, \
77                    `{WASI_HTTP_EXPORT_2023_11_10}`, \
78                    `{WASI_HTTP_EXPORT_0_2_PREFIX}.*`, \
79                    `{WASI_HTTP_EXPORT_0_3_0_RC_2025_09_16}`, \
80                     or `{SPIN_HTTP_EXPORT}` but it exported none of those. \
81                     This may mean the component handles a different trigger, or that its `wasi:http` export is newer then those supported by Spin. \
82                     If you're sure this is an HTTP module, check if a Spin upgrade is available: this may handle the newer version."
83                )
84            }
85            1 => Ok(candidates.pop().unwrap()),
86            _ => anyhow::bail!(
87                "component exports multiple different handlers but \
88                     it's expected to export only one"
89            ),
90        }
91    }
92
93    /// Validate that the [`HandlerType`] is compatible with the [`HttpExecutorType`].
94    pub fn validate_executor(&self, executor: &Option<HttpExecutorType>) -> anyhow::Result<()> {
95        match (self, executor) {
96            (HandlerType::Wasi0_3(_), Some(HttpExecutorType::Wasip3Unstable)) => {
97                terminal::warn!(
98                    "You’re using wasip3-unstable, an experimental executor for the \
99                    WASI Preview 3 RC interfaces, which will be removed in a future Spin release."
100                );
101                Ok(())
102            }
103            (HandlerType::Wasi0_3(_), Some(_) | None) => {
104                anyhow::bail!(
105                    "`{WASI_HTTP_EXPORT_0_3_0_RC_2025_09_16}` is currently unstable and will be \
106                    removed in a future Spin release. You can opt-in to this unstable interface \
107                    by adding `executor = {{ type = \"wasip3-unstable\" }}` to the appropriate \
108                    `[[trigger.http]]` section of your spin.toml file."
109                );
110            }
111            (handler_type, Some(HttpExecutorType::Wasip3Unstable)) => {
112                anyhow::bail!(
113                    "The wasip3-unstable trigger executor expected a component that \
114                    exports `{WASI_HTTP_EXPORT_0_3_0_RC_2025_09_16}` but found a \
115                    component with type {name}",
116                    name = handler_type.name(),
117                )
118            }
119            (_, _) => Ok(()),
120        }
121    }
122
123    fn name(&self) -> &str {
124        match self {
125            HandlerType::Spin => SPIN_HTTP_EXPORT,
126            HandlerType::Wasi0_2(_) => WASI_HTTP_EXPORT_0_2_PREFIX,
127            HandlerType::Wasi0_3(_) => WASI_HTTP_EXPORT_0_3_0_RC_2025_09_16,
128            HandlerType::Wasi2023_11_10(_) => WASI_HTTP_EXPORT_2023_11_10,
129            HandlerType::Wasi2023_10_18(_) => WASI_HTTP_EXPORT_2023_10_18,
130            _ => unreachable!(), // WAGI variant will never appear here
131        }
132    }
133}