Move bambulab JSON Schemas here from continuum-proxy; restructure
Schemas (any notation - proto, JSON Schema) belong in the schemas repo regardless of how many languages currently consume them; that's what makes this 'the schemas repo'. Rust-only code (the hand-written structs in continuum-proxy's bambu_commands/) is a different category and stays where it was. Also restructured per readability feedback: - envelope.schema.json moved out of v1/ to the bambulab/ root - it's shared across protocol generations, not v1-specific, same principle already applied to the (now-removed) proto/bambulab/common.proto. - v1/ split into v1/request/ and v1/report/, one file per command in each, instead of *.request.schema.json / *.response.schema.json filename suffixes doing the same job less clearly. The two top-level dispatcher schemas (matching BambuRequest/BambuReport) stay at the v1/ root since they aren't themselves a single command's schema. Re-verified after restructuring, not just moved and assumed still correct: resolution now has to handle a real basename collision (get_version.schema.json exists in both request/ and report/) — switched the validation approach to register schemas by (their actual base URI) rather than by filename, which is what makes relative resolve correctly here. All three real examples plus the negative tests from the original version still pass.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# bambulab/ JSON Schemas
|
||||
|
||||
Documentation/validation schemas for Bambu's raw MQTT wire format, mirroring
|
||||
continuum-proxy's `src/printer/bambu_commands/` — not used for codegen (the
|
||||
Rust structs there are hand-written; see that module's doc comment for why),
|
||||
but genuinely useful on their own: language-agnostic documentation of the
|
||||
wire format, and usable to validate a captured MQTT payload without writing
|
||||
any Rust to do it.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
bambulab/
|
||||
envelope.schema.json the base — sequence_id + command, shared by
|
||||
every command in every generation, so it lives
|
||||
here unversioned, not duplicated under v1/v2
|
||||
v1/
|
||||
request.schema.json BambuRequest dispatcher — {"info": ...} / {"pushing": ...}
|
||||
report.schema.json BambuReport dispatcher
|
||||
request/
|
||||
get_version.schema.json
|
||||
pushall.schema.json
|
||||
report/
|
||||
get_version.schema.json
|
||||
pushall.schema.json
|
||||
module_info.schema.json
|
||||
v2/ add the same request/report split once V2's
|
||||
wire format is known to differ from V1's
|
||||
```
|
||||
|
||||
## The "inheritance" pattern
|
||||
|
||||
Every command/response schema *extends* `envelope.schema.json` 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 with
|
||||
negative test cases, not just asserted — `envelope.schema.json`'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 dispatcher schemas
|
||||
(`v1/request.schema.json`/`v1/report.schema.json` do this correctly —
|
||||
check their `additionalProperties: false`).
|
||||
|
||||
## Validating something
|
||||
|
||||
`$ref` resolution here relies on each schema's `$id` as the base URI for
|
||||
its own relative refs (standard JSON Schema behavior) — register schemas
|
||||
by `$id`, not by filename, or you'll hit collisions (`get_version.schema.json`
|
||||
exists in both `request/` and `report/`):
|
||||
|
||||
```python
|
||||
from referencing import Registry, Resource
|
||||
from jsonschema import Draft202012Validator
|
||||
import json, glob
|
||||
|
||||
resources = [
|
||||
(json.load(open(p))["$id"], Resource.from_contents(json.load(open(p))))
|
||||
for p in glob.glob("**/*.schema.json", recursive=True)
|
||||
]
|
||||
registry = Registry().with_resources(resources)
|
||||
|
||||
schema = json.load(open("v1/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/envelope.schema.json",
|
||||
"title": "CommandEnvelope",
|
||||
"description": "Fields every Bambu MQTT command/response shares, across both protocol generations — unversioned, at the bambulab/ root, not duplicated under v1/ or v2/. 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 used across this project's 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/report.schema.json",
|
||||
"title": "BambuReport",
|
||||
"description": "Matches continuum-proxy's src/printer/bambu_commands::BambuReport.",
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"maxProperties": 1,
|
||||
"properties": {
|
||||
"info": { "$ref": "report/get_version.schema.json" },
|
||||
"pushing": { "$ref": "report/pushall.schema.json" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/report/get_version.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/report/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,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/report/pushall.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 continuum-proxy's 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/request.schema.json",
|
||||
"title": "BambuRequest",
|
||||
"description": "Matches continuum-proxy's 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": "request/get_version.schema.json" },
|
||||
"pushing": { "$ref": "request/pushall.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/get_version.schema.json",
|
||||
"title": "GetVersionRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "../../envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "get_version" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://continuum.internal/schemas/bambulab/v1/request/pushall.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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user