refactor(rust/core): unify job serialization across bindings via serde (Closes #1113) - #1120
Conversation
#1113) Binding-duplication cluster (final part of #1113; hygiene quick-wins landed in #1119). Wire-format-preserving — proven byte-identical end-to-end across all three native bindings. - Job wire-shape: add `Serialize` derive to `Job` and `JobEventReceipt` (`JobStatus`/`JobEvent` already had it); delete the three copied `job_status_to_str` (serde snake_case enum serialization is the single source of truth). Route every binding through serde — FFI via serde_json::to_string (Result handled, no unwrap), napi via to_value (still returns Value), py via to_value + the shared json_value_to_pyobject. No `skip_serializing_if` on Job/JobEvent/JobEventReceipt: they keep emitting None -> null (all keys present), which the Python SDK's strict snapshot["progress"] access depends on. - Dedup json_value_to_py: keep the lib.rs `&Value` version (now pub(crate)), delete the owned-Value copy in jobs_py.rs. - Dedup three near-identical timeout-secs validators into a shared validate_secs_to_duration(secs, negative_is_none); validate_deadline_secs and the wait / run-as-job deadline blocks left untouched (different shapes). Net ~245 lines of duplication removed. Added 6 Rust shape/helper unit tests (full + minimal keep-nulls Job shape, JobEvent "type" key, JobEventReceipt 3-key shape, progress f32 roundtrip, timeout edge cases). Validation: cargo test green (incl. new shape tests); cargo check clean across python/typescript/ffi feature variants; clippy no new warnings. Integration: src-tests rebuild 12/12 (rust-core, nodejs/napi, pyo3 python link, java FFI); MeshJob suites 72/72 — uc21 Python 21/21, uc22 TS 24/24, uc23 Java 27/27 — status snapshots (keep-nulls), events, and long-running lifecycle parse identically across all three SDKs. Closes #1113 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR consolidates duplicate job serialization and timeout validation logic across the three binding layers (Python/PyO3, JavaScript/N-API, C/FFI) by introducing Serialize derives on core types, a shared timeout validation helper, and refactoring all binding entry points to use canonical serde-based conversion instead of hand-written field assembly. ChangesBinding layer consolidation
Sequence Diagram(s)sequenceDiagram
participant FFI_Caller as FFI Caller
participant FFI_Function as FFI Function (mesh_job_proxy_status)
participant json_helper as JSON Helper (job_to_string)
participant write_out_json
participant thread_local as Thread-local Error
participant c_output as C Output (out_json)
FFI_Caller->>FFI_Function: mesh_job_proxy_status(..., out_json, ...)
FFI_Function->>json_helper: job_to_string(&job)
alt Serialization Success
json_helper-->>FFI_Function: Ok(json_str)
FFI_Function->>write_out_json: write_out_json(json_str.as_ptr(), out_json)
write_out_json->>c_output: copy json_str into *out_json
write_out_json-->>FFI_Function: 0 (success)
FFI_Function-->>FFI_Caller: 0 (success)
else Serialization Failure
json_helper-->>FFI_Function: Err(serde_json::Error)
FFI_Function->>write_out_json: write_out_json(err_json_str.as_ptr(), out_json)
write_out_json->>thread_local: set_last_error(message)
write_out_json-->>FFI_Function: -1 (error)
FFI_Function-->>FFI_Caller: -1 (error)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/runtime/core/src/jobs_napi.rs (1)
108-124: 💤 Low valueConsider propagating serialization errors for consistency.
The
unwrap_or(serde_json::Value::Null)fallback differs from Python (propagatesPyValueError) and FFI (sets last-error and returns-1). While current types are guaranteed serializable, this could silently mask issues if a non-serializable field is added in the future.If signature stability is the constraint, this is acceptable as-is.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/runtime/core/src/jobs_napi.rs` around lines 108 - 124, The three helpers (job_event_to_json, job_event_receipt_to_json, job_to_json) currently swallow serde errors by returning Value::Null; change their signatures to return Result<serde_json::Value, E> so serialization failures are propagated (e.g. Result<serde_json::Value, serde_json::Error> or mapped to napi::Error) and replace unwrap_or(...) with serde_json::to_value(&...)? to return the error; ensure callers are updated to handle or convert the Result into the appropriate FFI/napi error path so failures are reported instead of silently producing Null.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/runtime/core/src/jobs_napi.rs`:
- Around line 108-124: The three helpers (job_event_to_json,
job_event_receipt_to_json, job_to_json) currently swallow serde errors by
returning Value::Null; change their signatures to return
Result<serde_json::Value, E> so serialization failures are propagated (e.g.
Result<serde_json::Value, serde_json::Error> or mapped to napi::Error) and
replace unwrap_or(...) with serde_json::to_value(&...)? to return the error;
ensure callers are updated to handle or convert the Result into the appropriate
FFI/napi error path so failures are reported instead of silently producing Null.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 09e865a2-668e-4723-8de4-85854ddf7f23
📒 Files selected for processing (5)
src/runtime/core/src/jobs_ffi.rssrc/runtime/core/src/jobs_napi.rssrc/runtime/core/src/jobs_py.rssrc/runtime/core/src/lib.rssrc/runtime/core/src/task_backend.rs
Summary
The binding-duplication cluster of #1113 — the second and final part (hygiene quick-wins landed in #1119). Unifies job serialization across the three native bindings (Python/pyo3, Node/napi, FFI) by routing them all through serde instead of three hand-built per-binding mappers. Wire-format-preserving — proven byte-identical end-to-end.
SerializetoJobandJobEventReceipt(JobStatus/JobEventalready had it); delete the three copiedjob_status_to_str(serde'srename_all="snake_case"is the single source of truth — noas_str()added). Route each binding through serde: FFI →serde_json::to_string(Result handled, nounwrap), napi →serde_json::to_value(still returnsValue; signatures unchanged), py →serde_json::to_value+ the sharedjson_value_to_pyobject.skip_serializing_ifonJob/JobEvent/JobEventReceipt: theirOptionfields keep emittingNone → null(all keys present), which the Python SDK's strictsnapshot["progress"]access depends on. (#[serde(default)]is deserialize-only — no serialize effect.)json_value_to_py: keep thelib.rs&Valueversion (nowpub(crate)), delete the owned-Valuecopy injobs_py.rs.parse_*timeout_secs→ onevalidate_secs_to_duration(secs, negative_is_none)(py/napi reject-negative; FFI negative-as-none).validate_deadline_secsand the wait / run-as-job deadline blocks left untouched (different shapes).Net ~245 lines of duplication removed.
Review Notes
Independent review: 0 blockers, 0 warnings, 3 INFO (all non-defects, left as-is):
.unwrap_or(Value::Null)vs FFI/py error-propagation — cosmetic; these plain structs never fail to serialize.out of rangeoverflow substring is preserved for the napi test).progress_f32_roundtripsuses0.5(exact) — deterministic by design.Verified: keep-nulls intact, exact field-set/
"type"-key match, FFI errors routed (no panic across C ABI), timeout policy preserved per call site (incl.NEG_INFINITYordering),validate_deadline_secsnot folded in.Test plan
cargo testgreen incl. 6 new shape/helper tests (full + minimal/keep-nullsJob,JobEvent"type"key,JobEventReceipt3-key,progressf32 roundtrip, timeout edge cases)cargo checkclean acrosspython/typescript/ffifeature variants;cargo clippyno new warnings (one removed)tc02_build_rust_core,tc02a_build_rust_core_nodejs(napi), Python SDK (pyo3 link), Java SDK (FFI)uc21_meshjobPython 21/21,uc22_meshjob_ts24/24,uc23_meshjob_java27/27 — status snapshots (keep-nulls), events (recv/send/list/subscribe), and long-running lifecycle parse identically in every SDK. Zero serialization/parse mismatches.Completes #1113 alongside the already-merged #1119 quick-wins.
Closes #1113
Summary by CodeRabbit
Bug Fixes
Improvements