Skip to content

express error handling#657

Merged
rosado merged 9 commits into
mainfrom
rosado/express-error-handling
Nov 18, 2024
Merged

express error handling#657
rosado merged 9 commits into
mainfrom
rosado/express-error-handling

Conversation

@rosado
Copy link
Copy Markdown
Contributor

@rosado rosado commented Nov 13, 2024

What type of PR is this? (check all applicable)

  • Refactor
  • Bug Fix

Description

Due to the combination of the following:

  • data fetches that don't go through our generic middleware (fetchOne/fetchMany) don't handle promise rejections
  • express 4.x doesn't handle promise rejections in the middleware

We sometimes end up with unhandled errors (e.g. in fetchEntry()).

We can't switch to 5.x (at least not easily, the wizard library relies on something that broke between 4.x and 5.x), so we have to handle this explicitly. This PR adds a wrapper middleware to handle such errors for the few cases where fetchOne()/fetchMany() isn't used. 500 error is returned now.

Also, moves some of the data fetches to use generic middleware and removes some dead code.

Related Tickets & Documents

No ticket, but related Sentry error

Added/updated tests?

  • Yes

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new utility for improved error handling in asynchronous middleware.
    • Added pagination support for issue queries with a defined limit.
  • Bug Fixes

    • Enhanced error handling to prevent unhandled promise rejections in middleware functions.
  • Documentation

    • Updated comments and JSDoc annotations to reflect new method signatures and functionalities.
  • Tests

    • Expanded test coverage for new error handling scenarios and updated existing tests to accommodate changes in data structures.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Nov 13, 2024

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 67.93% 4415 / 6499
🔵 Statements 67.93% 4415 / 6499
🔵 Functions 67.37% 190 / 282
🔵 Branches 82.4% 548 / 665
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/middleware/issueDetails.middleware.js 80.34% 62.5% 41.66% 80.34% 51-63, 76-99, 207-213, 227-230, 233-236, 240-244
src/middleware/middleware.builders.js 87.13% 88.57% 83.33% 87.13% 36, 104-115, 128-141, 258-261, 282-290
src/middleware/overview.middleware.js 86.58% 85.71% 57.14% 86.58% 10-12, 24, 32, 46-50, 68-69, 116-125
src/serverSetup/errorHandlers.js 0% 0% 0% 0% 1-46
src/services/performanceDbApi.js 64.47% 92.59% 35.71% 64.47% 49-77, 114-116, 179-183, 234-240, 251-264, 273-293, 297-305, 314-326, 337-364, 374-379
Generated in workflow #293 for commit c77c3e0 by the Vitest Coverage Report Action

@rosado rosado force-pushed the rosado/express-error-handling branch from bf3001d to d56a04b Compare November 13, 2024 13:46
@digital-land digital-land deleted a comment from coderabbitai Bot Nov 13, 2024
@rosado rosado changed the title Rosado/express error handling express error handling Nov 13, 2024
@rosado rosado force-pushed the rosado/express-error-handling branch from d56a04b to ac7cd6b Compare November 14, 2024 13:53
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 14, 2024

Walkthrough

The changes primarily involve refactoring middleware functions across several files to enhance modularity and error handling. Key modifications include the introduction of new utility functions for handling promise rejections and updates to existing middleware to utilise these new functions. The structure of data being handled has also been modified, particularly in how counts are represented. Overall, the changes aim to streamline the middleware architecture and improve error management.

Changes

File Change Summary
src/middleware/issueDetails.middleware.js Refactored fetchIssues and fetchIssueEntitiesCount to use fetchMany and fetchOne. Updated handling of issueEntitiesCount to be an object. Minor adjustments to request parameter destructuring.
src/middleware/middleware.builders.js Added safeFn for error handling and handleRejections to wrap middleware functions for promise rejection management.
src/middleware/overview.middleware.js Wrapped fetchEntityCounts with handleRejections to improve error handling. No changes to function logic.
src/serverSetup/errorHandlers.js Added checks for res.headersSent in error handling and 404 middleware to prevent modifying responses after headers are sent.
src/services/performanceDbApi.js Introduced issuesQueryLimit, refactored methods to accept request objects for queries, and removed getEntityCount. Enhanced error handling in getEntityCounts.
test/unit/middleware/issueDetails.middleware.test.js Updated tests to reflect changes in issueEntitiesCount structure from integer to object with count property.
test/unit/middleware/middleware.builders.test.js Added tests for the new handleRejections function to ensure proper error handling and invocation of wrapped middleware.

Possibly related PRs

  • fix: a few type errors #589: The changes in this PR involve pagination logic and error handling improvements that align with the refactoring of middleware functions in the main PR.

Suggested reviewers

  • GeorgeGoodall-GovUk

🐰 In the meadow, changes bloom,
Middleware now dispels the gloom.
With rejections handled right,
Errors fade out of sight.
Count the issues, one, two, three,
A modular world, hop with glee! 🌼


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0c8875c and c77c3e0.

📒 Files selected for processing (1)
  • src/middleware/overview.middleware.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/middleware/overview.middleware.js

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@rosado rosado force-pushed the rosado/express-error-handling branch from ac7cd6b to 0c8875c Compare November 14, 2024 16:27
@rosado rosado marked this pull request as ready for review November 14, 2024 16:27
@rosado rosado added the bug Something isn't working label Nov 14, 2024
@rosado rosado self-assigned this Nov 14, 2024
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (7)
src/serverSetup/errorHandlers.js (1)

Line range hint 1-45: Consider enhancing error template management

The current implementation hard-codes error template paths. Consider extracting these to a configuration object for better maintainability and centralised error page management.

Example implementation:

const ERROR_TEMPLATES = {
  default: 'errorPages/500'
};

// Then in the error handler:
err.template = err.template || 
  (err.status && ERROR_TEMPLATES[err.status]) || 
  ERROR_TEMPLATES.default;
src/middleware/overview.middleware.js (1)

Line range hint 158-163: Consider consistent error handling across all async operations

While fetchEntityCounts is now protected, other async operations in the middleware chain might benefit from similar error handling. Particularly fetchLatestResources which performs database operations.

Consider applying handleRejections to other async operations in the chain:

export default [
  fetchOrgInfo,
- fetchLatestResources,
+ handleRejections(fetchLatestResources),
  handleRejections(fetchEntityCounts),
  fetchLpaOverview,
  prepareOverviewTemplateParams,
  getOverview,
  logPageError
]
test/unit/middleware/middleware.builders.test.js (1)

155-161: Consider adding assertion for error absence

Whilst the test correctly verifies that the middleware passes through to next() in the success case, it could be more explicit.

Consider adding an assertion to explicitly verify that no error was passed:

  expect(next).toHaveBeenCalledTimes(1)
+ expect(next).toHaveBeenCalledWith()
test/unit/middleware/issueDetails.middleware.test.js (1)

Line range hint 11-276: Add test cases for error handling scenarios

Given that this PR focuses on improving error handling in Express middleware, consider adding test cases to verify:

  1. Promise rejection scenarios
  2. Error propagation through the middleware chain
  3. Edge cases where issueEntitiesCount might be undefined or null

Would you like me to help generate these additional test cases?

src/middleware/middleware.builders.js (3)

293-299: Consider enhancing the safeFn implementation with TypeScript annotations and documentation.

The implementation is correct, but could benefit from additional clarity and type safety.

Consider applying these improvements:

+/**
+ * Wraps middleware execution in a try-catch block to ensure promise rejections
+ * are properly handled and passed to Express's error handling chain.
+ * @param {Request} req - Express request object
+ * @param {Response} res - Express response object
+ * @param {NextFunction} next - Express next function
+ * @returns {Promise<void>}
+ */
-async function safeFn (req, res, next) {
+async function safeFn (
+  req: Request,
+  res: Response,
+  next: NextFunction
+): Promise<void> {
   try {
     await this.middleware(req, res, next)
   } catch (err) {
+    // Pass the error to Express's error handling chain
     next(err)
   }
 }

301-311: Enhance documentation for the handleRejections function.

The implementation is solid, but the documentation could be more comprehensive.

Consider enhancing the documentation:

 /**
  * Express 4.x does not handle promise rejections in middleware on its own. The
  * @{fetchOne} and @{fetchMany} middleware handle that case but for any other async
  * code you can wrap it in this middleware to ensure rejections don't end up unhandled.
  *
+ * @example
+ * // Wrap an async middleware to handle rejections
+ * app.get('/api/data', handleRejections(async (req, res, next) => {
+ *   const data = await fetchSomeData();
+ *   res.json(data);
+ * }));
+ *
+ * @template T
+ * @param {(req: Request, res: Response, next: NextFunction) => Promise<T>} middleware
+ *   The async middleware function to wrap
- * @returns {any}
+ * @returns {(req: Request, res: Response, next: NextFunction) => Promise<void>}
+ *   A wrapped middleware that safely handles promise rejections
  */

292-311: Implementation effectively addresses promise rejection handling.

The solution elegantly solves the unhandled promise rejection issue in Express 4.x middleware while maintaining compatibility with existing error handling patterns. The implementation is clean, reusable, and aligns perfectly with the PR objectives.

Consider documenting this pattern in your team's best practices guide to ensure consistent error handling across the codebase.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 487d534 and 0c8875c.

📒 Files selected for processing (7)
  • src/middleware/issueDetails.middleware.js (7 hunks)
  • src/middleware/middleware.builders.js (1 hunks)
  • src/middleware/overview.middleware.js (2 hunks)
  • src/serverSetup/errorHandlers.js (2 hunks)
  • src/services/performanceDbApi.js (3 hunks)
  • test/unit/middleware/issueDetails.middleware.test.js (2 hunks)
  • test/unit/middleware/middleware.builders.test.js (1 hunks)
🧰 Additional context used
🪛 Biome
src/middleware/issueDetails.middleware.js

[error] 283-283: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🔇 Additional comments (15)
src/serverSetup/errorHandlers.js (2)

16-18: Excellent addition of the headersSent check!

This change properly prevents attempts to modify the response after headers have been sent, following Express.js best practices. It effectively resolves potential "Cannot set headers after they are sent" errors.


35-37: Good defensive programming with headersSent check

This addition ensures proper middleware chaining and prevents attempts to render the 404 page when the response has already been sent.

src/middleware/overview.middleware.js (2)

3-3: LGTM: Clean import addition

The addition of handleRejections import is well-placed alongside related middleware builders.


159-159: Verify error propagation in fetchEntityCounts

The addition of handleRejections wrapper aligns with the PR's goal of managing unhandled promise rejections. Let's verify the error handling chain.

✅ Verification successful

Let me gather more information about the error handling implementation.


Let me check the middleware builders implementation to complete the verification.


Error handling chain is properly implemented

The error handling implementation is complete and correct:

  • handleRejections middleware properly catches and forwards any promise rejections to Express error handlers
  • performanceDbApi.getEntityCounts has its own error handling that logs warnings and gracefully continues
  • The error handling chain aligns with the application's error handling patterns
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify error handling implementation across related files

# Check if performanceDbApi methods throw errors that need handling
rg -A 5 "getEntityCounts" --type js

# Verify consistent error handling pattern across middleware
rg "handleRejections\(" --type js

Length of output: 2288


Script:

#!/bin/bash
# Check handleRejections implementation
ast-grep --pattern 'function handleRejections($_) {
  $$$
}'

# Check datasette.runQuery implementation for potential error throwing
rg -A 5 "runQuery" --type js

# Look for error handling patterns in middleware.builders
rg -A 10 "handleRejections" test/unit/middleware/middleware.builders.test.js

Length of output: 13783


Script:

#!/bin/bash
# Check handleRejections implementation in middleware builders
cat src/middleware/middleware.builders.js

# Check for any error handling in overview middleware
rg -A 5 "fetchEntityCounts" src/middleware/overview.middleware.js

Length of output: 9877

test/unit/middleware/middleware.builders.test.js (2)

141-143: Well-structured test setup with clear helper function

The test suite is properly organised with a helper function trouble() that consistently produces rejections for testing. This promotes code reuse and maintains consistency across test cases.


144-153: Comprehensive rejection handling test

The test effectively verifies that unhandled promise rejections are caught and properly passed to Express's error handling middleware via next(error). This aligns perfectly with the PR's objective of improving error handling for promise rejections.

src/middleware/issueDetails.middleware.js (6)

1-1: Import 'issuesQueryLimit' correctly

Adding issuesQueryLimit to the import statement ensures it is available for use within the module.


13-13: Middleware builders imported successfully

Including fetchIf, fetchMany, fetchOne, FetchOptions, handleRejections, and renderTemplate supports the refactored middleware functions and enhances modularity.


35-39: Refactor 'fetchIssues' using 'fetchMany'

Refactoring fetchIssues with fetchMany simplifies the middleware and reduces boilerplate code.


104-108: Simplify 'fetchIssueEntitiesCount' with 'fetchOne'

Using fetchOne for fetchIssueEntitiesCount improves code clarity and consistency in data fetching.


295-295: Enhance error handling by wrapping 'fetchEntry'

Wrapping fetchEntry with handleRejections ensures that any unhandled promise rejections are properly managed, enhancing the application's robustness.


176-177: Verify consistent handling of 'issueEntitiesCount'

Now that issueEntitiesCount is an object with a count property, ensure all usages of issueEntitiesCount expect a numeric value to prevent potential runtime errors.

Run the following script to locate all usages of issueEntitiesCount:

✅ Verification successful

Let me analyze the findings from the shell script results.

The search results show all usages of issueEntitiesCount in the codebase, and I can verify that:

  1. In src/middleware/issueDetails.middleware.js, the code correctly destructures the count from the object:

    • Line 176-177 shows: const { count: issueEntitiesCount } = issueEntitiesCountObj
    • All subsequent uses in this file use the numeric value correctly
  2. In test files:

    • test/unit/middleware/issueDetails.middleware.test.js shows both object format { count: 1 } and numeric usage 1
    • test/unit/issueDetailsPage.test.js uses numeric values (10 and comparisons with 1)
  3. In schema definition:

    • src/routes/schemas.js explicitly defines it as v.integer()
  4. The middleware:

    • Has a default handler zeroIssueEntitiesCount that sets { count: 0 }
    • All numeric operations (comparisons, pagination) use the extracted count value

All usages are consistent with the new object format

The codebase correctly handles the transition between the object format { count: number } and the numeric value. The middleware extracts the count before any numeric operations, and the schema validation ensures type safety.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of 'issueEntitiesCount' in JavaScript files.

# Expectation: Verify that 'issueEntitiesCount' is correctly used as a number throughout the codebase.
rg 'issueEntitiesCount' --type js --context 3

Length of output: 10461

src/services/performanceDbApi.js (3)

169-170: Define issuesQueryLimit for consistent pagination

The introduction of the issuesQueryLimit constant promotes consistency in pagination limits across the application. Ensure this constant is used wherever pagination is required to maintain uniformity.


313-326: Prevent SQL injection by parameterising queries in getIssuesQuery

Similar to the previous comment, the SQL query in getIssuesQuery directly embeds user-provided values, which can lead to SQL injection vulnerabilities. Use parameterised queries to safely include these variables.


367-372: Prevent SQL injection by parameterising queries in entityCountQuery

Embedding orgEntity directly into the SQL string can result in SQL injection attacks. Utilise parameterised queries to securely handle user input.

Comment thread test/unit/middleware/issueDetails.middleware.test.js
Comment thread test/unit/middleware/issueDetails.middleware.test.js
Comment thread src/middleware/issueDetails.middleware.js
Comment thread src/services/performanceDbApi.js
Comment thread src/services/performanceDbApi.js
Copy link
Copy Markdown
Contributor

@GeorgeGoodall-GovUk GeorgeGoodall-GovUk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good!

@eveleighoj eveleighoj temporarily deployed to submit-pr-657 November 18, 2024 09:25 Inactive
@rosado rosado merged commit 46d27f5 into main Nov 18, 2024
@rosado rosado deleted the rosado/express-error-handling branch November 18, 2024 09:34
@neilfwar
Copy link
Copy Markdown

showing as done - but no end date?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working tech debt

Projects

No open projects
Status: Done Archived

Development

Successfully merging this pull request may close these issues.

4 participants