Drop Protobuf; move continuum/v1 domain model to JSON Schema
Nothing imported the proto-generated code (zero references to continuum-rust-types or @continuum/ts-types's proto export in any consumer repo), and protobuf's real value - one schema compiled to a compact binary wire format for both languages - never applied: BE<->proxy and FE<->BE are both JSON. Also fixes 8 broken $ref paths in the bambulab schemas and a root tsconfig.json that was sweeping the gitignored examples/ directory into typecheck. - proto/continuum/v1/*.proto -> schemas/continuum/v1/*.schema.json - packages/rust-types: hand-written serde structs instead of prost-build codegen - packages/ts-types: drop the generated/ proto barrel export, keep the hand-written TypeBox schemas as the TS source of truth - root package.json: drop build:ts, protoc, ts-proto
This commit is contained in:
@@ -8,11 +8,7 @@ name = "continuum_types"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
prost = "0.13"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.13"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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"),
|
||||
];
|
||||
|
||||
for file in &proto_files {
|
||||
println!("cargo:rerun-if-changed={}", file.display());
|
||||
}
|
||||
|
||||
let mut config = prost_build::Config::new();
|
||||
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");
|
||||
}
|
||||
@@ -1,46 +1,80 @@
|
||||
pub mod continuum {
|
||||
pub mod v1 {
|
||||
include!(concat!(env!("OUT_DIR"), "/continuum.v1.rs"));
|
||||
}
|
||||
// Hand-written structs mirroring the JSON Schemas in `schemas/continuum/v1/`
|
||||
// and the TypeBox schemas in `ts-types/src/schemas/` - this used to be
|
||||
// prost-generated from `.proto` sources, but nothing here needed protobuf's
|
||||
// actual value (cross-language binary codegen): both consumers (this crate
|
||||
// and ts-types) only ever exchange JSON, so a hand-synced struct is less
|
||||
// machinery than a `protoc`/`prost-build` pipeline for the same three
|
||||
// flat DTOs. Keep field names and the `PrintJobStatus` string values in
|
||||
// sync with `ts-types/src/schemas/` by hand when either side changes.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MaterialProfile {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub filament_type: String,
|
||||
pub nozzle_temp_c: f64,
|
||||
pub bed_temp_c: f64,
|
||||
pub density_g_cm3: f64,
|
||||
pub color_hex: String,
|
||||
}
|
||||
|
||||
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}")))
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PrintParameters {
|
||||
pub layer_height_mm: f64,
|
||||
pub infill_percent: i32,
|
||||
pub infill_pattern: String,
|
||||
pub print_speed_mm_s: f64,
|
||||
pub supports_enabled: bool,
|
||||
pub wall_count: i32,
|
||||
}
|
||||
|
||||
// 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.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Template {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub owner_id: String,
|
||||
pub model_file_key: String,
|
||||
pub material: MaterialProfile,
|
||||
pub print_parameters: PrintParameters,
|
||||
pub created_at_unix: i64,
|
||||
pub updated_at_unix: i64,
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PrintJobStatus {
|
||||
#[serde(rename = "PRINT_JOB_STATUS_UNSPECIFIED")]
|
||||
Unspecified,
|
||||
#[serde(rename = "PRINT_JOB_STATUS_QUEUED")]
|
||||
Queued,
|
||||
#[serde(rename = "PRINT_JOB_STATUS_PRINTING")]
|
||||
Printing,
|
||||
#[serde(rename = "PRINT_JOB_STATUS_PAUSED")]
|
||||
Paused,
|
||||
#[serde(rename = "PRINT_JOB_STATUS_COMPLETED")]
|
||||
Completed,
|
||||
#[serde(rename = "PRINT_JOB_STATUS_FAILED")]
|
||||
Failed,
|
||||
#[serde(rename = "PRINT_JOB_STATUS_CANCELLED")]
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PrintJob {
|
||||
pub id: String,
|
||||
pub farm_id: String,
|
||||
pub printer_id: String,
|
||||
pub template_id: String,
|
||||
pub status: PrintJobStatus,
|
||||
pub progress_percent: f64,
|
||||
pub started_at_unix: i64,
|
||||
pub completed_at_unix: i64,
|
||||
pub gcode_file_key: String,
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ fn sample(status: PrintJobStatus) -> PrintJob {
|
||||
farm_id: "farm-1".into(),
|
||||
printer_id: "printer-1".into(),
|
||||
template_id: "template-1".into(),
|
||||
status: status as i32,
|
||||
status,
|
||||
progress_percent: 50.0,
|
||||
started_at_unix: 0,
|
||||
completed_at_unix: 0,
|
||||
@@ -14,9 +14,8 @@ fn sample(status: PrintJobStatus) -> PrintJob {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Regression test: PrintJob.status must serialize as the full name string
|
||||
// (matching ts-types' TypeBox schema), not a bare enum discriminant.
|
||||
#[test]
|
||||
fn status_serializes_as_full_proto_enum_name() {
|
||||
let json = serde_json::to_value(sample(PrintJobStatus::Printing)).unwrap();
|
||||
@@ -28,5 +27,5 @@ 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);
|
||||
assert_eq!(restored.status, PrintJobStatus::Completed);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "^0.32.0",
|
||||
"protobufjs": "^7.4.0",
|
||||
"long": "^5.2.3"
|
||||
"@sinclair/typebox": "^0.32.0"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -1,2 +1 @@
|
||||
export * from "./schemas";
|
||||
export * as proto from "./generated";
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
export * from "./schemas";
|
||||
// `src/generated/**` is produced by `npm run build:ts` (protoc + ts-proto)
|
||||
// from the .proto sources in /proto and is not committed. Re-export it
|
||||
// here once generated so consumers get both the TypeBox runtime schemas
|
||||
// and the proto-derived TS types from a single package entry point.
|
||||
export * as proto from "./generated";
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
export * from "./schemas";
|
||||
|
||||
// `src/generated/**` is produced by `npm run build:ts` (protoc + ts-proto)
|
||||
// from the .proto sources in /proto and is not committed. Re-export it
|
||||
// here once generated so consumers get both the TypeBox runtime schemas
|
||||
// and the proto-derived TS types from a single package entry point.
|
||||
export * as proto from "./generated";
|
||||
|
||||
Reference in New Issue
Block a user