From 51dba8e896805049afcd6a5bdaf36ebe8d0990ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iwo=20Strzebo=C5=84ski?= Date: Mon, 31 Aug 2026 13:50:51 +0000 Subject: [PATCH] Fix broken v2 bambulab schema refs, wire up v2 dispatchers, fix PrintJobStatus JSON mismatch - v2/common/device/device.schema.json: stray backtick in bed $ref broke resolution - v2/report/print/print.schema.json: ams $ref pointed at wrong path (missing ams/ subdir) - v2/common/device/plate.schema.json: $id was copy-pasted from bed.schema.json - Add v2/request.schema.json and v2/report.schema.json dispatchers - v2's leaf schemas were unreachable without them, same oneOf convention as v1 - Rewrite schemas/bambulab/README.md layout section, which still described v1's pre-rename paths and claimed v2 was removed - PrintJob.status serialized as a raw i32 in Rust (prost stores proto3 enums as i32) while ts-types' TypeBox schema validates full enum name strings - add serde with= on the field so Rust JSON matches TS and protobuf's own canonical JSON enum mapping, plus a regression test --- packages/rust-types/Cargo.toml | 3 ++ packages/rust-types/build.rs | 11 ++++ packages/rust-types/src/lib.rs | 21 ++++++++ packages/rust-types/tests/print_job_status.rs | 32 +++++++++++ schemas/bambulab/README.md | 54 ++++++++++++++----- .../v2/common/device/device.schema.json | 2 +- .../v2/common/device/plate.schema.json | 2 +- schemas/bambulab/v2/report.schema.json | 22 ++++++++ .../v2/report/print/print.schema.json | 2 +- schemas/bambulab/v2/request.schema.json | 17 ++++++ 10 files changed, 151 insertions(+), 15 deletions(-) create mode 100644 packages/rust-types/tests/print_job_status.rs create mode 100644 schemas/bambulab/v2/report.schema.json create mode 100644 schemas/bambulab/v2/request.schema.json diff --git a/packages/rust-types/Cargo.toml b/packages/rust-types/Cargo.toml index b9283a8..1148332 100644 --- a/packages/rust-types/Cargo.toml +++ b/packages/rust-types/Cargo.toml @@ -13,3 +13,6 @@ serde = { version = "1", features = ["derive"] } [build-dependencies] prost-build = "0.13" + +[dev-dependencies] +serde_json = "1" diff --git a/packages/rust-types/build.rs b/packages/rust-types/build.rs index 2d99ad9..3bf15a6 100644 --- a/packages/rust-types/build.rs +++ b/packages/rust-types/build.rs @@ -21,6 +21,17 @@ fn main() { config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]"); config.type_attribute(".", "#[serde(rename_all = \"camelCase\")]"); + // prost stores proto3 enums as plain `i32` fields (not the generated + // enum type), so the blanket type_attribute above never actually runs + // for `status` - it'd serialize as a bare number, disagreeing with + // TypeBox's full-name string literals in ts-types. Route the field + // through PrintJobStatus's own as_str_name()/from_str_name() instead, + // matching protobuf's own canonical JSON enum representation. + config.field_attribute( + ".continuum.v1.PrintJob.status", + "#[serde(with = \"crate::print_job_status_serde\")]", + ); + config .compile_protos(&proto_files, &[proto_root]) .expect("failed to compile continuum .proto sources"); diff --git a/packages/rust-types/src/lib.rs b/packages/rust-types/src/lib.rs index e2eed22..697b532 100644 --- a/packages/rust-types/src/lib.rs +++ b/packages/rust-types/src/lib.rs @@ -8,6 +8,27 @@ pub use continuum::v1::{ MaterialProfile, PrintJob, PrintJobStatus, PrintParameters, Template, }; +// PrintJob.status is generated as a plain i32 (see build.rs's comment) - +// (de)serialize it via PrintJobStatus's full proto name strings +// (e.g. "PRINT_JOB_STATUS_QUEUED") so Rust's JSON matches ts-types' +// TypeBox schema and protobuf's own canonical JSON enum mapping. +mod print_job_status_serde { + use super::PrintJobStatus; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(value: &i32, serializer: S) -> Result { + let status = PrintJobStatus::try_from(*value).unwrap_or(PrintJobStatus::Unspecified); + status.as_str_name().serialize(serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + let name = String::deserialize(deserializer)?; + PrintJobStatus::from_str_name(&name) + .map(|status| status as i32) + .ok_or_else(|| serde::de::Error::custom(format!("unknown PrintJobStatus: {name}"))) + } +} + // 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 diff --git a/packages/rust-types/tests/print_job_status.rs b/packages/rust-types/tests/print_job_status.rs new file mode 100644 index 0000000..3544758 --- /dev/null +++ b/packages/rust-types/tests/print_job_status.rs @@ -0,0 +1,32 @@ +use continuum_types::{PrintJob, PrintJobStatus}; + +fn sample(status: PrintJobStatus) -> PrintJob { + PrintJob { + id: "job-1".into(), + farm_id: "farm-1".into(), + printer_id: "printer-1".into(), + template_id: "template-1".into(), + status: status as i32, + progress_percent: 50.0, + started_at_unix: 0, + completed_at_unix: 0, + gcode_file_key: "key".into(), + } +} + +// Regression test: PrintJob.status must serialize as the full proto enum +// name (matching ts-types' TypeBox schema), not the raw i32 prost stores +// it as internally. +#[test] +fn status_serializes_as_full_proto_enum_name() { + let json = serde_json::to_value(sample(PrintJobStatus::Printing)).unwrap(); + assert_eq!(json["status"], "PRINT_JOB_STATUS_PRINTING"); +} + +#[test] +fn status_round_trips_through_json() { + let original = sample(PrintJobStatus::Completed); + let json = serde_json::to_string(&original).unwrap(); + let restored: PrintJob = serde_json::from_str(&json).unwrap(); + assert_eq!(restored.status, PrintJobStatus::Completed as i32); +} diff --git a/schemas/bambulab/README.md b/schemas/bambulab/README.md index a28b700..1967abf 100644 --- a/schemas/bambulab/README.md +++ b/schemas/bambulab/README.md @@ -11,7 +11,8 @@ any Rust to do it. ``` bambulab/ - envelope.schema.json the base - sequence_id + command, shared by + common/ + 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/ @@ -19,6 +20,18 @@ bambulab/ ("info", "pushing", ...), each a oneOf (a category can have more than one request shape) report.schema.json BambuReport dispatcher, same idea + common/ shapes shared across v1's request/ and report/ + ams/ + ams.schema.json + ams_unit.schema.json + ams_tray.schema.json + vt_tray.schema.json shared base for AmsTray + the virtual/ + external spool slot, via allOf + $ref + module_info.schema.json + hms.schema.json, net.schema.json, online.schema.json, + upload.schema.json, upgrade_state.schema.json, xcam.schema.json, + ipcam.schema.json, lights_report.schema.json, nozzle_type.schema.json, + nozzle_diameter.schema.json request/ info/ get_version.schema.json @@ -27,21 +40,38 @@ bambulab/ report/ info/ get_version.schema.json - _module_info.schema.json print/ print.schema.json the ongoing print-state push - _ams.schema.json - _ams_unit.schema.json - _ams_tray.schema.json - _vt_tray.schema.json shared base for AmsTray + the virtual/ - external spool slot, via allOf + $ref - _upgrade_state.schema.json - _xcam.schema.json - v2/ add the same split once V2's wire format is - known to differ from V1's - removed for now - rather than leave broken copy-pasted stubs + v2/ same split as v1, once the wire format actually + diverges per field - built out below as that's + been confirmed, not copy-pasted speculatively + request.schema.json dispatcher, same shape as v1's + report.schema.json dispatcher, same shape as v1's + common/ v2 grew a much larger shared shape set than v1 + (device/, job/, care, info, ...) because more + of the wire format has been captured/confirmed + ams/, device/, job/, and the same flat common/*.schema.json files + as v1 (hms, net, online, upload, upgrade_state, xcam, ipcam, + lights_report, nozzle_type, nozzle_diameter, vt_tray) + request/ + info/ + get_version.schema.json + report/ + info/ + get_version.schema.json + _module_info.schema.json + print/ + print.schema.json the ongoing print-state push (command + "push_status") + _2d_report.schema.json, _3d_report.schema.json, + _nozzle_type.schema.json ``` +Add a category to a dispatcher (`v1|v2/request.schema.json` or +`report.schema.json`) whenever a new `request//` or +`report//` schema is added on disk - an unwired leaf schema is +unreachable from validation even though it parses fine on its own. + **One real correction baked into this layout**: the report category for whatever a `pushall` request triggers is `report/print/`, not `report/pushing/`. Sending `pushall` (which *does* go to the `pushing` key diff --git a/schemas/bambulab/v2/common/device/device.schema.json b/schemas/bambulab/v2/common/device/device.schema.json index acef50f..8a5c2c9 100644 --- a/schemas/bambulab/v2/common/device/device.schema.json +++ b/schemas/bambulab/v2/common/device/device.schema.json @@ -6,7 +6,7 @@ "type": "object", "properties": { "airduct": { "$ref": "./airduct.schema.json" }, - "bed": { "$ref": "./bed.schema.json`" }, + "bed": { "$ref": "./bed.schema.json" }, "cam": { "$ref": "./cam.schema.json" }, "ctc": { "$ref": "./ctc.schema.json" }, "ext_tool": { "$ref": "./ext_tool.schema.json" }, diff --git a/schemas/bambulab/v2/common/device/plate.schema.json b/schemas/bambulab/v2/common/device/plate.schema.json index ace82b6..155e703 100644 --- a/schemas/bambulab/v2/common/device/plate.schema.json +++ b/schemas/bambulab/v2/common/device/plate.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v2/common/device/bed.schema.json", + "$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v2/common/device/plate.schema.json", "title": "DevicePlate", "description": "Detailed info about Plate", "type": "object", diff --git a/schemas/bambulab/v2/report.schema.json b/schemas/bambulab/v2/report.schema.json new file mode 100644 index 0000000..91652df --- /dev/null +++ b/schemas/bambulab/v2/report.schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v2/report.schema.json", + "title": "BambuReportV2", + "description": "V2 equivalent of v1/report.schema.json - same 'what root key does this arrive under' convention (see v1/report.schema.json for the print-vs-pushing rationale). Only wires categories that actually have a v2 schema on disk; add a property here when a new v2/report// schema is added.", + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "properties": { + "info": { + "oneOf": [ + { "$ref": "report/info/get_version.schema.json" } + ] + }, + "print": { + "oneOf": [ + { "$ref": "report/print/print.schema.json" } + ] + } + }, + "additionalProperties": false +} diff --git a/schemas/bambulab/v2/report/print/print.schema.json b/schemas/bambulab/v2/report/print/print.schema.json index 0ee158e..58b7d4c 100644 --- a/schemas/bambulab/v2/report/print/print.schema.json +++ b/schemas/bambulab/v2/report/print/print.schema.json @@ -12,7 +12,7 @@ "2D": { "$ref": "./_2d_report.schema.json" }, "3D": { "$ref": "./_3d_report.schema.json" }, - "ams": { "$ref": "../../common/ams.schema.json" }, + "ams": { "$ref": "../../common/ams/ams.schema.json" }, "hms": { "$ref": "../../common/hms.schema.json" }, "lights_report": { "$ref": "../../common/lights_report.schema.json" }, "upgrade_state": { "$ref": "../../common/upgrade_state.schema.json" }, diff --git a/schemas/bambulab/v2/request.schema.json b/schemas/bambulab/v2/request.schema.json new file mode 100644 index 0000000..75e7873 --- /dev/null +++ b/schemas/bambulab/v2/request.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v2/request.schema.json", + "title": "BambuRequestV2", + "description": "V2 equivalent of v1/request.schema.json - one JSON key naming the category, each a oneOf to leave room for more than one request shape per category. Only wires categories that actually have a v2 schema on disk; add a property here when a new v2/request// schema is added.", + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "properties": { + "info": { + "oneOf": [ + { "$ref": "request/info/get_version.schema.json" } + ] + } + }, + "additionalProperties": false +}