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