diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 6ff286f6416f1d..3816b652ac5534 100644 --- a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -201,6 +201,8 @@ public class JaxrsClientReactiveProcessor { String.class, Object.class); private static final MethodDescriptor MULTIVALUED_MAP_ADD = MethodDescriptor.ofMethod(MultivaluedMap.class, "add", void.class, Object.class, Object.class); + private static final MethodDescriptor MULTIVALUED_MAP_ADD_ALL = MethodDescriptor.ofMethod(MultivaluedMap.class, "addAll", + void.class, Object.class, Object[].class); private static final MethodDescriptor PATH_GET_FILENAME = MethodDescriptor.ofMethod(Path.class, "getFileName", Path.class); private static final MethodDescriptor OBJECT_TO_STRING = MethodDescriptor.ofMethod(Object.class, "toString", String.class); @@ -956,7 +958,7 @@ A more full example of generated client (with sub-resource) can is at the bottom Supplier beanParamDescriptorsField = classContext .getLazyBeanParameterDescriptors(beanParam.type); - formParams = addBeanParamData(jandexMethod, methodCreator, handleBeanParamMethod, + formParams = addBeanParamData(jandexMethod, paramIdx, methodCreator, handleBeanParamMethod, invocationBuilderRef, classContext, beanParam.getItems(), methodCreator.getMethodParam(paramIdx), methodTarget, index, restClientInterface.getClassName(), @@ -1024,8 +1026,9 @@ A more full example of generated client (with sub-resource) can is at the bottom } else if (param.parameterType == ParameterType.FORM) { formParams = createFormDataIfAbsent(methodCreator, formParams, multipart); // NOTE: don't use type here, because we're not going through the collection converters and stuff - addFormParam(methodCreator, param.name, methodCreator.getMethodParam(paramIdx), param.declaredType, - param.signature, + Type parameterType = jandexMethod.parameterType(paramIdx); + addFormParam(methodCreator, param.name, methodCreator.getMethodParam(paramIdx), + parameterType, param.declaredType, param.signature, index, restClientInterface.getClassName(), methodCreator.getThis(), formParams, getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx), getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx), @@ -1459,7 +1462,7 @@ private void handleSubResourceMethod(List AssignableResultHandle invocationBuilderRef = handleBeanParamMethod .createVariable(Invocation.Builder.class); handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); - formParams = addBeanParamData(jandexMethod, subMethodCreator, handleBeanParamMethod, + formParams = addBeanParamData(jandexMethod, methodIndex, subMethodCreator, handleBeanParamMethod, invocationBuilderRef, subContext, beanParam.getItems(), paramValue, methodTarget, index, interfaceClass.name().toString(), @@ -1586,7 +1589,7 @@ private void handleSubResourceMethod(List AssignableResultHandle invocationBuilderRef = handleBeanParamMethod .createVariable(Invocation.Builder.class); handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); - formParams = addBeanParamData(jandexMethod, subMethodCreator, handleBeanParamMethod, + formParams = addBeanParamData(jandexMethod, methodIndex, subMethodCreator, handleBeanParamMethod, invocationBuilderRef, subContext, beanParam.getItems(), subMethodCreator.getMethodParam(paramIdx), methodTarget, index, interfaceClass.name().toString(), @@ -1771,8 +1774,8 @@ private AssignableResultHandle createRestClientField(String name, ClassCreator c } private void handleMultipartField(String formParamName, String partType, String partFilename, - String type, - String parameterGenericType, ResultHandle fieldValue, AssignableResultHandle multipartForm, + String type, String parameterSignature, + ResultHandle fieldValue, AssignableResultHandle multipartForm, BytecodeCreator methodCreator, ResultHandle client, String restClientInterfaceClassName, ResultHandle parameterAnnotations, ResultHandle genericType, String errorLocation) { @@ -1806,7 +1809,7 @@ private void handleMultipartField(String formParamName, String partType, String MethodDescriptor.ofMethod(Buffer.class, "buffer", Buffer.class, byte[].class), fieldValue); addBuffer(ifValueNotNull, multipartForm, formParamName, partType, partFilename, buffer, errorLocation); - } else if (parameterGenericType.equals(MULTI_BYTE_SIGNATURE)) { + } else if (parameterSignature.equals(MULTI_BYTE_SIGNATURE)) { addMultiAsFile(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue, errorLocation); } else if (partType != null) { if (partFilename != null) { @@ -2407,7 +2410,7 @@ private Optional getJavaMethod(ClassInfo interfaceClass, ResourceMet } private AssignableResultHandle addBeanParamData(MethodInfo jandexMethod, - BytecodeCreator methodCreator, + int paramIndex, BytecodeCreator methodCreator, // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder) BytecodeCreator invocationBuilderEnricher, AssignableResultHandle invocationBuilder, @@ -2429,7 +2432,8 @@ private AssignableResultHandle addBeanParamData(MethodInfo jandexMethod, formParams = createFormDataIfAbsent(methodCreator, formParams, multipart); } - addSubBeanParamData(jandexMethod, methodCreator, invocationBuilderEnricher, invocationBuilder, classContext, + addSubBeanParamData(jandexMethod, paramIndex, methodCreator, invocationBuilderEnricher, invocationBuilder, + classContext, beanParamItems, param, target, index, restClientInterfaceClassName, client, invocationEnricherClient, formParams, descriptorsField, multipart, beanParamClass); @@ -2437,7 +2441,7 @@ private AssignableResultHandle addBeanParamData(MethodInfo jandexMethod, return formParams; } - private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator methodCreator, + private void addSubBeanParamData(MethodInfo jandexMethod, int paramIndex, BytecodeCreator methodCreator, // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder) BytecodeCreator invocationBuilderEnricher, AssignableResultHandle invocationBuilder, @@ -2475,7 +2479,7 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method ResultHandle beanParamElementHandle = beanParamItem.extract(creator, param); Supplier newBeanParamDescriptorField = classContext .getLazyBeanParameterDescriptors(beanParamItem.className()); - addSubBeanParamData(jandexMethod, creator, invoEnricher, invocationBuilder, classContext, + addSubBeanParamData(jandexMethod, paramIndex, creator, invoEnricher, invocationBuilder, classContext, beanParamItem.items(), beanParamElementHandle, target, index, restClientInterfaceClassName, client, invocationEnricherClient, formParams, newBeanParamDescriptorField, multipart, @@ -2520,7 +2524,9 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method case FORM_PARAM: FormParamItem formParam = (FormParamItem) item; addFormParam(creator, formParam.getFormParamName(), formParam.extract(creator, param), - formParam.getParamType(), formParam.getParamSignature(), restClientInterfaceClassName, client, + jandexMethod.parameterType(paramIndex), formParam.getParamType(), formParam.getParamSignature(), + index, + restClientInterfaceClassName, client, formParams, getGenericTypeFromParameter(creator, beanParamDescriptorField, item.fieldName()), getAnnotationsFromParameter(creator, beanParamDescriptorField, item.fieldName()), @@ -2786,25 +2792,53 @@ private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle methodCreator.load(paramName), handle)); } - private void addFormParam(BytecodeCreator methodCreator, String paramName, ResultHandle formParamHandle, - String parameterType, String parameterGenericType, + private void addFormParam(BytecodeCreator methodCreator, + String paramName, + ResultHandle formParamHandle, + Type parameterType, + String parameterTypeStr, + String parameterSignature, + IndexView index, String restClientInterfaceClassName, ResultHandle client, AssignableResultHandle formParams, ResultHandle genericType, ResultHandle parameterAnnotations, boolean multipart, String partType, String partFilename, String errorLocation) { if (multipart) { - handleMultipartField(paramName, partType, partFilename, parameterType, parameterGenericType, formParamHandle, + handleMultipartField(paramName, partType, partFilename, parameterTypeStr, parameterSignature, formParamHandle, formParams, methodCreator, client, restClientInterfaceClassName, parameterAnnotations, genericType, errorLocation); } else { - BytecodeCreator notNullValue = methodCreator.ifNull(formParamHandle).falseBranch(); - ResultHandle convertedFormParam = convertParamToString(notNullValue, client, formParamHandle, parameterType, - genericType, parameterAnnotations); - BytecodeCreator parameterIsStringBranch = checkStringParam(notNullValue, convertedFormParam, - restClientInterfaceClassName, errorLocation); - parameterIsStringBranch.invokeInterfaceMethod(MULTIVALUED_MAP_ADD, formParams, - notNullValue.load(paramName), convertedFormParam); + BytecodeCreator creator = methodCreator.ifNull(formParamHandle).falseBranch(); + if (isCollection(parameterType, index)) { + String componentType = null; + if (parameterType.kind() == PARAMETERIZED_TYPE) { + Type paramType = parameterType.asParameterizedType().arguments().get(0); + if ((paramType.kind() == CLASS) || (paramType.kind() == PARAMETERIZED_TYPE)) { + componentType = paramType.name().toString(); + } + } + if (componentType == null) { + componentType = DotNames.OBJECT.toString(); + } + ResultHandle paramArray = creator.invokeStaticMethod( + MethodDescriptor.ofMethod(ToObjectArray.class, "collection", Object[].class, Collection.class), + formParamHandle); + 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)); + creator.invokeInterfaceMethod(MULTIVALUED_MAP_ADD_ALL, formParams, + creator.load(paramName), convertedParamArray); + } else { + ResultHandle convertedFormParam = convertParamToString(creator, client, formParamHandle, parameterTypeStr, + genericType, parameterAnnotations); + BytecodeCreator parameterIsStringBranch = checkStringParam(creator, convertedFormParam, + restClientInterfaceClassName, errorLocation); + parameterIsStringBranch.invokeInterfaceMethod(MULTIVALUED_MAP_ADD, formParams, + creator.load(paramName), convertedFormParam); + } } } diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/FormListTest.java b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/FormListTest.java new file mode 100644 index 00000000000000..b05f4c58b8031e --- /dev/null +++ b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/FormListTest.java @@ -0,0 +1,51 @@ +package io.quarkus.rest.client.reactive; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.util.List; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +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 FormListTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(VoidReturnTypeTest.Resource.class)); + + @TestHTTPResource + URI baseUri; + + @Test + void testHeadersWithSubresource() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + + assertThat(client.call(List.of("first", "second", "third"))).isEqualTo("first-second-third"); + assertThat(client.call(List.of("first"))).isEqualTo("first"); + } + + @Path("/test") + public static class Resource { + + @POST + public String response(@RestForm List input) { + return String.join("-", input); + } + } + + @Path("/test") + public interface Client { + + @POST + String call(@RestForm List input); + } +}