Skip to content

Commit

Permalink
Properly handle Map of form params in REST Client
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Oct 14, 2024
1 parent 8f670e6 commit 159377a
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.util.AbstractMap;
import java.util.ArrayList;
Expand Down Expand Up @@ -1152,7 +1150,7 @@ A more full example of generated client (with sub-resource) can is at the bottom
formParams = createFormDataIfAbsent(methodCreator, formParams, multipart);
// NOTE: don't use type here, because we're not going through the collection converters and stuff
Type parameterType = jandexMethod.parameterType(paramIdx);
addFormParam(methodCreator, param.name, methodCreator.getMethodParam(paramIdx),
addFormParam(jandexMethod, methodCreator, param.name, methodCreator.getMethodParam(paramIdx),
parameterType, param.signature, index,
restClientInterface.getClassName(), methodCreator.getThis(), formParams,
getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx),
Expand Down Expand Up @@ -2660,7 +2658,7 @@ private void addSubBeanParamData(MethodInfo jandexMethod, int paramIndex, Byteco
break;
case FORM_PARAM:
FormParamItem formParam = (FormParamItem) item;
addFormParam(creator, formParam.getFormParamName(), formParam.extract(creator, param),
addFormParam(jandexMethod, creator, formParam.getFormParamName(), formParam.extract(creator, param),
formParam.getParamType(), formParam.getParamSignature(),
index,
restClientInterfaceClassName, client,
Expand Down Expand Up @@ -2929,7 +2927,7 @@ private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle
methodCreator.load(paramName), handle));
}

private void addFormParam(BytecodeCreator methodCreator,
private void addFormParam(MethodInfo jandexMethod, BytecodeCreator methodCreator,
String paramName,
ResultHandle formParamHandle,
Type parameterType,
Expand Down Expand Up @@ -2964,10 +2962,56 @@ private void addFormParam(BytecodeCreator methodCreator,
ResultHandle convertedParamArray = creator.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParamArray", Object[].class, Object[].class,
Class.class, java.lang.reflect.Type.class, Annotation[].class),
client, paramArray, creator.loadClassFromTCCL(componentType), genericType, creator.newArray(
Annotation.class, 0));
client, paramArray, creator.loadClassFromTCCL(componentType), genericType,
creator.newArray(Annotation.class, 0));
creator.invokeInterfaceMethod(MULTIVALUED_MAP_ADD_ALL, formParams,
creator.load(paramName), convertedParamArray);
} else if (isMap(parameterType, index)) {
var resolvesTypes = resolveMapTypes(parameterType, index, jandexMethod);
var keyType = resolvesTypes.getKey();
if (!ResteasyReactiveDotNames.STRING.equals(keyType.name())) {
throw new IllegalArgumentException(
"Map parameter types must have String keys. Offending method is: " + jandexMethod);
}
// Loop through the keys
ResultHandle keySet = creator.invokeInterfaceMethod(ofMethod(Map.class, "keySet", Set.class),
formParamHandle);
ResultHandle keysIterator = creator.invokeInterfaceMethod(
ofMethod(Set.class, "iterator", Iterator.class), keySet);
BytecodeCreator loopCreator = creator.whileLoop(c -> iteratorHasNext(c, keysIterator)).block();
ResultHandle key = loopCreator.invokeInterfaceMethod(
ofMethod(Iterator.class, "next", Object.class), keysIterator);
// get the value and convert
ResultHandle value = loopCreator.invokeInterfaceMethod(ofMethod(Map.class, "get", Object.class, Object.class),
formParamHandle, key);
var valueType = resolvesTypes.getValue();
String componentType = valueType.name().toString();
ResultHandle paramArray;
if (isCollection(valueType, index)) {
if (valueType.kind() == PARAMETERIZED_TYPE) {
Type paramType = valueType.asParameterizedType().arguments().get(0);
if ((paramType.kind() == CLASS) || (paramType.kind() == PARAMETERIZED_TYPE)) {
componentType = paramType.name().toString();
}
}
if (componentType == null) {
componentType = DotNames.OBJECT.toString();
}
paramArray = loopCreator.invokeStaticMethod(
MethodDescriptor.ofMethod(ToObjectArray.class, "collection", Object[].class, Collection.class),
value);
} else {
paramArray = loopCreator
.invokeStaticMethod(ofMethod(ToObjectArray.class, "value", Object[].class, Object.class),
value);
}
ResultHandle convertedParamArray = loopCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParamArray", Object[].class, Object[].class,
Class.class, java.lang.reflect.Type.class, Annotation[].class),
client, paramArray, loopCreator.loadClassFromTCCL(componentType), genericType,
loopCreator.newArray(Annotation.class, 0));
loopCreator.invokeInterfaceMethod(MULTIVALUED_MAP_ADD_ALL, formParams,
key, convertedParamArray);
} else {
ResultHandle convertedFormParam = convertParamToString(creator, client, formParamHandle,
parameterType.name().toString(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.quarkus.rest.client.reactive;

import static org.assertj.core.api.Assertions.*;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MultivaluedMap;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.jboss.resteasy.reactive.RestForm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;

public class FormMapTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar.addClasses(Resource.class, Client.class));

@TestHTTPResource
URI baseUri;

@Test
void test() {
Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class);
String response = client.call(Map.of("foo", "bar", "k1", "v1", "k2", "v2"), new Holder(Collections.emptyMap()));
assertThat(response).isEqualTo("foo=bar-k1=v1-k2=v2");

String response2 = client.call(Collections.emptyMap(), new Holder(Map.of("foo", List.of("bar"), "k1", List.of("v1"))));
assertThat(response2).isEqualTo("foo=bar-k1=v1");

String response3 = client.call(Map.of("foo", "bar"), new Holder(Map.of("k", List.of("v1", "v2"))));
assertThat(response3).isEqualTo("foo=bar-k=v1,v2");
}

@Path("/test")
public static class Resource {

@POST
@Consumes("application/x-www-form-urlencoded")
public String response(MultivaluedMap<String, String> all) {
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
List<String> keys = new ArrayList<>(all.keySet());
Collections.sort(keys);
for (var key : keys) {
if (!isFirst) {
sb.append("-");
}
isFirst = false;
sb.append(key).append("=").append(String.join(",", all.get(key)));
}
return sb.toString();
}
}

@Path("/test")
public interface Client {

@POST
String call(@RestForm Map<String, String> formParams, @BeanParam Holder holder);
}

public static class Holder {
@RestForm
public Map<String, List<String>> more;

public Holder(Map<String, List<String>> more) {
this.more = more;
}
}
}

0 comments on commit 159377a

Please sign in to comment.