From 29f55f28ea0cb907b279ca0e32d6ec0efda5ce5c Mon Sep 17 00:00:00 2001 From: Kinflou Date: Sun, 1 Nov 2026 08:15:57 +0800 Subject: [PATCH] docs: documenting your schemas with `comline doc` A Guide page, the `comline doc` reference row and page, comline.toml's `[doc]` keys, `Render` in the generator protocol, and the documentation model's JSON Schema served at `schemas/doc-model/1.json` with a drift check beside the comline.toml schema's. The comline.toml schema copy is refreshed for `[doc]`. Module docs are now stored with each build, so the docstrings page says so. --- .github/workflows/schema-drift.yml | 10 +- docs/docs/guide/documenting.md | 136 ++ docs/docs/guide/idl/docstrings.md | 4 +- docs/docs/reference/cli.md | 1 + .../reference/congregation/comline-toml.md | 24 +- docs/docs/reference/generator-protocol.md | 41 +- docs/docs/reference/tools/doc.md | 69 + docs/docs/schemas/comline-toml/0.x.json | 51 + docs/docs/schemas/doc-model/1.json | 1930 +++++++++++++++++ docs/mkdocs.yml | 3 + scripts/sync-comline-toml-schema.py | 73 +- 11 files changed, 2305 insertions(+), 37 deletions(-) create mode 100644 docs/docs/guide/documenting.md create mode 100644 docs/docs/reference/tools/doc.md create mode 100644 docs/docs/schemas/doc-model/1.json diff --git a/.github/workflows/schema-drift.yml b/.github/workflows/schema-drift.yml index 4a5a605..d9822f8 100644 --- a/.github/workflows/schema-drift.yml +++ b/.github/workflows/schema-drift.yml @@ -2,15 +2,17 @@ name: Schema drift # `docs/docs/schemas/comline-toml/0.x.json` is served on the site as the # `comline.toml` JSON Schema, which `comline new` points a `#:schema` line at. -# It is a copy: generation derives it from `comline_codegen::config`. This -# checks the copy still equals generation's file. +# It is a copy: generation derives it from `comline_codegen::config`. +# `docs/docs/schemas/doc-model/1.json` is the documentation model's schema, +# a copy of core's `schemas/doc-model.json`. This checks both copies still +# equal their sources. # # Separate from the Docs workflow for the same reason as the other drift # checks: a fetch against another repo has no business blocking a deploy. on: schedule: - # A schema change lands in `generation` and never touches this repo, so a + # A schema change lands in `generation` or `core` and never touches this repo, so a # push-triggered run would not see it. Weekly catches that side. - cron: '45 7 * * 1' pull_request: @@ -37,7 +39,7 @@ jobs: with: python-version: "3.14" - - name: Compare with generation's schema + - name: Compare with generation's and core's schemas shell: bash run: | if ! python3 scripts/sync-comline-toml-schema.py | tee drift.txt; then diff --git a/docs/docs/guide/documenting.md b/docs/docs/guide/documenting.md new file mode 100644 index 0000000..53a5ace --- /dev/null +++ b/docs/docs/guide/documenting.md @@ -0,0 +1,136 @@ +# Documenting your schemas + +`comline doc` turns a package's docs into pages: the `//!` headers of +`config.idp` and of each schema, every declaration's `///` docstring, and what +each protocol resolves to on the wire. Nothing is written by hand, and +everything the package declares is documented. + +## 1. Write the docs + +Docs live in the schemas, next to what they describe: + +- **`///` docstrings** document the declaration below them: a struct, a field, + an enum, a protocol, a function, an error, a validator, a constant. A + `/// @name: ...` line documents one field or argument + ([Docstrings](idl/docstrings.md)). +- **`//!` headers** document a module, at the top of its `.ids` file, and the + package, at the top of `config.idp` + ([Module docs](idl/docstrings.md#module-docs)). + +```ids linenums="1" +//! The shop's API. + +/// One order. +struct Order { + /// What it costs, in cents. + total: u64 +} + +/// Orders things. +protocol Orders { + /// Place one. + /// @order: what to place + function place(order: Order) -> bool; +} +``` + +The first line of a docstring or header is its summary: index tables show it +next to each name. + +## 2. Render them + +```bash +comline doc +``` + +This compiles the working tree and writes Markdown into `docs/api/`: + +| File | What's on it | +|---|---| +| `index.md` | The package's name and version, its `config.idp` header, and a table of its modules. | +| `.md` | One per schema (`types::user` is `types/user.md`): its header, a table of what it declares, then each declaration with its docs, annotations, fields, arguments, variants, and each protocol's wire summary, the same table the editor's hover and `comline inspect` show. | +| `changelog.md` | With `--changelog-from`: what changed, as `comline diff` reports it. | +| `nav.yml` | The pages as a `nav` list for mkdocs or zensical. | + +Every declaration has an anchor, its name in lowercase (`api.md#order`), and a +type links to the declaration it names, on the same page or another module's. + +### Put them on a site + +The pages are plain Markdown, so they drop into an mkdocs or zensical site: +render into the site's `docs/` tree and copy `nav.yml`'s entries into +`mkdocs.yml`. + +```bash +comline doc --out site/docs/api +``` + +### Link to dependencies + +A type from a dependency or `std` is plain text, unless you say where that +package's docs live. Give each one a base URL in `comline.toml`: + +```toml +[doc] +links = { std = "https://example.com/std", billing = "https://docs.example.com/billing" } +``` + +A type from `billing`'s `api` module, `Invoice`, then links to +`https://docs.example.com/billing/api/#invoice`: the base, the module, and the +anchor, in the directory-URL form mkdocs and zensical publish pages at. + +## 3. Document a release + +`--version` documents a built version instead of the working tree: a version +string, a commit hash, or `HEAD`. + +```bash +comline doc --version 0.2.0 --out site/docs/api/0.2.0 +``` + +A build stores the `//!` headers with the version, so a version documents with +the headers it was built with, even after the sources change. Two things need +the source and are missing from a built version: protocols have no wire +summary, and dependency types have no link. A version built before headers +were stored has none. + +`--changelog-from` adds what changed since an earlier built version: + +```bash +comline doc --version 0.2.0 --changelog-from 0.1.0 +``` + +## 4. Or take the model + +`--format json` writes the documentation model itself, `model.json`: the same +data the pages are rendered from, as JSON. Its shape is a published schema, +[`doc-model/1.json`](../schemas/doc-model/1.json), so a tool of your own can +render it any way it likes. + +```bash +comline doc --format json +``` + +A renderer of your own can also run inside `comline doc`: a program speaking +the [generator protocol](../reference/generator-protocol.md#render) that answers +`Render` requests, named by `[doc] renderer` in `comline.toml`. It runs exactly +like an [external generator](extending/generators.md): a `.wasm` is sandboxed, +and every path it returns is checked before anything is written. + +## Settings + +Every flag has a `[doc]` key in [`comline.toml`](../reference/congregation/comline-toml.md#doc), +and the flag wins: + +| Key | Flag | Default | +|---|---|---| +| `out` | `--out` | `docs/api` | +| `format` | `--format` | `markdown` | +| `changelog_from` | `--changelog-from` | none | +| `renderer`, `timeout` | none | the built-in renderer | +| `links` | none | none | + +## What's next + +The standard library will be rendered onto this site the same way, so that +`links = { std = ... }` has somewhere to point. diff --git a/docs/docs/guide/idl/docstrings.md b/docs/docs/guide/idl/docstrings.md index 543306a..8771148 100755 --- a/docs/docs/guide/idl/docstrings.md +++ b/docs/docs/guide/idl/docstrings.md @@ -67,6 +67,8 @@ The editor shows them wherever a path segment is. In the modules in it, and hovering `validators` shows that module's docs and the types it declares. `use` completion shows the same docs beside each module and type. Like docstrings, module docs are for readers and tools: they don't change -a build, a version or generated code. +generated code or the version bump. A build stores them with the version, so +[`comline doc`](../documenting.md) documents a built version with the headers +it was built with; editing only docs makes a new build with no bump. [The standard library](use.md#the-standard-library) documents itself this way. diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index 7831a89..008360e 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -40,6 +40,7 @@ parse → resolve imports → validate → (freeze into .comline/) → (generate | `comline generate [--target ] [--out ] [--layout ] [--mode ] [--external-std] [--no-external-std] [--validate-on-decode ] [--dry-run] [--list] [--watch]` | Validate (no freeze), then write generated code. Location/layout from [`comline.toml`](congregation/comline-toml.md); targets from there or `config.idp`. A target can use an [external generator](../guide/extending/generators.md) (`generator = ""`) and map [encodings](../guide/extending/encodings.md#5-map-it-to-a-type). | | `comline release [1.0] [--raise --reason ] [--yes]` | Build, then release: create the version from the highest bump since the last release ([builds and releases](versioning.md#builds-and-releases)). `--raise patch\|minor\|major` goes higher, with a `--reason` recorded with the release; it never lowers. `1.0` makes a `0.x` package stable after showing what that commits to (its protocols, and what's worth settling first). Raising to major and `1.0` ask on a terminal; without one they refuse (exit `2`) unless `--yes` (`-y`). | | `comline diff []` | Show schema changes between two built versions. Each argument is a version (`0.2.0`), a commit hash (4+ chars), or `HEAD` (`` default). | +| `comline doc [--out ] [--version ] [--changelog-from ] [--format ]` | Write the package's documentation: Markdown pages (an index, one page per module with its `//!` header, every declaration's `///` docs and each protocol's wire summary, a changelog, an mkdocs/zensical `nav.yml`), or `--format json` for the documentation model. The working tree by default; `--version` documents a built version from its frozen IR and headers. Defaults from [`comline.toml`](congregation/comline-toml.md#doc)'s `[doc]`. No `.comline/` write. See [comline doc](tools/doc.md). | | `comline conformance --impl [--encoding ]... [--corpus ] [--timeout ]` | Run an implementation of an encoding against its vectors. The package's declared [`encodings`](../guide/extending/encodings.md) ship vectors beside their profiles (`profiles/x.vectors.json`); `--impl` is a process speaking the [ops protocol](conformance.md#the-ops-protocol), run once with a cleared environment, a time limit (`--timeout`, 60 s) and a capped stdout. `--encoding` limits the run to one encoding (repeatable); `--corpus` runs a corpus laid out like the runtime's `runtime/conformance` instead, which is how the official encodings are proven. A failed or unimplemented case, or an unproven profile fact, exits `1`. See [Formats and framings](../guide/extending/encodings.md#3-write-vectors-and-prove-an-implementation). | | `comline clean [--dry-run]` | Remove generated code (the `generate` output root or files). Never touches `.comline/`. | | `comline reset [--force] [--dry-run]` | **Destructive.** Delete `.comline/` — the whole version history, no undo — *and* generated code. Next `build` starts over at `0.0.1`. On a terminal it shows how many versions will be lost and makes you type `reset`; without a terminal it refuses (exit `2`) unless `--force`. | diff --git a/docs/docs/reference/congregation/comline-toml.md b/docs/docs/reference/congregation/comline-toml.md index dc6dd9f..a02ddec 100644 --- a/docs/docs/reference/congregation/comline-toml.md +++ b/docs/docs/reference/congregation/comline-toml.md @@ -1,7 +1,8 @@ # `comline.toml` Consumer-owned configuration for `comline generate`: where generated code goes, -in what layout, in what form, and for which package versions. It is committed, +in what layout, in what form, and for which package versions. Its `[doc]` +table configures `comline doc`. It is committed, never frozen, and never part of a published package. With no `comline.toml` (or an all-commented one) the defaults below apply and targets come from `config.idp`'s `code_generation.languages`. @@ -198,6 +199,27 @@ your `comline` build: declaring a language means you mean it, so a missing generator is an error rather than a silent skip, and `only` is how you say you want fewer. +## `[doc]` + +Settings for [`comline doc`](../tools/doc.md). Each key but `renderer`, +`timeout` and `links` has a flag that overrides it. + +| Key | Type | Default | Meaning | +|---|---|---|---| +| `out` | string (path) | `"docs/api"` | Output directory, relative to `comline.toml`. `--out` overrides it. | +| `format` | string | `"markdown"` | `markdown` (pages), `json` (the documentation model, `model.json`), or another renderer id `renderer` offers. `--format` overrides it. | +| `renderer` | string (path) | — | A renderer outside `comline`: a `.wasm` (run sandboxed under wasmtime) or an executable, relative to `comline.toml`'s directory. Run exactly like an external [`generator`](#generator). Unset, the built-in renderer for `format` is used. | +| `timeout` | integer (seconds, ≥ 1) | `60` | The wall-time limit for each call to `renderer`. | +| `links` | table | — | Where each dependency's docs live, by the name the package gives it (or `std`): a base URL. A type from a dependency listed here links to `//#`; one that isn't is plain text. | +| `changelog_from` | string | — | Add a changelog from this built version (a version or commit hash) to the documented one. `--changelog-from` overrides it. | + +```toml +[doc] +out = "site/docs/api" +links = { std = "https://example.com/std" } +changelog_from = "0.1.0" +``` + ## Layout variables | Variable | Value | diff --git a/docs/docs/reference/generator-protocol.md b/docs/docs/reference/generator-protocol.md index ac9fbd3..691f0a0 100644 --- a/docs/docs/reference/generator-protocol.md +++ b/docs/docs/reference/generator-protocol.md @@ -1,6 +1,7 @@ # Generator protocol -How the CLI talks to a code generator. For a walkthrough, see +How the CLI talks to a code generator, and to a documentation renderer. For a +walkthrough, see [Generators](../guide/extending/generators.md); to point a target at one, see the [CLI reference](cli.md#commands) and [`comline.toml`](congregation/comline-toml.md#generator). @@ -12,6 +13,9 @@ The Rust definitions are in `codegen/src/protocol.rs` of the **Protocol version: `0`** (`PROTOCOL_VERSION`). While Comline is in its early days, the protocol may change incompatibly, and the number moves when it does. +An additive change keeps the number: a new field with a default, or a new +request that only goes to a program whose `Description` says it handles it +(`Render` was added this way). The protocol is also pinned to an **IR format** (`IR_FORMAT`), the stamp on `.comline/` that `comline-core` owns. A generator accepts the IR formats it @@ -24,7 +28,7 @@ lists and refuses others. - Each is a single **CBOR** document, in the encoding `comline-core` stores its IR with (name-tagged fields). - A generator process serves one request per run. The CLI starts it once for - `Describe` and once for each `Generate`. + `Describe` and once for each `Generate` or `Render`. - Exit successfully whenever a response was written, an error response included. A nonzero exit or a response that isn't a protocol response is a failure. @@ -35,7 +39,7 @@ lists and refuses others. |---|---|---| | `protocol` | integer | the protocol version the sender speaks | | `ir_format` | integer | the IR format of every frozen unit in the body | -| `body` | `Describe` or `Generate(GenerateRequest)` | what is asked | +| `body` | `Describe`, `Generate(GenerateRequest)` or `Render(RenderRequest)` | what is asked | `protocol` and `ir_format` come first, and a generator reads them before the body. A mismatch is reported even when the body has a shape the generator @@ -72,12 +76,29 @@ Generate one target. A `GenerateRequest` holds: ([Map it to a type](../guide/extending/encodings.md#5-map-it-to-a-type)). - `mode` is `Code` or `Lib`. `dylib` isn't part of the protocol today. +### `Render` + +Render a package's documentation, for [`comline doc`](tools/doc.md). Only sent +to a program whose `Description` lists the renderer at the model version. A +`RenderRequest` holds: + +| Field | Type | Meaning | +|---|---|---| +| `renderer` | string | the renderer's id, from `Description.renderers`: `markdown` | +| `model_version` | integer | the documentation model's version (`DOC_MODEL_VERSION`, `1`), readable without parsing `model` | +| `model` | string | the documentation model as JSON text, in the shape of [`doc-model/1.json`](../schemas/doc-model/1.json) | +| `options` | `{ links }` | `links`: a dependency's name (or `std`) to the base URL of its docs | + +A renderer accepts every option and ignores the ones it has no use for. The +answer is `Render(Generated)`: files, checked like a generator's. + ## Response | Variant | Carries | |---|---| | `Describe(Description)` | the answer to `Describe` | | `Generate(Generated)` | the answer to `Generate` | +| `Render(Generated)` | the answer to `Render`: the rendered files | | `Error { message }` | the request was refused or generation failed | ### `Description` @@ -87,6 +108,7 @@ Generate one target. A `GenerateRequest` holds: | `languages` | one entry per generator; a language with several floors has several | | `protocols` | `(min, max)` protocol versions it accepts, inclusive | | `ir_formats` | the IR formats it accepts | +| `renderers` | the documentation renderers it offers, each `{ id, model_versions }`; empty, or absent from an older program, when it offers none | A language entry holds: @@ -114,6 +136,9 @@ target's version. both sides. - **Language:** the generator must offer the target's language at the target's version, or the CLI refuses it. +- **Renderer:** for `comline doc`, the program must list the format in + `renderers`, with the CLI's model version in `model_versions`, or the CLI + refuses it before sending `Render`. - **Output:** every path, then the total size, is checked before anything is written. @@ -146,9 +171,13 @@ environment and isn't otherwise sandboxed. The `comline_codegen::protocol` module exports: -- `Request`, `RequestBody`, `GenerateRequest`, `Response`, `Description`, - `Generated` and the types in the tables above; +- `Request`, `RequestBody`, `GenerateRequest`, `RenderRequest`, + `RenderOptions`, `Response`, `Description`, `RendererInfo`, `Generated` and + the types in the tables above; - `encode` and `decode`, to and from CBOR; - `respond(registry, bytes)`, which answers one request without panicking, and `handle`, which does the same on bytes; -- `serve_stdio(registry)`: a generator process's whole `main`. +- `serve_stdio(registry)`: a generator process's whole `main`. A registry + holds renderers too (`Registry::register_renderer`), so one binary can + generate, render, or both. The official Markdown renderer is + `comline-doc-markdown` in the generation repository. diff --git a/docs/docs/reference/tools/doc.md b/docs/docs/reference/tools/doc.md new file mode 100644 index 0000000..503450d --- /dev/null +++ b/docs/docs/reference/tools/doc.md @@ -0,0 +1,69 @@ +# comline doc + +``` +comline doc [--out ] [--version ] [--changelog-from ] [--format markdown|json] +``` + +`comline doc` writes a package's documentation. Like `comline check`, it writes +nothing to `.comline/`. For a walkthrough, see +[Documenting your schemas](../../guide/documenting.md). + +## Flags + +| Flag | Default | Meaning | +|---|---|---| +| `--out ` | `[doc] out`, else `docs/api` | Where to write, relative to the project. | +| `--version ` | the working tree | Document a built version: a version string, a commit hash (4+ characters), or `HEAD`. | +| `--changelog-from ` | `[doc] changelog_from` | Add what changed from built version `` to the documented one. | +| `--format ` | `[doc] format`, else `markdown` | `markdown`, `json`, or another renderer id `[doc] renderer` offers. | + +The `[doc]` keys are in [`comline.toml`](../congregation/comline-toml.md#doc). + +## Output + +`markdown`, rendered by the official renderer built into `comline`: + +| File | Contents | +|---|---| +| `index.md` | Package name and version, its `config.idp` `//!` header, a module table, a link to the changelog. | +| `.md` | Per schema (`types::user` → `types/user.md`): its `//!` header, a table of its declarations, then each one with its `///` docs, annotations, fields and arguments (a field's own docs, else its parent's `/// @name:` line), enum variants, an error's ordinal and message, a validator's targets, parameters and asserts, and each protocol's functions and wire summary. | +| `changelog.md` | With a changelog: breaking changes, additions and other changes. | +| `nav.yml` | A `nav` list for mkdocs or zensical. | + +Each declaration has an anchor, its name in lowercase; a function has +`-`. A type links to its declaration's anchor. A type from +a dependency links to `//#` when `[doc] links` gives the +dependency a base URL, and is plain text otherwise. + +The wire summary has the rows [`comline inspect`](inspect.md) prints: the +format, framing and handshake with where each came from, then each guarantee, +held or given up, with its cause and acceptance. + +`json` writes `model.json`, the documentation model, in the shape of +[`doc-model/1.json`](../../schemas/doc-model/1.json). + +## Built versions + +With `--version`, the model comes from the version's frozen IR and the `//!` +headers stored with it. A built version keeps no source, so: + +- its protocols have no wire summary; +- a type from a dependency has no link. + +A version built before headers were stored has no headers. + +## External renderers + +`[doc] renderer = ""` runs a renderer outside `comline`. It speaks the +[generator protocol](../generator-protocol.md#render), and runs like an +external generator: a `.wasm` under wasmtime with no filesystem, environment or +network, a native executable with an empty environment, the same time limit +(`[doc] timeout`, 60 seconds), output caps and path checks. `comline` asks it +to describe itself first and refuses it unless it lists the format, at the +model version this `comline` writes, in its `renderers`. + +## Exit codes + +`0` on success. `1` when the package doesn't compile, a version doesn't exist, +or the renderer refuses or fails. `2` when `--version` or `--changelog-from` is +given and the project was never built. diff --git a/docs/docs/schemas/comline-toml/0.x.json b/docs/docs/schemas/comline-toml/0.x.json index bfc630c..5dc8b31 100644 --- a/docs/docs/schemas/comline-toml/0.x.json +++ b/docs/docs/schemas/comline-toml/0.x.json @@ -3,6 +3,49 @@ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { + "Doc": { + "additionalProperties": false, + "description": "The `[doc]` table: `comline doc`'s settings. Each key has a flag that\noverrides it.", + "properties": { + "changelog_from": { + "description": "Add a changelog from this built version (a version or commit hash) to\nthe documented one. `--changelog-from` overrides it.", + "type": "string" + }, + "format": { + "default": "markdown", + "description": "What to write: `markdown` (pages, from the official renderer or\n`renderer`), `json` (the documentation model itself, `model.json`), or\nanother renderer id `renderer` offers. Default `markdown`. `--format`\noverrides it.", + "examples": [ + "markdown", + "json" + ], + "type": "string" + }, + "links": { + "additionalProperties": { + "type": "string" + }, + "default": {}, + "description": "Where each dependency's documentation lives, by the name the package\ngives the dependency (or `std`): `links = { std = \"https://...\" }`. A\ntype from a dependency listed here links into its docs; one that\nisn't is plain text.", + "type": "object" + }, + "out": { + "default": "docs/api", + "description": "Output directory, relative to `comline.toml`. Default `docs/api`.\n`--out` overrides it.", + "type": "string" + }, + "renderer": { + "description": "A renderer outside this binary: a path to a `.wasm` (run sandboxed\nunder wasmtime) or to an executable, relative to `comline.toml`'s\ndirectory, run exactly like an external `generator`. Unset, the\nbuilt-in renderer for `format` is used.", + "type": "string" + }, + "timeout": { + "description": "The wall-time limit for each call to `renderer`, in seconds.\nDefault 60.", + "format": "uint64", + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, "EncodingEntry": { "anyOf": [ { @@ -217,6 +260,14 @@ }, "description": "`comline.toml`: where `comline generate` writes code, and how. Which\nlanguages are generated comes from `config.idp`'s\n`code_generation.languages`; this file says where each one lands.", "properties": { + "doc": { + "allOf": [ + { + "$ref": "#/definitions/Doc" + } + ], + "description": "Documentation settings: where `comline doc` writes, in what format,\nwith which renderer." + }, "generate": { "allOf": [ { diff --git a/docs/docs/schemas/doc-model/1.json b/docs/docs/schemas/doc-model/1.json new file mode 100644 index 0000000..352b73c --- /dev/null +++ b/docs/docs/schemas/doc-model/1.json @@ -0,0 +1,1930 @@ +{ + "$id": "https://comlineproject.github.io/docs/schemas/doc-model/1.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Acceptance": { + "description": "Whether a given-up guarantee has been accepted by a consumer.", + "oneOf": [ + { + "description": "It's held: there's nothing to accept.", + "properties": { + "state": { + "const": "not_needed", + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + { + "description": "The package's own protocol: its consumers accept it in their own\n`config.idp`, which a package can't see.", + "properties": { + "state": { + "const": "unknown", + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + { + "description": "A dependency's protocol, accepted by the inspected package (named).", + "properties": { + "by": { + "type": "string" + }, + "state": { + "const": "accepted", + "type": "string" + } + }, + "required": [ + "state", + "by" + ], + "type": "object" + }, + { + "description": "A dependency's protocol the inspected package doesn't accept (the\nbuild refuses it).", + "properties": { + "state": { + "const": "not_accepted", + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + } + ] + }, + "Annotation": { + "description": "One `@key` or `@key = value` annotation.", + "properties": { + "key": { + "type": "string" + }, + "value": { + "description": "The value as written; `None` for a bare `@key`.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "Argument": { + "properties": { + "name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/Type" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "Assert": { + "description": "One `assert(condition, message)` of a validator.", + "properties": { + "condition": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "condition", + "message" + ], + "type": "object" + }, + "BreakingChange": { + "description": "Breaking changes that require a major version bump", + "oneOf": [ + { + "properties": { + "kind": { + "const": "removed_struct", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "removed_enum", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "description": "A field disappeared, optional or not: its name is gone for every\nconsumer that reads it.", + "properties": { + "field_name": { + "type": "string" + }, + "kind": { + "const": "removed_field", + "type": "string" + }, + "type_name": { + "type": "string" + } + }, + "required": [ + "kind", + "type_name", + "field_name" + ], + "type": "object" + }, + { + "properties": { + "field_name": { + "type": "string" + }, + "field_type": { + "type": "string" + }, + "kind": { + "const": "added_required_field", + "type": "string" + }, + "type_name": { + "type": "string" + } + }, + "required": [ + "kind", + "type_name", + "field_name", + "field_type" + ], + "type": "object" + }, + { + "properties": { + "field_name": { + "type": "string" + }, + "kind": { + "const": "changed_field_type", + "type": "string" + }, + "new_type": { + "type": "string" + }, + "old_type": { + "type": "string" + }, + "type_name": { + "type": "string" + } + }, + "required": [ + "kind", + "type_name", + "field_name", + "old_type", + "new_type" + ], + "type": "object" + }, + { + "description": "`optional` flipped, either way. Made optional: old readers may now\nreceive nothing where they expect a value. Made required: old writers\nmay still leave it out.", + "properties": { + "field_name": { + "type": "string" + }, + "kind": { + "const": "changed_field_optionality", + "type": "string" + }, + "now_optional": { + "type": "boolean" + }, + "type_name": { + "type": "string" + } + }, + "required": [ + "kind", + "type_name", + "field_name", + "now_optional" + ], + "type": "object" + }, + { + "properties": { + "enum_name": { + "type": "string" + }, + "kind": { + "const": "removed_enum_variant", + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": [ + "kind", + "enum_name", + "variant" + ], + "type": "object" + }, + { + "description": "One variant removed and one added at the same position: reported as\nthe rename it most likely is, rather than a removal plus an addition.\nStill breaking, since variants travel by name.", + "properties": { + "enum_name": { + "type": "string" + }, + "kind": { + "const": "renamed_enum_variant", + "type": "string" + }, + "new_name": { + "type": "string" + }, + "old_name": { + "type": "string" + } + }, + "required": [ + "kind", + "enum_name", + "old_name", + "new_name" + ], + "type": "object" + }, + { + "properties": { + "function_name": { + "type": "string" + }, + "kind": { + "const": "removed_function", + "type": "string" + }, + "protocol_name": { + "type": "string" + } + }, + "required": [ + "kind", + "protocol_name", + "function_name" + ], + "type": "object" + }, + { + "properties": { + "details": { + "type": "string" + }, + "function_name": { + "type": "string" + }, + "kind": { + "const": "changed_function_signature", + "type": "string" + }, + "protocol_name": { + "type": "string" + } + }, + "required": [ + "kind", + "protocol_name", + "function_name", + "details" + ], + "type": "object" + }, + { + "description": "A function no longer declares an error it used to (`! Name`): the\ngenerated error variant disappears for its callers.", + "properties": { + "error": { + "type": "string" + }, + "function_name": { + "type": "string" + }, + "kind": { + "const": "removed_thrown_error", + "type": "string" + }, + "protocol_name": { + "type": "string" + } + }, + "required": [ + "kind", + "protocol_name", + "function_name", + "error" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "removed_protocol", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "removed_error", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "description": "The error's ordinal is what travels on the wire, and it shifts when a\nlocal error is inserted, removed or reordered before it.", + "properties": { + "kind": { + "const": "changed_error_ordinal", + "type": "string" + }, + "name": { + "type": "string" + }, + "new_ordinal": { + "format": "uint16", + "maximum": 65535, + "minimum": 0, + "type": "integer" + }, + "old_ordinal": { + "format": "uint16", + "maximum": 65535, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "kind", + "name", + "old_ordinal", + "new_ordinal" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "removed_constant", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "changed_constant_type", + "type": "string" + }, + "name": { + "type": "string" + }, + "new_type": { + "type": "string" + }, + "old_type": { + "type": "string" + } + }, + "required": [ + "kind", + "name", + "old_type", + "new_type" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "removed_type_alias", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "changed_type_alias_target", + "type": "string" + }, + "name": { + "type": "string" + }, + "new_target": { + "type": "string" + }, + "old_target": { + "type": "string" + } + }, + "required": [ + "kind", + "name", + "old_target", + "new_target" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "removed_validator", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "description": "A validator declaration's targets, properties or body changed. What\nit accepts can't be compared in general, so any change counts as\nnarrowing.", + "properties": { + "kind": { + "const": "changed_validator", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "description": "The validators applied to a declaration changed other than by pure\nremoval: what's accepted may have narrowed.", + "properties": { + "kind": { + "const": "validation_tightened", + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target" + ], + "type": "object" + }, + { + "description": "An annotation that changes what travels on the wire (`@framing`).", + "properties": { + "key": { + "type": "string" + }, + "kind": { + "const": "changed_wire_annotation", + "type": "string" + }, + "new_value": { + "type": [ + "string", + "null" + ] + }, + "old_value": { + "type": [ + "string", + "null" + ] + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target", + "key" + ], + "type": "object" + }, + { + "description": "A protocol gave up guarantees it used to keep (`@unverified`, or its\nset growing): peers relied on them.", + "properties": { + "guarantees": { + "items": { + "type": "string" + }, + "type": "array" + }, + "kind": { + "const": "gave_up_guarantees", + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target", + "guarantees" + ], + "type": "object" + } + ] + }, + "Changelog": { + "description": "What changed from one version to another, as `comline diff` reports it.\n\nNames in it are as the declarations write them, not qualified by\nmodule.", + "properties": { + "breaking": { + "description": "Changes an older peer can't work with: a major bump.", + "items": { + "$ref": "#/definitions/BreakingChange" + }, + "type": "array" + }, + "features": { + "description": "Additions: a minor bump.", + "items": { + "$ref": "#/definitions/NewFeature" + }, + "type": "array" + }, + "from": { + "type": "string" + }, + "modifications": { + "description": "The rest: a patch bump.", + "items": { + "$ref": "#/definitions/Modification" + }, + "type": "array" + }, + "to": { + "type": "string" + } + }, + "required": [ + "from", + "to", + "breaking", + "features", + "modifications" + ], + "type": "object" + }, + "Choice": { + "description": "A resolved encoding: its name, its parameters and where it came from.", + "properties": { + "params": { + "additionalProperties": { + "type": "string" + }, + "description": "Its parameters (`@framing`'s dictionary form, less `name`), each\nvalue as written.", + "type": "object" + }, + "source": { + "$ref": "#/definitions/Source" + }, + "value": { + "description": "The name, canonical (`datagram@2`, `jsonrpc`, `comline@1`).", + "type": "string" + } + }, + "required": [ + "value", + "params", + "source" + ], + "type": "object" + }, + "Doc": { + "description": "A `///` docstring: its description and its `/// @name: ...` lines.", + "properties": { + "params": { + "description": "The `/// @name: description` lines, in order: a field or an argument\neach.", + "items": { + "$ref": "#/definitions/DocParam" + }, + "type": "array" + }, + "text": { + "description": "Every line that isn't an `@name:` line, joined with newlines.", + "type": "string" + } + }, + "required": [ + "text", + "params" + ], + "type": "object" + }, + "DocParam": { + "properties": { + "name": { + "type": "string" + }, + "text": { + "type": "string" + } + }, + "required": [ + "name", + "text" + ], + "type": "object" + }, + "Field": { + "properties": { + "annotations": { + "items": { + "$ref": "#/definitions/Annotation" + }, + "type": "array" + }, + "default": { + "description": "`= value`, as written.", + "type": [ + "string", + "null" + ] + }, + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "type": { + "$ref": "#/definitions/Type" + } + }, + "required": [ + "name", + "annotations", + "type", + "optional" + ], + "type": "object" + }, + "Function": { + "properties": { + "annotations": { + "items": { + "$ref": "#/definitions/Annotation" + }, + "type": "array" + }, + "arguments": { + "items": { + "$ref": "#/definitions/Argument" + }, + "type": "array" + }, + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "returns": { + "anyOf": [ + { + "$ref": "#/definitions/Type" + }, + { + "type": "null" + } + ], + "description": "`None` when the function has no reply at all; [`Type::Unit`] for `()`." + }, + "throws": { + "description": "The errors it declares with `!`, each a reference to the error.", + "items": { + "$ref": "#/definitions/Type" + }, + "type": "array" + } + }, + "required": [ + "name", + "annotations", + "arguments", + "throws" + ], + "type": "object" + }, + "GuaranteeRow": { + "description": "One guarantee of one protocol.", + "properties": { + "acceptance": { + "$ref": "#/definitions/Acceptance" + }, + "causes": { + "description": "Why it's given up, one entry per cause ([`unverified::causes`]).\nEmpty when held.", + "items": { + "type": "string" + }, + "type": "array" + }, + "guarantee": { + "type": "string" + }, + "held": { + "type": "boolean" + }, + "reason": { + "description": "The `@unverified` reason, when it's given up and the marker carries\none.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "guarantee", + "held", + "causes", + "acceptance" + ], + "type": "object" + }, + "Modification": { + "description": "Non-breaking modifications that may warrant a patch bump", + "oneOf": [ + { + "description": "A declaration's own docstring text changed with nothing else about\nit - doc/comment-only changes warrant a patch bump, not a\nmajor/minor one. Only tracked at the declaration level (struct,\nenum, protocol, const, error, alias, validator) for now, not\nper-field/per-function.", + "properties": { + "kind": { + "const": "docstring_changed", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "properties": { + "field_name": { + "type": "string" + }, + "kind": { + "const": "changed_field_default", + "type": "string" + }, + "type_name": { + "type": "string" + } + }, + "required": [ + "kind", + "type_name", + "field_name" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "changed_error_message", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "description": "An annotation that doesn't change the wire changed: `@timeout_ms`,\n`@idempotent`, `@deprecated` removed or reworded, or one core doesn't\nknow.", + "properties": { + "key": { + "type": "string" + }, + "kind": { + "const": "changed_annotation", + "type": "string" + }, + "new_value": { + "type": [ + "string", + "null" + ] + }, + "old_value": { + "type": [ + "string", + "null" + ] + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target", + "key" + ], + "type": "object" + } + ] + }, + "Module": { + "description": "One schema file.", + "properties": { + "docs": { + "description": "Its `//!` header.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "Its path within the package: `api`, `types::user`.", + "type": "string" + }, + "units": { + "description": "What it declares, in declaration order.", + "items": { + "$ref": "#/definitions/Unit" + }, + "type": "array" + } + }, + "required": [ + "path", + "units" + ], + "type": "object" + }, + "NewFeature": { + "description": "New features that require a minor version bump", + "oneOf": [ + { + "properties": { + "field_count": { + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "kind": { + "const": "added_struct", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name", + "field_count" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "added_enum", + "type": "string" + }, + "name": { + "type": "string" + }, + "variant_count": { + "format": "uint", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "kind", + "name", + "variant_count" + ], + "type": "object" + }, + { + "properties": { + "field_name": { + "type": "string" + }, + "field_type": { + "type": "string" + }, + "kind": { + "const": "added_field", + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "type_name": { + "type": "string" + } + }, + "required": [ + "kind", + "type_name", + "field_name", + "field_type", + "optional" + ], + "type": "object" + }, + { + "properties": { + "enum_name": { + "type": "string" + }, + "kind": { + "const": "added_enum_variant", + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": [ + "kind", + "enum_name", + "variant" + ], + "type": "object" + }, + { + "properties": { + "function_name": { + "type": "string" + }, + "kind": { + "const": "added_function", + "type": "string" + }, + "protocol_name": { + "type": "string" + }, + "signature": { + "type": "string" + } + }, + "required": [ + "kind", + "protocol_name", + "function_name", + "signature" + ], + "type": "object" + }, + { + "description": "A function declares a new error (`! Name`). Older callers see it as\na remote error they don't know.", + "properties": { + "error": { + "type": "string" + }, + "function_name": { + "type": "string" + }, + "kind": { + "const": "added_thrown_error", + "type": "string" + }, + "protocol_name": { + "type": "string" + } + }, + "required": [ + "kind", + "protocol_name", + "function_name", + "error" + ], + "type": "object" + }, + { + "properties": { + "function_count": { + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "kind": { + "const": "added_protocol", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name", + "function_count" + ], + "type": "object" + }, + { + "properties": { + "field_count": { + "format": "uint", + "minimum": 0, + "type": "integer" + }, + "kind": { + "const": "added_error", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name", + "field_count" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "added_constant", + "type": "string" + }, + "name": { + "type": "string" + }, + "ty": { + "type": "string" + } + }, + "required": [ + "kind", + "name", + "ty" + ], + "type": "object" + }, + { + "description": "A constant's value changed. Counted as minor for now: whether it\nreaches the wire (a validator argument, a default) isn't traced yet.", + "properties": { + "kind": { + "const": "changed_constant_value", + "type": "string" + }, + "name": { + "type": "string" + }, + "new_value": { + "type": [ + "string", + "null" + ] + }, + "old_value": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "added_type_alias", + "type": "string" + }, + "name": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "name", + "target" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "added_validator", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "description": "Validators were only removed from a declaration: what's accepted\nwidens.", + "properties": { + "kind": { + "const": "validation_loosened", + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target" + ], + "type": "object" + }, + { + "description": "`@deprecated` was added to a declaration.", + "properties": { + "kind": { + "const": "deprecated", + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target" + ], + "type": "object" + }, + { + "description": "A protocol keeps guarantees it used to give up.", + "properties": { + "guarantees": { + "items": { + "type": "string" + }, + "type": "array" + }, + "kind": { + "const": "restored_guarantees", + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "kind", + "target", + "guarantees" + ], + "type": "object" + } + ] + }, + "Package": { + "properties": { + "docs": { + "description": "The `//!` header of its `config.idp`.", + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "version": { + "description": "The version it was built as, when the caller knows it.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "ProtocolSummary": { + "description": "One protocol, as [`protocols`] reports it.", + "properties": { + "dependency": { + "description": "Whether it's a dependency's protocol rather than the package's own.", + "type": "boolean" + }, + "format": { + "$ref": "#/definitions/Choice" + }, + "framing": { + "$ref": "#/definitions/Choice" + }, + "guarantees": { + "description": "Every guarantee, in [`Guarantee::ALL`] order.", + "items": { + "$ref": "#/definitions/GuaranteeRow" + }, + "type": "array" + }, + "handshake": { + "$ref": "#/definitions/Choice" + }, + "name": { + "type": "string" + }, + "package": { + "description": "The package that declares it: the inspected package's name, or the\ndependency's, as the inspected package names it.", + "type": "string" + }, + "schema": { + "description": "The schema it's in (`api`, or `billing::api` for a dependency's).", + "type": "string" + }, + "unverified": { + "description": "Whether it's marked `@unverified`.", + "type": "boolean" + } + }, + "required": [ + "package", + "dependency", + "schema", + "name", + "format", + "framing", + "handshake", + "unverified", + "guarantees" + ], + "type": "object" + }, + "SettingsEntry": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "Source": { + "description": "Where a [`Choice`] came from.", + "oneOf": [ + { + "const": "annotation", + "description": "The protocol's own `@framing` or `@handshake`.", + "type": "string" + }, + { + "const": "package_default", + "description": "The package's `wire` or `handshake` in its `config.idp`.", + "type": "string" + }, + { + "const": "core_default", + "description": "Core's default (`packed@1`, `datagram@2`, `comline@1`).", + "type": "string" + }, + { + "const": "framing", + "description": "The format JSON-RPC implies: JSON, when the package names no\nformat.", + "type": "string" + } + ] + }, + "Type": { + "description": "A type as written, with every name in it resolved to the unit it names.", + "oneOf": [ + { + "description": "`u32`, `string`, ...", + "properties": { + "kind": { + "const": "primitive", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "description": "A struct, enum, error or alias, by the name written (`User`,\n`types::User`), and the unit it resolves to; `None` when it doesn't\nresolve to a compiled unit.", + "properties": { + "kind": { + "const": "named", + "type": "string" + }, + "name": { + "type": "string" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/UnitRef" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + { + "description": "`T[]` (`length: None`) or `T[N]`.", + "properties": { + "element": { + "$ref": "#/definitions/Type" + }, + "kind": { + "const": "array", + "type": "string" + }, + "length": { + "format": "uint64", + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "kind", + "element" + ], + "type": "object" + }, + { + "description": "`union(A B ...)`.", + "properties": { + "kind": { + "const": "union", + "type": "string" + }, + "members": { + "items": { + "$ref": "#/definitions/Type" + }, + "type": "array" + } + }, + "required": [ + "kind", + "members" + ], + "type": "object" + }, + { + "description": "`()`.", + "properties": { + "kind": { + "const": "unit", + "type": "string" + } + }, + "required": [ + "kind" + ], + "type": "object" + } + ] + }, + "Unit": { + "description": "A declared unit.", + "oneOf": [ + { + "properties": { + "annotations": { + "items": { + "$ref": "#/definitions/Annotation" + }, + "type": "array" + }, + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "fields": { + "items": { + "$ref": "#/definitions/Field" + }, + "type": "array" + }, + "kind": { + "const": "struct", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name", + "annotations", + "fields" + ], + "type": "object" + }, + { + "properties": { + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "kind": { + "const": "enum", + "type": "string" + }, + "name": { + "type": "string" + }, + "variants": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "kind", + "name", + "variants" + ], + "type": "object" + }, + { + "properties": { + "annotations": { + "items": { + "$ref": "#/definitions/Annotation" + }, + "type": "array" + }, + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "functions": { + "items": { + "$ref": "#/definitions/Function" + }, + "type": "array" + }, + "kind": { + "const": "protocol", + "type": "string" + }, + "name": { + "type": "string" + }, + "wire": { + "anyOf": [ + { + "$ref": "#/definitions/ProtocolSummary" + }, + { + "type": "null" + } + ], + "description": "What it resolves to on the wire ([`crate::inspect`])." + } + }, + "required": [ + "kind", + "name", + "annotations", + "functions" + ], + "type": "object" + }, + { + "properties": { + "annotations": { + "items": { + "$ref": "#/definitions/Annotation" + }, + "type": "array" + }, + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "fields": { + "items": { + "$ref": "#/definitions/Field" + }, + "type": "array" + }, + "kind": { + "const": "error", + "type": "string" + }, + "message": { + "description": "The message template, `{path}` placeholders intact.", + "type": "string" + }, + "name": { + "type": "string" + }, + "ordinal": { + "description": "Its slot in the schema's error space.", + "format": "uint16", + "maximum": 65535, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "kind", + "name", + "annotations", + "ordinal", + "message", + "fields" + ], + "type": "object" + }, + { + "properties": { + "annotations": { + "items": { + "$ref": "#/definitions/Annotation" + }, + "type": "array" + }, + "asserts": { + "items": { + "$ref": "#/definitions/Assert" + }, + "type": "array" + }, + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "kind": { + "const": "validator", + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "description": "Its parameters.", + "items": { + "$ref": "#/definitions/Field" + }, + "type": "array" + }, + "targets": { + "description": "The types it applies to (after `for`).", + "items": { + "$ref": "#/definitions/Type" + }, + "type": "array" + } + }, + "required": [ + "kind", + "name", + "annotations", + "targets", + "params", + "asserts" + ], + "type": "object" + }, + { + "properties": { + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "kind": { + "const": "constant", + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/Type" + }, + "value": { + "description": "The value as written.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "name", + "type" + ], + "type": "object" + }, + { + "properties": { + "annotations": { + "items": { + "$ref": "#/definitions/Annotation" + }, + "type": "array" + }, + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "kind": { + "const": "type_alias", + "type": "string" + }, + "name": { + "type": "string" + }, + "target": { + "$ref": "#/definitions/Type" + } + }, + "required": [ + "kind", + "name", + "annotations", + "target" + ], + "type": "object" + }, + { + "properties": { + "docs": { + "anyOf": [ + { + "$ref": "#/definitions/Doc" + }, + { + "type": "null" + } + ] + }, + "kind": { + "const": "settings", + "type": "string" + }, + "name": { + "description": "`None` for the schema's unnamed block.", + "type": [ + "string", + "null" + ] + }, + "values": { + "description": "Each leaf by its dotted key, in key order.", + "items": { + "$ref": "#/definitions/SettingsEntry" + }, + "type": "array" + } + }, + "required": [ + "kind", + "values" + ], + "type": "object" + } + ] + }, + "UnitRef": { + "description": "Where a unit is declared.", + "properties": { + "name": { + "type": "string" + }, + "package": { + "description": "The package: the documented one, a dependency (by the name the\ndocumented package gives it) or `std`.", + "type": "string" + }, + "schema": { + "description": "The module within that package (`api`, `validators`).", + "type": "string" + } + }, + "required": [ + "package", + "schema", + "name" + ], + "type": "object" + } + }, + "description": "A package's documentation.", + "properties": { + "changelog": { + "default": [], + "description": "What changed, one entry per pair of versions compared. Empty unless\none was asked for.", + "items": { + "$ref": "#/definitions/Changelog" + }, + "type": "array" + }, + "model_version": { + "description": "[`DOC_MODEL_VERSION`] when it was produced.", + "format": "uint32", + "minimum": 0, + "type": "integer" + }, + "modules": { + "description": "The package's modules (schemas), in the order they compiled.", + "items": { + "$ref": "#/definitions/Module" + }, + "type": "array" + }, + "package": { + "$ref": "#/definitions/Package" + } + }, + "required": [ + "model_version", + "package", + "modules" + ], + "title": "Comline documentation model", + "type": "object" +} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c807960..6257521 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -37,6 +37,8 @@ nav: - 'Packages & dependencies': 'guide/packages/index.md' + - Documenting your schemas: 'guide/documenting.md' + - 'Extending Comline': - 'guide/extending/index.md' - Formats & framings: 'guide/extending/encodings.md' @@ -87,6 +89,7 @@ nav: - Tools: 'reference/tools/index.md' - CLI: 'reference/cli.md' - comline inspect: 'reference/tools/inspect.md' + - comline doc: 'reference/tools/doc.md' - Extending: - Generator protocol: 'reference/generator-protocol.md' diff --git a/scripts/sync-comline-toml-schema.py b/scripts/sync-comline-toml-schema.py index 1c041d6..ec469d3 100644 --- a/scripts/sync-comline-toml-schema.py +++ b/scripts/sync-comline-toml-schema.py @@ -1,16 +1,22 @@ #!/usr/bin/env python3 -"""Keep the served `comline.toml` JSON Schema equal to generation's. +"""Keep the served JSON Schemas equal to their sources. -`comline_codegen::config` is the source of truth: generation derives -`schemas/comline-toml.json` from it and drift-checks that file there. This repo -only serves a copy, at `/schemas/comline-toml/0.x.json`, because `comline new` -writes that URL into a fresh `comline.toml` as a `#:schema` line. +- `comline.toml`: `comline_codegen::config` is the source of truth; generation + derives `schemas/comline-toml.json` from it and drift-checks that file there. + This repo serves a copy at `/schemas/comline-toml/0.x.json`, because + `comline new` writes that URL into a fresh `comline.toml` as a `#:schema` + line. +- The documentation model: core's `comline_core::doc` derives + `schemas/doc-model.json` (its `$id` is this site's + `/schemas/doc-model/1.json`) and drift-checks it there. This repo serves the + copy renderers validate against. The `1` is `DOC_MODEL_VERSION`: a new model + version is a new file, and the old ones stay. python3 scripts/sync-comline-toml-schema.py # check; exit 1 on drift - python3 scripts/sync-comline-toml-schema.py --write # refresh the copy - python3 scripts/sync-comline-toml-schema.py --rev + python3 scripts/sync-comline-toml-schema.py --write # refresh the copies + python3 scripts/sync-comline-toml-schema.py --rev --core-rev -The schema is fetched from generation's default branch (`HEAD`) unless pinned. +Each schema is fetched from its repo's default branch (`HEAD`) unless pinned. """ from __future__ import annotations @@ -21,33 +27,50 @@ from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -COPY = ROOT / "docs/docs/schemas/comline-toml/0.x.json" -RAW = "https://raw.githubusercontent.com/ComlineProject/generation/{rev}/schemas/comline-toml.json" +# (served copy, source URL template, which `--...rev` pins it) +SCHEMAS = [ + ( + ROOT / "docs/docs/schemas/comline-toml/0.x.json", + "https://raw.githubusercontent.com/ComlineProject/generation/{rev}/schemas/comline-toml.json", + "rev", + ), + ( + ROOT / "docs/docs/schemas/doc-model/1.json", + "https://raw.githubusercontent.com/ComlineProject/core/{rev}/schemas/doc-model.json", + "core_rev", + ), +] def main() -> int: ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) ap.add_argument("--write", action="store_true", help="overwrite the committed copy") ap.add_argument("--rev", default="HEAD", help="generation ref to read (default: HEAD)") + ap.add_argument("--core-rev", default="HEAD", help="core ref to read (default: HEAD)") args = ap.parse_args() - url = RAW.format(rev=args.rev) - with urllib.request.urlopen(url, timeout=30) as r: - upstream = r.read() + drifted = False + for copy, raw, pin in SCHEMAS: + url = raw.format(rev=getattr(args, pin)) + with urllib.request.urlopen(url, timeout=30) as r: + upstream = r.read() + name = copy.relative_to(ROOT) - if args.write: - COPY.write_bytes(upstream) - print(f"wrote {COPY.relative_to(ROOT)} from {url}") - return 0 + if args.write: + copy.parent.mkdir(parents=True, exist_ok=True) + copy.write_bytes(upstream) + print(f"wrote {name} from {url}") + elif copy.exists() and copy.read_bytes() == upstream: + print(f"{name}: up to date") + else: + print(f"{name} differs from {url}") + drifted = True - if COPY.read_bytes() == upstream: - print("comline.toml schema: up to date") - return 0 - - print(f"{COPY.relative_to(ROOT)} differs from generation's schemas/comline-toml.json") - print("Refresh it with:") - print(" python3 scripts/sync-comline-toml-schema.py --write") - return 1 + if drifted: + print("Refresh with:") + print(" python3 scripts/sync-comline-toml-schema.py --write") + return 1 + return 0 if __name__ == "__main__":