Skip to content

fix: findOrFail with no row answers 404, not 500 - #41

Merged
necessarylion merged 1 commit into
mainfrom
fix-model-not-found-404
Nov 6, 2026
Merged

necessarylion merged 1 commit into
mainfrom
fix-model-not-found-404

Conversation

@mydoxie

@mydoxie mydoxie commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Problem

findOrFail() and firstOrFail() throw ModelNotFoundException when no row matches. It was a plain Exception. So in a Dox app the answer was 500, and Dox logged it as a server error. Laravel answers 404 here.

Fix

ModelNotFoundException now extends IHttpException with code 404. IHttpException is in dox_annotation, which dox_orm already uses. No new dependency.

The answer is the normal Dox 404 body:

{"code": "not_found", "message": "Not Found"}

The table, column and id are not sent to the client. Dox does not log 4xx HTTP exceptions, so it is no longer logged as a server error.

Not breaking

  • Same class name. on ModelNotFoundException catch still works.
  • Same table, id and column fields, and the same toString().
  • It still implements Exception.
  • One small change: the constructor is no longer const. IHttpException has fields that can change, so a const constructor is not possible. Only code that wrote const ModelNotFoundException(...) must drop const.

Tests

  • Unit test: the exception is an IHttpException with code 404, keeps its fields, and its body has no table or id.
  • HTTP test: a route with findOrFail and no row gives 404 with the body above.
  • Both fail without the fix. DRIVER=sqlite dart test: 560 passed, 2 skipped.

ModelNotFoundException was a plain Exception. So Dox answered 500 and
logged it as a server error when findOrFail() or firstOrFail() found
no row. Now it extends IHttpException with code 404. The body is the
normal Dox 404 body. The table, column and id are not sent.
@mydoxie

mydoxie commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Bot review

Verdict: OK to merge. Nothing blocking.

What I checked

  • dox_core turns it into a 404. http_response_handler.dart checks payload is IHttpException first. It sends toResponse() with code as the status. This runs before the "hide error text unless development" check. So the body is {"code": "not_found", "message": "Not Found"} in every APP_ENV. The table, column, id and stack trace are not sent.
  • It is not logged as a 500. dox_core 3.0.2 and main skip report() for an IHttpException with a 4xx code. dox_core 3.0.0 and 3.0.1 skip report() for every IHttpException. The same response path is in 3.0.0 to 3.0.2. toResponse() is in dox_annotation 3.0.0 and later. So it works with the released packages, not only main.
  • Nothing else changes. The class name, the fields, toString() and on ModelNotFoundException catch stay the same. implements Exception plus extends IHttpException is fine: IHttpException does not implement Exception, so is Exception stays true. No code in dox, dox-auth or dox-test catches IHttpException before this exception. None of them call findOrFail/firstOrFail.
  • Tests: DRIVER=sqlite dart test passed 560 tests. dart analyze --fatal-infos packages found no issues. dart format changed nothing. CI is green.
  • I put the old exception file back to check the tests. The HTTP test fails (Expected: <404> Actual: <500>). read_helpers_test.dart does not compile. So the tests prove the fix.
  • The CHANGELOG entry is under ## Unreleased. The version is not bumped.

Non-blocking

  1. The const constructor is gone. The only const ModelNotFoundException(...) in all the dox repos was in this PR's own test. User code almost never builds this exception itself, so the risk is very low. To keep const, the class would have to implements IHttpException with getter and setter pairs and copy toResponse(). That is not worth it.
  2. Docs (dox repo, separate PR): docs/database/query-builder.md around line 79 could say that in a route this exception answers 404. Tick todo.md line 75 after the merge.

@necessarylion
necessarylion merged commit 49b0835 into main Nov 6, 2026
2 checks passed
@necessarylion
necessarylion deleted the fix-model-not-found-404 branch November 6, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants