use std::path::PathBuf; fn main() { let proto_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../proto") .canonicalize() .expect("proto root must exist"); let proto_files = [ proto_root.join("continuum/v1/material.proto"), 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 { println!("cargo:rerun-if-changed={}", file.display()); } 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 .compile_protos(&proto_files, &[proto_root]) .expect("failed to compile continuum .proto sources"); }