Wire up proto/bambulab/ build pipeline (boilerplate — message bodies TBD)

Adds bambulab/v1 and bambulab/v2 to the prost-build pipeline, scoped
separately from continuum.v1's camelCase convention: bambulab.* uses
snake_case, matching Bambu's actual MQTT wire JSON exactly, since this is
edge-only traffic (continuum-proxy) that has to match the printer's real
format rather than a schema we get to design.

Verified the scoping doesn't collide: type_attribute calls are cumulative,
so the existing '.' blanket selector had to become '.continuum' specifically
- keeping it global would stack a second, conflicting #[serde(rename_all)]
onto every bambulab type too and fail to compile.

No message bodies added - proto/bambulab/{common,v1/*,v2/*}.proto are
intentionally just syntax+package+guidance comments (matching what was
already started for v1). build.rs has inline notes on the two real gotchas
already hit while prototyping this: the field_attribute path-matching
oneof/variant collision (solution: hand-write the envelope enum in Rust
instead of modeling it as a proto oneof), and serde needing #[serde(default)]
on fields that aren't always present.
This commit is contained in:
2026-08-28 22:38:20 +00:00
parent e806a13c70
commit 7505e759e1
6 changed files with 94 additions and 2 deletions
+40 -2
View File
@@ -11,6 +11,11 @@ fn main() {
proto_root.join("continuum/v1/print_params.proto"), proto_root.join("continuum/v1/print_params.proto"),
proto_root.join("continuum/v1/template.proto"), proto_root.join("continuum/v1/template.proto"),
proto_root.join("continuum/v1/print_job.proto"), proto_root.join("continuum/v1/print_job.proto"),
proto_root.join("bambulab/common.proto"),
proto_root.join("bambulab/v1/request.proto"),
proto_root.join("bambulab/v1/report.proto"),
proto_root.join("bambulab/v2/request.proto"),
proto_root.join("bambulab/v2/report.proto"),
]; ];
for file in &proto_files { for file in &proto_files {
@@ -18,8 +23,41 @@ fn main() {
} }
let mut config = prost_build::Config::new(); let mut config = prost_build::Config::new();
config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
config.type_attribute(".", "#[serde(rename_all = \"camelCase\")]"); // continuum.v1 — Continuum's own cross-service API. camelCase to match
// the TS side's existing convention (this is a schema we designed
// ourselves, so we get to pick the convention).
config.type_attribute(".continuum", "#[derive(serde::Serialize, serde::Deserialize)]");
config.type_attribute(".continuum", "#[serde(rename_all = \"camelCase\")]");
// bambulab.* — mirrors Bambu's actual MQTT wire JSON exactly
// (snake_case), because this isn't a schema we get to design: it's
// edge-only traffic (continuum-proxy only, never crosses into TS) that
// has to match whatever the printer actually sends, byte for byte.
//
// IMPORTANT: these two type_attribute calls are scoped to ".bambulab"
// specifically, not "." (global) — type_attribute calls are cumulative,
// so a "." blanket rule here would stack a second, conflicting
// #[serde(rename_all = ...)] onto every bambulab type alongside this
// one and fail to compile. Verified: see commit history for this file.
config.type_attribute(".bambulab", "#[derive(serde::Serialize, serde::Deserialize)]");
config.type_attribute(".bambulab", "#[serde(rename_all = \"snake_case\")]");
// Some Bambu response fields aren't always present (e.g. `result`/
// `reason` on some info responses) — serde requires every struct field
// present in the JSON unless told otherwise. Add one line per such
// field as you find them, e.g.:
// config.field_attribute(".bambulab.v1.InfoResponse.result", "#[serde(default)]");
//
// Do NOT try to model the top-level {"info": {...}} / {"pushing": {...}}
// envelope as a proto `oneof` here — prost-build's field/variant
// attribute matching can't distinguish "the oneof field" from "the
// variants inside it" (both match the same path prefix), so
// `#[serde(flatten)]` lands in the wrong place and won't compile. Write
// the envelope as a plain Rust enum instead, in continuum-proxy —
// serde's default enum representation already produces exactly
// {"info": {...}} with no configuration at all. See
// continuum-proxy/src/printer/bambu_protocol.rs.
config config
.compile_protos(&proto_files, &[proto_root]) .compile_protos(&proto_files, &[proto_root])
+9
View File
@@ -0,0 +1,9 @@
syntax = "proto3";
package bambulab;
// Fields/messages genuinely shared between v1 and v2 (unversioned, since
// they don't change across generations) go here — composition, embedded as
// a field in v1/v2 messages via `import "bambulab/common.proto";`, not
// inheritance. e.g. every Bambu command/response shares sequence_id +
// command; that's a good candidate for a message here.
+15
View File
@@ -0,0 +1,15 @@
syntax = "proto3";
package bambulab.v1;
// import "bambulab/common.proto"; once common.proto has a shared message
// worth embedding (composition, not inheritance — see that file).
// Your report/response messages go here, e.g.:
//
// message InfoResponse {
// string sequence_id = 1;
// string command = 2;
// string result = 3; // not always present — see build.rs field_attribute
// string reason = 4; // not always present — see build.rs field_attribute
// }
+13
View File
@@ -0,0 +1,13 @@
syntax = "proto3";
package bambulab.v1;
// import "bambulab/common.proto"; once common.proto has a shared message
// worth embedding (composition, not inheritance — see that file).
// Your command messages go here, e.g.:
//
// message InfoCommand {
// string sequence_id = 1;
// string command = 2;
// }
+9
View File
@@ -0,0 +1,9 @@
syntax = "proto3";
package bambulab.v2;
// import "bambulab/common.proto"; once common.proto has a shared message
// worth embedding (composition, not inheritance — see that file).
// V2-generation (X1/H2 series) report/response messages go here — e.g.
// whatever AMS/report-schema fields V2 has that V1 doesn't.
+8
View File
@@ -0,0 +1,8 @@
syntax = "proto3";
package bambulab.v2;
// import "bambulab/common.proto"; once common.proto has a shared message
// worth embedding (composition, not inheritance — see that file).
// V2-generation (X1/H2 series) command messages go here.