Repository navigation
fix(aw-server): return JSON errors with a reason for 400/404/422 under /api - #772
Conversation
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #772 +/- ##
===========================================
+ Coverage 70.81% 83.70% +12.88%
===========================================
Files 51 82 +31
Lines 2916 10664 +7748
===========================================
+ Hits 2065 8926 +6861
- Misses 851 1738 +887 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@greptileai review |
|
CI status note: both red checks are unrelated to this diff.
The Greptile P2 (400 catcher untested) is addressed in c2ed8b8. |
|
CI-green and mergeable (Greptile 5/5) — waiting only on a maintainer click. This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted. |
🤖 AI code reviewAdds 400/404/422 catchers scoped to /api that return JSON error bodies, plus an ApiJson<T> data guard that records the serde parse error in a request-local cache so the 422 catcher can include a reason, and replaces Json<T> on the JSON POST handlers for buckets, events, heartbeat, query, settings and import. Also trims two clippy allow attributes in aw-query and adds an API-error integration test. Safe to merge — no P0/P1 findingsConfidence 5/5 ✅ No thread-worthy findings. Advisory notes follow; they are retained without opening review threads. 2 advisory findings (summary-only, not scored)These P2 guard, heuristic, trade-off, or documentation claims are retained for judgment without opening review threads.
The new reason mechanism only works for handlers switched to ApiJson<T>. bucket_import_form still parses its payload through Form<ImportForm> with an embedded Json<BucketsExport> (import.rs:152), so a malformed JSON body inside the multipart upload fails the Form data guard before any ApiJson::from_data runs and BodyError is never populated. The matching /api catcher (400 or 422) therefore returns {"message":"Bad Request"} or {"message":"Unprocessable Entity"} with no serde detail, while the sibling POST /api/0/import JSON route now returns the full reason. A client whose multipart import has a typo gets exactly the unhelpful message this PR was written to remove, and diagnosis still falls back to server logs. The Form guard's error needs the same stash, or the form route's parse error text surfaced. How this was verified: BodyError is only written in ApiJson::from_data (util.rs:77); bucket_import_form at import.rs:158 bypasses that path entirely, so no reason text is available to api_error_json.
api_error_json makes Rocket's reason phrase plus the raw serde_json rendering of the parse failure the client-facing message, e.g. "Unprocessable Entity: invalid type: map, expected a sequence at line 1 column 16". The tail is Rocket's JsonError Display text, not a stable public contract; serde_json/Rocket have rephrased these strings between releases. Because this message is now the documented replacement for the default HTML page, any API client parsing body["message"] (and the new test, which asserts msg.contains("line 1")) depends on internal wording and on serde's byte-column arithmetic. The position info is also noise for most callers, who want the missing field name, not offsets. Keeping the human phrase stable and returning the serde text as a separate structured detail (e.g. an object with message/reason) would make the wire contract robust to dependency upgrades. How this was verified: The only 422 catcher output is api_error_json, which forwards serde's Display text unchanged; api.rs:961 pins the 'line 1' rendering, confirming the test depends on that unstable format. Files changed (8) — the diff as I read it
Previous review passes
Reviewed Maintainer commands
|
* fix(aw-transform): empty regex rules never match
RegexRule::new("") compiled to a match-all regex in fancy_regex, so a
blank category rule matched every event. aw-core's Python
aw_transform.classify.Rule deliberately never matches an empty regex
("would erroneously match everything") — aw-server-rust silently
diverged from it. ActivityWatch/aw-webui#1026 hides this for the web UI
by sending blank rules as {type: 'none'}, but other clients (aw-client
categorize queries, scripts, the CLI) still see the divergence.
Add a never_matches flag set when the pattern is empty, checked before
any regex evaluation, so an empty-regex rule behaves like Rule::None
instead of a wildcard. Covered by a new regression test that fails on
master.
Also carries the aw-query clippy::block_scrutinee allow from #771/#772
(unrelated Rust 1.99+ toolchain drift) so this branch's own CI/clippy
passes independently of that PR's merge.
Git-Session-Id: 1d99
* fix(aw-transform): derive never_matches in From<Regex> conversion
Greptile P1: Rule::from(Regex::new("").unwrap()) hard-coded
never_matches: false, so the public conversion path still let an empty
regex match every event — the exact bug this PR fixes, reachable past
the new guard. Derive the flag from the source pattern and cover the
From path in the regression test.
Git-Session-Id: 2b7710f0-a6ec-5fa0-b895-bf715c0f8ab4
4bc6f41 to
68926b8
Compare
|
CI update: the new red clippy is unrelated to this diff. It's a The fix is #779 (all checks green, mergeable). Once #779 lands on |
…r /api Git-Session-Id: 86cf
…quest Git-Session-Id: 6c812caf-4c8d-55e1-bbde-94c86942e236
…attrs Rust 1.99 reports "duplicated attribute" when the same lint is allowed both via an outer #[allow] on `mod parser` in lib.rs AND via #![allow] inner attrs in parser.rs itself. Commit 32637fa (PR ActivityWatch#769) added #![allow(unknown_lints)] and Commit 7ed5bb8 (PR ActivityWatch#771) then also added clippy::block_scrutinee to the outer #[allow] on `mod parser`, and the outer attr already had a stray second copy of the lint — causing three duplicate-attribute errors. Remove clippy::block_scrutinee and unknown_lints from the outer attr in lib.rs; parser.rs's inner attrs already cover them. Git-Session-Id: 3cac9398-0b45-46e7-b3d7-f63b74e52cb1
8343b0c to
60c96cc
Compare
|
Rebased onto master (conflict in |
The rebase conflict resolution in 60c96cc dropped clippy::block_scrutinee from the outer #[allow] on mod parser. parser.rs inner attrs do not cover that lint, so clippy fails with 9 block_scrutinee errors. Master already landed the duplicate-attr cleanup in ActivityWatch#785 and is clippy-green. Restore the allow list to match master so this JSON-error PR does not regress query lints. Git-Session-Id: aa33f88b-48bd-5a09-9e0e-a9d5de6fc4a8
|
Clippy on 60c96cc was this PR, not master: the rebase conflict resolution dropped Restored the allow list to match master in a3c139c. Master already landed the duplicate-attr cleanup in #785 and is clippy-green. Local |
|
@TimeToBuildBob Conflicts |
Request-guard failures (malformed event JSON, bad body, unknown /api route) returned Rocket's default HTML page; the real reason only reached the server log ("No 422 catcher registered"). Clients can't tell what failed (see #174). aw-server (Python) already returns JSON
{"message": ...}./apireturning the same{"message": ...}shape asHttpErrorJson.ApiJson<T>, a drop-in forJson<T>on the event/bucket/heartbeat/query/settings/import-JSON handlers that stashes the serde error so the catcher can include it, e.g.Unprocessable Entity: missing field \timestamp` at line 1 column 15`.test_api_errors_are_json_with_reason.cargo test -p aw-server, clippy and fmt clean.Not included:
?limit=abcbeing silently ignored (separate change).