Skip to content

fix(hooks): document manual wiring requirement; plan auto-invocation in follow-up #126

@RAprogramm

Description

@RAprogramm

Problem

`#[entity(table = "…", hooks)]` generates a `{Entity}Hooks` trait with default `Ok(())` methods and a doc comment promising "If a `before_*` hook returns an error, the operation is aborted." Users reading the docs reasonably expect generated `create` / `update` / `delete` to invoke those hooks automatically.

They don't. `crates/entity-derive-impl/src/entity/hooks.rs` emits the trait; the CRUD generators in `sql/postgres/crud.rs` never call it. The `examples/hooks/src/main.rs` invokes hooks manually from HTTP handlers — that is the only path that actually fires them today.

Root cause

Generated CRUD lives in `impl {Entity}Repository for sqlx::PgPool`. The hooks trait would have to be implemented for `PgPool` to be invokable from there, but:

  • Orphan rule blocks user crates from writing `impl {Entity}Hooks for sqlx::PgPool` — both type and trait are foreign.
  • A blanket `impl {Entity}Hooks for sqlx::PgPool` emitted by the macro pins all users to the no-op default; specialization is unstable.

Real auto-invocation needs a wrapper type owned by the user crate (e.g. `pub struct UserRepo<H: UserHooks> { pool: PgPool, hooks: H }`) and a parallel `Repository` impl on that wrapper. That is a meaningful API surface change.

Scope of this PR

Honest documentation while the architecture is fixed:

  1. `crates/entity-derive-impl/src/entity/hooks.rs` module docs — add a "Status" block at the top stating clearly that traits are emitted for the user to implement and invoke manually; auto-invocation is tracked in the follow-up issue.
  2. `README.md` Features table — soften the "Lifecycle Hooks" entry to reflect that hooks are an opt-in user contract, not auto-fired, and link to the follow-up issue.
  3. `examples/hooks/src/main.rs` — add a short top-of-file comment naming the manual-wiring pattern explicitly so users don't waste time looking for the missing wiring.

Out of scope (separate follow-up issue, opened on merge)

Auto-invocation in generated CRUD via a wrapper struct or a generic on the trait — pick one of:

  • `pub struct UserRepo<H: UserHooks = NoopUserHooks> { pool: PgPool, hooks: H }` + `impl<H: UserHooks> UserRepository for UserRepo`. Backward compatible: pool-as-repo keeps working without hooks; `UserRepo::new(pool, MyHooks)` opts in.
  • Hook trait method on `Repository` (`fn hooks(&self) -> &impl …Hooks`) with a no-op default impl — needs GATs cleanup.

Either path is a minor-version bump and deserves its own RFC.

Acceptance

After this PR, a user reading the README / module docs / example can no longer be misled into thinking generated CRUD invokes their hooks. The follow-up issue carries the design discussion for the real fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions