Skip to content

Commit e9427f3

Browse files
authored
Merge pull request #2997 from itowlson/schema-for-manifest
Generate schema for v2 manifest
2 parents eb7a06d + ba914b8 commit e9427f3

File tree

13 files changed

+242
-22
lines changed

13 files changed

+242
-22
lines changed

.github/workflows/release.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
extraArgs: "--features openssl/vendored",
3535
target: "",
3636
targetDir: "target/release",
37+
generateManifestSchema: true,
3738
}
3839
- {
3940
os: "ubuntu-22.04",
@@ -132,6 +133,10 @@ jobs:
132133
--output-signature spin.sig \
133134
${{ matrix.config.targetDir }}/spin${{ matrix.config.extension }}
134135
136+
- name: Generate manifest schema
137+
if: matrix.config.generateManifestSchema
138+
run: ${{ matrix.config.targetDir }}/spin${{ matrix.config.extension }} maintenance generate-manifest-schema -o manifest.schema.json
139+
135140
- name: package release assets
136141
if: runner.os != 'Windows'
137142
shell: bash
@@ -167,6 +172,13 @@ jobs:
167172
with:
168173
name: spin-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}
169174
path: _dist/spin-${{ env.RELEASE_VERSION }}-${{ env.RUNNER_OS }}-${{ matrix.config.arch }}.zip
175+
176+
- name: Upload schema as GitHub artifact
177+
if: matrix.config.generateManifestSchema
178+
uses: actions/upload-artifact@v4
179+
with:
180+
name: manifest.schema.json
181+
path: manifest.schema.json
170182

171183
checksums:
172184
name: generate release checksums
@@ -206,13 +218,20 @@ jobs:
206218
steps:
207219
- uses: actions/checkout@v3
208220

209-
- name: download release assets
221+
- name: download binaries for release assets
210222
uses: actions/download-artifact@v4
211223
with:
212224
pattern: spin-*
213225
path: _dist
214226
merge-multiple: true
215227

228+
- name: download schema for release assets
229+
uses: actions/download-artifact@v4
230+
with:
231+
pattern: manifest.schema.json
232+
path: _dist
233+
merge-multiple: true
234+
216235
- name: check if pre-release
217236
shell: bash
218237
run: |

Cargo.lock

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pretty_assertions = "1.3"
3636
regex = { workspace = true }
3737
reqwest = { workspace = true }
3838
rpassword = "7"
39+
schemars = { version = "0.8.21", features = ["indexmap2", "semver"] }
3940
semver = { workspace = true }
4041
serde = { version = "1", features = ["derive"] }
4142
serde_json = { workspace = true }

crates/manifest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition = { workspace = true }
77
[dependencies]
88
anyhow = { workspace = true }
99
indexmap = { workspace = true, features = ["serde"] }
10+
schemars = { version = "0.8.21", features = ["indexmap2", "semver"] }
1011
semver = { workspace = true, features = ["serde"] }
1112
serde = { workspace = true }
1213
spin-serde = { path = "../serde" }

crates/manifest/src/compat.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub fn v1_to_v2_app(manifest: v1::AppManifestV1) -> Result<v2::AppManifest, Erro
6060
};
6161
components.insert(
6262
component_id.clone(),
63+
#[allow(deprecated)]
6364
v2::Component {
6465
source: component.source,
6566
description: component.description,

crates/manifest/src/schema/common.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use std::fmt::Display;
22

3+
use schemars::JsonSchema;
34
use serde::{Deserialize, Serialize};
45

56
use wasm_pkg_common::{package::PackageRef, registry::Registry};
67

78
/// Variable definition
8-
#[derive(Clone, Debug, Serialize, Deserialize)]
9+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
910
#[serde(deny_unknown_fields)]
1011
pub struct Variable {
1112
/// `required = true`
@@ -20,7 +21,7 @@ pub struct Variable {
2021
}
2122

2223
/// Component source
23-
#[derive(Clone, Debug, Serialize, Deserialize)]
24+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2425
#[serde(deny_unknown_fields, untagged)]
2526
pub enum ComponentSource {
2627
/// `"local.wasm"`
@@ -35,8 +36,10 @@ pub enum ComponentSource {
3536
/// `{ ... }`
3637
Registry {
3738
/// `registry = "example.com"`
39+
#[schemars(with = "Option<String>")]
3840
registry: Option<Registry>,
3941
/// `package = "example:component"`
42+
#[schemars(with = "String")]
4043
package: PackageRef,
4144
/// `version = "1.2.3"`
4245
version: String,
@@ -64,7 +67,7 @@ impl Display for ComponentSource {
6467
}
6568

6669
/// WASI files mount
67-
#[derive(Clone, Debug, Serialize, Deserialize)]
70+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
6871
#[serde(deny_unknown_fields, untagged)]
6972
pub enum WasiFilesMount {
7073
/// `"images/*.png"`
@@ -79,7 +82,7 @@ pub enum WasiFilesMount {
7982
}
8083

8184
/// Component build configuration
82-
#[derive(Clone, Debug, Serialize, Deserialize)]
85+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
8386
#[serde(deny_unknown_fields)]
8487
pub struct ComponentBuildConfig {
8588
/// `command = "cargo build"`
@@ -104,7 +107,7 @@ impl ComponentBuildConfig {
104107
}
105108

106109
/// Component build command or commands
107-
#[derive(Clone, Debug, Serialize, Deserialize)]
110+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
108111
#[serde(untagged)]
109112
pub enum Commands {
110113
/// `command = "cargo build"`
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use crate::schema::v2::{ComponentSpec, Map, OneOrManyComponentSpecs};
2+
use schemars::JsonSchema;
3+
4+
// The structs here allow dead code because they exist only
5+
// to represent JSON schemas, and are never instantiated.
6+
7+
#[allow(dead_code)]
8+
#[derive(JsonSchema)]
9+
pub struct TriggerSchema {
10+
/// HTTP triggers
11+
#[schemars(default)]
12+
http: Vec<HttpTriggerSchema>,
13+
/// Redis triggers
14+
#[schemars(default)]
15+
redis: Vec<RedisTriggerSchema>,
16+
}
17+
18+
#[allow(dead_code)]
19+
#[derive(JsonSchema)]
20+
#[schemars(deny_unknown_fields)]
21+
pub struct HttpTriggerSchema {
22+
/// `id = "trigger-id"`
23+
#[serde(default, skip_serializing_if = "String::is_empty")]
24+
pub id: String,
25+
/// `component = ...`
26+
#[serde(default, skip_serializing_if = "Option::is_none")]
27+
pub component: Option<ComponentSpec>,
28+
/// `components = { ... }`
29+
#[serde(default, skip_serializing_if = "Map::is_empty")]
30+
pub components: Map<String, OneOrManyComponentSpecs>,
31+
/// `route = "/user/:name/..."`
32+
route: HttpRouteSchema,
33+
/// `executor = { type = "wagi" }
34+
#[schemars(default, schema_with = "toml_table")]
35+
executor: Option<toml::Table>,
36+
}
37+
38+
#[allow(dead_code)]
39+
#[derive(JsonSchema)]
40+
#[schemars(untagged)]
41+
pub enum HttpRouteSchema {
42+
/// `route = "/user/:name/..."`
43+
Route(String),
44+
/// `route = { private = true }`
45+
Private(HttpPrivateEndpoint),
46+
}
47+
48+
#[allow(dead_code)]
49+
#[derive(JsonSchema)]
50+
#[schemars(deny_unknown_fields)]
51+
pub struct HttpPrivateEndpoint {
52+
/// Whether the private endpoint is private. This must be true.
53+
pub private: bool,
54+
}
55+
56+
#[allow(dead_code)]
57+
#[derive(JsonSchema)]
58+
#[schemars(deny_unknown_fields)]
59+
pub struct RedisTriggerSchema {
60+
/// `id = "trigger-id"`
61+
#[serde(default, skip_serializing_if = "String::is_empty")]
62+
pub id: String,
63+
/// `component = ...`
64+
#[serde(default, skip_serializing_if = "Option::is_none")]
65+
pub component: Option<ComponentSpec>,
66+
/// `components = { ... }`
67+
#[serde(default, skip_serializing_if = "Map::is_empty")]
68+
pub components: Map<String, OneOrManyComponentSpecs>,
69+
/// `channel = "my-messages"`
70+
channel: String,
71+
/// `address = "redis://redis.example.com:6379"`
72+
#[serde(default, skip_serializing_if = "Option::is_none")]
73+
address: Option<String>,
74+
}
75+
76+
pub fn toml_table(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
77+
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
78+
instance_type: Some(schemars::schema::SingleOrVec::Single(Box::new(
79+
schemars::schema::InstanceType::Object,
80+
))),
81+
..Default::default()
82+
})
83+
}
84+
85+
pub fn map_of_toml_tables(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
86+
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
87+
instance_type: Some(schemars::schema::SingleOrVec::Single(Box::new(
88+
schemars::schema::InstanceType::Object,
89+
))),
90+
..Default::default()
91+
})
92+
}
93+
94+
pub fn one_or_many<T: schemars::JsonSchema>(
95+
gen: &mut schemars::gen::SchemaGenerator,
96+
) -> schemars::schema::Schema {
97+
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
98+
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
99+
one_of: Some(vec![
100+
gen.subschema_for::<T>(),
101+
gen.subschema_for::<Vec<T>>(),
102+
]),
103+
..Default::default()
104+
})),
105+
..Default::default()
106+
})
107+
}

crates/manifest/src/schema/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod v2;
1010
// Types common between manifest versions. Re-exported from versioned modules
1111
// to make them easier to split if necessary.
1212
pub(crate) mod common;
13+
mod json_schema;
1314

1415
#[derive(Deserialize)]
1516
pub(crate) struct VersionProbe {

0 commit comments

Comments
 (0)