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
This commit is contained in:
2026-08-31 13:50:51 +00:00
parent 1b23c58eb6
commit 51dba8e896
10 changed files with 151 additions and 15 deletions
+3
View File
@@ -13,3 +13,6 @@ serde = { version = "1", features = ["derive"] }
[build-dependencies]
prost-build = "0.13"
[dev-dependencies]
serde_json = "1"
+11
View File
@@ -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");
+21
View File
@@ -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<S: Serializer>(value: &i32, serializer: S) -> Result<S::Ok, S::Error> {
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<i32, D::Error> {
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
@@ -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);
}