Add JSON Schema documentation for bambulab/v1, with real inheritance
Mirrors src/printer/bambu_commands/ as language-agnostic schema docs (not used for codegen — the Rust structs stay hand-written). envelope.schema.json is the base; get_version/pushall request+response schemas extend it via allOf + $ref, which is genuinely closer to real inheritance than anything else in this project's schema stack: proto and Rust only ever gave us composition (embed a field holding the base struct), never a mechanism that reuses a *referenced* schema's constraints automatically. Verified with the jsonschema Python library, not just written by hand: all three real examples from the conversation validate correctly, and — importantly — negative tests confirm the base schema's required fields and the command consts are actually enforced through the allOf chain, not just documented. README covers the one real gotcha (additionalProperties: false on a base schema silently breaks allOf composition, since allOf validates each sub-schema against the whole instance independently rather than merging them).
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# bambulab/v1 JSON Schemas
|
||||
|
||||
Documentation/validation schemas mirroring `src/printer/bambu_commands/` —
|
||||
not used for codegen (the Rust structs are hand-written, see that module's
|
||||
doc comment for why), but useful on their own: as language-agnostic
|
||||
documentation of the wire format, or for validating a captured MQTT
|
||||
payload without writing Rust to do it.
|
||||
|
||||
## The "inheritance" pattern
|
||||
|
||||
`envelope.schema.json` is the base — `sequence_id` + `command`, the two
|
||||
fields every command/response shares. Every other schema here *extends*
|
||||
it with `allOf` + `$ref`:
|
||||
|
||||
```json
|
||||
{
|
||||
"allOf": [
|
||||
{ "$ref": "envelope.schema.json" },
|
||||
{ "type": "object", "properties": { "command": { "const": "get_version" } } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This is genuinely closer to real inheritance than anything else in this
|
||||
project's schema stack (proto, Rust): the instance must satisfy the base
|
||||
schema *and* the extension schema simultaneously, and — verified, not just
|
||||
asserted — the base's `required: ["sequence_id", "command"]` is actually
|
||||
enforced on every schema that extends it, not just copy-pasted as
|
||||
documentation. Proto and Rust only ever gave us composition (embed a field
|
||||
holding the base), never something that reuses a *named, referenced*
|
||||
schema's constraints automatically like this.
|
||||
|
||||
**One gotcha if you add a new base-style schema**: don't set
|
||||
`"additionalProperties": false` on something meant to be `allOf`-extended.
|
||||
`allOf` validates every sub-schema against the *whole* instance
|
||||
independently — it doesn't merge object schemas — so a closed base schema
|
||||
would reject every field the extending schema adds. Leave
|
||||
`additionalProperties` unset (defaults to allowed) on anything used as a
|
||||
base; enforce closedness only on the top-level schemas
|
||||
(`request.schema.json`/`report.schema.json` do this correctly — check
|
||||
their `additionalProperties: false`).
|
||||
|
||||
## Files
|
||||
|
||||
- `envelope.schema.json` — the base
|
||||
- `module_info.schema.json` — nested object used inside `get_version`'s response
|
||||
- `get_version.request.schema.json` / `get_version.response.schema.json`
|
||||
- `pushall.request.schema.json` / `pushall.response.schema.json`
|
||||
- `request.schema.json` / `report.schema.json` — the top-level `{"info": ...}` / `{"pushing": ...}` dispatch, matching `BambuRequest`/`BambuReport`
|
||||
|
||||
## Validating something
|
||||
|
||||
```python
|
||||
from referencing import Registry, Resource
|
||||
from jsonschema import Draft202012Validator
|
||||
import json, glob, os
|
||||
|
||||
resources = []
|
||||
for path in glob.glob("*.schema.json"):
|
||||
contents = json.load(open(path))
|
||||
resources.append((contents["$id"], Resource.from_contents(contents)))
|
||||
resources.append((os.path.basename(path), Resource.from_contents(contents)))
|
||||
registry = Registry().with_resources(resources)
|
||||
|
||||
schema = json.load(open("request.schema.json"))
|
||||
Draft202012Validator(schema, registry=registry).validate({"info": {"sequence_id": "0", "command": "get_version"}})
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/envelope.schema.json",
|
||||
"title": "CommandEnvelope",
|
||||
"description": "Fields every Bambu MQTT command/response shares. This is the 'base class' — other schemas extend it via allOf + $ref. JSON Schema doesn't have inheritance either, strictly speaking (no 'extends' keyword) — allOf is composition: it says the instance must satisfy THIS schema AND whatever else is listed alongside it. It's the closest thing to real inheritance ergonomics of anything we've used across this whole schema stack (proto, Rust), because $ref + allOf lets you name and reuse a shape without re-declaring its fields, which proto/Rust composition can't quite do (there you still write `common: PrinterReportCommon` by hand on every struct).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sequence_id": { "type": "string" },
|
||||
"command": { "type": "string" }
|
||||
},
|
||||
"required": ["sequence_id", "command"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/get_version.request.schema.json",
|
||||
"title": "GetVersionRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "get_version" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/get_version.response.schema.json",
|
||||
"title": "GetVersionResponse",
|
||||
"allOf": [
|
||||
{ "$ref": "envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "get_version" },
|
||||
"result": { "type": "string" },
|
||||
"reason": { "type": "string" },
|
||||
"module": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "module_info.schema.json" }
|
||||
}
|
||||
},
|
||||
"required": ["module"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/module_info.schema.json",
|
||||
"title": "ModuleInfo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hw_ver": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"sn": { "type": "string" },
|
||||
"sw_ver": { "type": "string" }
|
||||
},
|
||||
"required": ["hw_ver", "name", "sn", "sw_ver"]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/pushall.request.schema.json",
|
||||
"title": "PushallRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "pushall" },
|
||||
"version": { "type": "integer" },
|
||||
"push_target": { "type": "integer" }
|
||||
},
|
||||
"required": ["version", "push_target"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/pushall.response.schema.json",
|
||||
"title": "PushallResponse",
|
||||
"description": "The real pushall/print report (the 'print' category push) has a lot more fields (ams, xcam, tray_exist_bits, ...) — add them under `properties` here the same way you'd add them to src/printer/bambu_commands/pushall.rs. Not required in `required` unless Bambu always sends them.",
|
||||
"allOf": [
|
||||
{ "$ref": "envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": { "type": "string" },
|
||||
"reason": { "type": "string" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/report.schema.json",
|
||||
"title": "BambuReport",
|
||||
"description": "Matches src/printer/bambu_commands::BambuReport.",
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"maxProperties": 1,
|
||||
"properties": {
|
||||
"info": { "$ref": "get_version.response.schema.json" },
|
||||
"pushing": { "$ref": "pushall.response.schema.json" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/request.schema.json",
|
||||
"title": "BambuRequest",
|
||||
"description": "Matches src/printer/bambu_commands::BambuRequest — one JSON key naming the category ('info', 'pushing', ...), the value is that category's request schema.",
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"maxProperties": 1,
|
||||
"properties": {
|
||||
"info": { "$ref": "get_version.request.schema.json" },
|
||||
"pushing": { "$ref": "pushall.request.schema.json" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
Reference in New Issue
Block a user