Implement §2 method translators — OSchema → Rust trait methods + Python dataclass methods #60

Open
opened 2026-05-19 02:21:58 +00:00 by timur · 1 comment
Owner

Context

Follow-up from #55. The hybrid hero_rpc2 SDK rails landed on branch issue-55-codegen-alignment (commits up through 479a558) but two method-translator pieces were deliberately scoped out — the per-method codegen that turns OSchema-defined RPC methods into actual Rust trait methods (hero_rpc2 #[rpc(server, client)] shape) and Python dataclass methods. Both have compile_error! / # TODO markers in the generated output today.

What's done (rails)

Per #55 closing summary and the vendor commits:

  • crates/hero_rpc2/ vendored from delandtj/hero_rpc2 (e2f4d27).
  • HeroRequestContext types + ServerBuilder header-lift config (479a558).
  • Python SDK codegen target wired into OschemaBuildConfig / generator dispatch.
  • Generated output for both Rust trait + Python dataclass currently emits compile_error!("TODO: method translator") / # TODO placeholders where method bodies should go.

What this issue does

Part A — OSchema → Rust trait methods

For every OSchema-defined service method, emit a #[method(name = "...")] entry inside the generated trait (the one annotated with hero_rpc2::prelude::#[rpc(server, client)]). Signature must:

  • Take ctx: Option<HeroRequestContext> as the first arg (per the parent META locked decision).
  • Map OSchema types to their Rust generated types (already produced by crate::generate::Generator).
  • Return RpcResult<T> matching the OSchema return type.
  • Remove the compile_error! placeholder.

Server impl stub stays preserved (don't regenerate body — only the trait signature is generated; the impl rpc.rs is contributor-owned).

Part B — OSchema → Python dataclass methods

For every OSchema-defined service method, emit a Python method on the SDK class:

  • Async client method that serialises args to JSON-RPC params, calls the configured transport, deserialises result into the Python dataclass equivalent of the OSchema return type.
  • Python dataclasses for all OSchema types should already exist (generated by the Python target's type emitter from hero_rpc2 vendor work).
  • Remove the # TODO: method translator markers.

Cross-target consistency

Both translators read the same OSchema method definitions. Factor shared parsing logic to keep them in sync.

Constraints

  • Builds on top of issue-55-codegen-alignment branch until that PR merges. Coordinate with the merger.
  • Existing scaffold + generator tests (141 currently passing per #55) stay green; add tests for the translator output.
  • Reference: example/recipe_server should compile and the generated Rust client + Python SDK should be usable end-to-end against the running server.

Acceptance

  • No compile_error!("TODO: method translator") in generated Rust output.
  • No # TODO: method translator in generated Python output.
  • recipe_server builds with the generated Rust client; round-trip RPC call test passes.
  • Python SDK has matching async methods; smoke test (Python script calling system.ping over UDS) passes.
  • New tests cover at least: scalar param, struct param, list return, optional context arg.
  • #55 — parent landing PR; rails were added there.
  • #59 — Generator per-target refactor; will move the translator code into per-target modules. Coordinate scheduling.
  • Parent META: hero_skills#262.
## Context Follow-up from [#55](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55). The hybrid hero_rpc2 SDK rails landed on branch `issue-55-codegen-alignment` (commits up through `479a558`) but two method-translator pieces were deliberately scoped out — the per-method codegen that turns OSchema-defined RPC methods into actual Rust trait methods (hero_rpc2 `#[rpc(server, client)]` shape) and Python dataclass methods. Both have `compile_error!` / `# TODO` markers in the generated output today. ## What's done (rails) Per [#55 closing summary](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55) and the vendor commits: - `crates/hero_rpc2/` vendored from `delandtj/hero_rpc2` ([e2f4d27](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/e2f4d27)). - `HeroRequestContext` types + `ServerBuilder` header-lift config ([479a558](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/479a558)). - Python SDK codegen target wired into `OschemaBuildConfig` / generator dispatch. - Generated output for both Rust trait + Python dataclass currently emits `compile_error!("TODO: method translator")` / `# TODO` placeholders where method bodies should go. ## What this issue does ### Part A — OSchema → Rust trait methods For every OSchema-defined service method, emit a `#[method(name = "...")]` entry inside the generated trait (the one annotated with `hero_rpc2::prelude::#[rpc(server, client)]`). Signature must: - Take `ctx: Option<HeroRequestContext>` as the first arg (per the parent META locked decision). - Map OSchema types to their Rust generated types (already produced by `crate::generate::Generator`). - Return `RpcResult<T>` matching the OSchema return type. - Remove the `compile_error!` placeholder. Server impl stub stays preserved (don't regenerate body — only the trait signature is generated; the impl `rpc.rs` is contributor-owned). ### Part B — OSchema → Python dataclass methods For every OSchema-defined service method, emit a Python method on the SDK class: - Async client method that serialises args to JSON-RPC params, calls the configured transport, deserialises result into the Python dataclass equivalent of the OSchema return type. - Python dataclasses for all OSchema types should already exist (generated by the Python target's type emitter from `hero_rpc2` vendor work). - Remove the `# TODO: method translator` markers. ### Cross-target consistency Both translators read the same OSchema method definitions. Factor shared parsing logic to keep them in sync. ## Constraints - Builds on top of `issue-55-codegen-alignment` branch until that PR merges. Coordinate with the merger. - Existing scaffold + generator tests (141 currently passing per #55) stay green; add tests for the translator output. - Reference: `example/recipe_server` should compile and the generated Rust client + Python SDK should be usable end-to-end against the running server. ## Acceptance - [ ] No `compile_error!("TODO: method translator")` in generated Rust output. - [ ] No `# TODO: method translator` in generated Python output. - [ ] `recipe_server` builds with the generated Rust client; round-trip RPC call test passes. - [ ] Python SDK has matching async methods; smoke test (Python script calling `system.ping` over UDS) passes. - [ ] New tests cover at least: scalar param, struct param, list return, optional context arg. ## Related - [#55](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55) — parent landing PR; rails were added there. - [#59](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/59) — Generator per-target refactor; will move the translator code into per-target modules. Coordinate scheduling. - Parent META: [hero_skills#262](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/262).
Author
Owner

Pushed issue-60-method-translators on top of issue-55-codegen-alignment with the §2 method translators.

What landed

Rust (crates/generator/src/build/emit/rust_rpc2.rs)

  • Replaces the compile_error! stub with full OSchema → trait emission.
  • Each service Foo { ... } method becomes #[method(name = "<svc>.<m>")] async fn <svc>_<m>(&self, ctx: Option<HeroRequestContext>, …) -> RpcResult<T> inside the #[rpc(server, client)] trait.
  • Inlines WASM-style type defs at the top of <sdk>/src/<domain>.rs so the trait file is self-contained (no hero_rpc_osis dep — SDK-friendly).
  • crates/hero_rpc2/src/context.rs: HeroRequestContext now derives Serialize / Deserialize with #[serde(default)] so the locked trait shape survives jsonrpsee's Params::parse while staying interoperable with the existing header-lift transport (JSON nullNone).

Python (crates/generator/src/build/emit/python_sdk.rs)

  • Replaces the # TODO method translator stub with @dataclass(kw_only=True) + per-method async def emission.
  • Generates a small _transport.py next to the domain modules: UdsJsonRpcTransport(socket_path, headers=…) wrapping httpx.AsyncClient over UDS.
  • Method bodies serialise args into a named-object params and await the transport's JSON-RPC 2.0 dispatch.

Acceptance evidence

  • cargo test -p hero_rpc_generator --lib build::emit10/10 pass, covering scalar params, struct params, list returns, the ctx: Option<HeroRequestContext> first-arg shape, and the no-schema fallback.
  • cargo build -p recipe_sdk_rpc2 — a new workspace crate that pulls in example/recipe_server/schemas/recipes/, runs the rpc2 + Python emitters, and compiles the generated trait. ✓ builds end-to-end.
  • HERO_RPC_PYTHON=<venv>/bin/python3 cargo test -p recipe_sdk_rpc2 --features python-e2e --test python_ping_e2e — spins up hero_rpc2's HTTP-on-UDS server with a hand-registered system.ping, subprocesses Python through the generated UdsJsonRpcTransport, and asserts the echoed payload. ✓ round-trip passes. Skips gracefully when python3 / httpx aren't on PATH so bare-image CI stays green.

Files

  • crates/generator/src/build/emit/rust_rpc2.rs — method translator + tests
  • crates/generator/src/build/emit/python_sdk.rs — method translator + transport module + tests
  • crates/generator/src/build/builder.rs — pass schemas_dir through to the emitters
  • crates/hero_rpc2/src/context.rsSerialize / Deserialize on HeroRequestContext
  • example/recipe_sdk_rpc2/ — end-to-end validation crate (Cargo.toml, build.rs, lib.rs, tests/python_ping_e2e.rs, tests/debug_curl.rs)

Notes

  • Branch is issue-60-method-translators built on issue-55-codegen-alignment per the parent META's tier-2 instructions; once #55 lands on development, this rebases on top.
  • The #[rpc(server, client)] macro requires a direct jsonrpsee dep on consumer crates so the proc-macro's proc_macro_crate lookup resolves — recipe_sdk_rpc2/Cargo.toml carries that as a small dep alongside hero_rpc2.
Pushed [`issue-60-method-translators`](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/issue-60-method-translators) on top of `issue-55-codegen-alignment` with the §2 method translators. ## What landed **Rust** (`crates/generator/src/build/emit/rust_rpc2.rs`) - Replaces the `compile_error!` stub with full OSchema → trait emission. - Each `service Foo { ... }` method becomes `#[method(name = "<svc>.<m>")] async fn <svc>_<m>(&self, ctx: Option<HeroRequestContext>, …) -> RpcResult<T>` inside the `#[rpc(server, client)]` trait. - Inlines WASM-style type defs at the top of `<sdk>/src/<domain>.rs` so the trait file is self-contained (no `hero_rpc_osis` dep — SDK-friendly). - `crates/hero_rpc2/src/context.rs`: `HeroRequestContext` now derives `Serialize` / `Deserialize` with `#[serde(default)]` so the locked trait shape survives jsonrpsee's `Params::parse` while staying interoperable with the existing header-lift transport (JSON `null` → `None`). **Python** (`crates/generator/src/build/emit/python_sdk.rs`) - Replaces the `# TODO method translator` stub with `@dataclass(kw_only=True)` + per-method `async def` emission. - Generates a small `_transport.py` next to the domain modules: `UdsJsonRpcTransport(socket_path, headers=…)` wrapping `httpx.AsyncClient` over UDS. - Method bodies serialise args into a named-object `params` and await the transport's JSON-RPC 2.0 dispatch. ## Acceptance evidence - `cargo test -p hero_rpc_generator --lib build::emit` — **10/10 pass**, covering scalar params, struct params, list returns, the `ctx: Option<HeroRequestContext>` first-arg shape, and the no-schema fallback. - `cargo build -p recipe_sdk_rpc2` — a new workspace crate that pulls in `example/recipe_server/schemas/recipes/`, runs the rpc2 + Python emitters, and compiles the generated trait. **✓ builds end-to-end.** - `HERO_RPC_PYTHON=<venv>/bin/python3 cargo test -p recipe_sdk_rpc2 --features python-e2e --test python_ping_e2e` — spins up `hero_rpc2`'s HTTP-on-UDS server with a hand-registered `system.ping`, subprocesses Python through the *generated* `UdsJsonRpcTransport`, and asserts the echoed payload. **✓ round-trip passes.** Skips gracefully when `python3` / `httpx` aren't on PATH so bare-image CI stays green. ## Files - `crates/generator/src/build/emit/rust_rpc2.rs` — method translator + tests - `crates/generator/src/build/emit/python_sdk.rs` — method translator + transport module + tests - `crates/generator/src/build/builder.rs` — pass `schemas_dir` through to the emitters - `crates/hero_rpc2/src/context.rs` — `Serialize` / `Deserialize` on `HeroRequestContext` - `example/recipe_sdk_rpc2/` — end-to-end validation crate (Cargo.toml, build.rs, lib.rs, `tests/python_ping_e2e.rs`, `tests/debug_curl.rs`) ## Notes - Branch is `issue-60-method-translators` built on `issue-55-codegen-alignment` per the parent META's tier-2 instructions; once #55 lands on `development`, this rebases on top. - The `#[rpc(server, client)]` macro requires a direct `jsonrpsee` dep on consumer crates so the proc-macro's `proc_macro_crate` lookup resolves — `recipe_sdk_rpc2/Cargo.toml` carries that as a small dep alongside `hero_rpc2`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_rpc#60
No description provided.