Files
continuum-schemas/schemas/bambulab/README.md
T
octoturge 8d4a08e36d 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.
2026-08-28 22:58:18 +00:00

3.3 KiB

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:

{
  "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/):

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"}}
)