bynk_project/manifest_schema.rs
1//! #2096: the JSON Schema types for the `bynk.toml` tables this crate checks.
2//!
3//! `[project]`, `[paths]`, `[workers]`, `[unstable]` and the packaging tables
4//! are read by hand, from a `toml::Table`, against
5//! [`MANIFEST_TABLES`](crate::MANIFEST_TABLES) and
6//! [`MANIFEST_MAP_TABLES`](crate::MANIFEST_MAP_TABLES), so there is no serde
7//! struct to derive a schema from. These types describe them for an
8//! editor instead: each field's doc comment is the description a TOML extension
9//! shows on hover. `[lsp]` is here too, because its reader lives in `bynk-lsp`,
10//! which nothing that assembles the schema can depend on; `bynk-lsp`'s own test
11//! holds its `LspSection` to this one. `[fmt]` is not here: `bynk-fmt` derives it
12//! from the struct that reads it (`bynk_fmt::fmt_table_schema`).
13//!
14//! `cargo xtask manifest-schema` assembles the whole schema, and its test fails
15//! when a table or key here disagrees with
16//! [`MANIFEST_TABLES`](crate::MANIFEST_TABLES) or the committed
17//! schema is stale. Only built with the `schema` feature.
18
19use schemars::{JsonSchema, Schema, SchemaGenerator};
20
21/// A list of paths, or one path on its own (`include = "src"`).
22#[derive(JsonSchema)]
23#[serde(untagged)]
24#[allow(dead_code)]
25enum PathList {
26 One(String),
27 Many(Vec<String>),
28}
29
30/// Project metadata. Neither key affects compilation: both are for display
31/// only, and have no default.
32#[derive(JsonSchema)]
33#[serde(deny_unknown_fields)]
34#[allow(dead_code)]
35struct ProjectTable {
36 /// Display name for the project.
37 name: Option<String>,
38 /// Version string for the project.
39 version: Option<String>,
40}
41
42/// The project's source trees. Each path is relative to the directory holding
43/// `bynk.toml`. A conventional `src/` (and `tests/`) project, or a flat one with
44/// its `.bynk` files at the root, needs no `[paths]` table.
45#[derive(JsonSchema)]
46#[serde(deny_unknown_fields)]
47#[allow(dead_code)]
48struct PathsTable {
49 /// Trees to compile. Default: the conventional roots that exist (`src`, and
50 /// `tests` when present), or the project root itself when neither does.
51 include: Option<PathList>,
52 /// Subtrees to skip during discovery (monorepo, vendored or generated
53 /// `.bynk`). The tool's own `out` and `node_modules` caches and
54 /// dot-directories are always skipped. Default: `[]`.
55 #[schemars(extend("default" = []))]
56 exclude: Option<PathList>,
57}
58
59/// Language-server settings, read by `bynkc-lsp`.
60#[derive(JsonSchema)]
61#[serde(deny_unknown_fields)]
62#[allow(dead_code)]
63struct LspTable {
64 /// When diagnostics are computed: `"live"` (as you type) or `"on_save"`.
65 /// Default: `"live"`.
66 #[schemars(extend("enum" = ["live", "on_save"], "default" = "live"))]
67 diagnostics_mode: Option<String>,
68 /// Debounce interval, in milliseconds, for live diagnostics. Default: `300`.
69 #[schemars(extend("default" = 300))]
70 diagnostics_debounce_ms: Option<u64>,
71}
72
73/// Settings for the Cloudflare Workers a Workers build emits (`bynkc compile
74/// --target workers`, `bynk dev`, `bynk deploy`).
75#[derive(JsonSchema)]
76#[serde(deny_unknown_fields)]
77#[allow(dead_code)]
78struct WorkersTable {
79 /// Extra Cloudflare compatibility flags for every Worker's `wrangler.toml`,
80 /// after the default set (`global_fetch_strictly_public`). Names aren't
81 /// checked; a flag listed twice, or one already on by default, is dropped
82 /// with a warning. Default: `[]`.
83 #[schemars(extend("default" = []))]
84 compatibility_flags: Option<Vec<String>>,
85}
86
87/// Opts in to experimental standard-library items, which carry no
88/// compatibility promise and can't be used until they are listed here.
89#[derive(JsonSchema)]
90#[serde(deny_unknown_fields)]
91#[allow(dead_code)]
92struct UnstableTable {
93 /// Item paths to allow: a unit (`bynk.cloudflare`), an item it exports
94 /// (`bynk.cloudflare.Kv`), or a capability operation
95 /// (`bynk.cloudflare.Kv.list`). An entry covers its path and everything
96 /// under it. Default: `[]`.
97 #[schemars(extend("default" = []))]
98 allow: Option<Vec<String>>,
99}
100
101/// The deployments `bynk test --against <name>` may run the `system` suites
102/// against (end-to-end testing). A bare URL is never accepted on the command
103/// line: a target has to be named here.
104#[derive(JsonSchema)]
105#[serde(deny_unknown_fields)]
106#[allow(dead_code)]
107struct TestTable {
108 /// Named targets, as `[test.targets.<name>]`. `dev` is reserved for the
109 /// local `bynk dev` the runner starts itself.
110 targets: Option<std::collections::BTreeMap<String, TestTargetTable>>,
111}
112
113/// One deployment `bynk test --against <name>` runs against.
114#[derive(JsonSchema)]
115#[serde(deny_unknown_fields)]
116#[allow(dead_code)]
117struct TestTargetTable {
118 /// Each Worker's base URL, keyed by its worker-directory name
119 /// (`url.shop-orders = "https://shop-orders-preview.example.workers.dev"`).
120 url: std::collections::BTreeMap<String, String>,
121 /// The names of the secrets the deployed Workers verify with. Their values
122 /// are read from the environment when the suites run, never from this
123 /// file. Default: `[]`.
124 #[schemars(extend("default" = []))]
125 secrets: Option<Vec<String>>,
126 /// The non-secret values the target was deployed with. A case runs
127 /// against the target only if its `env` agrees with them. Default: `{}`.
128 vars: Option<std::collections::BTreeMap<String, String>>,
129 /// Marks a production deployment, which `--against` refuses. Default:
130 /// `false`.
131 #[schemars(extend("default" = false))]
132 production: Option<bool>,
133}
134
135/// A shareable project's identity. A project has `[package]` or `[project]`,
136/// never both.
137#[derive(JsonSchema)]
138#[serde(deny_unknown_fields)]
139#[allow(dead_code)]
140struct PackageTable {
141 /// The package's name, `organisation/package` (`acme/money`). The
142 /// organisation matches `[a-z][a-z0-9-]*`; the package matches
143 /// `[a-z][a-z0-9_]*` and isn't a Bynk keyword, since a dependency's
144 /// alias defaults to it. The organisation `bynk` is reserved.
145 #[schemars(extend("pattern" = "^[a-z][a-z0-9-]*/[a-z][a-z0-9_]*$"))]
146 name: String,
147 /// The package's version, in strict semver (`1.4.0`). Publishing will
148 /// require it.
149 version: Option<String>,
150 /// A one-line description of the package.
151 description: Option<String>,
152 /// The package's licence, as an SPDX expression (`MIT`).
153 license: Option<String>,
154 /// The URL of the package's source repository.
155 repository: Option<String>,
156 /// The units the package shares: commons and adapters it declares, never
157 /// a context. Default: every commons and adapter.
158 exports: Option<Vec<String>>,
159}
160
161/// One dependency: `package`, and exactly one of `version`, `git` (with
162/// `rev`) or `path`. `path` and `git` dependencies resolve (`git` through
163/// `bynk.lock`); `version` is refused until packaging slice 4.
164#[derive(JsonSchema)]
165#[serde(deny_unknown_fields)]
166#[allow(dead_code)]
167struct DependencyEntry {
168 /// The package, as `organisation/package`.
169 package: String,
170 /// A pinned version range from the registry (`^1.4`). Refused until
171 /// packaging slice 4.
172 version: Option<String>,
173 /// A git repository URL (`https://`, `ssh://` or `user@host:path`). Needs
174 /// `rev`; locked to a commit in `bynk.lock`.
175 git: Option<String>,
176 /// The tag, branch or commit of `git` to use.
177 rev: Option<String>,
178 /// A directory holding the package's `bynk.toml`, relative to this
179 /// project.
180 path: Option<String>,
181}
182
183/// The packages this project depends on, keyed by alias: the name source
184/// reaches each one's units by (`<alias>.<unit>`). An alias matches
185/// `[a-z][a-z0-9_]*`, isn't a Bynk keyword, and isn't `bynk`. Only a
186/// dependency's exported commons and adapters are reachable.
187#[derive(JsonSchema)]
188#[schemars(extend("propertyNames" = { "pattern": "^[a-z][a-z0-9_]*$" }))]
189#[allow(dead_code)]
190struct DependenciesTable(std::collections::BTreeMap<String, DependencyEntry>);
191
192/// The packages only this project's tests depend on, keyed by alias, as in
193/// `[dependencies]`. Only a suite may reach them, and a deployable build
194/// never includes them.
195#[derive(JsonSchema)]
196#[schemars(extend("propertyNames" = { "pattern": "^[a-z][a-z0-9_]*$" }))]
197#[allow(dead_code)]
198struct DevDependenciesTable(std::collections::BTreeMap<String, DependencyEntry>);
199
200/// One package registry. Credentials never go here: a token is read from
201/// `BYNK_REGISTRY_<NAME>_TOKEN` or the user credentials file.
202#[derive(JsonSchema)]
203#[serde(deny_unknown_fields)]
204#[allow(dead_code)]
205struct RegistryEntry {
206 /// The registry's index URL: `https://`, or `http://` to `localhost` only.
207 index: String,
208 /// The organisations whose packages resolve on this registry. An
209 /// organisation is listed under one registry at most, and never `bynk`.
210 organisations: Option<Vec<String>>,
211 /// The registry this one mirrors: `bynk` (the first-party registry) or
212 /// another registry here that isn't itself a mirror. Not with
213 /// `organisations`.
214 #[serde(rename = "mirror-of")]
215 mirror_of: Option<String>,
216 /// The registry's pinned signing key, `ed25519:<base64>`.
217 key: Option<String>,
218}
219
220/// Package registries, keyed by name (`[a-z][a-z0-9-]*`, not `bynk`). Checked
221/// now, used once packages are fetched. In a workspace, only the root declares
222/// them.
223#[derive(JsonSchema)]
224#[schemars(extend("propertyNames" = { "pattern": "^[a-z][a-z0-9-]*$" }))]
225#[allow(dead_code)]
226struct RegistriesTable(std::collections::BTreeMap<String, RegistryEntry>);
227
228/// Makes this directory a workspace root: a group of projects that `bynk
229/// check`, `fmt` and `test` run across. A workspace root isn't a project:
230/// its `bynk.toml` holds only `[workspace]` and `[registries]`.
231#[derive(JsonSchema)]
232#[serde(deny_unknown_fields)]
233#[allow(dead_code)]
234struct WorkspaceTable {
235 /// The member directories, relative to the root. A component may use `*`
236 /// and `?`, each `*` within one directory level (`packages/*`). Each
237 /// member holds its own `bynk.toml`.
238 members: Vec<String>,
239 /// Directories `members` matches that aren't members, as patterns.
240 /// Default: `[]`.
241 #[schemars(extend("default" = []))]
242 exclude: Option<Vec<String>>,
243}
244
245/// The schema of the `bynk.toml` table `name`, or `None` for a table this
246/// crate doesn't describe (`fmt`, which is `bynk-fmt`'s, or an unknown name).
247pub fn table_schema(name: &str, generator: &mut SchemaGenerator) -> Option<Schema> {
248 Some(match name {
249 "project" => generator.subschema_for::<ProjectTable>(),
250 "paths" => generator.subschema_for::<PathsTable>(),
251 "lsp" => generator.subschema_for::<LspTable>(),
252 "workers" => generator.subschema_for::<WorkersTable>(),
253 "unstable" => generator.subschema_for::<UnstableTable>(),
254 "test" => generator.subschema_for::<TestTable>(),
255 "package" => generator.subschema_for::<PackageTable>(),
256 "workspace" => generator.subschema_for::<WorkspaceTable>(),
257 "dependencies" => generator.subschema_for::<DependenciesTable>(),
258 "dev-dependencies" => generator.subschema_for::<DevDependenciesTable>(),
259 "registries" => generator.subschema_for::<RegistriesTable>(),
260 _ => return None,
261 })
262}