Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(di): add support chain interceptor on the same method #2915

Merged
merged 1 commit into from
Dec 4, 2024

Conversation

Romakita
Copy link
Collaborator

@Romakita Romakita commented Dec 3, 2024

Closes: #2914

Information

Type Breaking change
Feature No

Add chained interceptor support

Todos

  • Tests
  • Coverage
  • Example
  • Documentation

Summary by CodeRabbit

  • New Features

    • Introduced MyInterceptor2 class for enhanced interception capabilities.
    • Added a new chained method in ServiceTest to apply multiple interceptors in sequence.
    • Added a reverseChained method in ServiceTest to apply interceptors in reverse order.
  • Bug Fixes

    • Updated return values for interceptors to reflect new behavior in tests.
  • Refactor

    • Improved method binding process in the bindIntercept function for better error handling and token management.

Copy link

coderabbitai bot commented Dec 3, 2024

Walkthrough

The changes introduce a new interceptor class, MyInterceptor2, which works alongside an existing interceptor, MyInterceptor. Modifications to the bindIntercept function improve the method binding process, allowing both interceptors to be applied to service methods. The test cases have been updated to ensure that both interceptors are executed in sequence when chained. Additionally, the method signatures in the intercept.ts file have been refined to reflect these changes.

Changes

File Path Change Summary
packages/di/src/common/decorators/intercept.spec.ts - Added MyInterceptor2 class implementing InterceptorMethods with an intercept method.
- Updated ServiceTest to include a new chained method applying both interceptors.
- Modified test cases to reflect new return values.
packages/di/src/common/decorators/intercept.ts - Updated bindIntercept function to encapsulate original logic in newMethod and handle errors.
- Added token property to newMethod and updated method signatures for bindIntercept and Intercept.

Assessment against linked issues

Objective Addressed Explanation
Ensure that both interceptors are triggered when chained (#[2914])
Modify method binding to support new interceptor functionality (#[2914])

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.

@Romakita Romakita mentioned this pull request Dec 3, 2024
Copy link

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between 6b92cb5 and 1f8db0b.

📒 Files selected for processing (2)
  • packages/di/src/common/decorators/intercept.spec.ts (4 hunks)
  • packages/di/src/common/decorators/intercept.ts (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/di/src/common/decorators/intercept.spec.ts

[error] 71-71: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

packages/di/src/common/decorators/intercept.ts

[error] 57-57: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 60-60: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

🔇 Additional comments (3)
packages/di/src/common/decorators/intercept.ts (1)

Line range hint 22-53: LGTM: Robust implementation of the interceptor chain

The implementation correctly:

  • Preserves the original method context via this binding
  • Constructs proper interceptor context with all required properties
  • Handles error propagation through the next callback
  • Maintains invoke options for dependency injection
packages/di/src/common/decorators/intercept.spec.ts (2)

20-27: LGTM: Well-structured second interceptor implementation

The implementation of MyInterceptor2 follows the same pattern as MyInterceptor, ensuring consistency.


69-79: Verify the order of interceptor execution

The test verifies that both interceptors are executed, but let's ensure the order is consistent with the decorator order.

✅ Verification successful

The interceptor execution order is correct

Looking at the implementation and test, the execution order is working as expected. When multiple @Intercept decorators are applied, they are executed in reverse order (bottom-up), which is the standard decorator behavior in TypeScript/JavaScript. This is verified by the test's expected output: "Chained - param data - options data - intercepted 2 - options data - intercepted 1", showing MyInterceptor2 (second) executes before MyInterceptor (first).

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other instances of multiple @Intercept decorators to verify consistent ordering
ast-grep --pattern $'@Intercept($_)
@Intercept($_)
$_'

Length of output: 55


Script:

#!/bin/bash
# Let's try a different approach to find the interceptor declarations and their usage
rg "@Intercept" -B 2 -A 2

Length of output: 7723


Script:

#!/bin/bash
# Let's examine the implementation of the intercept decorator to understand the execution order
cat packages/di/src/common/decorators/intercept.ts

Length of output: 2643

🧰 Tools
🪛 Biome (1.9.4)

[error] 71-71: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

packages/di/src/common/decorators/intercept.ts Outdated Show resolved Hide resolved
Copy link

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between 1f8db0b and f8833ae.

📒 Files selected for processing (2)
  • packages/di/src/common/decorators/intercept.spec.ts (4 hunks)
  • packages/di/src/common/decorators/intercept.ts (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/di/src/common/decorators/intercept.spec.ts

[error] 77-77: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 89-89: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

packages/di/src/common/decorators/intercept.ts

[error] 55-55: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 58-58: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

🔇 Additional comments (1)
packages/di/src/common/decorators/intercept.ts (1)

Line range hint 22-53: Verify error propagation in interceptor chain

The error handling in the interceptor chain needs careful consideration. Currently, if an error occurs in one interceptor, it immediately breaks the chain and throws the error. Consider whether this is the intended behavior or if we should:

  1. Allow subsequent interceptors to handle/transform the error
  2. Collect errors from all interceptors before propagating
✅ Verification successful

Based on the search results, I can now provide a final response regarding the error propagation in the interceptor chain:

Error handling behavior is correctly implemented and follows established patterns

The current error handling implementation in the interceptor chain is the intended behavior, as evidenced by:

  1. The test cases in intercept.spec.ts explicitly verify this behavior with test cases like "should intercept the method and throw error"
  2. The implementation follows a fail-fast pattern where errors are immediately propagated up the chain through the next(err) callback
  3. The test assertions confirm that errors are properly propagated with their original messages intact

This approach aligns with common interceptor patterns where error conditions should immediately halt the chain to prevent potentially unsafe operations from continuing. If an interceptor encounters an error condition, it's typically desirable to stop processing and handle the error at a higher level rather than continue with potentially invalid state.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for error handling patterns in interceptors
rg -A 5 "catch|throw|Error" packages/di/src/common/decorators/

Length of output: 15507

Comment on lines +55 to +58
descriptor!.value = newMethod;

Reflect.deleteProperty(klass.prototype, propertyKey);
Reflect.defineProperty(klass.prototype, propertyKey, descriptor!);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider preserving method properties during replacement

The current implementation might lose method metadata during property replacement. Consider using Object.getOwnPropertyDescriptor to preserve all property attributes.

- Reflect.deleteProperty(klass.prototype, propertyKey);
- Reflect.defineProperty(klass.prototype, propertyKey, descriptor!);
+ const originalDescriptor = Object.getOwnPropertyDescriptor(klass.prototype, propertyKey);
+ Reflect.defineProperty(klass.prototype, propertyKey, {
+   ...originalDescriptor,
+   value: newMethod
+ });
📝 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
descriptor!.value = newMethod;
Reflect.deleteProperty(klass.prototype, propertyKey);
Reflect.defineProperty(klass.prototype, propertyKey, descriptor!);
descriptor!.value = newMethod;
const originalDescriptor = Object.getOwnPropertyDescriptor(klass.prototype, propertyKey);
Reflect.defineProperty(klass.prototype, propertyKey, {
...originalDescriptor,
value: newMethod
});
🧰 Tools
🪛 Biome (1.9.4)

[error] 55-55: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 58-58: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

Comment on lines +20 to 27
@Interceptor()
class MyInterceptor2 implements InterceptorMethods {
intercept(context: InterceptorContext<any>) {
const r = typeof context.args[0] === "string" ? undefined : new Error(`Error message`);
const retValue = context.next(r);

return `${retValue} - ${context.options || ""} - intercepted 2`;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider adding validation in MyInterceptor2

MyInterceptor2 duplicates error checking logic from MyInterceptor. Consider extracting common validation into a base class or utility function.

// Example base class or utility
class BaseInterceptor implements InterceptorMethods {
  protected validateInput(args: any[]): Error | undefined {
    return typeof args[0] === "string" ? undefined : new Error("Error message");
  }
}

@Interceptor()
class MyInterceptor2 extends BaseInterceptor {
  intercept(context: InterceptorContext<any>) {
    const error = this.validateInput(context.args);
    const retValue = context.next(error);
    return `${retValue} - ${context.options || ""} - intercepted 2`;
  }
}

Comment on lines +75 to +97
it("should chained interceptor", async () => {
// GIVEN
const serviceTest = await DITest.invoke<ServiceTest>(ServiceTest)!;

// WHEN
const result = serviceTest.chained("param data");

expect(getInterceptorOptions(ServiceTest, "chained")).toEqual("options data");

// THEN
expect(result).toEqual("Chained - param data - options data - intercepted 2 - options data - intercepted 1");
});
it("should chained interceptor (reversed)", async () => {
// GIVEN
const serviceTest = await DITest.invoke<ServiceTest>(ServiceTest)!;

// WHEN
const result = serviceTest.reverseChained("param data");

expect(getInterceptorOptions(ServiceTest, "reverseChained")).toEqual("options data");

// THEN
expect(result).toEqual("Chained - param data - options data - intercepted 1 - options data - intercepted 2");
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test coverage for interceptor chaining

While the basic chaining tests are good, consider adding:

  1. Tests with more than two interceptors
  2. Tests for error scenarios in the middle of the chain
  3. Tests with different options for each interceptor

Example additional test:

it("should handle errors in middle of chain", async () => {
  // GIVEN
  const serviceTest = await DITest.invoke<ServiceTest>(ServiceTest, [
    {
      token: MyInterceptor2,
      use: {
        intercept: vi.fn().mockImplementation(() => {
          throw new Error("Chain error");
        })
      }
    }
  ]);

  // WHEN
  const error = catchError(() => serviceTest.chained("param data"));

  // THEN
  expect(error?.message).toEqual("Chain error");
});
🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 89-89: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

@Romakita Romakita merged commit 3729b18 into production Dec 4, 2024
12 checks passed
@Romakita Romakita deleted the feat-v8-chained-interceptor branch December 4, 2024 17:03
@Romakita
Copy link
Collaborator Author

Romakita commented Dec 4, 2024

🎉 This PR is included in version 8.1.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Chaining Interceptors
1 participant