Skip to content

Add opt-in protobuf-JSON serde support to Rust binding#279

Merged
johannesschrimpf merged 2 commits into
masterfrom
rust-serde
Jul 6, 2026
Merged

Add opt-in protobuf-JSON serde support to Rust binding#279
johannesschrimpf merged 2 commits into
masterfrom
rust-serde

Conversation

@johannesschrimpf

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in serde cargo feature to the Rust crate that generates protobuf-JSON Serialize/Deserialize impls via pbjson, scoped to PersistentStorageSettings only.

The motivation is the drone's /data/.persistent_storage_settings.json file: the downstream consumer (libblunux) currently hand-writes the JSON serializer to match C++ MessageToJsonString conventions, so new proto fields can silently go missing from the file. Generating the serializer from the schema closes that gap.

Changes

  • rust/Cargo.toml: new serde feature pulling in optional pbjson/serde deps; pbjson-build 0.9 in build-dependencies (requires prost ^0.14, matching the existing prost 0.14). Off by default.
  • rust/build.rs: when the feature is enabled (CARGO_FEATURE_SERDE), runs pbjson-build on the same protox descriptor set, configured with:
    • preserve_proto_field_names() — snake_case keys, matching the existing file format
    • emit_fields() — default-valued fields are always written
    • ignore_unknown_fields() — lenient deserialization
  • rust/src/lib.rs: feature-gated include of the generated serde impls.
  • rust/tests/serde_json.rs: feature-gated tests covering snake_case keys, all-fields-emitted (including false), unknown-field tolerance, acceptance of both webserver_log and webserverLog spellings, and a round-trip.

Generation is deliberately scoped to .blueye.protocol.PersistentStorageSettings rather than the whole package: other messages (guest-port info) intentionally need the opposite emit-defaults behavior, and package-wide generation would also cover well-known types that prost-types has no serde impls for.

Verification

  • cargo build / cargo test without the feature: unchanged, all tests pass; the generated blueye.protocol.rs is byte-identical to master.
  • cargo build --features serde / cargo test --features serde: all 11 tests pass.
  • The generated blueye.protocol.serde.rs contains impls for PersistentStorageSettings and nothing else.

🤖 Generated with Claude Code

Add a `serde` cargo feature that generates protobuf-JSON
Serialize/Deserialize impls via pbjson, scoped to
PersistentStorageSettings. The generated code matches C++
MessageToJsonString conventions for the settings file: snake_case proto
field names, default-valued fields always emitted, and unknown fields
ignored on read (accepting both snake_case and camelCase keys).

Generation is deliberately limited to PersistentStorageSettings: other
messages need the opposite emit-defaults behavior, and package-wide
generation would cover well-known types that prost-types has no serde
impls for.

The feature is off by default; the no-feature build output is
byte-identical to before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in serde Cargo feature to the Rust blueye_protocol crate to generate protobuf-JSON Serialize/Deserialize implementations (via pbjson) for PersistentStorageSettings, aligning JSON output with MessageToJsonString-style conventions and reducing the risk of fields silently being omitted in persisted settings.

Changes:

  • Introduces a serde feature with optional pbjson/serde deps, plus pbjson-build for codegen and serde_json for tests.
  • Extends build.rs to generate a feature-gated blueye.protocol.serde.rs with pbjson-build configured for snake_case, emit-defaults, and ignore-unknown-fields.
  • Adds feature-gated Rust tests validating JSON key casing, emitting default-valued fields, unknown-field tolerance, and round-trip behavior.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
rust/Cargo.toml Adds the opt-in serde feature and declares the new (optional) serde/pbjson dependencies plus test dependency.
rust/build.rs Conditionally runs pbjson-build codegen when CARGO_FEATURE_SERDE is enabled.
rust/src/lib.rs Feature-gated inclusion of the generated pbjson serde impls alongside the prost-generated bindings.
rust/tests/serde_json.rs Adds feature-gated tests for protobuf-JSON serialization/deserialization conventions for PersistentStorageSettings.
rust/Cargo.lock Locks new dependency resolutions introduced by pbjson/serde/serde_json additions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rust/tests/serde_json.rs Outdated
for key in expected_keys {
assert!(object.contains_key(key), "missing key {key:?} in {object:?}");
}
assert_eq!(object.len(), expected_keys.len());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e69f1fa, with a stronger assertion instead of >=: the expected key set is now derived from the message descriptor via prost-reflect (already a dependency), and the fixture uses ..Default::default(). Relaxing to >= wouldn't have removed the churn anyway — the exhaustive struct literal in the fixture also broke compilation on every new field. Now new proto fields require no test changes, and the test automatically verifies each one is emitted, which is the point of generating the serializer.

Comment thread rust/Cargo.toml
Comment on lines +31 to +32
[dev-dependencies]
serde_json = "1.0"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cargo doesn't support this: optional = true under [dev-dependencies] is rejected with "dev-dependencies are not allowed to be optional" (verified against this manifest), and cfg(feature = ...) isn't allowed in target-specific dependency tables either. Since dev-dependencies are never resolved by downstream consumers, the only cost is fetching serde_json when running cargo test in this repo, so leaving it unconditional.

The exact key-count assertion (and the exhaustive struct literal in the
fixture) meant every new PersistentStorageSettings field would break
this test. Deriving the expected key set from the prost-reflect
descriptor and defaulting unset fixture fields keeps the test in sync
with the schema automatically, while strengthening the assertion: every
schema field must appear in the JSON output, including future ones.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@johannesschrimpf johannesschrimpf merged commit 05fb7ac into master Jul 6, 2026
4 checks passed
@johannesschrimpf johannesschrimpf deleted the rust-serde branch July 6, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants