You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every service crate manually implements IntoResponse for its ApiError enum using an identical structural pattern: match each variant to a (StatusCode, message) pair and return (status, Json(json!({ \"error\": message }))).into_response(). A #[derive(ApiIntoResponse)] proc-macro would eliminate this boilerplate, enforce the consistent JSON shape, and make the status-code mapping declarative rather than imperative.
This issue tracks both creating the agentd-macros crate and implementing the first derive macro within it. It is complementary to #126 (which consolidates error types) — the derive macro makes the remaining per-service IntoResponse impls trivially thin regardless of whether types are fully unified.
Current State
The following four files each contain an identical ~10-line impl IntoResponse block:
crates/macros crate scaffolded with proc-macro = true and proc-macro2, syn, quote deps
#[derive(ApiIntoResponse)] derive macro implemented and tested in the macros crate
#[status(StatusCode::X)] helper attribute accepted on each enum variant
impl IntoResponse removed from common, hook, monitor, and ask error files
JSON response shape is { "error": "<Display output>" } — identical to current behavior
All existing error response tests pass unchanged
cargo clippy produces no new warnings
No change to public API behavior
Notes
The proc-macro crate should depend on syn with full features and quote/proc-macro2 — these are already transitive deps via serde, thiserror, and sea-orm, so compile overhead is minimal.
Variants that wrap sub-errors (e.g., ask::ApiError::TmuxError(#[from] TmuxError)) work naturally since the generated impl calls self.to_string() which delegates to thiserror's Display.
ask/src/error.rs has a manual impl From<anyhow::Error> that is unrelated and should be preserved.
Overview
Every service crate manually implements
IntoResponsefor itsApiErrorenum using an identical structural pattern: match each variant to a(StatusCode, message)pair and return(status, Json(json!({ \"error\": message }))).into_response(). A#[derive(ApiIntoResponse)]proc-macro would eliminate this boilerplate, enforce the consistent JSON shape, and make the status-code mapping declarative rather than imperative.This issue tracks both creating the
agentd-macroscrate and implementing the first derive macro within it. It is complementary to #126 (which consolidates error types) — the derive macro makes the remaining per-serviceIntoResponseimpls trivially thin regardless of whether types are fully unified.Current State
The following four files each contain an identical ~10-line
impl IntoResponseblock:Desired State
A new
crates/macrosproc-macro crate exposes a#[derive(ApiIntoResponse)]derive macro. Error variants are annotated with their HTTP status code:The generated impl always returns
(status, Json(json!({ "error": self.to_string() }))).into_response(), delegating the message formatting tothiserror'sDisplayimpl.New Crate Structure
Cargo.tomlworkspace member entry andagentd-macrosadded as a dependency tocommon,hook,monitor,ask.Affected Files
crates/macros/— new crate (create)Cargo.toml— add workspace membercrates/common/src/error.rs— apply derive, remove manual impl (~14 lines)crates/hook/src/error.rs— apply derive, remove manual impl (~12 lines)crates/monitor/src/error.rs— apply derive, remove manual impl (~11 lines)crates/ask/src/error.rs— apply derive, remove manual impl (~18 lines)Acceptance Criteria
crates/macroscrate scaffolded withproc-macro = trueandproc-macro2,syn,quotedeps#[derive(ApiIntoResponse)]derive macro implemented and tested in the macros crate#[status(StatusCode::X)]helper attribute accepted on each enum variantimpl IntoResponseremoved fromcommon,hook,monitor, andaskerror files{ "error": "<Display output>" }— identical to current behaviorcargo clippyproduces no new warningsNotes
synwithfullfeatures andquote/proc-macro2— these are already transitive deps viaserde,thiserror, andsea-orm, so compile overhead is minimal.ask::ApiError::TmuxError(#[from] TmuxError)) work naturally since the generated impl callsself.to_string()which delegates tothiserror'sDisplay.ask/src/error.rshas a manualimpl From<anyhow::Error>that is unrelated and should be preserved.macro_rules!as a lighter-weight alternative if proc-macro complexity is not justified; revisit after Consolidate duplicated error types into agentd-common #126 lands.