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

Properly handle Map of form params in REST Client #43855

Merged
merged 1 commit into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.assertThat;

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;
}
}
}
Loading