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}