Drop proto/bambulab/ — plain hand-written Rust structs instead
Reconsidered after actually feeling the friction: this data is JSON over MQTT (not binary protobuf), only continuum-proxy ever parses it (raw vendor wire format, never crosses into TS), so none of protobuf's actual value (cross-language codegen, binary wire efficiency) applied here. Every gotcha hit while building this (field_attribute's oneof/variant path collision, needing .bambulab-scoped type_attribute calls, explicit #[serde(default)] per not-always-present field) came from forcing prost-build's codegen model onto a plain-JSON, Rust-only use case. Verified the replacement is strictly simpler, not just smaller: a hand-written struct's Option<T> field needs zero extra attributes to handle a missing JSON key (prost's generated String needed an explicit field_attribute call per field), and #[serde(flatten)] on a hand-written field has no ambiguity at all (prost-build's oneof/variant path collision doesn't exist without prost-build's codegen in the picture). The replacement lives in continuum-proxy: src/printer/bambu_commands/. continuum.v1 is unaffected — that schema is genuinely cross-language and proto still earns its keep there.
This commit is contained in:
@@ -11,11 +11,6 @@ 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 {
|
||||
@@ -23,41 +18,8 @@ fn main() {
|
||||
}
|
||||
|
||||
let mut config = prost_build::Config::new();
|
||||
|
||||
// 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.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
|
||||
config.type_attribute(".", "#[serde(rename_all = \"camelCase\")]");
|
||||
|
||||
config
|
||||
.compile_protos(&proto_files, &[proto_root])
|
||||
|
||||
@@ -8,20 +8,18 @@ pub use continuum::v1::{
|
||||
MaterialProfile, PrintJob, PrintJobStatus, PrintParameters, Template,
|
||||
};
|
||||
|
||||
pub mod bambulab {
|
||||
// common.proto and bambulab/v2/*.proto have no messages yet, so
|
||||
// there's nothing for prost to emit for them — `include!` against a
|
||||
// nonexistent file breaks the build. Uncomment each once it has real
|
||||
// content:
|
||||
//
|
||||
// include!(concat!(env!("OUT_DIR"), "/bambulab.rs")); // common.proto's messages
|
||||
pub mod v1 {
|
||||
// request.proto + report.proto both declare `package bambulab.v1;`,
|
||||
// so prost combines them into one output file, regardless of which
|
||||
// input file each message came from.
|
||||
include!(concat!(env!("OUT_DIR"), "/bambulab.v1.rs"));
|
||||
}
|
||||
// pub mod v2 {
|
||||
// include!(concat!(env!("OUT_DIR"), "/bambulab.v2.rs"));
|
||||
// }
|
||||
}
|
||||
// Bambu's raw MQTT command/report structs are NOT modeled here — they're
|
||||
// plain hand-written Rust + serde in continuum-proxy's
|
||||
// src/printer/bambu_commands/, not proto. That data is JSON-over-MQTT
|
||||
// (not binary protobuf), and only continuum-proxy ever needs to parse it
|
||||
// — none of protobuf's actual value (cross-language codegen, binary wire
|
||||
// efficiency) applies, and forcing it through prost-build's codegen model
|
||||
// anyway produced real, avoidable friction (see this crate's git history
|
||||
// for the specifics: a field/variant attribute-matching collision on
|
||||
// `oneof`, needing package-scoped `type_attribute` calls, explicit
|
||||
// `field_attribute(..., "#[serde(default)]")` for fields that aren't
|
||||
// always present — every one of these disappears with a hand-written
|
||||
// struct, where `Option<T>` and `#[serde(flatten)]` just work with zero
|
||||
// configuration). Proto stays for continuum.v1 above, which — unlike
|
||||
// this — is a schema we designed ourselves and that both TS and Rust
|
||||
// genuinely need.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
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.
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
// }
|
||||
@@ -1,23 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package bambulab.v1;
|
||||
|
||||
// One real, fully-wired example — copy this shape for every other command
|
||||
// (pushing/pushall, print control, etc.). sequence_id is `string`, not
|
||||
// int64: Bambu sends it as a JSON string ("0"), and this crosses the wire
|
||||
// as JSON over MQTT, not binary protobuf — the Rust type needs to match
|
||||
// what serde_json actually produces, not what "feels more correct" for a
|
||||
// counter.
|
||||
message InfoCommand {
|
||||
string sequence_id = 1;
|
||||
string command = 2;
|
||||
}
|
||||
|
||||
// Add your next message here, e.g.:
|
||||
//
|
||||
// message PushingCommand {
|
||||
// string sequence_id = 1;
|
||||
// string command = 2;
|
||||
// int32 version = 3;
|
||||
// int32 push_target = 4;
|
||||
// }
|
||||
@@ -1,9 +0,0 @@
|
||||
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.
|
||||
@@ -1,8 +0,0 @@
|
||||
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.
|
||||
Reference in New Issue
Block a user