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 #[serde(default = "default_base")]
17 pub base: String,
18}
19
20pub fn default_base() -> String {
21 "/".into()
22}
23
24pub 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
34const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-10-18";
36const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-11-10";
38const WASI_HTTP_EXPORT_0_2_PREFIX: &str = "wasi:http/incoming-handler@0.2";
40const WASI_HTTP_EXPORT_0_3_0_RC_2025_09_16: &str = "wasi:http/handler@0.3.0-rc-2025-09-16";
42const SPIN_HTTP_EXPORT: &str = "fermyon:spin/inbound-http";
44
45impl<T, S: HandlerState<StoreData = T>> HandlerType<S> {
46 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 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!(), }
132 }
133}