Skip to content

Commit

Permalink
Fix query param serialization for requests with enums (#140)
Browse files Browse the repository at this point in the history
## Changes
Query parameters are determined reflectively from request classes by
scanning for fields with a QueryParam annotation. Serialization of query
parameters is recursive in order to support complex types like the
filter structures used for listing in the SQL query history service.
This PR makes the following changes:

1. Terminate recursion when the request field is an enum.
2. When iterating through the request object's fields, skip any fields
not annotated with QueryParam (but recursively, all fields do need to be
serialized).
3. Rename the inner class from HeaderEntry to QueryParamPair (it
represents query param pairs, not header entries).

## Tests
Test for this change is dependent on other testing refactors that will
be merged as part of
#139.
  • Loading branch information
mgyucht authored Aug 24, 2023
1 parent 2dc895b commit d716538
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private static <I> void setQuery(Request in, I entity) {
if (entity == null) {
return;
}
for (GrpcTranscodingQueryParamsSerializer.HeaderEntry e :
for (GrpcTranscodingQueryParamsSerializer.QueryParamPair e :
GrpcTranscodingQueryParamsSerializer.serialize(entity)) {
in.withQueryParam(e.getKey(), e.getValue());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
* documentation for gRPC transcoding</a> for more details.
*/
public class GrpcTranscodingQueryParamsSerializer {
public static class HeaderEntry {
public static class QueryParamPair {
private final String key;
private final String value;

public HeaderEntry(String key, String value) {
public QueryParamPair(String key, String value) {
this.key = key;
this.value = value;
}
Expand All @@ -47,25 +47,19 @@ public String getValue() {
* @param o The object to serialize.
* @return A list of query parameter entries compatible with gRPC-transcoding.
*/
public static List<HeaderEntry> serialize(Object o) {
Map<String, Object> flattened = flattenObject(o);
for (Field f : o.getClass().getDeclaredFields()) {
QueryParam queryParam = f.getAnnotation(QueryParam.class);
if (queryParam == null) {
flattened.remove(getFieldName(f));
}
}
public static List<QueryParamPair> serialize(Object o) {
Map<String, Object> flattened = flattenObject(o, true);

List<HeaderEntry> result = new ArrayList<>();
List<QueryParamPair> result = new ArrayList<>();
for (Map.Entry<String, Object> entry : flattened.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Collection) {
for (Object v : (Collection<Object>) value) {
result.add(new HeaderEntry(key, v.toString()));
result.add(new QueryParamPair(key, v.toString()));
}
} else {
result.add(new HeaderEntry(key, value.toString()));
result.add(new QueryParamPair(key, value.toString()));
}
}
return result;
Expand Down Expand Up @@ -103,24 +97,30 @@ private static String getFieldName(Field f) {
}
}

private static Map<String, Object> flattenObject(Object o) {
private static Map<String, Object> flattenObject(Object o, Boolean onlyAnnotatedFields) {
// LinkedHashMap ensures consistent ordering of fields.
Map<String, Object> result = new LinkedHashMap<>();
Field[] fields = o.getClass().getDeclaredFields();
for (Field f : fields) {
if (onlyAnnotatedFields && f.getAnnotation(QueryParam.class) == null) {
continue;
}
f.setAccessible(true);
try {
String name = getFieldName(f);
Object value = f.get(o);
if (value == null) {
continue;
}
// check if object is a primitive type or a collection of some kind
if (primitiveTypes.contains(f.getType()) || Iterable.class.isAssignableFrom(f.getType())) {
// check if object is a primitive type, a collection of some kind, or an enum
Class<?> type = f.getType();
if (primitiveTypes.contains(type)
|| Iterable.class.isAssignableFrom(type)
|| type.isEnum()) {
result.put(name, value);
} else {
// recursively flatten the object
Map<String, Object> flattened = flattenObject(value);
Map<String, Object> flattened = flattenObject(value, false);
for (Map.Entry<String, Object> entry : flattened.entrySet()) {
result.put(name + "." + entry.getKey(), entry.getValue());
}
Expand Down

0 comments on commit d716538

Please sign in to comment.