refactor-openapi-gen #21
Loading…
Reference in a new issue
No description provided.
Delete branch "refactor-openapi-gen"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
WIP: refactor-openapi-gento refactor-openapi-genrefactor-openapi-gento WIP: refactor-openapi-genWIP: refactor-openapi-gento refactor-openapi-genReview Summary
This PR refactors the OpenAPI codegen pipeline: instead of a committed stub crate at
target/generated/serverproduced byscripts/codegen.sh, the generated server code now lives in a proper workspace cratecore/whosebuild.rsinvokesopenapi-gen-build(from a private forgejo registry) at build time, writing output intocore/src/generated. CI and Nix packaging are reworked accordingly, and the generated source is gitignored.The direction is sound — moving codegen into a workspace member with a build script removes the bootstrap/stub-crate hack and the pre-codegen step for plain
cargo build. Lockfile, imports, docs, and CI wiring are all internally consistent, and the--lockedCI runs (which install Java + the generator CLI) should exercise the whole path.The main concern is that
core/build.rswrites generated output into the source tree (core/src/generated). Undernix buildthe source is a read-only Nix store path, so the build script's writes will fail with a permission/read-only error — this breaks the documentednix build/just imageworkflows (which the new CI no longer covers). It also dirties the working tree on every build (mitigated only by gitignore) and can race between concurrent cargo invocations.Secondary issues: the generator CLI jar is downloaded in CI without a checksum pin (supply-chain), the CARGO_* substitution is duplicated across two build scripts and (in
core/build.rs) substitutes everyCARGO_*env var rather than just theCARGO_PKG_*set, which means the spec fed to the generator resolvesCARGO_PKG_NAME/CARGO_PKG_DESCRIPTIONto therecurring-events-corecrate identity instead of the app's (cosmetic today, but a latent correctness trap).*🤖 Review by opencode (opencode/deepseek-v4-flash-free) — verdict: changes
Code review findings from opencode.
@ -28,1 +16,4 @@commands:- version=7.24.0- apt-get update -qq && apt-get install -y -qq default-jre-headless curl- curl -fsSL --retry 3 https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/$version/openapi-generator-cli-$version.jar -o /usr/local/bin/openapi-generator-cli.jar[security] The
openapi-generator-cli-7.24.0.jaris downloaded from Maven Central over HTTPS but its SHA-256 is never verified. Because this jar executes Java code with full build-script privileges, pin/verify the checksum (as is done for other pinned artifacts) to guard against tampering or a compromised mirror.@ -0,0 +5,4 @@let workspace_root = manifest_dir.join("..");let spec = workspace_root.join("openapi.yaml");let rendered_spec = PathBuf::from(env::var("OUT_DIR").unwrap()).join("openapi.yaml");let generated_dir = manifest_dir.join("src/generated");[bug] Generated code is written into
manifest_dir/src/generated, i.e. into the source tree. Withnix build,lib.cleanSource ../.places the source in a read-only Nix store path, sofs::writein this build script will fail with a permission/read-only error. The oldpreBuildapproach wrote into the writabletarget/generated. Since CI no longer exercisesnix build/just image, this path is unverified and will likely break. Prefer writing generated output intoOUT_DIR/CARGO_TARGET_DIRandinclude!-ing it (or make the output dir configurable via an env var, as the oldGENERATED_DIRdid). Writing into the source tree also dirties the checkout on every build and can race between concurrent cargo invocations.@ -0,0 +8,4 @@let generated_dir = manifest_dir.join("src/generated");let mut content = fs::read_to_string(&spec).expect("openapi.yaml missing");for (name, value) in env::vars() {[style] The loop substitutes every
CARGO_*env var into the spec, but the set exported to a build script is much broader than theCARGO_PKG_*placeholders the spec uses (it includes CARGO_MANIFEST_DIR, CARGO_HOME, CARGO_CFG_*, etc.). Restrict substitution toCARGO_PKG_prefixed vars to avoid accidentally replacing future spec tokens with host-specific paths. The same logic is also duplicated verbatim in the rootbuild.rs— consider extracting a shared helper (or a single rendering script) so the two copies cannot drift.@ -0,0 +10,4 @@let mut content = fs::read_to_string(&spec).expect("openapi.yaml missing");for (name, value) in env::vars() {if name.starts_with("CARGO_") {content = content.replace(&format!("{{{{{name}}}}}"), &value);[style] Because this build script runs in the
recurring-events-corecrate,CARGO_PKG_NAMEandCARGO_PKG_DESCRIPTIONresolve torecurring-events-core/ 'Generated OpenAPI server code...'. The spec consumed by the generator therefore carries the core crate's identity rather than the app's (the oldscripts/codegen.shread values from the root[package]). The version is unaffected (shared workspace version), and the root-rendered spec served by Swagger UI is still correct, so this is cosmetic today — but it silently changesinfo.title/info.descriptionin the generated code and is easy to trip on. Consider reading the root package identity explicitly (e.g. from the workspaceCargo.toml) instead of relying on the core crate's env vars.Bash appears unavailable; I'll use file tools instead.
{
"summary": "# Review Summary\n\nSolid refactor: codegen moves into a dedicated
corecrate driven by a build script, CI is consolidated into one step, andAGENTS.md/docs are updated to match. The generated-server-as-workspace-member pattern (core/src/generated, gitignored, produced bycore/build.rs) is coherent and the router/import updates insrc/are consistent.\n\nMain concerns are around supply-chain hygiene and environment consistency rather than core logic:\n\n- CI downloads a JAR from Maven Central and executes it with only atest -ssize check — no checksum verification.\n- Generator version drift: CI pinsopenapi-generator-cli7.24.0, but the Nix devshell andnix builduse whatever nixpkgs ships. Generated server code can differ between the CI-validated binary and the nix-built image.\n- New hard dependency on a private Forgejo cargo registry for theopenapi-gen-buildbuild-dependency; everycargo/nixbuild now requires reachability (and possibly auth) tocode.martials.no.\n- Codegen writes into the crate's own source tree (core/src/generated) rather thanOUT_DIR; everycargocommand in the workspace now hard-requires Java + the CLI on PATH, and the oldnix run nixpkgs#openapi-generator-clifallback fromscripts/codegen.shis gone.\n- Minor: thejust releaseflow's awk/sed now match[workspace.package] versiononly by coincidence (they ignore TOML sections), a broken sentence in AGENTS.md, and CI builds twice (cargo build+cargo test --all-targets).\n\nNone of these are blockers on their own, but the checksum pinning and single-source-of-truth for the generator version are worth fixing before merge.","verdict": "changes",
"comments": [
{
"file": ".woodpecker/verify.yaml",
"line": 19,
"severity": "security",
"message": "The openapi-generator-cli JAR is downloaded from Maven Central and later executed by
core/build.rs, but only atest -ssize check is performed — no checksum verification. Pin and verify a SHA-256 (e.g.curl ... | sha256sum -c) so a tampered or truncated artifact fails the build instead of running. Also consider--max-timeon curl to avoid stalling CI on a hanging download."},
{
"file": ".woodpecker/verify.yaml",
"line": 17,
"severity": "bug",
"message": "Generator version drift: CI pins
openapi-generator-cli7.24.0, butflake.nix:64(devshell) and thenix buildnativeBuildInputs (.nix/pkgs.nix:12) pull whatever version nixpkgs ships. The generatedcore/src/generatedcode is not committed, so CI-validated output and the nix-built image can differ and produce divergent API behavior. Pin a single generator version across CI, devshell, and the Nix build (e.g. via an overlay or a pinned derivation)."},
{
"file": ".cargo/config.toml",
"line": 5,
"severity": "security",
"message": "The new
openapi-gen-buildbuild-dependency (core/Cargo.toml:46) resolves from a private Forgejo sparse registry. Every cargo/nix build (including CI and any future contributor) now requires network access — and possibly credentials — tocode.martials.no; if that registry is unavailable or moves, all builds break and there is no crates.io fallback. Consider mirroring the crate to crates.io or vendoring it so builds stay reproducible and portable."},
{
"file": "core/build.rs",
"line": 8,
"severity": "style",
"message": "Generating into the crate's own source tree (
core/src/generated) instead ofOUT_DIRis unusual: every cargo command in the workspace now hard-requires Java +openapi-generator-clion PATH, and the previous graceful fallback (nix run nixpkgs#openapi-generator-cli) fromscripts/codegen.shis gone. It also means thecorecrate can never build standalone or be published. This is documented in AGENTS.md, but worth confirming the tradeoff is intentional — usinginclude!fromOUT_DIRwould avoid the source-tree churn."},
{
"file": "core/build.rs",
"line": 11,
"severity": "bug",
"message": "The placeholder substitution iterates
env::vars()(nondeterministic order) over ALLCARGO_*vars using the core crate's values — e.g.{{CARGO_PKG_NAME}}would render asrecurring-events-core, and{{CARGO_MANIFEST_DIR}}would point intocore/. Only{{CARGO_PKG_VERSION}}is used today, so this is latent, but the logic is also duplicated verbatim with the rootbuild.rs. Consider a shared helper that substitutes only the explicitly-supportedCARGO_PKG_*placeholders from the workspace[package]/[workspace.package]values."},
{
"file": ".woodpecker/verify.yaml",
"line": 23,
"severity": "performance",
"message": "
cargo build --workspace --all-targets --lockedfollowed bycargo test --workspace --all-targets --all-features --lockedcompiles the workspace twice.cargo testbuilds on its own, so the explicit build step is redundant. Also--all-featurespulls in theconversiondeps (frunk, itertools, syn 1/2/3) into the lockfile and build even though that feature isn't exercised; scope--all-featuresto the tests that actually need it if possible."},
{
"file": "Cargo.toml",
"line": 6,
"severity": "style",
"message": "The version now lives under
[workspace.package](version = \"0.3.3\"). The Justfile release flow (_current_version/write_version, Justfile:65-70) still greps/seds for a top-level^version = \"...\"line and only works because awk/sed ignore TOML sections — fragile forjust release. Update those recipes to read/write the[workspace.package]version explicitly."},
{
"file": "AGENTS.md",
"line": 93,
"severity": "style",
"message": "Broken sentence introduced by the doc rewrite: 'Any
{{CARGO_PKG_<KEY>}}token in the spec is substituted with the' (line 92) is followed by 'The rootbuild.rsrenders...' (line 93). Drop the dangling 'the' and merge the two lines."}
]
}
*🤖 Review by opencode (opencode/deepseek-v4-flash-free) — verdict: comment
Review Summary
This PR refactors code generation so the generated OpenAPI server code moves out of a
target/generated/serverworkspace member into a dedicatedcorecrate (recurring-events-core) whosebuild.rsrunsopenapi-gen-buildat build time. It also converts the rootCargo.tomlinto a workspace, replaces the shellscripts/codegen.shflow, updates CI to install Java + the generator CLI inline, pinsmy-nix-libtov0.1.0, and updates docs.Overall this is a well-executed, cohesive change: AGENTS.md is updated to match reality, the
{{CARGO_PKG_VERSION}}substitution is preserved through the core crate's OUT_DIR, generated sources are gitignored and excluded from treefmt, and the flake/CI wiring is documented. The migration itself is sound.Issues worth addressing before merge:
just release/tag-current/bump-patchare broken by the workspace migration. The Justfile still readsCargo.tomlwithawk '/^version = /'and writes withsed 's/^version = ".*"/...', but the root package now usesversion.workspace = trueand the real version lives indented under[workspace.package]._current_versionreturns an empty string, so the release flow tagsvand computes a garbagenew_version. This is a concrete regression.The core build script writes generated code into its own package source tree (
core/src/generated). Concurrent cargo invocations (cargo build+cargo check/cargo clippy/rust-analyzer) will run the generator into the same directory and can race/interleave files, andcargo packageexplicitly disallows build scripts modifying the package dir. Writing to a per-target/OUT_DIR location (or guarding with a lock) would be more robust.CI executes an unverified JAR.
verify.yamldownloadsopenapi-generator-cli-7.24.0.jarvia curl and runs it as root with only a non-empty (test -s) check. Pinning and verifying a SHA-256 checksum would remove a supply-chain vector.Minor: the new build script only emits
rerun-if-changedforopenapi.yaml; the old one also watchedCargo.toml, which matters for the{{CARGO_PKG_VERSION}}placeholder. Worth adding for robustness.No runtime/security problems found in the application code paths (
src/server.rs,src/service/date_service.rsimport updates are mechanical and correct).*🤖 Review by opencode (opencode/deepseek-v4-flash-free) — verdict: changes
Code review findings from opencode.
@ -28,1 +16,4 @@commands:- version=7.24.0- apt-get update -qq && apt-get install -y -qq default-jre-headless curl- curl -fsSL --retry 3 https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/$version/openapi-generator-cli-$version.jar -o /usr/local/bin/openapi-generator-cli.jar[security] The CI pipeline downloads
openapi-generator-cli-7.24.0.jarfrom Maven Central with curl and executes it as root, but only verifies the file is non-empty (test -s). A tampered or replaced artifact would execute arbitrary code with the CI job's credentials. Pin and verify a known SHA-256 checksum (e.g. viasha256sum -cor an--checksumcapable downloader) before running it.[bug] The version helpers are now broken by the workspace migration. Root
Cargo.tomlusesversion.workspace = true(line 12) and the actual version lives indented under[workspace.package](line 6)._current_versionrunsawk -F'"' '/^version = / {print $2}'which only matches a column-0version = "..."line, so it returns an empty string;write_version'ssed 's/^version = ".*"/...'has the same column-0 anchor. As a resultjust release,tag-currentandbump-patchwill tagvand compute a garbagenew_version(just releaseis now a footgun on a fresh checkout of this branch). Update both helpers to read/write the[workspace.package] versionentry.@ -0,0 +5,4 @@let workspace_root = manifest_dir.join("..");let spec = workspace_root.join("openapi.yaml");let rendered_spec = PathBuf::from(env::var("OUT_DIR").unwrap()).join("openapi.yaml");let generated_dir = manifest_dir.join("src/generated");[style] The build script writes generated output into its own package source directory (
manifest_dir/src/generated). This is a Cargo anti-pattern: (1) concurrent cargo invocations (e.g.cargo buildwhilecargo check/cargo clippy/rust-analyzer run) will invoke the generator into the same directory and can race/interleave files, producing nondeterministic output; (2)cargo packagerefuses to package crates whose build script modifies the package dir; (3) stale files from a changed spec are not cleaned, so an interrupted generator run can leave a partially-overwritten tree. Consider writing to a target-relative or per-build directory and including via a stable path, or at minimum serialize generation (file lock) and clear the directory before regenerating.@ -0,0 +23,4 @@.expect("failed to generate OpenAPI server").emit_rerun_if_changed();println!("cargo:rerun-if-changed={}", spec.display());[style] The previous root
build.rsemittedcargo:rerun-if-changed=Cargo.tomlso a version bump would re-render the{{CARGO_PKG_VERSION}}placeholder. The new script only watchesopenapi.yamland relies on a version bump incidentally invalidating the crate fingerprint. Since AGENTS.md documents that editing the workspace version is the only step needed to bump the API version, emitprintln!("cargo:rerun-if-changed={}", workspace_root.join("Cargo.toml").display())to make regeneration robust and explicit.Review Summary
The PR moves OpenAPI code generation from a standalone
scripts/codegen.sh+ committed generated crate undertarget/into a build-time step: a newcoreworkspace member (recurring-events-core) whosebuild.rsinvokesopenapi-gen-build(fetched from a private Forgejo registry) and writes generated modules intocore/src/generated. Application code is updated to import fromrecurring_events_core::generated::contract::*, the rootbuild.rsis deleted, CI is consolidated into a single step that installs Java/CLI beforecargo build/test/clippy, and the Nix package drops itspreBuildcodegen hook. AGENTS.md and treefmt are updated consistently.Overall this is a clean, well-executed refactor: generated code is properly gitignored, excluded from treefmt, workspace versioning is shared via
[workspace.package], and the Renovate circular-dependency bootstrap hack disappears becausecore/Cargo.tomlis committed with explicit deps. Thecargo:rerun-if-changedwiring for the spec and Cargo.toml is correct.The main blocker is the release tooling in
Justfile: the_current_version/write_version/tag-current/bump-patchrecipes still parseversion = "..."from the rootCargo.toml, but this PR changes it toversion.workspace = true, sojust releasewould create a tag literally namedvandbump-patchwould no-op/fail. Secondary points: the generated-code-into-source-tree approach is fragile for read-only/sandboxed builds, the CI generator jar is downloaded without a checksum pin,CARGO_PKG_*placeholder substitution now resolves against thecorepackage (not the root), the whole build now depends on a private registry, and clippy-D warningsruns over generated code with only a small allow-list.*🤖 Review by opencode (opencode/deepseek-v4-flash-free) — verdict: changes
Code review findings from opencode.
@ -0,0 +2,4 @@git-fetch-with-cli = true[registries.forgejo]index = "sparse+https://code.martials.no/api/packages/Personal/cargo/"[style] The build now hard-depends on
openapi-gen-buildfrom a private Forgejo registry: both CI (cargo build --locked) andnix buildmust reachcode.martials.noto resolve dependencies (and thegit-fetch-with-cli = truesetting requires a git CLI in every build environment). If that server is unreachable, nothing builds. Worth confirming the Nix build sandbox has network access to the registry and documenting the availability expectation, e.g. in AGENTS.md.@ -28,1 +16,4 @@commands:- version=7.24.0- apt-get update -qq && apt-get install -y -qq default-jre-headless curl- curl -fsSL --retry 3 https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/$version/openapi-generator-cli-$version.jar -o /usr/local/bin/openapi-generator-cli.jar[security] The openapi-generator-cli jar is downloaded over HTTPS but its SHA-256 is not pinned;
test -sonly checks that the file is non-empty. This is a supply-chain risk on the build tooling. Consider pinning the expected checksum (and verifying with e.g.sha256sum -c) and ideally caching the jar.@ -29,2 +23,4 @@- cargo build --workspace --all-targets --locked- cargo test --workspace --all-targets --all-features --locked- rustup component add clippy- cargo clippy --workspace --all-targets --locked -- -D warnings[style]
cargo clippy --workspace --all-targets -- -D warningsalso lints the generatedcore/src/generatedmodules, which are only covered by a small crate-level allow-list (non_camel_case_types,unused_imports,unused_mut). If the generator output ever trips any clippy lint (e.g.too_many_arguments,needless_borrow), CI will break for reasons unrelated to the app code. Consider adding#![allow(clippy::all)]incore/src/lib.rsor scoping clippy to non-generated code.[bug] This PR changes the root package version to
version.workspace = true(Cargo.toml line 12), but the release recipes still parseversion = "..."._current_version(awk/^version = /) now matches nothing, sojust tag-currentbuildstag="v${version}"=vand would create/push a tag literally namedv;bump-patchthen errors on the emptymajor.minor.patchsplit, andwrite_version's sed pattern no longer matches anything, so the commit no-ops.just releaseis broken by this PR. Update these recipes to read/write the version from the[workspace.package]table.@ -0,0 +5,4 @@let workspace_root = manifest_dir.join("..");let spec = workspace_root.join("openapi.yaml");let rendered_spec = PathBuf::from(env::var("OUT_DIR").unwrap()).join("openapi.yaml");let generated_dir = manifest_dir.join("src/generated");[style] The build script writes generated code into the source tree (
core/src/generated). This requires the source directory to be writable at build time, which is fragile for read-only checkouts/sandboxed builds and leaves build artifacts in the working tree. It happens to work for the current nixpkgs/Crane unpack flow, but a future change (e.g. vendoring, read-only CI cache,SOURCE_DATE_EPOCH) would silently breakcargo build. Consider emitting intoOUT_DIRand wiring viainclude!, or at least documenting the writable-source requirement.@ -0,0 +8,4 @@let generated_dir = manifest_dir.join("src/generated");let mut content = fs::read_to_string(&spec).expect("openapi.yaml missing");for (name, value) in env::vars() {[style]
{{CARGO_PKG_*}}substitution now runs in thecorecrate's build script, soCARGO_PKG_NAME/CARGO_PKG_DESCRIPTIONresolve torecurring-events-corerather than the rootrecurring-eventspackage. Only{{CARGO_PKG_VERSION}}is currently used (workspace version matches, so no breakage), but this is a latent footgun for future spec edits — worth a comment or a pinned substitution of the root package values.