spin_manifest/schema/
v1.rs

1use serde::Deserialize;
2
3use spin_serde::FixedStringVersion;
4
5pub use super::common::{
6    ComponentBuildConfig as ComponentBuildConfigV1, ComponentSource as ComponentSourceV1,
7    Variable as VariableV1, WasiFilesMount as WasiFilesMountV1,
8};
9
10type Map<K, V> = indexmap::IndexMap<K, V>;
11
12/// App manifest
13#[derive(Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct AppManifestV1 {
16    /// `spin_manifest_version = "1"`
17    #[serde(alias = "spin_version")]
18    #[allow(dead_code)]
19    spin_manifest_version: FixedStringVersion<1>,
20    /// `name = "my-app"`
21    pub name: String,
22    /// `version = "1.0.0"`
23    #[serde(default)]
24    pub version: String,
25    /// `description = "App description"`
26    #[serde(default)]
27    pub description: String,
28    /// `authors = ["author@example.com"]`
29    #[serde(default)]
30    pub authors: Vec<String>,
31    /// `trigger = { ... }`
32    pub trigger: AppTriggerV1,
33    /// `[variables]`
34    #[serde(default)]
35    pub variables: Map<String, VariableV1>,
36    /// `[[component]]`
37    #[serde(rename = "component")]
38    #[serde(default)]
39    pub components: Vec<ComponentV1>,
40}
41
42/// App trigger config
43#[derive(Deserialize)]
44pub struct AppTriggerV1 {
45    /// `type = "trigger-type"`
46    #[serde(rename = "type")]
47    pub trigger_type: String,
48    /// Trigger config
49    #[serde(flatten)]
50    pub config: toml::Table,
51}
52
53/// Component definition
54#[derive(Deserialize)]
55#[serde(deny_unknown_fields)]
56pub struct ComponentV1 {
57    /// `id = "component-id"
58    pub id: String,
59    /// `source = ...`
60    pub source: ComponentSourceV1,
61    /// `[component.trigger]`
62    pub trigger: toml::Table,
63    /// `description = "Component description"`
64    #[serde(default)]
65    pub description: String,
66    /// `config = { name = "{{ app_var }}"}`
67    #[serde(default)]
68    pub config: Map<String, String>,
69    /// `environment = { VAR = "value" }`
70    #[serde(default)]
71    pub environment: Map<String, String>,
72    /// `files = [...]`
73    #[serde(default)]
74    pub files: Vec<WasiFilesMountV1>,
75    /// `exclude_files = ["secrets/*"]`
76    #[serde(default)]
77    pub exclude_files: Vec<String>,
78    /// `allowed_http_hosts = ["example.com"]`
79    #[serde(default)]
80    pub allowed_http_hosts: Vec<String>,
81    /// `allowed_outbound_hosts` = ["redis://redis.com:6379"]`
82    #[serde(default)]
83    pub allowed_outbound_hosts: Option<Vec<String>>,
84    /// `key_value_stores = ["default"]`
85    #[serde(default)]
86    pub key_value_stores: Vec<String>,
87    /// `sqlite_databases = ["default"]`
88    #[serde(default)]
89    pub sqlite_databases: Vec<String>,
90    /// `ai_models = ["llama2-chat"]`
91    #[serde(default)]
92    pub ai_models: Vec<String>,
93    /// Build configuration
94    #[serde(default)]
95    pub build: Option<ComponentBuildConfigV1>,
96}
97
98#[cfg(test)]
99mod tests {
100    use toml::toml;
101
102    use super::*;
103
104    #[derive(Deserialize)]
105    #[allow(dead_code)]
106    struct FakeGlobalTriggerConfig {
107        global_option: bool,
108    }
109
110    #[derive(Deserialize)]
111    #[allow(dead_code)]
112    struct FakeTriggerConfig {
113        option: Option<bool>,
114    }
115
116    #[test]
117    fn deserializing_trigger_configs() {
118        let manifest = AppManifestV1::deserialize(toml! {
119            spin_manifest_version = "1"
120            name = "trigger-configs"
121            trigger = { type = "fake", global_option = true }
122            [[component]]
123            id = "my-component"
124            source = "example.wasm"
125            [component.trigger]
126            option = true
127        })
128        .unwrap();
129
130        FakeGlobalTriggerConfig::deserialize(manifest.trigger.config).unwrap();
131
132        FakeTriggerConfig::deserialize(manifest.components[0].trigger.clone()).unwrap();
133    }
134}