From 7505e759e1f1932069333b12ac53bef944294b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iwo=20Strzebo=C5=84ski?= Date: Fri, 28 Aug 2026 22:38:20 +0000 Subject: [PATCH] =?UTF-8?q?Wire=20up=20proto/bambulab/=20build=20pipeline?= =?UTF-8?q?=20(boilerplate=20=E2=80=94=20message=20bodies=20TBD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- packages/rust-types/build.rs | 42 +++++++++++++++++++++++++++++++-- proto/bambulab/common.proto | 9 +++++++ proto/bambulab/v1/report.proto | 15 ++++++++++++ proto/bambulab/v1/request.proto | 13 ++++++++++ proto/bambulab/v2/report.proto | 9 +++++++ proto/bambulab/v2/request.proto | 8 +++++++ 6 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 proto/bambulab/common.proto create mode 100644 proto/bambulab/v1/report.proto create mode 100644 proto/bambulab/v1/request.proto create mode 100644 proto/bambulab/v2/report.proto create mode 100644 proto/bambulab/v2/request.proto diff --git a/packages/rust-types/build.rs b/packages/rust-types/build.rs index 2d99ad9..be59304 100644 --- a/packages/rust-types/build.rs +++ b/packages/rust-types/build.rs @@ -11,6 +11,11 @@ fn main() { proto_root.join("continuum/v1/print_params.proto"), proto_root.join("continuum/v1/template.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 { @@ -18,8 +23,41 @@ fn main() { } 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 .compile_protos(&proto_files, &[proto_root]) diff --git a/proto/bambulab/common.proto b/proto/bambulab/common.proto new file mode 100644 index 0000000..aebc009 --- /dev/null +++ b/proto/bambulab/common.proto @@ -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. diff --git a/proto/bambulab/v1/report.proto b/proto/bambulab/v1/report.proto new file mode 100644 index 0000000..dbfff32 --- /dev/null +++ b/proto/bambulab/v1/report.proto @@ -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 +// } diff --git a/proto/bambulab/v1/request.proto b/proto/bambulab/v1/request.proto new file mode 100644 index 0000000..10439c4 --- /dev/null +++ b/proto/bambulab/v1/request.proto @@ -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; +// } diff --git a/proto/bambulab/v2/report.proto b/proto/bambulab/v2/report.proto new file mode 100644 index 0000000..16fdfc1 --- /dev/null +++ b/proto/bambulab/v2/report.proto @@ -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. diff --git a/proto/bambulab/v2/request.proto b/proto/bambulab/v2/request.proto new file mode 100644 index 0000000..e771e9a --- /dev/null +++ b/proto/bambulab/v2/request.proto @@ -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.