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 spin_factor_outbound_http::wasi_2026_03_15::ServiceIndices as ServiceIndices2026_03_15;
5use wasmtime::component::InstancePre;
6use wasmtime_wasi::p2::bindings::CommandIndices;
7use wasmtime_wasi_http::handler::{HandlerState, ProxyHandler};
8use wasmtime_wasi_http::p2::bindings::ProxyIndices;
9use wasmtime_wasi_http::p3::bindings::ServicePre;
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<S: HandlerState> {
25    Spin,
26    Wagi(CommandIndices),
27    Wasi0_2(ProxyIndices),
28    Wasi0_3(ProxyHandler<S>),
29    Wasi2023_11_10(ProxyIndices2023_11_10),
30    Wasi2023_10_18(ProxyIndices2023_10_18),
31    Wasi2026_03_15(ServiceIndices2026_03_15),
32}
33
34impl<S: HandlerState> Clone for HandlerType<S> {
35    fn clone(&self) -> Self {
36        match self {
37            Self::Spin => Self::Spin,
38            Self::Wagi(indices) => Self::Wagi(indices.clone()),
39            Self::Wasi0_2(indices) => Self::Wasi0_2(indices.clone()),
40            Self::Wasi2023_11_10(indices) => Self::Wasi2023_11_10(indices.clone()),
41            Self::Wasi2023_10_18(indices) => Self::Wasi2023_10_18(indices.clone()),
42            Self::Wasi2026_03_15(indices) => Self::Wasi2026_03_15(indices.clone()),
43            Self::Wasi0_3(handler) => Self::Wasi0_3(handler.clone()),
44        }
45    }
46}
47
48/// The `incoming-handler` export for `wasi:http` version 0.2.0-rc-2023-10-18
49const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-10-18";
50/// The `incoming-handler` export for `wasi:http` version 0.2.0-rc-2023-11-10
51const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-11-10";
52/// The `incoming-handler` export prefix for all `wasi:http` 0.2 versions
53const WASI_HTTP_EXPORT_0_2_PREFIX: &str = "wasi:http/incoming-handler@0.2";
54/// The `handler` export `wasi:http` version 0.3.0-rc-2025-08-15
55const WASI_HTTP_EXPORT_0_3_0_RC_03_15: &str = "wasi:http/handler@0.3.0-rc-2026-03-15";
56/// The `handler` export prefix for all `wasi:http` 0.3 versions
57const WASI_HTTP_EXPORT_0_3_PREFIX: &str = "wasi:http/handler@0.3";
58/// The `inbound-http` export for `fermyon:spin`
59const SPIN_HTTP_EXPORT: &str = "fermyon:spin/inbound-http";
60
61impl<T, S: HandlerState<StoreData = T>> HandlerType<S> {
62    /// Determine the handler type from the exports of a component.
63    pub fn from_instance_pre(pre: &InstancePre<T>, handler_state: S) -> anyhow::Result<Self> {
64        let mut candidates = Vec::new();
65        if let Ok(indices) = ProxyIndices::new(pre) {
66            candidates.push(HandlerType::Wasi0_2(indices));
67        }
68        if ServicePre::new(pre.clone()).is_ok() {
69            candidates.push(HandlerType::Wasi0_3(ProxyHandler::new(handler_state)));
70        }
71        if let Ok(indices) = ProxyIndices2023_10_18::new(pre) {
72            candidates.push(HandlerType::Wasi2023_10_18(indices));
73        }
74        if let Ok(indices) = ProxyIndices2023_11_10::new(pre) {
75            candidates.push(HandlerType::Wasi2023_11_10(indices));
76        }
77        if let Ok(indices) = ServiceIndices2026_03_15::new(pre) {
78            candidates.push(HandlerType::Wasi2026_03_15(indices));
79        }
80        if pre
81            .component()
82            .get_export_index(None, SPIN_HTTP_EXPORT)
83            .is_some()
84        {
85            candidates.push(HandlerType::Spin);
86        }
87
88        match candidates.len() {
89            0 => {
90                anyhow::bail!(
91                    "Expected component to export one of \
92                    `{WASI_HTTP_EXPORT_2023_10_18}`, \
93                    `{WASI_HTTP_EXPORT_2023_11_10}`, \
94                    `{WASI_HTTP_EXPORT_0_2_PREFIX}.*`, \
95                    `{WASI_HTTP_EXPORT_0_3_0_RC_03_15}`, \
96                    `{WASI_HTTP_EXPORT_0_3_PREFIX}.*`, \
97                     or `{SPIN_HTTP_EXPORT}` but it exported none of those. \
98                     This may mean the component handles a different trigger, or that its `wasi:http` export is newer then those supported by Spin. \
99                     If you're sure this is an HTTP module, check if a Spin upgrade is available: this may handle the newer version."
100                )
101            }
102            1 => Ok(candidates.pop().unwrap()),
103            _ => anyhow::bail!(
104                "component exports multiple different handlers but \
105                     it's expected to export only one"
106            ),
107        }
108    }
109}