Skip to content

Answer every unknown path under /api with the JSON 404 - #215

Open
edubraqd wants to merge 2 commits into
rubyforgood:ruby-for-good-2026from
edubraqd:fix/api-namespace-routing
Open

edubraqd wants to merge 2 commits into
rubyforgood:ruby-for-good-2026from
edubraqd:fix/api-namespace-routing

Conversation

@edubraqd

Copy link
Copy Markdown

What this does

Three small fixes to the JSON 404 handling under the api namespace, all in the routing/error seam:

  • /api and /api/ answered with the HTML 404 page. The catch-all was *unmatched, and a wildcard segment needs at least one character, so the namespace root fell through. The glob is now optional (/api(/*unmatched)), which also covers the bare prefix.
  • unmatched_route was a public method on Api::BaseController, so every API controller inherited it as an action (Api::V1::TasksController.action_methods included it). It now lives on a dedicated Api::ErrorsController, the only thing the catch-all reaches.
  • GET /api/v1/tasks.xml answered 200 with a JSON body, as if the extension had been honoured. The tasks resources now carry constraints: { format: :json }, so /tasks and /tasks.json behave as before and any other extension falls through to the JSON 404.

The mobile app negotiates via the Accept header, not the extension, so it is not affected.

Issue

Closes #89
Closes #90
Closes #91

Testing

  • Covered by tests

Integration tests for /api, /api/, /api/v1, /api/nope (GET) and /api (POST); for /api/v1/tasks.xml and /api/v1/tasks/1.xml; and an assertion that unmatched_route is no longer an action on the tasks controller. The /api, /api/ and POST cases fail without the first commit; the .xml ones without the second.

Ran bin/rails test (139 runs, 0 failures), bin/rubocop and bin/brakeman clean, and bin/rails routes | grep api to eyeball the result.

Written with help from Claude Code; I reviewed and ran everything myself.

🤖 Generated with Claude Code

edubraqd and others added 2 commits September 17, 2026 09:33
The catch-all under the api namespace was `*unmatched`, and a wildcard
segment needs at least one character, so the namespace root itself fell
through to the HTML 404 page. Making the glob optional turns the route
into `/api(/*unmatched)`, which also covers the bare prefix.

Adds an integration test for /api, /api/, /api/v1 and /api/nope (GET) and
/api (POST); the first two and the POST fail without the change.

Fixes rubyforgood#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`unmatched_route` was a public method on `Api::BaseController`, so every
API controller inherited it as an action (`Api::V1::TasksController
.action_methods` included it). It now lives on a dedicated
`Api::ErrorsController`, which is the only thing the catch-all route
reaches.

`GET /api/v1/tasks.xml` answered 200 with a JSON body, as if the
extension had been honoured. The tasks resources now carry
`constraints: { format: :json }`: `/tasks` and `/tasks.json` behave as
before, and any other extension (`/tasks.xml`, `/tasks/1.xml`) falls
through to the JSON 404 like any other unknown path.

Tests cover both, and assert the catch-all is no longer an action on
the tasks controller.

Fixes rubyforgood#90
Fixes rubyforgood#91

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant