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

Add support for multiple ResponseInterceptors #1829

Merged
merged 12 commits into from Aug 29, 2023

Conversation

velo
Copy link
Member

@velo velo commented Nov 9, 2022

In general, I avoid breaking feign API contracts like the plague, but I couldn't keep the old contract and have multiple response interceptors.

Take a look if this change makes any sense, and lemme know if we can include this, probably on feign 13

FWIW, this is heavily inspired on how spring graphql does interceptors chaining

Summary by CodeRabbit

  • New Feature: Introduced the ability to chain multiple response interceptors in Feign. This allows for more flexible and powerful handling of responses.
  • Refactor: Replaced single responseInterceptor with a list of responseInterceptors across various classes, allowing for multiple interceptors.
  • New Feature: Added enrich(ResponseInterceptor.Chain chain) method in Capability interface to enhance the functionality of response interceptor chains.
  • Test: Updated tests in BaseBuilderTest to accommodate the new response interceptor chaining feature.
  • Refactor: Renamed InvocationContext class to ResponseInterceptor.Context for better clarity and understanding of its role in the interception process.

}
return thisB;
}

/**
* Adds a single response interceptor to the builder.
*/
public B responseInterceptor(ResponseInterceptor responseInterceptor) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about add Deprecated annotation? Without looking at the implementation it seems difficult to infer the behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

But it's not deprecated, it just changed from you can have only one, to you can have as many as you want, like requestInterceptor

Copy link
Collaborator

@wplong11 wplong11 left a comment

Choose a reason for hiding this comment

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

Good

Copy link
Member

@kdavisk6 kdavisk6 left a comment

Choose a reason for hiding this comment

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

Love the idea, feel like we may be re-inventing the wheel though. Any thoughts to using Function.andThen or Consumer.andThen instead?

@@ -269,5 +281,19 @@ List<Field> getFieldsToEnrich() {
.collect(Collectors.toList());
}

protected ResponseInterceptor.Chain executionChain() {
Copy link
Member

Choose a reason for hiding this comment

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

can we consider a more descriptive name? How about interceptors or responseInterceptorChain?

* @param nextInterceptor the interceptor to delegate to after the current
* @return a new interceptor that chains the two
*/
default ResponseInterceptor andThen(ResponseInterceptor nextInterceptor) {
Copy link
Member

@kdavisk6 kdavisk6 Nov 21, 2022

Choose a reason for hiding this comment

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

This feels an awful lot like Function.andThen or Consumer.andThen. Do you think there's a way we could use those instead?

@kdavisk6 kdavisk6 added the feedback provided Feedback has been provided to the author label Dec 31, 2022
@coderabbitai
Copy link

coderabbitai bot commented Aug 29, 2023

Walkthrough

The changes primarily revolve around the replacement of a single responseInterceptor with a chain of interceptors, enhancing flexibility and control over response handling. This is achieved through modifications in method signatures, introduction of new methods, and alterations in test cases to accommodate these changes.

Changes

File(s) Summary
core/src/main/java/feign/AsyncFeign.java,
core/src/main/java/feign/Feign.java,
kotlin/src/main/java/feign/kotlin/CoroutineFeign.java
The internalBuild() method has been modified to replace the responseInterceptor parameter with a call to the executionChain() or responseInterceptorChain() method.
core/src/main/java/feign/BaseBuilder.java The responseInterceptor field is replaced with a responseInterceptors list. New methods for setting interceptors and creating an execution chain are introduced.
core/src/main/java/feign/ResponseInterceptor.java Significant changes to the interface include renaming and reparameterizing methods, adding a chaining method, and introducing nested interfaces and classes for context encapsulation and interceptor chaining.
core/src/main/java/feign/ResponseHandler.java,
core/src/main/java/feign/AsyncResponseHandler.java
Constructor signatures have been modified to replace the ResponseInterceptor parameter with ResponseInterceptor.Chain.
core/src/test/java/feign/BaseBuilderTest.java Test methods have been updated to include calls to the responseInterceptor method with lambda expressions defining interceptor behavior.
core/src/main/java/feign/Capability.java A new method enrich(ResponseInterceptor.Chain chain) has been added to the interface.

🐇💻

In the land of code where the shadows lie,

An interceptor chain now flies high.

With each link strong, and purpose clear,

Response handling gains a gear.

So let's hop on, no time to sigh,

For in this change, we unify! 🎉


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • Mention @coderabbitai in any review comment for bot assistance. The bot stays silent unless called upon.
  • Note: Review comments are made on code diffs or files, not on the PR overview.

Pause Incremental Reviews

  • Insert @coderabbitai: ignore in the PR description to halt the bot's ongoing reviews. Remove the line to resume.

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.

Review Status

Actionable comments generated: 4

Commits Files that changed from the base of the PR and between c39376f and a74dbd7 commits.
Files selected for processing (9)
  • core/src/main/java/feign/AsyncFeign.java (1 hunks)
  • core/src/main/java/feign/AsyncResponseHandler.java (1 hunks)
  • core/src/main/java/feign/BaseBuilder.java (3 hunks)
  • core/src/main/java/feign/Capability.java (1 hunks)
  • core/src/main/java/feign/Feign.java (1 hunks)
  • core/src/main/java/feign/ResponseHandler.java (3 hunks)
  • core/src/main/java/feign/ResponseInterceptor.java (1 hunks)
  • core/src/test/java/feign/BaseBuilderTest.java (3 hunks)
  • kotlin/src/main/java/feign/kotlin/CoroutineFeign.java (1 hunks)
Additional comments (Suppressed): 15
core/src/main/java/feign/AsyncFeign.java (1)
  • 200-203: The executionChain() method is used instead of the responseInterceptor. Ensure that all instances of responseInterceptor have been replaced with executionChain() throughout the codebase and that the executionChain() method is correctly implemented to handle the chain of interceptors.
core/src/main/java/feign/Feign.java (1)
  • 205-211: The executionChain() method is used instead of the responseInterceptor in the ResponseHandler constructor. Ensure that all instances of ResponseHandler are updated to use this new constructor throughout the codebase.
core/src/main/java/feign/AsyncResponseHandler.java (2)
  • 30-34: The constructor of AsyncResponseHandler has been updated to accept a ResponseInterceptor.Chain instead of a single ResponseInterceptor. Ensure that all instantiations of AsyncResponseHandler throughout the codebase have been updated to match this new constructor signature.

  • 32-34: The ResponseHandler is now initialized with a ResponseInterceptor.Chain instead of a single ResponseInterceptor. This change aligns with the PR's goal of supporting multiple ResponseInterceptors.

core/src/main/java/feign/Capability.java (1)
  • 93-95: The new method enrich(ResponseInterceptor.Chain chain) has been added to the Capability interface. This method is designed to enrich a ResponseInterceptor.Chain. By default, it returns the input chain as is, without performing any enrichment. If you want to provide custom enrichment logic for ResponseInterceptor.Chain, you should override this method in your implementation of the Capability interface.

Please ensure that all implementations of the Capability interface throughout the codebase have been updated to handle this new method appropriately.

core/src/main/java/feign/ResponseHandler.java (2)
  • 42-55: The constructor of ResponseHandler has been modified to replace the single responseInterceptor with a chain of interceptors, represented by ResponseInterceptor.Chain. Ensure that all instances of ResponseHandler throughout the codebase have been updated to match the new constructor signature.

  • 124-127: The aroundDecode() method of the ResponseInterceptor interface has been replaced with the next() method of the ResponseInterceptor.Chain interface. This change is consistent with the modifications in the ResponseInterceptor interface and its implementation in ResponseHandler.

kotlin/src/main/java/feign/kotlin/CoroutineFeign.java (1)
  • 175-175: The change from responseInterceptor to responseInterceptors is noted. Ensure that all instances where this builder method is called have been updated to pass a list of interceptors instead of a single interceptor.
core/src/main/java/feign/BaseBuilder.java (3)
  • 36-38: The change from a single ResponseInterceptor to a list of ResponseInterceptors is a significant one. This allows for multiple interceptors to be used, which can provide more flexibility and control over the response handling process. However, this also means that all existing uses of the responseInterceptor field will need to be updated to work with the new list structure. Please ensure that these updates have been made throughout the codebase.

  • 221-223: This method adds a single interceptor to the list of response interceptors. It's a straightforward and expected change given the shift from a single interceptor to a list of interceptors.

  • 304-314: This method creates an execution chain from the list of response interceptors. The use of the reduce operation with the andThen method effectively creates a chain of interceptors, where each interceptor is applied after the previous one. This is a good use of functional programming techniques to create a complex structure in a concise and readable way. However, please ensure that the andThen method in the ResponseInterceptor interface has been properly implemented to support this chaining behavior.

core/src/main/java/feign/ResponseInterceptor.java (3)
  • 37-37: Ensure that all calls to this function throughout the codebase have been updated to match the new signature. The method intercept(Context context, Chain chain) has replaced the old aroundDecode(InvocationContext invocationContext). This change in method signature could potentially break existing code if not properly updated.

  • 46-49: This is a good use of the decorator pattern to chain interceptors. It allows for flexibility and extensibility in handling responses.

  • 54-66: The default implementation of the Chain interface handles exceptions well, ensuring that any FeignException or RuntimeException thrown during decoding are appropriately caught and rethrown with more context. However, it's important to ensure that these exceptions are handled upstream where the next(Context context) method is called.

core/src/test/java/feign/BaseBuilderTest.java (1)
  • 46-49: Good addition of the assertion to check if the list is not empty before accessing it. This prevents potential IndexOutOfBoundsException if the list is empty.

Comment on lines +87 to +111
public class Context {

private final Decoder decoder;
private final Type returnType;
private final Response response;

Context(Decoder decoder, Type returnType, Response response) {
this.decoder = decoder;
this.returnType = returnType;
this.response = response;
}

public Decoder decoder() {
return decoder;
}

public Type returnType() {
return returnType;
}

public Response response() {
return response;
}

}
Copy link

Choose a reason for hiding this comment

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

The Context class encapsulates the decoder, return type, and response. This is a good practice as it improves readability and maintainability by grouping related data together. However, the constructor for Context is package-private, which means it can only be instantiated within the same package. If this is intentional and aligns with your design, then it's fine. Otherwise, consider making the constructor public if instances of Context need to be created outside the package.

Comment on lines 30 to +31
test(AsyncFeign.builder().requestInterceptor(template -> {
}), 14);
}).responseInterceptor((ic, c) -> c.next(ic)), 14);
Copy link

Choose a reason for hiding this comment

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

The responseInterceptor lambda function is empty and does not provide any functionality. If this is intentional, consider adding a comment to clarify its purpose.

- }).responseInterceptor((ic, c) -> c.next(ic)), 14);
+ }).responseInterceptor((ic, c) -> {
+   // Add response interceptor logic here
+   return c.next(ic);
+ }), 14);

Comment on lines 60 to +61
test(Feign.builder().requestInterceptor(template -> {
}), 12);
}).responseInterceptor((ic, c) -> c.next(ic)), 12);
Copy link

Choose a reason for hiding this comment

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

Similar to the previous comment, the responseInterceptor lambda function is empty. Consider adding a comment or implementing the necessary functionality.

- }).responseInterceptor((ic, c) -> c.next(ic)), 12);
+ }).responseInterceptor((ic, c) -> {
+   // Add response interceptor logic here
+   return c.next(ic);
+ }), 12);

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.

Review Status

Actionable comments generated: 1

Commits Files that changed from the base of the PR and between a74dbd7 and 2852b6a commits.
Files selected for processing (3)
  • core/src/main/java/feign/AsyncFeign.java (1 hunks)
  • core/src/main/java/feign/BaseBuilder.java (3 hunks)
  • core/src/main/java/feign/Feign.java (1 hunks)
Files skipped from review due to trivial changes (1)
  • core/src/main/java/feign/Feign.java
Additional comments (Suppressed): 3
core/src/main/java/feign/AsyncFeign.java (1)
  • 200-203: The responseInterceptorChain() method is used here instead of the previous responseInterceptor. Ensure that all instances of responseInterceptor have been replaced with responseInterceptorChain() throughout the codebase and that the new method is correctly implemented.
core/src/main/java/feign/BaseBuilder.java (2)
  • 36-38: The change from a single ResponseInterceptor to a list of ResponseInterceptors is a significant one. This allows for multiple interceptors to be used, which can provide more flexibility and control over the response handling process. However, this also introduces additional complexity and potential issues. For example, the order in which the interceptors are added to the list can affect the behavior of the system. It's important to ensure that all existing and future uses of this class take this into account.

  • 304-314: This method creates a chain of response interceptors. The use of the reduce operation here is clever, as it effectively chains together all of the interceptors in the list. However, this code assumes that the andThen method of ResponseInterceptor correctly handles chaining, which should be verified. Additionally, the use of Capability.enrich at the end suggests that capabilities can modify the chain of interceptors. This could potentially introduce unexpected behavior if not properly managed.

Comment on lines +210 to +214
public B responseInterceptors(Iterable<ResponseInterceptor> responseInterceptors) {
this.responseInterceptors.clear();
for (ResponseInterceptor responseInterceptor : responseInterceptors) {
this.responseInterceptors.add(responseInterceptor);
}
Copy link

Choose a reason for hiding this comment

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

This method clears the current list of response interceptors and replaces it with a new set. This could potentially remove interceptors that were previously added and are still needed. Consider providing a method to append to the list of interceptors instead of replacing them entirely.

@velo velo merged commit 2c00066 into OpenFeign:master Aug 29, 2023
3 checks passed
@velo velo deleted the interceptor-chaining branch August 29, 2023 05:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feedback provided Feedback has been provided to the author
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants