1use serde::{Deserialize, Serialize};
2use spin_http_routes::HttpTriggerRouteConfig;
3
4#[derive(Clone, Debug, Deserialize, Serialize)]
6#[serde(deny_unknown_fields)]
7pub struct HttpTriggerConfig {
8 pub component: Option<String>,
10 pub static_response: Option<StaticResponse>,
12 pub route: HttpTriggerRouteConfig,
14 #[serde(default)]
16 pub executor: Option<HttpExecutorType>,
17}
18
19impl HttpTriggerConfig {
20 pub fn lookup_key(&self, trigger_id: &str) -> anyhow::Result<crate::routes::TriggerLookupKey> {
21 match (&self.component, &self.static_response) {
22 (None, None) => Err(anyhow::anyhow!("Triggers must specify either component or static_response - {trigger_id} has neither")),
23 (Some(_), Some(_)) => Err(anyhow::anyhow!("Triggers must specify either component or static_response - {trigger_id} has both")),
24 (Some(c), None) => Ok(crate::routes::TriggerLookupKey::Component(c.to_string())),
25 (None, Some(_)) => Ok(crate::routes::TriggerLookupKey::Trigger(trigger_id.to_string())),
26 }
27 }
28}
29
30#[derive(Clone, Debug, Default, Deserialize, Serialize)]
36#[serde(deny_unknown_fields, rename_all = "kebab-case", tag = "type")]
37pub enum HttpExecutorType {
38 #[default]
42 #[serde(alias = "spin")]
43 Http,
44 Wagi(WagiTriggerConfig),
46 Wasip3Unstable,
48}
49
50#[derive(Clone, Debug, Deserialize, Serialize)]
52#[serde(default, deny_unknown_fields)]
53pub struct WagiTriggerConfig {
54 #[serde(skip_serializing)]
56 pub entrypoint: String,
57
58 pub argv: String,
68}
69
70impl Default for WagiTriggerConfig {
71 fn default() -> Self {
72 const WAGI_DEFAULT_ENTRYPOINT: &str = "_start";
74 const WAGI_DEFAULT_ARGV: &str = "${SCRIPT_NAME} ${ARGS}";
75
76 Self {
77 entrypoint: WAGI_DEFAULT_ENTRYPOINT.to_owned(),
78 argv: WAGI_DEFAULT_ARGV.to_owned(),
79 }
80 }
81}
82
83#[derive(Clone, Debug, Deserialize, Serialize)]
86pub struct StaticResponse {
87 #[serde(default)]
88 status_code: Option<u16>,
89 #[serde(default)]
90 headers: indexmap::IndexMap<String, String>,
91 #[serde(default)]
92 body: Option<String>,
93}
94
95impl StaticResponse {
96 pub fn status(&self) -> u16 {
97 self.status_code.unwrap_or(200)
98 }
99
100 pub fn headers(&self) -> impl Iterator<Item = (&String, &String)> {
101 self.headers.iter()
102 }
103
104 pub fn body(&self) -> Option<&String> {
105 self.body.as_ref()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn wagi_config_smoke_test() {
115 let HttpExecutorType::Wagi(config) = toml::toml! { type = "wagi" }.try_into().unwrap()
116 else {
117 panic!("wrong type");
118 };
119 assert_eq!(config.entrypoint, "_start");
120 assert_eq!(config.argv, "${SCRIPT_NAME} ${ARGS}");
121 }
122}