spin_manifest/schema/
common.rs

1use std::fmt::Display;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use wasm_pkg_common::{package::PackageRef, registry::Registry};
7
8use super::json_schema;
9
10/// The name of the application variable.
11#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
12#[serde(deny_unknown_fields)]
13pub struct Variable {
14    /// A brief description of the variable.
15    ///
16    /// Example: `description = "This is a variable"`
17    ///
18    /// Learn more: https://spinframework.dev/variables#adding-variables-to-your-applications
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub description: Option<String>,
21    /// Whether a value must be supplied at runtime. If not specified, required defaults
22    /// to `false`, and `default` must be provided.
23    ///
24    /// Example: `required = true`
25    ///
26    /// Learn more: https://spinframework.dev/variables#adding-variables-to-your-applications
27    #[serde(default, skip_serializing_if = "is_false")]
28    pub required: bool,
29    /// The value of the variable if no value is supplied at runtime. If specified,
30    /// the value must be a string. If not specified, `required`` must be `true`.
31    ///
32    /// Example: `default = "default value"`
33    ///
34    /// Learn more: https://spinframework.dev/variables#adding-variables-to-your-applications
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub default: Option<String>,
37    /// If set, this variable should be treated as sensitive.
38    ///
39    /// Example: `secret = true`
40    ///
41    /// Learn more: https://spinframework.dev/variables#adding-variables-to-your-applications
42    #[serde(default, skip_serializing_if = "is_false")]
43    pub secret: bool,
44}
45
46/// The file, package, or URL containing the component Wasm binary. This may be:
47///
48/// - The path to a Wasm file (relative to the manifest file)
49///
50/// Example: `source = "bin/cart.wasm"`
51///
52/// - The URL of a Wasm file downloadable over HTTP, accompanied by a digest to ensure integrity
53///
54/// Example: `source = { url = "https://example.com/example.wasm", digest = "sha256:6503...2375" }`
55///
56/// - The registry, package and version of a component from a registry
57///
58/// Example: `source = { registry = "ttl.sh", package = "user:registrytest", version="1.0.0" }`
59///
60/// Learn more: https://spinframework.dev/writing-apps#the-component-source
61#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
62#[serde(deny_unknown_fields, untagged)]
63pub enum ComponentSource {
64    /// `source = "bin/cart.wasm"`
65    #[schemars(description = "")] // schema docs are on the parent
66    Local(String),
67    /// `source = { url = "https://example.com/example.wasm", digest = "sha256:6503...2375" }`
68    #[schemars(description = "")] // schema docs are on the parent
69    Remote {
70        /// The URL of the Wasm component binary.
71        ///
72        /// Example: `url = "https://example.test/remote.wasm"`
73        ///
74        /// Learn more: https://spinframework.dev/writing-apps#the-component-source
75        url: String,
76        /// The SHA256 digest of the Wasm component binary. This must be prefixed with `sha256:`.
77        ///
78        /// Example: `digest = `"sha256:abc123..."`
79        ///
80        /// Learn more: https://spinframework.dev/writing-apps#the-component-source
81        digest: String,
82    },
83    /// `source = { registry = "ttl.sh", package = "user:registrytest", version="1.0.0" }`
84    #[schemars(description = "")] // schema docs are on the parent
85    Registry {
86        /// The registry containing the Wasm component binary.
87        ///
88        /// Example: `registry = "example.com"`
89        ///
90        /// Learn more: https://spinframework.dev/writing-apps#the-component-source
91        #[schemars(with = "Option<String>")]
92        registry: Option<Registry>,
93        /// The package containing the Wasm component binary.
94        ///
95        /// Example: `package = "example:component"`
96        ///
97        /// Learn more: https://spinframework.dev/writing-apps#the-component-source
98        #[schemars(with = "String")]
99        package: PackageRef,
100        /// The version of the package containing the Wasm component binary.
101        ///
102        /// Example: `version = "1.2.3"`
103        ///
104        /// Learn more: https://spinframework.dev/writing-apps#the-component-source
105        version: String,
106    },
107}
108
109impl Display for ComponentSource {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            ComponentSource::Local(path) => write!(f, "{path:?}"),
113            ComponentSource::Remote { url, digest } => write!(f, "{url:?} with digest {digest:?}"),
114            ComponentSource::Registry {
115                registry,
116                package,
117                version,
118            } => {
119                let registry_suffix = match registry {
120                    None => "default registry".to_owned(),
121                    Some(r) => format!("registry {r:?}"),
122                };
123                write!(f, "\"{package}@{version}\" from {registry_suffix}")
124            }
125        }
126    }
127}
128
129/// The files the component is allowed to read. Each list entry is either:
130///
131/// - a glob pattern (e.g. "assets/**/*.jpg"); or
132///
133/// - a source-destination pair indicating where a host directory should be mapped in the guest (e.g. { source = "assets", destination = "/" })
134///
135/// Learn more: https://spinframework.dev/writing-apps#including-files-with-components
136#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
137#[serde(deny_unknown_fields, untagged)]
138pub enum WasiFilesMount {
139    /// `"images/*.png"`
140    #[schemars(description = "")] // schema docs are on the parent
141    Pattern(String),
142    /// `{ ... }`
143    #[schemars(description = "")] // schema docs are on the parent
144    Placement {
145        /// The directory to be made available in the guest.
146        ///
147        /// Example: `source = "content/dir"`
148        ///
149        /// Learn more: https://spinframework.dev/writing-apps#including-files-with-components
150        source: String,
151        /// The path where the `source` directory appears in the guest. Must be absolute.
152        ///
153        /// `destination = "/"`
154        ///
155        /// Learn more: https://spinframework.dev/writing-apps#including-files-with-components
156        destination: String,
157    },
158}
159
160/// Component build configuration
161#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
162#[serde(deny_unknown_fields)]
163pub struct ComponentBuildConfig {
164    /// The command or commands to build the application. If multiple commands
165    /// are specified, they are run sequentially from left to right.
166    ///
167    /// Example: `command = "cargo build"`, `command = ["npm install", "npm run build"]`
168    ///
169    /// Learn more: https://spinframework.dev/build#setting-up-for-spin-build
170    pub command: Commands,
171    /// The working directory for the build command. If omitted, the build working
172    /// directory is the directory containing `spin.toml`.
173    ///
174    /// Example: `workdir = "components/main"
175    ///
176    /// Learn more: https://spinframework.dev/build#overriding-the-working-directory
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub workdir: Option<String>,
179    /// Source files to use in `spin watch`. This is a set of paths or glob patterns (relative
180    /// to the build working directory). A change to any matching file causes
181    /// `spin watch` to rebuild the application before restarting the application.
182    ///
183    /// Example: `watch = ["src/**/*.rs"]`
184    ///
185    /// Learn more: https://spinframework.dev/running-apps#monitoring-applications-for-changes
186    #[serde(default, skip_serializing_if = "Vec::is_empty")]
187    #[schemars(with = "Vec<json_schema::WatchCommand>")]
188    pub watch: Vec<String>,
189}
190
191impl ComponentBuildConfig {
192    /// The commands to execute for the build
193    pub fn commands(&self) -> impl ExactSizeIterator<Item = &String> {
194        let as_vec = match &self.command {
195            Commands::Single(cmd) => vec![cmd],
196            Commands::Multiple(cmds) => cmds.iter().collect(),
197        };
198        as_vec.into_iter()
199    }
200}
201
202/// The command or commands to build the application. If multiple commands
203/// are specified, they are run sequentially from left to right.
204///
205/// Example: `command = "cargo build"`, `command = ["npm install", "npm run build"]`
206///
207/// Learn more: https://spinframework.dev/build#setting-up-for-spin-build
208#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
209#[serde(untagged)]
210pub enum Commands {
211    /// `command = "cargo build"`
212    #[schemars(description = "")] // schema docs are on the parent
213    Single(String),
214    /// `command = ["cargo build", "wac encode compose-deps.wac -d my:pkg=app.wasm --registry fermyon.com"]`
215    #[schemars(description = "")] // schema docs are on the parent
216    Multiple(Vec<String>),
217}
218
219fn is_false(v: &bool) -> bool {
220    !*v
221}