Skip to content

Adopt domain exceptions#170

Open
Sanaquia wants to merge 1 commit into
VolunChain:mainfrom
Sanaquia:organization/Adopt-Domain-Exceptions-
Open

Adopt domain exceptions#170
Sanaquia wants to merge 1 commit into
VolunChain:mainfrom
Sanaquia:organization/Adopt-Domain-Exceptions-

Conversation

@Sanaquia
Copy link
Copy Markdown

@Sanaquia Sanaquia commented Aug 26, 2025

🚀 Volunchain Pull Request

Mark with an x all the checkboxes that apply (like [x])

  • Closes #
  • Added tests (if necessary)
  • Run tests
  • Run formatting
  • Evidence attached
  • Commented the code

📌 Type of Change

  • Documentation (updates to README, docs, or comments)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

📝 Changes description

Replaced ad-hoc error handling in OrganizationController with domain exceptions (OrganizationNotFoundException).


📸 Evidence (A photo is required as evidence)


⏰ Time spent breakdown

Refactoring controller logic: 45h

Middleware and exception integration: 25m


🌌 Comments


Thank you for contributing to Volunchain, we are glad that you have chosen us as your project of choice and we hope that you continue to contribute to this great project, so that together we can make our mark at the top!

Summary by CodeRabbit

  • New Features

    • Standardized API error responses: consistent JSON with success, errorCode, and message.
    • Clear 404 response when an organization ID is not found, with a specific error code.
    • Clear 429 response when rate limits are exceeded, including retry-after guidance.
  • Refactor

    • Centralized error handling for rate limiting and domain errors to ensure consistent responses across endpoints.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 26, 2025

Walkthrough

Refactors rate-limiting flow to throw a domain exception instead of returning an “allowed” flag, adds new domain exceptions (including OrganizationNotFound and RateLimitExceeded), and introduces a centralized Express error-handling middleware that maps DomainException instances to structured HTTP responses.

Changes

Cohort / File(s) Summary
Rate limiting middleware and use case
src/middleware/rateLimitMiddleware.ts, src/modules/shared/middleware/rate-limit/use-cases/rate-limit-use-case.ts, src/modules/shared/middleware/rate-limit/domain/exceptions/rate-limit-exceeded.exception.ts
Middleware stops blocking inline and removes logs; use case now throws RateLimitExceededException on exceed and returns only { remaining, retryAfter }; new specific exception added for 429 with retry-after minutes.
Domain exceptions base and new subclasses
src/modules/shared/domain/exceptions/domain.exception.ts, src/modules/shared/domain/exceptions/organization-not-found.exception.ts
Base DomainException now carries statusCode and errorCode via options-based constructor; added OrganizationNotFoundException (404) with standardized payload.
Global error handler
src/modules/shared/middleware/error-handler/error-handler.middleware.ts
New Express error handler: maps DomainException to statusCode/errorCode JSON; falls back to 500 with generic payload for unknown errors.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor C as Client
  participant S as Express Server
  participant M as RateLimitMiddleware
  participant U as RateLimitUseCase
  participant E as ErrorHandler

  C->>S: HTTP Request
  S->>M: Invoke middleware
  M->>U: checkRateLimit(req)
  alt Within limit
    U-->>M: { remaining, retryAfter }
    M-->>S: next()
    S-->>C: Continue to route handler (normal flow)
  else Exceeded
    U--x M: throw RateLimitExceededException
    M-->>S: next(err)
    S->>E: errorHandler(err, req, res, next)
    E-->>C: 429 JSON { success:false, errorCode, message }
  end
Loading
sequenceDiagram
  autonumber
  actor C as Client
  participant S as Express Server
  participant E as ErrorHandler

  C->>S: HTTP Request triggers error
  S->>E: errorHandler(err)
  alt err is DomainException
    E-->>C: err.statusCode + JSON { success:false, errorCode, message }
  else unknown error
    E-->>C: 500 + JSON { success:false, errorCode:"INTERNAL_SERVER_ERROR", message:"An unexpected error occurred" }
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Villarley

Poem

A rabbit taps code with a gentle thump,
Exceptions hop—no more inline bump.
Limits exceeded? We throw, not sigh;
The handler waves a structured reply.
404s and 429s align—
Carrots of clarity, neatly in line. 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

Copy link
Copy Markdown

@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: 3

🧹 Nitpick comments (11)
src/modules/shared/domain/exceptions/domain.exception.ts (3)

2-2: Use override for the name property to align with TS best practices

Since Error already defines name: string, explicitly mark the redeclaration as override to avoid surprises with noImplicitOverride and improve intent clarity.

-  public readonly name: string;
+  public override readonly name: string;

Also applies to: 16-16


6-14: Extract and reuse an options type; optionally allow a cause/details payload

Defining an exported DomainExceptionOptions interface improves reuse and consistency across all domain exceptions. Adding optional cause (and/or details) enables richer diagnostics while keeping the constructor backwards-compatible.

+export interface DomainExceptionOptions {
+  message: string;
+  errorCode: string;
+  statusCode: number;
+  cause?: unknown;
+  details?: Record<string, unknown>;
+}
+
   constructor({
     message,
     errorCode,
     statusCode,
-  }: {
-    message: string;
-    errorCode: string;
-    statusCode: number;
-  }) {
+    cause,
+    details,
+  }: DomainExceptionOptions) {
     super(message);
     this.name = new.target.name;
     this.errorCode = errorCode;
     this.statusCode = statusCode;
+    // Optional: attach for debugging/observability
+    if (cause !== undefined) (this as any).cause = cause;
+    if (details !== undefined) (this as any).details = details;

16-18: Guard against invalid HTTP status codes

Minor hardening: assert that statusCode falls within 400–599 to prevent accidental 2xx/3xx usage in exceptions.

     this.errorCode = errorCode;
-    this.statusCode = statusCode;
+    this.statusCode = statusCode;
+    if (this.statusCode < 400 || this.statusCode > 599) {
+      // Fail fast in dev; keep as-is in prod if you prefer
+      // eslint-disable-next-line no-console
+      console.warn(`[DomainException] Non-error statusCode provided: ${this.statusCode} for ${this.name}`);
+    }
src/modules/shared/middleware/error-handler/error-handler.middleware.ts (2)

1-3: Import path hygiene

Current file uses a relative import for DomainException, while other new files mix relative and alias (@/…). Consider unifying to one style to reduce path-churn. If @ alias is configured, you can adopt it here too for consistency.


5-25: Optional: log unknown errors here to centralize diagnostics

You currently log in rate-limit middleware, but other routes may throw unknown errors too. Consider logging here (behind env guard) to ensure consistent observability.

src/middleware/rateLimitMiddleware.ts (2)

17-18: Remove unused retryAfter or emit a reset header

retryAfter is destructured but unused. If noUnusedLocals is enabled, this will fail compilation. Either remove it or emit a helpful header like X-RateLimit-Reset.

Option A — remove:

-        const { remaining, retryAfter } =
+        const { remaining } =
           await this.rateLimitUseCase.checkRateLimit(req);

Option B — emit reset timestamp (epoch seconds):

-        const { remaining, retryAfter } =
+        const { remaining, retryAfter } =
           await this.rateLimitUseCase.checkRateLimit(req);

         res.setHeader("X-RateLimit-Remaining", remaining.toString());
+        const resetEpochSeconds = Math.ceil(Date.now() / 1000) + retryAfter * 60;
+        res.setHeader("X-RateLimit-Reset", resetEpochSeconds.toString());

35-36: Mark the async call as intentionally fire-and-forget

Use void to satisfy linters (e.g., no-floating-promises) and signal intent that Express will continue once next() is called inside the async IIFE.

-    checkRateLimit();
+    void checkRateLimit();
src/modules/shared/middleware/rate-limit/use-cases/rate-limit-use-case.ts (4)

44-56: Use actual window TTL for retryAfter, and avoid an extra Redis round-trip

Currently retryAfter returns the full windowMinutes, which can over-warn clients. Also, getRemainingRequests adds a second Redis call that you can avoid because you already have currentCount. Consider using the key’s TTL for accurate retryAfter and compute remaining locally.

Apply this diff within this block:

-    const allowed = currentCount <= routeConfig.maxRequests;
-    const remaining = await this.repository.getRemainingRequests(
-      identifier,
-      routeType,
-      routeConfig.maxRequests
-    );
-
-    if (!allowed) {
-      throw new RateLimitExceededException(routeConfig.windowMinutes);
-    }
-
-    return { remaining, retryAfter: routeConfig.windowMinutes };
+    const allowed = currentCount <= routeConfig.maxRequests;
+    const ttlSeconds = await this.repository.getWindowTTLSeconds(
+      identifier,
+      routeType
+    );
+
+    if (!allowed) {
+      throw new RateLimitExceededException(
+        Math.max(1, Math.ceil(ttlSeconds / 60))
+      );
+    }
+
+    const remaining = Math.max(0, routeConfig.maxRequests - currentCount);
+    return {
+      remaining,
+      retryAfter: Math.max(1, Math.ceil(ttlSeconds / 60)),
+    };

If RedisRateLimitRepository lacks getWindowTTLSeconds, add it (see snippet below). This keeps behavior accurate and reduces one network hop.

Additional repository helper (outside this file):

// In RedisRateLimitRepository
async getWindowTTLSeconds(identifier: string, routeType: string): Promise<number> {
  const key = this.buildKey(identifier, routeType); // reuse your existing key builder
  const ttl = await this.client.ttl(key); // -2=no key, -1=no expire
  return ttl > 0 ? ttl : 0;
}

51-53: Add targeted tests for the exception path

Add a unit/integration test asserting that the 429 is produced and that Retry-After (or your JSON field) reflects the remaining TTL, not the static window size.

Happy to scaffold tests for:

  • within-limit: returns remaining > 0 and retryAfter > 0
  • at-limit: throws RateLimitExceededException with expected retryAfter
  • route-specific configs (auth/wallet/email) honored

19-23: Prefer user ID over IP (and avoid raw IP storage); fall back to trusted IP/XFF

Comment says “Use IP or user ID” but code only uses req.ip. Prefer a stable user identifier when authenticated, and if you must use IP, pull the client IP from X-Forwarded-For when trust proxy is enabled. Consider hashing the IP to reduce PII exposure in Redis keys.

Example improvement:

-  // Use IP or user ID if authenticated
-  return req.ip || "anonymous";
+  // Prefer authenticated user identifier; fall back to client IP
+  const userId = (req as any)?.user?.id ?? (req as any)?.auth?.sub;
+  if (userId) return `user:${userId}`;
+  const xff = (req.headers["x-forwarded-for"] as string) || "";
+  const clientIp = xff.split(",")[0]?.trim() || req.ip;
+  return clientIp ? `ip:${clientIp}` : "anonymous";

Optional anonymization (outside this file):

import { createHmac } from "crypto";
// ...
const anon = createHmac("sha256", process.env.IP_HASH_SALT || "default_salt")
  .update(clientIp)
  .digest("base64url");
return `ip:${anon}`;

24-29: Route matching by substring is brittle; use req.path and config-driven matchers

Using path.includes risks false positives (and originalUrl includes query). Prefer req.path and regex/configured matchers.

Light refactor:

-  private getRouteType(path: string): keyof RateLimitConfig["routes"] {
-    if (path.includes("/auth")) return "auth";
-    if (path.includes("/wallet")) return "wallet";
-    if (path.includes("/email")) return "email";
+  private getRouteType(pathname: string): keyof RateLimitConfig["routes"] {
+    const p = pathname.toLowerCase();
+    if (/\/auth(\/|$)/i.test(p)) return "auth";
+    if (/\/wallet(\/|$)/i.test(p)) return "wallet";
+    if (/\/email(\/|$)/i.test(p)) return "email";
     return "default";
   }

And call with req.path instead of req.originalUrl.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 23a1f32 and 408ffcb.

📒 Files selected for processing (6)
  • src/middleware/rateLimitMiddleware.ts (2 hunks)
  • src/modules/shared/domain/exceptions/domain.exception.ts (1 hunks)
  • src/modules/shared/domain/exceptions/organization-not-found.exception.ts (1 hunks)
  • src/modules/shared/middleware/error-handler/error-handler.middleware.ts (1 hunks)
  • src/modules/shared/middleware/rate-limit/domain/exceptions/rate-limit-exceeded.exception.ts (1 hunks)
  • src/modules/shared/middleware/rate-limit/use-cases/rate-limit-use-case.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/modules/shared/domain/exceptions/organization-not-found.exception.ts (1)
src/modules/project/domain/Project.ts (1)
  • id (45-47)
src/modules/shared/middleware/rate-limit/use-cases/rate-limit-use-case.ts (1)
src/modules/shared/middleware/rate-limit/domain/exceptions/rate-limit-exceeded.exception.ts (1)
  • RateLimitExceededException (3-11)
🔇 Additional comments (4)
src/modules/shared/domain/exceptions/organization-not-found.exception.ts (1)

3-11: LGTM: clean domain exception with specific HTTP status and code

Constructor payload is crisp and aligns with the centralized error handler contract. Message is safe to expose.

src/modules/shared/middleware/rate-limit/domain/exceptions/rate-limit-exceeded.exception.ts (1)

1-1: Alias path configuration verified
The tsconfig.json clearly defines the @/* alias ("baseUrl": ".", "paths": { "@/*": ["src/*"], … }), and imports such as import { DomainException } from "@/modules/shared/domain/exceptions/domain.exception"; correctly resolve to src/modules/.... A repository‐wide grep also shows multiple modules using the same alias, confirming it’s supported and in use consistently.
No changes required here.

src/middleware/rateLimitMiddleware.ts (1)

46-52: Global error handler registered after all routes and middlewares

Verified that in src/index.ts the application is initialized at line 31 and the custom error handler is wired up at the very end (around line 82), after all calls to app.use —including rate limiting and route registrations. No further action is needed.

src/modules/shared/middleware/rate-limit/use-cases/rate-limit-use-case.ts (1)

8-8: LGTM: adopting domain exception import

Import path and usage of RateLimitExceededException are consistent with the new domain exception approach.

Comment on lines +5 to +25
export function errorHandler(
err: unknown,
req: Request,
res: Response,
next: NextFunction
): void {
if (err instanceof DomainException) {
res.status(err.statusCode).json({
success: false,
errorCode: err.errorCode,
message: err.message,
});
return;
}

res.status(500).json({
success: false,
errorCode: "INTERNAL_SERVER_ERROR",
message: "An unexpected error occurred",
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Set Retry-After for 429 and surface rate-limit metadata

When the rate limit is exceeded, clients benefit from a standards-compliant Retry-After response header. Extend the handler to recognize RateLimitExceededException and set the header (seconds). This also requires the exception to expose retryAfterMinutes. See paired suggestion in the exception class.

 import { DomainException } from "../../domain/exceptions/domain.exception";
+import { RateLimitExceededException } from "../rate-limit/domain/exceptions/rate-limit-exceeded.exception";

 export function errorHandler(
   err: unknown,
   req: Request,
   res: Response,
   next: NextFunction
 ): void {
-  if (err instanceof DomainException) {
+  if (err instanceof RateLimitExceededException) {
+    // Per RFC 6585/7231, use seconds for Retry-After
+    res.setHeader("Retry-After", String(err.retryAfterMinutes * 60));
+    res.status(err.statusCode).json({
+      success: false,
+      errorCode: err.errorCode,
+      message: err.message,
+    });
+    return;
+  } else if (err instanceof DomainException) {
     res.status(err.statusCode).json({
       success: false,
       errorCode: err.errorCode,
       message: err.message,
     });
     return;
   }
 
   res.status(500).json({
     success: false,
     errorCode: "INTERNAL_SERVER_ERROR",
     message: "An unexpected error occurred",
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function errorHandler(
err: unknown,
req: Request,
res: Response,
next: NextFunction
): void {
if (err instanceof DomainException) {
res.status(err.statusCode).json({
success: false,
errorCode: err.errorCode,
message: err.message,
});
return;
}
res.status(500).json({
success: false,
errorCode: "INTERNAL_SERVER_ERROR",
message: "An unexpected error occurred",
});
}
import { DomainException } from "../../domain/exceptions/domain.exception";
import { RateLimitExceededException } from "../rate-limit/domain/exceptions/rate-limit-exceeded.exception";
export function errorHandler(
err: unknown,
req: Request,
res: Response,
next: NextFunction
): void {
if (err instanceof RateLimitExceededException) {
// Per RFC 6585/7231, use seconds for Retry-After
res.setHeader("Retry-After", String(err.retryAfterMinutes * 60));
res.status(err.statusCode).json({
success: false,
errorCode: err.errorCode,
message: err.message,
});
return;
} else if (err instanceof DomainException) {
res.status(err.statusCode).json({
success: false,
errorCode: err.errorCode,
message: err.message,
});
return;
}
res.status(500).json({
success: false,
errorCode: "INTERNAL_SERVER_ERROR",
message: "An unexpected error occurred",
});
}
🤖 Prompt for AI Agents
In src/modules/shared/middleware/error-handler/error-handler.middleware.ts
around lines 5 to 25, the handler doesn’t special-case rate-limit errors or set
a Retry-After header; update it to recognize RateLimitExceededException (in
addition to DomainException), respond with status 429, set the HTTP
"Retry-After" header to the number of seconds computed from the exception's
retryAfterMinutes (retryAfterMinutes * 60) before sending the JSON body, and
include any rate-limit metadata required in the response; ensure you
import/reference RateLimitExceededException and guard access to
retryAfterMinutes.

Comment on lines +3 to +11
export class RateLimitExceededException extends DomainException {
constructor(retryAfterMinutes: number) {
super({
message: `Rate limit exceeded. Try again in ${retryAfterMinutes} minutes.`,
errorCode: "RATE_LIMIT_EXCEEDED",
statusCode: 429,
});
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Expose retryAfterMinutes on the exception to enable Retry-After header

Store the argument as a public readonly property so the error handler can set standards-compliant headers. This integrates with the earlier middleware suggestion.

 export class RateLimitExceededException extends DomainException {
-  constructor(retryAfterMinutes: number) {
+  public readonly retryAfterMinutes: number;
+  constructor(retryAfterMinutes: number) {
     super({
       message: `Rate limit exceeded. Try again in ${retryAfterMinutes} minutes.`,
       errorCode: "RATE_LIMIT_EXCEEDED",
       statusCode: 429,
     });
+    this.retryAfterMinutes = retryAfterMinutes;
   }
 }
🤖 Prompt for AI Agents
In
src/modules/shared/middleware/rate-limit/domain/exceptions/rate-limit-exceeded.exception.ts
around lines 3 to 11, the constructor accepts retryAfterMinutes but does not
expose it as a property; add a public readonly retryAfterMinutes: number
property on the class and assign it from the constructor argument before calling
super (or immediately after) so downstream error handlers can read this value to
set a Retry-After header; keep the existing super payload unchanged and ensure
the property is strongly typed as number.

Comment on lines 31 to 34
async checkRateLimit(
req: Request
): Promise<{ allowed: boolean; remaining: number; retryAfter: number }> {
): Promise<{ remaining: number; retryAfter: number }> {
const identifier = this.getIdentifier(req);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Public API shift to exception-based flow—verify all call sites and middleware

checkRateLimit now returns only remaining/retryAfter and throws on exceed. Ensure all callers stop checking an "allowed" flag and rely on exception handling (central error middleware) instead.

Run this quick scan to find impacted call sites and error handling:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Call sites of checkRateLimit:"
rg -nP -C3 '\bcheckRateLimit\s*\(' --type=ts --type=tsx

echo
echo "Usages of deprecated patterns (allowed, RATE_LIMIT_EXCEEDED handling, Retry-After headers):"
rg -nP -C2 '\ballowed\b|Retry-After|X-RateLimit-(Limit|Remaining|Reset)|RateLimitExceededException' --type=ts --type=tsx

echo
echo "Central error middleware presence (DomainException mapping):"
rg -nP -C3 '(errorHandler|DomainException|statusCode:\s*429|RATE_LIMIT_EXCEEDED)' --type=ts --type=tsx

Length of output: 174


I’ll retry scanning without relying on built-in file types—using globs instead:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Call sites of checkRateLimit:"
rg -nP -C3 '\bcheckRateLimit\s*\(' --glob '*.ts' --glob '*.tsx'

echo
echo "Deprecated patterns (allowed flag, Retry-After, X-RateLimit headers, RateLimitExceededException):"
rg -nP -C2 '\ballowed\b|Retry-After|X-RateLimit-(Limit|Remaining|Reset)|RateLimitExceededException' --glob '*.ts' --glob '*.tsx'

echo
echo "Central error middleware references (errorHandler, DomainException, statusCode: 429, RATE_LIMIT_EXCEEDED):"
rg -nP -C3 '(errorHandler|DomainException|statusCode:\s*429|RATE_LIMIT_EXCEEDED)' --glob '*.ts' --glob '*.tsx'

Length of output: 33729


Action Required: Migrate all callers and tests to the new exception-based rate-limit API

The scan confirms that although checkRateLimit now only returns { remaining, retryAfter } and throws a RateLimitExceededException on breach, there are still numerous call sites (especially in tests) checking an obsolete allowed flag and relying on manual header logic. You’ll need to refactor both production middleware and your tests to remove any allowed checks and lean entirely on the thrown exception (handled by your central errorHandler).

Impacted locations and recommended changes:

  • tests/rate-limit/rate-limit-use-case.test.ts

    • Remove every expect(result.allowed)… assertion.
    • Assert only remaining and retryAfter for allowed cases.
    • Add a test that mocks zero remaining requests and expects checkRateLimit to throw RateLimitExceededException.
  • tests/rate-limit/rate-limit-middleware.test.ts

    • Replace commented-out “allowed” scenarios with:
      • One scenario where checkRateLimit resolves normally (assert on X-RateLimit-Remaining and next() being called).
      • One scenario where checkRateLimit rejects with RateLimitExceededException (assert that your middleware (or central errorHandler) responds with status 429 and propagates the exception to errorHandler).
  • src/middleware/rateLimitMiddleware.ts

    • Remove any logic that reads an allowed flag.
    • In your try { … } catch (err) { … }, simply re-throw or next(err) for RateLimitExceededException—letting the shared errorHandler emit the 429 response.
    • (Optional) If you want a Retry-After header on the 429, extend the exception or error handler to expose the retry interval and set res.setHeader("Retry-After", retryAfterInSeconds).
  • All other call sites

    • Grep for any remaining .allowed references in the codebase and clean up.

These refactors are mandatory to align with the new public API and ensure your middleware and tests remain green.

🤖 Prompt for AI Agents
In src/modules/shared/middleware/rate-limit/use-cases/rate-limit-use-case.ts
around lines 31 to 34, callers and tests still rely on an obsolete `allowed`
flag instead of the new exception-based API that returns only `{ remaining,
retryAfter }` and throws `RateLimitExceededException`; update all impacted code
by removing any `allowed` checks and header-manipulation logic, change tests
(tests/rate-limit/rate-limit-use-case.test.ts and
tests/rate-limit/rate-limit-middleware.test.ts) to assert only `remaining` and
`retryAfter` for allowed cases and add tests that expect the exception when
remaining is zero, modify src/middleware/rateLimitMiddleware.ts to stop reading
`allowed` and in catch blocks re-throw or call next(err) for
RateLimitExceededException (optionally let shared errorHandler set 429 and
Retry-After), and grep the repo to remove any remaining `.allowed` references
and update call sites accordingly.

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