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 @QueryMap mapEncoder attribute #2098

Merged
merged 2 commits into from Oct 4, 2023

Conversation

pilak
Copy link
Contributor

@pilak pilak commented Jun 21, 2023

  • use mapEncoder attribute at method level for what encoder to use
  • still use builder QueryMapEncoder if no attribute specified

Hello,

Working on a project where we are using FeignBuilder in a separate library that is used like a client factory. When using some particular DTO request object, with @QueryMap annotation, we want to pass through the POJO getters. Unfortunately, we have to change the Builder instance of QueryMapEncoder from FieldQueryMapEncoder to BeanQueryMapEncoder to do so, and the consequence is that this implementation will be used in every client that will be built by this factory.
Also we are wondering why the choice of this encoder implementation is not closer to the annocation itself.

Here's a PR that makes possible to add the choice of the QueryMapEncoder implementation in a @QueryMap annotation attribute.

Doing so it is possible to change a QueryMapEncoder only of a specific method in the same Client

Summary by CodeRabbit

Release Notes:

  • New Feature: Introduced a flexible query map encoding strategy in Feign. Users can now customize the encoding of query parameters by choosing between BEAN, FIELD, and DEFAULT encoding strategies.
  • Refactor: Updated BaseBuilder, Contract, MethodMetadata, and RequestTemplateFactoryResolver classes to support the new query map encoding feature.
  • Test: Added new test cases in FeignTest to verify the functionality of the new query map encoding feature and its impact on child POJOs altered by getters.

Please note that these changes may require updates to your existing code if you are directly using any of the modified classes or methods.

@pilak pilak force-pushed the query-map-encoder-choice branch 3 times, most recently from e318408 to a56de3c Compare June 23, 2023 13:30
@pilak pilak marked this pull request as ready for review June 23, 2023 13:32
@velo velo requested a review from wplong11 June 27, 2023 22:16
@velo
Copy link
Member

velo commented Jun 27, 2023

Can you give project team edit access so I can rebase this branch and get CI rinnung?

@pilak
Copy link
Contributor Author

pilak commented Jun 28, 2023

Can you give project team edit access so I can rebase this branch and get CI rinnung?

I guess this is done

@pilak pilak force-pushed the query-map-encoder-choice branch 3 times, most recently from 09c5852 to a77998b Compare July 5, 2023 09:37
@pilak pilak requested a review from velo July 6, 2023 13:12
@pilak pilak force-pushed the query-map-encoder-choice branch from a77998b to b0506d3 Compare July 7, 2023 07:59
* use `mapEncoder` attribute at method level for what encoder to use
* still use builder `QueryMapEncoder` if no attribute specified
@coderabbitai
Copy link

coderabbitai bot commented Sep 7, 2023

Walkthrough

This pull request introduces a flexible way to encode query parameters in Feign. It adds a new queryMapEncoder field to the MethodMetadata class and allows users to specify the implementation of QueryMapEncoder via the @QueryMap annotation.

Changes

File Summary
.../feign/BaseBuilder.java Updated assignment of queryMapEncoder field with QueryMap.MapEncoder.FIELD.instance().
.../feign/Contract.java, .../feign/MethodMetadata.java Added queryMapEncoder field to MethodMetadata class and initialized it in Default constructor.
.../feign/QueryMap.java Added MapEncoder enum to QueryMap annotation for specifying QueryMapEncoder implementation.
.../feign/RequestTemplateFactoryResolver.java Introduced queryMapEncoder parameter to toQueryMap method for encoding with specific encoder.
.../feign/ChildPojo.java Added new private property parentPrivatePropertyAlteredByGetter in ParentPojo class.
.../feign/FeignTest.java Added new test case and method to TestInterface interface for testing query map encoding.

🐇💻

With each pull request, we strive,

To make our code more alive.

Query maps now have a new friend,

Encoding options that extend and bend.

So celebrate this code so neat,

And hop along to the merge beat! 🎉🥕


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • Mention @coderabbitai in any review comment for bot assistance.
  • 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.
  • Additionally, enter @coderabbitai pause to halt the bot's ongoing reviews as any issue or review comment. To
    resume reviews, enter @coderabbitai resume as any issue or review comment.

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

Commits Files that changed from the base of the PR and between 3d5f5ec and 8a2c298 commits.
Files selected for processing (7)
  • core/src/main/java/feign/BaseBuilder.java (1 hunks)
  • core/src/main/java/feign/Contract.java (1 hunks)
  • core/src/main/java/feign/MethodMetadata.java (2 hunks)
  • core/src/main/java/feign/QueryMap.java (2 hunks)
  • core/src/main/java/feign/RequestTemplateFactoryResolver.java (1 hunks)
  • core/src/test/java/feign/ChildPojo.java (1 hunks)
  • core/src/test/java/feign/FeignTest.java (3 hunks)
Additional comments (Suppressed): 12
core/src/test/java/feign/ChildPojo.java (1)
  • 16-30: The new private property parentPrivatePropertyAlteredByGetter and its getter method could potentially introduce a side effect when accessed, as it appends "FromGetter" to the returned value. Ensure that this behavior is intended and does not cause unexpected results in the rest of your codebase.
-    return parentPrivatePropertyAlteredByGetter + "FromGetter";
+    return parentPrivatePropertyAlteredByGetter;
core/src/main/java/feign/BaseBuilder.java (1)
  • 47-47: The default QueryMapEncoder has been changed from FieldQueryMapEncoder to QueryMap.MapEncoder.FIELD.instance(). Ensure that this change does not affect the existing functionality where FieldQueryMapEncoder was expected as the default encoder.
- protected QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
+ protected QueryMapEncoder queryMapEncoder = QueryMap.MapEncoder.FIELD.instance();
core/src/main/java/feign/Contract.java (2)
  • 315-318: The new code introduces a call to queryMap.mapEncoder().instance(). Ensure that the mapEncoder() method and the instance() method are properly defined and return the expected types. Also, verify that the instance() method does not have any side effects that could affect the program's behavior.

  • 320-321: No issues found in these lines. The changes seem to be consistent with the existing codebase and follow the same pattern as previous implementations.

core/src/main/java/feign/MethodMetadata.java (1)
  • 29-35: The addition of QueryMapEncoder queryMapEncoder; is a good approach to allow more granular control over query map encoding strategies. However, please ensure that this new field is properly serialized and deserialized if the MethodMetadata object needs to be passed across different JVMs or stored for later use, as the class implements Serializable.
core/src/main/java/feign/QueryMap.java (1)
  • 72-74: The encoded attribute is marked as deprecated. Ensure that this change does not break existing functionality where the encoded attribute is used.
core/src/main/java/feign/RequestTemplateFactoryResolver.java (2)
  • 108-112: The changes here are introducing a new way to encode the query map parameters using the queryMapEncoder from the method metadata. This allows for more granular control over the encoding strategy at the method level.

  • 115-119: Similar to the query map parameters, the header map parameters are also being encoded using the queryMapEncoder from the method metadata. However, it seems odd to use a queryMapEncoder for headers. Please verify if this is intended or if a separate encoder should be used for headers.

core/src/test/java/feign/FeignTest.java (4)
  • 16-22: The import statement for feign.QueryMap.MapEncoder is added to support the new feature of specifying a QueryMapEncoder at the method level. This change aligns with the goals of the PR.

  • 921-933: This test case verifies that the query parameters are correctly formed when using a ChildPojo object with the queryMapPropertyInheritence method. The expected behavior is that all properties (public, protected, and private) should be included in the query parameters. The test case seems to cover this scenario adequately.

  • 936-957: This test case checks the functionality of the new attribute mapEncoder in the @QueryMap annotation. It uses a FieldQueryMapEncoder as the encoder and expects the query parameters to include all properties (public, protected, and private), with the private property altered by its getter method. This test case provides good coverage for the new feature introduced in this PR.

  • 1232-1241: These changes reflect the addition of a new method queryMapPropertyInheritenceWithBeanMapEncoder in the TestInterface. This method uses the new mapEncoder attribute in the @QueryMap annotation to specify a BEAN encoder. This change is consistent with the PR's goal of allowing more granular control over query map encoding strategies.

Comment on lines +113 to +120
public QueryMapEncoder queryMapEncoder() {
return queryMapEncoder;
}

public MethodMetadata queryMapEncoder(QueryMapEncoder queryMapEncoder) {
this.queryMapEncoder = queryMapEncoder;
return this;
}
Copy link

@coderabbitai coderabbitai bot Sep 7, 2023

Choose a reason for hiding this comment

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

The getter and setter methods for queryMapEncoder are correctly implemented. However, it would be beneficial to add null checks in the setter method to prevent potential NullPointerExceptions.

public MethodMetadata queryMapEncoder(QueryMapEncoder queryMapEncoder) {
+   if (queryMapEncoder == null) {
+       throw new IllegalArgumentException("QueryMapEncoder cannot be null");
+   }
    this.queryMapEncoder = queryMapEncoder;
    return this;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

queryMapEncoder is nullable

Copy link

Choose a reason for hiding this comment

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

@pilak Thank you for your clarification. If queryMapEncoder is indeed nullable, then the null check isn't necessary. However, it's important to ensure that any code using this setter method handles the potential null value appropriately to avoid NullPointerExceptions. Here's the updated code:

public MethodMetadata queryMapEncoder(QueryMapEncoder queryMapEncoder) {
    this.queryMapEncoder = queryMapEncoder;
    return this;
}

Please make sure to handle the null case wherever this method is used.

Comment on lines +76 to +96
/**
* Specifies the QueryMapEncoder implementation to use to transform DTO into query map.
*
* @return the enum containing the instance of QueryMapEncoder
*/
MapEncoder mapEncoder() default MapEncoder.DEFAULT;

public enum MapEncoder {
// the latter DEFAULT will use BaseBuilder instance
BEAN(new BeanQueryMapEncoder()), FIELD(new FieldQueryMapEncoder()), DEFAULT(null);

private QueryMapEncoder mapEncoder;

private MapEncoder(QueryMapEncoder mapEncoder) {
this.mapEncoder = mapEncoder;
}

public QueryMapEncoder instance() {
return mapEncoder;
}
}
Copy link

@coderabbitai coderabbitai bot Sep 7, 2023

Choose a reason for hiding this comment

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

The new mapEncoder attribute and MapEncoder enum are introduced to specify the QueryMapEncoder implementation at the method level. This is a good improvement for more granular control over query map encoding strategies.

MapEncoder mapEncoder() default MapEncoder.DEFAULT;

public enum MapEncoder {
  BEAN(new BeanQueryMapEncoder()), FIELD(new FieldQueryMapEncoder()), DEFAULT(null);

  private QueryMapEncoder mapEncoder;

  private MapEncoder(QueryMapEncoder mapEncoder) {
    this.mapEncoder = mapEncoder;
  }

  public QueryMapEncoder instance() {
    return mapEncoder;
  }
}

However, it's important to ensure that the DEFAULT value of MapEncoder is handled correctly in the codebase where mapEncoder().instance() is called, as it could potentially return null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this issue is correctly handled

Copy link

Choose a reason for hiding this comment

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

@pilak That's great to hear! It's crucial to handle potential null values to prevent unexpected NullPointerExceptions. If the DEFAULT case is properly handled in the codebase, this change should provide a more flexible and granular control over query map encoding strategies. Good job on addressing this concern!

Comment on lines +125 to 135
private Map<String, Object> toQueryMap(Object value, QueryMapEncoder queryMapEncoder) {
if (value instanceof Map) {
return (Map<String, Object>) value;
}
try {
return queryMapEncoder.encode(value);
// encode with @QueryMap annotation if exists otherwise with the one from this resolver
return queryMapEncoder != null ? queryMapEncoder.encode(value)
: this.queryMapEncoder.encode(value);
} catch (EncodeException e) {
throw new IllegalStateException(e);
}
Copy link

@coderabbitai coderabbitai bot Sep 7, 2023

Choose a reason for hiding this comment

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

This change introduces a fallback mechanism for encoding values into a query map. If a specific QueryMapEncoder is provided in the method metadata, it will be used; otherwise, the default encoder from the resolver will be used. This provides flexibility and allows for different encoding strategies on a per-method basis.

-        return queryMapEncoder.encode(value);
+        return queryMapEncoder != null ? queryMapEncoder.encode(value)
+            : this.queryMapEncoder.encode(value);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what can I do here?

Copy link

Choose a reason for hiding this comment

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

@pilak: There are a few things you can do here:

  1. Review the code: Look at the changes made in this pull request and provide feedback. Are there any potential issues or improvements that could be made? Do you understand the purpose of the changes?

  2. Test the changes: If possible, test the new functionality to ensure it works as expected. This could involve writing unit tests or manually testing the feature.

  3. Understand the context: Try to understand why these changes were made. What problem is this solving? How does it fit into the larger project?

  4. Ask questions: If anything is unclear, ask for clarification. This could be about the code itself, the reasoning behind it, or how it's supposed to work.

  5. Approve or request changes: Based on your review, you can approve the changes if everything looks good, or request further changes if necessary.

@pilak
Copy link
Contributor Author

pilak commented Oct 2, 2023

@velo do I have to resolve rabbitai reviews?

@velo
Copy link
Member

velo commented Oct 2, 2023

@velo do I have to resolve rabbitai reviews?

not really.

I'm thinking about removing it, as it's not that useful

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

Commits Files that changed from the base of the PR and between 8a2c298 and f9a898d.
Files selected for processing (1)
  • core/src/test/java/feign/FeignTest.java (3 hunks)
Additional comments (Suppressed): 2
core/src/test/java/feign/FeignTest.java (2)
  • 940-961: This new test case queryMap_with_child_pojo_altered_by_getter_while_using_overriding_encoder() appears to be testing the functionality of using an overriding encoder (FieldQueryMapEncoder) with the queryMapPropertyInheritenceWithBeanMapEncoder method. The test case looks well-written and covers the necessary assertions. Good job on adding this test case.

  • 1236-1245: The TestInterface interface has been updated with a new method queryMapPropertyInheritenceWithBeanMapEncoder. This method uses the @QueryMap annotation with the mapEncoder attribute set to MapEncoder.BEAN. Ensure that all calls to this method throughout the codebase have been updated to match the new signature.

Comment on lines 16 to 22
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import feign.Feign.ResponseMappingDecoder;
import feign.QueryMap.MapEncoder;
import feign.Request.HttpMethod;
import feign.Target.HardCodedTarget;
import feign.codec.DecodeException;
Copy link

Choose a reason for hiding this comment

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

The import statement for feign.QueryMap.MapEncoder is not properly formatted. It should be aligned with the other import statements.

-  import feign.QueryMap.MapEncoder;
+ import feign.QueryMap.MapEncoder;

Comment on lines 925 to +937
childPojo.setChildPrivateProperty("first");
childPojo.setParentProtectedProperty("second");
childPojo.setParentPublicProperty("third");
childPojo.setParentPrivatePropertyAlteredByGetter("fourth");

server.enqueue(new MockResponse());
api.queryMapPropertyInheritence(childPojo);
assertThat(server.takeRequest())
.hasQueryParams(
"parentPublicProperty=third",
"parentProtectedProperty=second",
"childPrivateProperty=first");
"childPrivateProperty=first",
"parentPrivatePropertyAlteredByGetter=fourth");
Copy link

Choose a reason for hiding this comment

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

This test case seems to be testing the query parameters generated when a ChildPojo object is passed to the queryMapPropertyInheritence method. The expected query parameters are correctly listed in the assertion. However, it would be beneficial to add some comments explaining what this test case is specifically testing for future reference.

@pilak
Copy link
Contributor Author

pilak commented Oct 3, 2023

@velo do I have to resolve rabbitai reviews?

not really.

I'm thinking about removing it, as it's not that useful

yeah awesome but a tiny bit intrusive 😄

@velo velo merged commit 80bdec0 into OpenFeign:master Oct 4, 2023
2 checks passed
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.

None yet

2 participants