Skip to content

Commit

Permalink
Add @QueryMap mapEncoder attribute
Browse files Browse the repository at this point in the history
* use `mapEncoder` attribute at method level for what encoder to use
* still use builder `QueryMapEncoder` if no attribute specified
  • Loading branch information
Pierre Lakreb authored and pilak committed Jul 3, 2023
1 parent 26dadc2 commit 09c5852
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 6 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/feign/BaseBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public abstract class BaseBuilder<B extends BaseBuilder<B>> {
protected Encoder encoder = new Encoder.Default();
protected Decoder decoder = new Decoder.Default();
protected boolean closeAfterDecode = true;
protected QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
protected QueryMapEncoder queryMapEncoder = QueryMap.MapEncoder.FIELD.instance();
protected ErrorDecoder errorDecoder = new ErrorDecoder.Default();
protected Options options = new Options();
protected InvocationHandlerFactory invocationHandlerFactory =
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/feign/Contract.java
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ public Default() {
checkState(data.queryMapIndex() == null,
"QueryMap annotation was present on multiple parameters.");
data.queryMapIndex(paramIndex);
data.queryMapEncoder(queryMap.mapEncoder().instance());
});
super.registerParameterAnnotation(HeaderMap.class, (queryMap, data, paramIndex) -> {
checkState(data.headerMapIndex() == null,
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/feign/MethodMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public final class MethodMetadata implements Serializable {
private Integer bodyIndex;
private Integer headerMapIndex;
private Integer queryMapIndex;
private QueryMapEncoder queryMapEncoder;
private boolean alwaysEncodeBody;
private transient Type bodyType;
private final RequestTemplate template = new RequestTemplate();
Expand Down Expand Up @@ -109,6 +110,15 @@ public MethodMetadata queryMapIndex(Integer queryMapIndex) {
return this;
}

public QueryMapEncoder queryMapEncoder() {
return queryMapEncoder;
}

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

@Experimental
public boolean alwaysEncodeBody() {
return alwaysEncodeBody;
Expand Down
24 changes: 24 additions & 0 deletions core/src/main/java/feign/QueryMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import java.lang.annotation.Retention;
import java.util.List;
import java.util.Map;
import feign.querymap.BeanQueryMapEncoder;
import feign.querymap.FieldQueryMapEncoder;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

Expand Down Expand Up @@ -70,4 +72,26 @@
* @deprecated
*/
boolean encoded() default false;

/**
* 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;
}
}
}
10 changes: 6 additions & 4 deletions core/src/main/java/feign/RequestTemplateFactoryResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,26 +108,28 @@ public RequestTemplate create(Object[] argv) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value);
Map<String, Object> queryMap = toQueryMap(value, metadata.queryMapEncoder());
template = addQueryMapQueryParameters(queryMap, template);
}

if (metadata.headerMapIndex() != null) {
// add header map parameters for a resolution of the user pojo object
Object value = argv[metadata.headerMapIndex()];
Map<String, Object> headerMap = toQueryMap(value);
Map<String, Object> headerMap = toQueryMap(value, metadata.queryMapEncoder());
template = addHeaderMapHeaders(headerMap, template);
}

return template;
}

private Map<String, Object> toQueryMap(Object value) {
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);
}
Expand Down
9 changes: 9 additions & 0 deletions core/src/test/java/feign/ChildPojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
class ParentPojo {
public String parentPublicProperty;
protected String parentProtectedProperty;
private String parentPrivatePropertyAlteredByGetter;

public String getParentPrivatePropertyAlteredByGetter() {
return parentPrivatePropertyAlteredByGetter + "FromGetter";
}

public void setParentPrivatePropertyAlteredByGetter(String parentPrivatePropertyAlteredByGetter) {
this.parentPrivatePropertyAlteredByGetter = parentPrivatePropertyAlteredByGetter;
}

public String getParentPublicProperty() {
return parentPublicProperty;
Expand Down
32 changes: 31 additions & 1 deletion core/src/test/java/feign/FeignTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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.querymap.BeanQueryMapEncoder;
Expand Down Expand Up @@ -916,14 +917,39 @@ public void queryMap_with_child_pojo() throws Exception {
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");
}

@Test
public void queryMap_with_child_pojo_altered_by_getter_while_using_overriding_encoder()
throws Exception {
TestInterface api = new TestInterfaceBuilder()
.queryMapEndcoder(new FieldQueryMapEncoder())
.target("http://localhost:" + server.getPort());

ChildPojo childPojo = new ChildPojo();
childPojo.setChildPrivateProperty("first");
childPojo.setParentProtectedProperty("second");
childPojo.setParentPublicProperty("third");
childPojo.setParentPrivatePropertyAlteredByGetter("fourth");

server.enqueue(new MockResponse());
api.queryMapPropertyInheritenceWithBeanMapEncoder(childPojo);
assertThat(server.takeRequest())
.hasQueryParams(
"parentPublicProperty=third",
"parentProtectedProperty=second",
"childPrivateProperty=first",
"parentPrivatePropertyAlteredByGetter=fourthFromGetter");
}

@Test
Expand Down Expand Up @@ -1084,6 +1110,10 @@ void queryMapWithQueryParams(@Param("name") String name,
@RequestLine("GET /")
void queryMapPropertyPojo(@QueryMap PropertyPojo object);

@RequestLine("GET /")
void queryMapPropertyInheritenceWithBeanMapEncoder(@QueryMap(
mapEncoder = MapEncoder.BEAN) ChildPojo object);

@RequestLine("GET /")
void queryMapPropertyInheritence(@QueryMap ChildPojo object);

Expand Down

0 comments on commit 09c5852

Please sign in to comment.