-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Added support for custom POJO query param encoding #667
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
Changes from all commits
f4be32f
6b8ac82
0eea796
186f66c
4867c11
54a0e9e
dde5e10
7d72d22
a44c472
336f6bf
1c3f822
eb8faf9
818deb5
7fdafd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,7 +123,9 @@ protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method me | |
| } | ||
|
|
||
| if (data.queryMapIndex() != null) { | ||
| checkMapString("QueryMap", parameterTypes[data.queryMapIndex()], genericParameterTypes[data.queryMapIndex()]); | ||
| if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) { | ||
| checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]); | ||
| } | ||
| } | ||
|
|
||
| return data; | ||
|
|
@@ -132,6 +134,10 @@ protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method me | |
| private static void checkMapString(String name, Class<?> type, Type genericType) { | ||
| checkState(Map.class.isAssignableFrom(type), | ||
| "%s parameter must be a Map: %s", name, type); | ||
| checkMapKeys(name, genericType); | ||
| } | ||
|
|
||
| private static void checkMapKeys(String name, Type genericType) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this looks like a good add - but I didn't see a test covering it in the case that it isn't a string (I assume tests already cover the case where it is)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wasn't an addition, it was a DRY split. I suppose I could revert the |
||
| Type[] parameterTypes = ((ParameterizedType) genericType).getActualTypeArguments(); | ||
| Class<?> keyClass = (Class<?>) parameterTypes[0]; | ||
| checkState(String.class.equals(keyClass), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| /** | ||
| * Copyright 2012-2018 The Feign Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
| * in compliance with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under the License | ||
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
| * or implied. See the License for the specific language governing permissions and limitations under | ||
| * the License. | ||
| */ | ||
| package feign; | ||
|
|
||
| import feign.codec.EncodeException; | ||
| import java.lang.reflect.Field; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * A QueryMapEncoder encodes Objects into maps of query parameter names to values. | ||
| */ | ||
| public interface QueryMapEncoder { | ||
|
|
||
| /** | ||
| * Encodes the given object into a query map. | ||
| * | ||
| * @param object the object to encode | ||
| * @return the map represented by the object | ||
| */ | ||
| Map<String, Object> encode (Object object); | ||
|
|
||
| class Default implements QueryMapEncoder { | ||
|
|
||
| private final Map<Class<?>, ObjectParamMetadata> classToMetadata = | ||
| new HashMap<Class<?>, ObjectParamMetadata>(); | ||
|
|
||
| @Override | ||
| public Map<String, Object> encode (Object object) throws EncodeException { | ||
| try { | ||
| ObjectParamMetadata metadata = getMetadata(object.getClass()); | ||
| Map<String, Object> fieldNameToValue = new HashMap<String, Object>(); | ||
| for (Field field : metadata.objectFields) { | ||
| Object value = field.get(object); | ||
| if (value != null && value != object) { | ||
| fieldNameToValue.put(field.getName(), value); | ||
| } | ||
| } | ||
| return fieldNameToValue; | ||
| } catch (IllegalAccessException e) { | ||
| throw new EncodeException("Failure encoding object into query map", e); | ||
| } | ||
| } | ||
|
|
||
| private ObjectParamMetadata getMetadata(Class<?> objectType) { | ||
| ObjectParamMetadata metadata = classToMetadata.get(objectType); | ||
| if (metadata == null) { | ||
| metadata = ObjectParamMetadata.parseObjectType(objectType); | ||
| classToMetadata.put(objectType, metadata); | ||
| } | ||
| return metadata; | ||
| } | ||
|
|
||
| private static class ObjectParamMetadata { | ||
|
|
||
| private final List<Field> objectFields; | ||
|
|
||
| private ObjectParamMetadata (List<Field> objectFields) { | ||
| this.objectFields = Collections.unmodifiableList(objectFields); | ||
| } | ||
|
|
||
| private static ObjectParamMetadata parseObjectType(Class<?> type) { | ||
| List<Field> fields = new ArrayList<Field>(); | ||
| for (Field field : type.getDeclaredFields()) { | ||
| if (!field.isAccessible()) { | ||
| field.setAccessible(true); | ||
| } | ||
| fields.add(field); | ||
| } | ||
| return new ObjectParamMetadata(fields); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /** | ||
| * Copyright 2012-2018 The Feign Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
| * in compliance with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under the License | ||
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
| * or implied. See the License for the specific language governing permissions and limitations under | ||
| * the License. | ||
| */ | ||
| package feign; | ||
|
|
||
| public class CustomPojo { | ||
|
|
||
| private final String name; | ||
| private final Integer number; | ||
|
|
||
| CustomPojo(String name, Integer number) { | ||
| this.name = name; | ||
| this.number = number; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice docs.
We should discuss with the wider audience if we want all member variables (including non-exposed private ones), or if we want those that are exposed in some way (public or have a getter), and if we handle transient ones or not. It's worth asking as I see below in the code that we're changing access modifiers on the fly (making accessible if not) - something that may not be something we do out-of-the box or so...
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In another PR of a similar nature, I made the suggestion that we should support objects via the Java Beans api. That should allow for the widest range of support and interoperability. However straight reflection is ok too from my perspective.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still argue in favor of annotating the methods/fields that you want to use with something like
@Param. The only reason to use all fields, was for simplicity (I guess additional annotations weren't simple enough). Personally, I'm less a fan of parsing all getter method names (javabeans style) than I am of reading all fields. But if it'll get this stuff merged...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rage-shadowman let's get more thoughts on it from a few people. It may be that the concensus is this works just fine and we like it (and then just leave it alone). It may also be that we go Java Beans style or something else. let's see what others think before taking any drastic decision.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the map builder has an interface that can be passed into the feign builder, then I could even write my own in my local client that would use my annotations without complicating anything internal to feign. We could then have an EJB implementation that parses getters as a separate possibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that a Serializer would fit nicely, so as long as we have a sensible default Serializer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created a
QueryMapEncoderthat can be specified via theFeign.Builder. I think it answers this concern and cleans up the naming issues.I haven't pushed it into this pull request as I don't want to jump the gun on this. You can check that out in https://github.com/rage-shadowman/feign/tree/feature/shadowman/query-param-encoder
Should I push that into this pull request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any agreement here? Shall I make the
QueryMapencoder something to be added to theFeign.builder()as in the aforementioned branch (this one gets my vote)? Should I add it as a parameter toQueryMaprequiring a 0-arg public constructor (similar toParam.Expander)? Or should I just clean up the naming and packaging of what is here and call it done?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rage-shadowman I'd add your encoder to the PR and suggest that it be used by default, without any additional customization to start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.