Refactor crate::generate::Generator per-target emission into modules #59

Open
opened 2026-05-19 00:29:32 +00:00 by timur · 1 comment
Owner

Context

Follow-up from #56. The agent who modularized build/build.rs flagged (comment 33741) that per-target code emission (rust_types, rust_rpc, openrpc, js, rhai) does not live in build.rs — it lives in crate::generate::Generator. #56 was scoped "no behaviour change," so the per-target split couldn't be done there.

This issue tracks splitting Generator itself.

Goal

Extract per-target emission from the monolithic crate::generate::Generator into focused modules, one per output target:

  • generate/rust_types.rs — Rust struct/enum emission from OSchema types
  • generate/rust_server.rs — OSIS server handler emission
  • generate/rust_rpc.rs — RPC trait + handler emission (will need updating again once hero_rpc2 macro path lands in #55)
  • generate/openrpc.rs — OpenRPC 1.3.2 spec emission
  • generate/js.rs — JS/TS SDK class emission
  • generate/rhai.rs — Rhai binding emission
  • generate/python.rs — Python SDK emission (new target per #55; empty stub for now if hero_rpc2 work hasn't added the Python codegen yet)

Keep Generator itself as a thin orchestrator that loads schemas, picks targets per the build config, and dispatches to the per-target modules.

Constraints

  • No behaviour change. Same files emitted, same content, same paths.
  • recipe_server and petstore_server must build unchanged.
  • All existing unit + integration tests stay green.
  • Each new module ≤ ~500 LOC (same target as #56).
  • Public API of Generator stays stable.

Out of scope

  • Adding Python emitter logic — tracked in #55 as part of the hero_rpc2 vendor work. This issue only adds the empty python.rs placeholder.
  • Changing emission semantics for any existing target.
  • Touching build/build.rs further (handled in #56).

Acceptance

  • No file under crates/generator/src/generate/ exceeds ~500 LOC.
  • cargo build, cargo test, cargo clippy all green.
  • example/recipe_server and example/petstore_* build unchanged.
  • Public surface (Generator::from_dir, Generator::generate, etc.) unchanged.
  • Each per-target module has a 1-line doc comment naming the target and the source OSchema fields it consumes.
  • #56 — preceding build.rs split.
  • #55 — hero_rpc2 vendor + Python SDK (will modify the modules created here).
  • Parent META: hero_skills#262.
## Context Follow-up from [#56](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/56). The agent who modularized `build/build.rs` flagged ([comment 33741](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/56#issuecomment-33741)) that per-target code emission (`rust_types`, `rust_rpc`, `openrpc`, `js`, `rhai`) does **not** live in `build.rs` — it lives in `crate::generate::Generator`. #56 was scoped "no behaviour change," so the per-target split couldn't be done there. This issue tracks splitting `Generator` itself. ## Goal Extract per-target emission from the monolithic `crate::generate::Generator` into focused modules, one per output target: - `generate/rust_types.rs` — Rust struct/enum emission from OSchema types - `generate/rust_server.rs` — OSIS server handler emission - `generate/rust_rpc.rs` — RPC trait + handler emission (will need updating again once hero_rpc2 macro path lands in [#55](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55)) - `generate/openrpc.rs` — OpenRPC 1.3.2 spec emission - `generate/js.rs` — JS/TS SDK class emission - `generate/rhai.rs` — Rhai binding emission - `generate/python.rs` — Python SDK emission ([new target per #55](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55#issuecomment-33681); empty stub for now if hero_rpc2 work hasn't added the Python codegen yet) Keep `Generator` itself as a thin orchestrator that loads schemas, picks targets per the build config, and dispatches to the per-target modules. ## Constraints - **No behaviour change.** Same files emitted, same content, same paths. - `recipe_server` and `petstore_server` must build unchanged. - All existing unit + integration tests stay green. - Each new module ≤ ~500 LOC (same target as #56). - Public API of `Generator` stays stable. ## Out of scope - Adding Python emitter logic — tracked in [#55](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55) as part of the hero_rpc2 vendor work. This issue only adds the empty `python.rs` placeholder. - Changing emission semantics for any existing target. - Touching `build/build.rs` further (handled in #56). ## Acceptance - [ ] No file under `crates/generator/src/generate/` exceeds ~500 LOC. - [ ] `cargo build`, `cargo test`, `cargo clippy` all green. - [ ] `example/recipe_server` and `example/petstore_*` build unchanged. - [ ] Public surface (`Generator::from_dir`, `Generator::generate`, etc.) unchanged. - [ ] Each per-target module has a 1-line doc comment naming the target and the source OSchema fields it consumes. ## Related - [#56](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/56) — preceding `build.rs` split. - [#55](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55) — hero_rpc2 vendor + Python SDK (will modify the modules created here). - Parent META: [hero_skills#262](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/262).
Author
Owner

Status: implementation complete, branch issue-59-generate-modular

Single commit 9b1d6b4: pure refactor, no behaviour change.

Layout — crates/generator/src/generate/

File LOC Role
mod.rs 470 Generator struct, builders, generate() dispatch, GenerationResult, helpers, tests
rust_types.rs 154 types_generated.rs, types_wasm_generated.rs, types.rs wrapper, domain core mod.rs
rust_server.rs 129 osis_server_generated.rs, optional server/mod.rs (nested layout)
rust_rpc.rs 137 Legacy OSIS client (osis_client_generated.rs + client mod.rs). hero_rpc2 lives in build::emit::rust_rpc2
openrpc.rs 29 openrpc.json (shared helper for core + server dirs)
js.rs 41 JS SDK types_generated.js
rhai.rs 27 rhai_types_generated.rs
python.rs 6 Empty stub — populated by hero_rpc2 work, currently in build::emit::python_sdk
examples.rs 331 basic_crud example + run.sh + README scaffolding
e2e.rs 214 End-to-end client_server.rs Tokio example
tests_emit.rs 233 Per-domain tests.rs CRUD round-trip generation
wasm_build_script.rs 207 Existing #[allow(dead_code)] WASM packaging helper, isolated

All files ≤ 500 LOC ✓.

Pattern

Per-target methods use impl super::Generator { pub(in crate::generate) fn ... } blocks across sibling modules. Generator fields are pub(in crate::generate) so submodules can read them. Shared helpers (to_pascal_case, to_snake_case, write_file) stay in mod.rs.

openrpc::write_openrpc_json and rhai::write_rhai_types are free functions because both rust_types.rs and rust_server.rs invoke openrpc.

Acceptance

  • No file under crates/generator/src/generate/ exceeds ~500 LOC (largest: mod.rs at 470).
  • cargo build green (workspace).
  • cargo test -p hero_rpc_generator --lib: 125 passed, 1 ignored.
  • example/recipe_server and example/petstore_server build unchanged.
  • Public surface unchanged — build::emit::rust_sdk, build::emit::domain, build::scaffold, build::config all still compile against the same Generator::from_dir/.core/.server/.client/.js/.rhai/.tests/.e2e_examples/.nested_layout/.server_output_dir/.sdk_output_dir/.client_only/.client_types_crate/.debug/.generate() API.
  • Each per-target module carries a 1-line doc comment naming the target and the source OSchema fields it consumes (see header of each file).

Byte-for-byte verification

diff -r baseline/recipes example/recipe_server/src/recipes   # empty
diff -r baseline/petstore_src example/petstore_server/src    # empty

Notes

  • Pre-existing clippy::too_many_arguments and clippy::items_after_test_module errors in build/emit/python_sdk.rs and build/scaffold.rs (from #54/#60) remain — confirmed they reproduce on development. Out of scope for #59.
  • cargo fmt --check flags pre-existing formatting in build/emit/python_sdk.rs; also pre-existing on development. generate/ itself is fmt-clean.

Ready for review. PR can target development.

## Status: implementation complete, branch `issue-59-generate-modular` Single commit [`9b1d6b4`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/9b1d6b4): pure refactor, no behaviour change. ### Layout — `crates/generator/src/generate/` | File | LOC | Role | |---|---:|---| | `mod.rs` | 470 | `Generator` struct, builders, `generate()` dispatch, `GenerationResult`, helpers, tests | | `rust_types.rs` | 154 | `types_generated.rs`, `types_wasm_generated.rs`, `types.rs` wrapper, domain core `mod.rs` | | `rust_server.rs` | 129 | `osis_server_generated.rs`, optional `server/mod.rs` (nested layout) | | `rust_rpc.rs` | 137 | Legacy OSIS client (`osis_client_generated.rs` + client `mod.rs`). hero_rpc2 lives in `build::emit::rust_rpc2` | | `openrpc.rs` | 29 | `openrpc.json` (shared helper for core + server dirs) | | `js.rs` | 41 | JS SDK `types_generated.js` | | `rhai.rs` | 27 | `rhai_types_generated.rs` | | `python.rs` | 6 | Empty stub — populated by hero_rpc2 work, currently in `build::emit::python_sdk` | | `examples.rs` | 331 | `basic_crud` example + `run.sh` + `README` scaffolding | | `e2e.rs` | 214 | End-to-end `client_server.rs` Tokio example | | `tests_emit.rs` | 233 | Per-domain `tests.rs` CRUD round-trip generation | | `wasm_build_script.rs` | 207 | Existing `#[allow(dead_code)]` WASM packaging helper, isolated | All files ≤ 500 LOC ✓. ### Pattern Per-target methods use `impl super::Generator { pub(in crate::generate) fn ... }` blocks across sibling modules. Generator fields are `pub(in crate::generate)` so submodules can read them. Shared helpers (`to_pascal_case`, `to_snake_case`, `write_file`) stay in `mod.rs`. `openrpc::write_openrpc_json` and `rhai::write_rhai_types` are free functions because both `rust_types.rs` and `rust_server.rs` invoke openrpc. ### Acceptance - [x] No file under `crates/generator/src/generate/` exceeds ~500 LOC (largest: `mod.rs` at 470). - [x] `cargo build` green (workspace). - [x] `cargo test -p hero_rpc_generator --lib`: **125 passed, 1 ignored**. - [x] `example/recipe_server` and `example/petstore_server` build unchanged. - [x] Public surface unchanged — `build::emit::rust_sdk`, `build::emit::domain`, `build::scaffold`, `build::config` all still compile against the same `Generator::from_dir/.core/.server/.client/.js/.rhai/.tests/.e2e_examples/.nested_layout/.server_output_dir/.sdk_output_dir/.client_only/.client_types_crate/.debug/.generate()` API. - [x] Each per-target module carries a 1-line doc comment naming the target and the source OSchema fields it consumes (see header of each file). ### Byte-for-byte verification ```bash diff -r baseline/recipes example/recipe_server/src/recipes # empty diff -r baseline/petstore_src example/petstore_server/src # empty ``` ### Notes - Pre-existing `clippy::too_many_arguments` and `clippy::items_after_test_module` errors in `build/emit/python_sdk.rs` and `build/scaffold.rs` (from #54/#60) remain — confirmed they reproduce on `development`. Out of scope for #59. - `cargo fmt --check` flags pre-existing formatting in `build/emit/python_sdk.rs`; also pre-existing on `development`. `generate/` itself is fmt-clean. Ready for review. PR can target `development`.
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#59
No description provided.