Skip to main content

spin_http/
config.rs

1use serde::{Deserialize, Serialize};
2use spin_http_routes::HttpTriggerRouteConfig;
3
4/// Configuration for the HTTP trigger
5#[derive(Clone, Debug, Deserialize, Serialize)]
6#[serde(deny_unknown_fields)]
7pub struct HttpTriggerConfig {
8    /// Component ID to invoke
9    pub component: Option<String>,
10    /// Static response to send
11    pub static_response: Option<StaticResponse>,
12    /// HTTP route the component will be invoked for
13    pub route: HttpTriggerRouteConfig,
14    /// The HTTP executor the component requires
15    #[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/// The executor for the HTTP component.
37/// The component can either implement the Spin HTTP interface,
38/// the `wasi-http` interface, or the Wagi CGI interface.
39///
40/// If an executor is not specified, the inferred default is `HttpExecutor::Spin`.
41#[derive(Clone, Debug, Default, Deserialize, Serialize)]
42#[serde(deny_unknown_fields, rename_all = "kebab-case", tag = "type")]
43pub enum HttpExecutorType {
44    /// The component implements an HTTP based interface.
45    ///
46    /// This can be either `fermyon:spin/inbound-http` or `wasi:http/incoming-handler`
47    #[default]
48    #[serde(alias = "spin")]
49    Http,
50    /// The component implements the Wagi CGI interface.
51    Wagi(WagiTriggerConfig),
52}
53
54/// Wagi specific configuration for the http executor.
55#[derive(Clone, Debug, Deserialize, Serialize)]
56#[serde(default, deny_unknown_fields)]
57pub struct WagiTriggerConfig {
58    /// The name of the entrypoint. (DEPRECATED)
59    #[serde(skip_serializing)]
60    pub entrypoint: String,
61
62    /// A string representation of the argv array.
63    ///
64    /// This should be a space-separate list of strings. The value
65    /// ${SCRIPT_NAME} will be replaced with the Wagi SCRIPT_NAME,
66    /// and the value ${ARGS} will be replaced with the query parameter
67    /// name/value pairs presented as args. For example,
68    /// `param1=val1&param2=val2` will become `param1=val1 param2=val2`,
69    /// which will then be presented to the program as two arguments
70    /// in argv.
71    pub argv: String,
72}
73
74impl Default for WagiTriggerConfig {
75    fn default() -> Self {
76        /// This is the default Wagi entrypoint.
77        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/// A static response to be served directly by the host
88/// without instantiating a component.
89#[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}