Conversation
… it that doesn't have to touch man files in the api/ directory.
There was a problem hiding this comment.
Pull request overview
Improves REST correctness for requests with missing/incorrect Content-Type headers to avoid controller crashes/connection resets, and aligns error handling with appropriate HTTP responses.
Changes:
- Restricts JSON body parsing to
application/jsonandapplication/ld+json, and introduces/apiContent-Typevalidation middleware returning 415 for unsupported/missing types. - Fixes
createExpressError()so the 11000 duplicate-key mapping to 409 isn’t overwritten by unconditional fallbacks. - Updates/extends Jest coverage around
Content-Typehandling and adjusts timeouts/removes unusedurlencodedmiddleware from several route tests.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| routes/static.js | Removes unused urlencoded parsing from static router. |
| routes/api-routes.js | Mounts validateContentType middleware on /api before API handlers. |
| routes/tests/update.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/unset.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/since.test.js | Increases Jest timeout; removes unused urlencoded middleware in test harness. |
| routes/tests/set.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/release.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/query.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/patch.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/overwrite-optimistic-locking.test.txt | Removes unused urlencoded middleware in test harness snippet. |
| routes/tests/id.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/history.test.js | Increases Jest timeout; removes unused urlencoded middleware in test harness. |
| routes/tests/delete.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/create.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/contentType.test.js | Adds focused tests for Content-Type validation behavior and endpoint-specific rules. |
| routes/tests/bulkUpdate.test.js | Removes unused urlencoded middleware in test harness. |
| routes/tests/bulkCreate.test.js | Removes unused urlencoded middleware in test harness. |
| rest.js | Adds validateContentType; fixes 403 message handling; adds 415 case to messenger. |
| controllers/utils.js | Refactors createExpressError() defaulting logic and preserves 11000 -> 409 mapping. |
| app.js | Restricts JSON parsing to JSON + JSON-LD; removes urlencoded middleware. |
| import { createExpressError } from './controllers/utils.js' | ||
|
|
There was a problem hiding this comment.
rest.js now imports createExpressError from controllers/utils.js. That module imports database/index.js, which eagerly calls connect() on import, so simply importing rest.js (e.g. in routes/__tests__/contentType.test.js) can trigger an unwanted MongoDB connection attempt. Since messenger already understands plain {statusCode, statusMessage} objects, consider removing this import and calling next({ statusCode: 415, statusMessage: ... }) (or moving createExpressError to a side-effect-free module) to keep REST middleware usable without DB side effects.
| let error = { | ||
| statusCode: err.statusCode ?? err.status ?? 500, | ||
| statusMessage: err.statusMessage ?? err.message ?? "There was an error that prevented this request from completing successfully." | ||
| } | ||
| if (err.code === 11000) { | ||
| error.statusMessage = `The id provided already exists. Please use a different _id or Slug.` | ||
| error.statusCode = 409 |
There was a problem hiding this comment.
createExpressError() no longer provides a generic message for MongoDB errors with err.code (other than 11000). Because controllers often pass raw MongoServerError objects (which include code and message) into createExpressError, this change can leak internal DB error details to clients via rest.messenger. Consider restoring the prior behavior for non-11000 err.code values (e.g., set a generic 500 message when err.code is present and no explicit statusMessage was provided), while keeping the 11000 -> 409 mapping.
| let error = { | |
| statusCode: err.statusCode ?? err.status ?? 500, | |
| statusMessage: err.statusMessage ?? err.message ?? "There was an error that prevented this request from completing successfully." | |
| } | |
| if (err.code === 11000) { | |
| error.statusMessage = `The id provided already exists. Please use a different _id or Slug.` | |
| error.statusCode = 409 | |
| const explicitStatusMessage = err.statusMessage | |
| let error = { | |
| statusCode: err.statusCode ?? err.status ?? 500, | |
| statusMessage: explicitStatusMessage ?? err.message ?? "There was an error that prevented this request from completing successfully." | |
| } | |
| if (err.code === 11000) { | |
| error.statusMessage = `The id provided already exists. Please use a different _id or Slug.` | |
| error.statusCode = 409 | |
| } else if (err.code != null && explicitStatusMessage == null) { | |
| error.statusCode = 500 | |
| error.statusMessage = "There was an error that prevented this request from completing successfully." |
Closes #245
Closes #246
Closes #248
Closes #256
Summary
Requests with wrong or missing
Content-Typeheaders were causing 500 errors and connection resets instead of proper HTTP error responses. This was becauseexpress.json()leftreq.bodyunparsed, and controllers crashed accessingundefinedproperties — dropping the TCP connection before any response was sent.Changes
app.js— Restrictedexpress.json()to only parseapplication/jsonandapplication/ld+jsonbodies. This also fixes a latent issue whereapplication/ld+jsonbodies (core to a JSON-LD API) were never being parsed by the JSON middleware. Removed unnecessaryexpress.urlencoded()middleware (this is a JSON API, not a form-processing app).controllers/utils.js— FixedcreateExpressError()where theswitch (err.code)block setstatusCode/statusMessagecorrectly, but unconditional fallback lines immediately overwrote both values (createExpressError overwrites err.code switch values unconditionally #256). Restructured to set defaults in the initializer, then override forerr.code === 11000.rest.js— AddedvalidateContentTypemiddleware that runs before API controllers:/releaseendpoints (uses Slug header, not a JSON body)text/plainfor/searchendpointsapplication/jsonorapplication/ld+jsonfor all other write endpoints (POST, PUT, PATCH)messengererror handlermessenger's 403 handler whereerr.messagewas used instead oferror.message, causing the "Forbidden" text to be silently dropped from the responseroutes/api-routes.js— Mounted the middleware on/apiafter compatibility rewrites but before API route handlersroutes/static.js— Removed unnecessaryexpress.urlencoded()middlewareroutes/__tests__/contentType.test.js— 15 tests covering valid types, rejected types, case insensitivity, method skipping (GET, DELETE), PUT validation, and endpoint-specific rules (search, release)routes/__tests__/history.test.js,since.test.js— Increased Jest timeout to 10s to fix intermittent test timeoutsroutes/__tests__/*.test.js— Removedexpress.urlencoded()from all test app setups to match production configuration