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!(
23 "Triggers must specify either component or static_response - {trigger_id} has neither"
24 )),
25 (Some(_), Some(_)) => Err(anyhow::anyhow!(
26 "Triggers must specify either component or static_response - {trigger_id} has both"
27 )),
28 (Some(c), None) => Ok(crate::routes::TriggerLookupKey::Component(c.to_string())),
29 (None, Some(_)) => Ok(crate::routes::TriggerLookupKey::Trigger(
30 trigger_id.to_string(),
31 )),
32 }
33 }
34}
35
36#[derive(Clone, Debug, Default, Deserialize, Serialize)]
42#[serde(deny_unknown_fields, rename_all = "kebab-case", tag = "type")]
43pub enum HttpExecutorType {
44 #[default]
48 #[serde(alias = "spin")]
49 Http,
50 Wagi(WagiTriggerConfig),
52}
53
54#[derive(Clone, Debug, Deserialize, Serialize)]
56#[serde(default, deny_unknown_fields)]
57pub struct WagiTriggerConfig {
58 #[serde(skip_serializing)]
60 pub entrypoint: String,
61
62 pub argv: String,
72}
73
74impl Default for WagiTriggerConfig {
75 fn default() -> Self {
76 const WAGI_DEFAULT_ENTRYPOINT: &str = "_start";
78 const WAGI_DEFAULT_ARGV: &str = "${SCRIPT_NAME} ${ARGS}";
79
80 Self {
81 entrypoint: WAGI_DEFAULT_ENTRYPOINT.to_owned(),
82 argv: WAGI_DEFAULT_ARGV.to_owned(),
83 }
84 }
85}
86
87#[derive(Clone, Debug, Deserialize, Serialize)]
90pub struct StaticResponse {
91 #[serde(default)]
92 status_code: Option<u16>,
93 #[serde(default)]
94 headers: indexmap::IndexMap<String, String>,
95 #[serde(default)]
96 body: Option<String>,
97}
98
99impl StaticResponse {
100 pub fn status(&self) -> u16 {
101 self.status_code.unwrap_or(200)
102 }
103
104 pub fn headers(&self) -> impl Iterator<Item = (&String, &String)> {
105 self.headers.iter()
106 }
107
108 pub fn body(&self) -> Option<&String> {
109 self.body.as_ref()
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn wagi_config_smoke_test() {
119 let HttpExecutorType::Wagi(config) = toml::toml! { type = "wagi" }.try_into().unwrap()
120 else {
121 panic!("wrong type");
122 };
123 assert_eq!(config.entrypoint, "_start");
124 assert_eq!(config.argv, "${SCRIPT_NAME} ${ARGS}");
125 }
126}