From 09255a1512e0b2234109bba05ac9f6bfbe3f8968 Mon Sep 17 00:00:00 2001 From: poojah Date: Tue, 25 Aug 2026 12:34:42 +0200 Subject: [PATCH] Fix handling multiple request form --- .../com/adyen/httpclient/AdyenHttpClient.java | 156 ++++++++++++++++-- .../com/adyen/httpclient/ClientInterface.java | 32 ++++ .../com/adyen/service/resource/Resource.java | 60 ++++++- .../java/com/adyen/httpclient/ClientTest.java | 120 ++++++++++++++ .../java/com/adyen/service/ResourceTest.java | 18 ++ templates-v7/libraries/jersey3/api.mustache | 25 ++- .../jersey3/api_overload_invoke.mustache | 2 +- .../libraries/jersey3/api_parameters.mustache | 4 +- .../libraries/jersey3/api_summary.mustache | 3 + .../jersey3/api_summary_overload.mustache | 3 + 10 files changed, 395 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/adyen/httpclient/AdyenHttpClient.java b/src/main/java/com/adyen/httpclient/AdyenHttpClient.java index 301feb2d8..192953a52 100644 --- a/src/main/java/com/adyen/httpclient/AdyenHttpClient.java +++ b/src/main/java/com/adyen/httpclient/AdyenHttpClient.java @@ -36,12 +36,15 @@ import com.adyen.Config; import com.adyen.constants.ApiConstants; import com.adyen.model.RequestOptions; +import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.net.ssl.HostnameVerifier; @@ -55,10 +58,13 @@ import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.net.URIBuilder; @@ -201,10 +207,36 @@ public String request( HttpUriRequestBase httpRequest = createRequest( endpoint, requestBody, config, isApiKeyRequired, requestOptions, httpMethod, params); + return executeRequest(httpclient, httpRequest); + } - // Execute request with a custom response handler - AdyenResponse response = httpclient.execute(httpRequest, new AdyenResponseHandler()); + @Override + public String requestMultipart( + String endpoint, + Map formParams, + Config config, + boolean isApiKeyRequired, + RequestOptions requestOptions, + ApiConstants.HttpMethod httpMethod, + Map params) + throws IOException, HTTPClientException { + CloseableHttpClient httpclient = getOrCreateHttpClient(config); + HttpEntity multipartEntity = createMultipartEntity(formParams); + HttpUriRequestBase httpRequest = + createRequest( + endpoint, + new RequestBody(multipartEntity, multipartEntity.getContentType()), + config, + isApiKeyRequired, + requestOptions, + httpMethod, + params); + return executeRequest(httpclient, httpRequest); + } + private String executeRequest(CloseableHttpClient httpclient, HttpUriRequestBase httpRequest) + throws IOException, HTTPClientException { + AdyenResponse response = httpclient.execute(httpRequest, new AdyenResponseHandler()); if (response.getStatus() < 200 || response.getStatus() >= 300) { throw new HTTPClientException( response.getStatus(), "HTTP Exception", response.getHeaders(), response.getBody()); @@ -235,8 +267,51 @@ HttpUriRequestBase createRequest( ApiConstants.HttpMethod httpMethod, Map params) throws HTTPClientException { + HttpEntity requestEntity = + requestBody == null || requestBody.isEmpty() + ? null + : new StringEntity(requestBody, Charset.forName(CHARSET)); + return createRequest( + endpoint, + new RequestBody(requestEntity, APPLICATION_JSON_TYPE), + config, + isApiKeyRequired, + requestOptions, + httpMethod, + params); + } + + HttpUriRequestBase createMultipartRequest( + String endpoint, + Map formParams, + Config config, + boolean isApiKeyRequired, + RequestOptions requestOptions, + ApiConstants.HttpMethod httpMethod, + Map params) + throws HTTPClientException, IOException { + HttpEntity multipartEntity = createMultipartEntity(formParams); + return createRequest( + endpoint, + new RequestBody(multipartEntity, multipartEntity.getContentType()), + config, + isApiKeyRequired, + requestOptions, + httpMethod, + params); + } + + private HttpUriRequestBase createRequest( + String endpoint, + RequestBody requestBody, + Config config, + boolean isApiKeyRequired, + RequestOptions requestOptions, + ApiConstants.HttpMethod httpMethod, + Map params) + throws HTTPClientException { HttpUriRequestBase httpRequest = - createHttpRequestBase(createUri(endpoint, params), requestBody, httpMethod); + createHttpRequestBase(createUri(endpoint, params), requestBody.entity, httpMethod); RequestConfig.Builder builder = RequestConfig.custom(); @@ -256,15 +331,19 @@ HttpUriRequestBase createRequest( httpRequest.setConfig(builder.build()); setAuthentication(httpRequest, isApiKeyRequired, config); - setHeaders(config, requestOptions, httpRequest); + setHeaders(config, requestOptions, httpRequest, requestBody.contentType); return httpRequest; } private void setHeaders( - Config config, RequestOptions requestOptions, HttpUriRequestBase httpUriRequest) { + Config config, + RequestOptions requestOptions, + HttpUriRequestBase httpUriRequest, + String contentType) { - setContentType(httpUriRequest, APPLICATION_JSON_TYPE); + setContentType(httpUriRequest, contentType); + boolean isMultipart = contentType != null && contentType.startsWith("multipart/"); httpUriRequest.addHeader(ACCEPT_CHARSET, CHARSET); String applicationName = config.getApplicationName(); @@ -292,18 +371,24 @@ private void setHeaders( } if (requestOptions.getAdditionalServiceHeaders() != null) { - requestOptions.getAdditionalServiceHeaders().forEach(httpUriRequest::addHeader); + requestOptions + .getAdditionalServiceHeaders() + .forEach( + (name, value) -> { + if (CONTENT_TYPE.equalsIgnoreCase(name)) { + if (!isMultipart) { + httpUriRequest.setHeader(name, value); + } + } else { + httpUriRequest.addHeader(name, value); + } + }); } } } private HttpUriRequestBase createHttpRequestBase( - URI endpoint, String requestBody, ApiConstants.HttpMethod httpMethod) { - StringEntity requestEntity = null; - if (requestBody != null && !requestBody.isEmpty()) { - requestEntity = new StringEntity(requestBody, Charset.forName(CHARSET)); - } - + URI endpoint, HttpEntity requestEntity, ApiConstants.HttpMethod httpMethod) { switch (httpMethod) { case GET: return new HttpGet(endpoint); @@ -321,6 +406,49 @@ private HttpUriRequestBase createHttpRequestBase( } } + HttpEntity createMultipartEntity(Map formParams) throws IOException { + MultipartEntityBuilder builder = + MultipartEntityBuilder.create() + .setContentType(ContentType.create("multipart/form-data")) + .setCharset(StandardCharsets.UTF_8); + for (Map.Entry entry : formParams.entrySet()) { + addMultipartPart(builder, entry.getKey(), entry.getValue()); + } + return builder.build(); + } + + private void addMultipartPart(MultipartEntityBuilder builder, String name, Object value) + throws IOException { + if (value == null) { + return; + } + if (value instanceof Iterable) { + for (Object element : (Iterable) value) { + addMultipartPart(builder, name, element); + } + return; + } + if (value instanceof File) { + File file = (File) value; + String mimeType = Files.probeContentType(file.toPath()); + ContentType contentType = + mimeType == null ? ContentType.APPLICATION_OCTET_STREAM : ContentType.create(mimeType); + builder.addBinaryBody(name, file, contentType, file.getName()); + return; + } + builder.addTextBody(name, String.valueOf(value), ContentType.create("text/plain", CHARSET)); + } + + private static final class RequestBody { + private final HttpEntity entity; + private final String contentType; + + private RequestBody(HttpEntity entity, String contentType) { + this.entity = entity; + this.contentType = contentType; + } + } + private URI createUri(String endpoint, Map params) throws HTTPClientException { try { URIBuilder uriBuilder = new URIBuilder(endpoint); @@ -406,7 +534,7 @@ private void setAuthentication( /** Sets the Content-Type header on the request. */ private void setContentType(HttpUriRequest httpUriRequest, String contentType) { - httpUriRequest.addHeader(CONTENT_TYPE, contentType); + httpUriRequest.setHeader(CONTENT_TYPE, contentType); } /** Sets the X-API-Key header on the request. */ diff --git a/src/main/java/com/adyen/httpclient/ClientInterface.java b/src/main/java/com/adyen/httpclient/ClientInterface.java index 5ff43e387..a9059a974 100644 --- a/src/main/java/com/adyen/httpclient/ClientInterface.java +++ b/src/main/java/com/adyen/httpclient/ClientInterface.java @@ -136,4 +136,36 @@ String request( ApiConstants.HttpMethod httpMethod, Map params) throws IOException, HTTPClientException; + + /** + * Sends a multipart/form-data request with the specified method, authentication, request options, + * and query string parameters. + * + *

The default implementation preserves binary compatibility for custom HTTP client + * implementations that do not support multipart requests. + * + * @param endpoint the full URL of the API endpoint + * @param formParams multipart form fields, including files and text values + * @param config the client configuration + * @param isApiKeyRequired whether API key authentication is mandatory + * @param requestOptions additional request options (idempotency key, custom headers) + * @param httpMethod the HTTP method (POST or PATCH) + * @param params query string parameters appended to the URL + * @return the JSON response body + * @throws IOException if a network error occurs + * @throws HTTPClientException if the server returns a non-2xx status code + */ + default String requestMultipart( + String endpoint, + Map formParams, + Config config, + boolean isApiKeyRequired, + RequestOptions requestOptions, + ApiConstants.HttpMethod httpMethod, + Map params) + throws IOException, HTTPClientException { + throw new UnsupportedOperationException( + "Multipart requests are not supported by this HTTP client. " + + "Custom ClientInterface implementations must override requestMultipart()."); + } } diff --git a/src/main/java/com/adyen/service/resource/Resource.java b/src/main/java/com/adyen/service/resource/Resource.java index edb1619c4..10f33f6bf 100644 --- a/src/main/java/com/adyen/service/resource/Resource.java +++ b/src/main/java/com/adyen/service/resource/Resource.java @@ -115,16 +115,55 @@ public String request( throws ApiException, IOException { ClientInterface clientInterface = service.getClient().getHttpClient(); Config config = service.getClient().getConfig(); + return executeRequest( + () -> + clientInterface.request( + resolve(pathParams), + json, + config, + service.isApiKeyRequired(), + requestOptions, + httpMethod, + queryString)); + } + + /** + * Sends a multipart/form-data request with optional path and query string parameters. + * + * @param formParams multipart form fields + * @param requestOptions additional request options + * @param httpMethod HTTP method + * @param pathParams parameters used to resolve path placeholders + * @param queryString query string parameters + * @return JSON response + * @throws ApiException when an API error is returned + * @throws IOException when an unexpected error occurs + */ + public String requestMultipart( + Map formParams, + RequestOptions requestOptions, + ApiConstants.HttpMethod httpMethod, + Map pathParams, + Map queryString) + throws ApiException, IOException { + ClientInterface clientInterface = service.getClient().getHttpClient(); + Config config = service.getClient().getConfig(); + return executeRequest( + () -> + clientInterface.requestMultipart( + resolve(pathParams), + formParams, + config, + service.isApiKeyRequired(), + requestOptions, + httpMethod, + queryString)); + } + + private String executeRequest(RequestCall requestCall) throws ApiException, IOException { ApiException apiException; try { - return clientInterface.request( - resolve(pathParams), - json, - config, - service.isApiKeyRequired(), - requestOptions, - httpMethod, - queryString); + return requestCall.execute(); } catch (HTTPClientException e) { try { // build ApiException @@ -145,6 +184,11 @@ public String request( throw apiException; } + @FunctionalInterface + private interface RequestCall { + String execute() throws IOException, HTTPClientException; + } + private String resolve(Map params) { if (endpoint == null || params == null || endpoint.isEmpty() || params.isEmpty()) { return endpoint; diff --git a/src/test/java/com/adyen/httpclient/ClientTest.java b/src/test/java/com/adyen/httpclient/ClientTest.java index 919ff9aad..afeb7a858 100644 --- a/src/test/java/com/adyen/httpclient/ClientTest.java +++ b/src/test/java/com/adyen/httpclient/ClientTest.java @@ -9,7 +9,12 @@ import com.adyen.enums.Environment; import com.adyen.enums.Region; import com.adyen.model.RequestOptions; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Stream; import javax.net.ssl.SSLContext; @@ -18,7 +23,9 @@ import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -245,6 +252,7 @@ public void testUserAgentWithoutApplicationName() throws Exception { public void testRequestWithHttpHeaders() throws Exception { AdyenHttpClient client = new AdyenHttpClient(); HashMap additionalHeaders = new HashMap<>(); + additionalHeaders.put("Content-Type", "application/vnd.adyen+json"); additionalHeaders.put("X-Custom-Header", "custom-value"); RequestOptions requestOptions = @@ -271,11 +279,123 @@ public void testRequestWithHttpHeaders() throws Exception { assertNotNull(customHeader); assertEquals("custom-value", customHeader.getValue()); + Header[] contentTypeHeaders = request.getHeaders("Content-Type"); + assertEquals(1, contentTypeHeaders.length); + assertEquals("application/vnd.adyen+json", contentTypeHeaders[0].getValue()); + Header wwwAuthenticate = request.getFirstHeader("WWW-Authenticate"); assertNotNull(wwwAuthenticate); assertEquals("www-authenticate-header", wwwAuthenticate.getValue()); } + @Test + public void testMultipartRequestContainsFormFieldsAndBoundary(@TempDir Path temporaryDirectory) + throws Exception { + Path file = temporaryDirectory.resolve("QRMJC25GDZRKDM92.pdf"); + byte[] fileContents = "PDF contents".getBytes(StandardCharsets.UTF_8); + Files.write(file, fileContents); + + Map formParams = new LinkedHashMap<>(); + formParams.put("context", "paCbInvoice"); + formParams.put("file", file.toFile()); + formParams.put("merchantAccount", "YourMerchantAccount"); + + HashMap additionalHeaders = new HashMap<>(); + additionalHeaders.put("Content-Type", "application/json"); + additionalHeaders.put("X-Custom-Header", "custom-value"); + RequestOptions requestOptions = + new RequestOptions().additionalServiceHeaders(additionalHeaders); + + AdyenHttpClient client = new AdyenHttpClient(); + HttpUriRequestBase request = + client.createMultipartRequest( + "https://document-collector-test.adyen.com/v1/crossBorderInvoices", + formParams, + new Config().apiKey("test-api-key"), + true, + requestOptions, + ApiConstants.HttpMethod.POST, + Map.of()); + + Header[] contentTypeHeaders = request.getHeaders("Content-Type"); + assertEquals(1, contentTypeHeaders.length); + assertTrue( + contentTypeHeaders[0].getValue().startsWith("multipart/form-data; boundary="), + contentTypeHeaders[0]::getValue); + assertEquals("custom-value", request.getFirstHeader("X-Custom-Header").getValue()); + assertEquals("test-api-key", request.getFirstHeader("x-api-key").getValue()); + + HttpEntity entity = request.getEntity(); + assertNotNull(entity); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + entity.writeTo(output); + String body = output.toString(StandardCharsets.UTF_8); + assertTrue(body.contains("name=\"context\"")); + assertTrue(body.contains("paCbInvoice")); + assertTrue(body.contains("name=\"file\"")); + assertTrue(body.contains("filename=\"QRMJC25GDZRKDM92.pdf\"")); + assertTrue(body.contains("name=\"merchantAccount\"")); + assertTrue(body.contains("YourMerchantAccount")); + assertTrue(body.contains(new String(fileContents, StandardCharsets.UTF_8))); + } + + @Test + public void testDefaultMultipartMethodSupportsLegacyClients() throws Exception { + ClientInterface legacyClient = + new ClientInterface() { + @Override + public String request(String endpoint, String requestBody, Config config) { + return "response"; + } + + @Override + public String request( + String endpoint, String requestBody, Config config, boolean isApiKeyRequired) { + return "response"; + } + + @Override + public String request( + String endpoint, + String requestBody, + Config config, + boolean isApiKeyRequired, + RequestOptions requestOptions) { + return "response"; + } + + @Override + public String request( + String endpoint, + String requestBody, + Config config, + boolean isApiKeyRequired, + RequestOptions requestOptions, + ApiConstants.HttpMethod httpMethod) { + return "response"; + } + + @Override + public String request( + String endpoint, + String requestBody, + Config config, + boolean isApiKeyRequired, + RequestOptions requestOptions, + ApiConstants.HttpMethod httpMethod, + Map params) { + return "response"; + } + }; + + assertEquals("response", legacyClient.request("", "", new Config())); + assertThrows( + UnsupportedOperationException.class, + () -> + legacyClient.requestMultipart( + "", Map.of(), new Config(), false, null, ApiConstants.HttpMethod.POST, null)); + } + @Test public void testGetHttpClientReturnsSameInstance() { Client client = new Client("apiKey", Environment.TEST); diff --git a/src/test/java/com/adyen/service/ResourceTest.java b/src/test/java/com/adyen/service/ResourceTest.java index ff30c4788..b5bfa1e45 100644 --- a/src/test/java/com/adyen/service/ResourceTest.java +++ b/src/test/java/com/adyen/service/ResourceTest.java @@ -37,6 +37,7 @@ import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -94,6 +95,23 @@ public void testRequestQueryString() throws Exception { queryString); } + @Test + public void testMultipartRequest() throws Exception { + Map formParams = new LinkedHashMap<>(); + formParams.put("file", "document"); + Resource resource = new Resource(serviceMock, "/documents/{documentId}", null); + Map pathParams = Collections.singletonMap("documentId", "123"); + + when(clientInterfaceMock.requestMultipart( + "/documents/123", formParams, null, false, null, ApiConstants.HttpMethod.POST, null)) + .thenReturn("response"); + + assertEquals( + "response", + resource.requestMultipart( + formParams, null, ApiConstants.HttpMethod.POST, pathParams, null)); + } + @Test public void testNonJsonError() throws Exception { Map pathParams = Collections.singletonMap("companyId", "adyen"); diff --git a/templates-v7/libraries/jersey3/api.mustache b/templates-v7/libraries/jersey3/api.mustache index cd0aa898c..ef46096cf 100644 --- a/templates-v7/libraries/jersey3/api.mustache +++ b/templates-v7/libraries/jersey3/api.mustache @@ -12,6 +12,7 @@ import com.adyen.service.resource.Resource; import java.io.IOException; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.List; @@ -78,14 +79,32 @@ public class {{classname}} extends Service { {{/queryParams}} {{/hasQueryParams}} - String requestBody = {{#bodyParam}}{{paramName}}.toJson(){{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + {{#hasFormParams}} + //Add form params + Map formParams = new LinkedHashMap<>(); + {{#formParams}} + {{#required}} + if ({{{paramName}}} == null) { + throw new IllegalArgumentException("Please provide the {{{paramName}}} form parameter"); + } + formParams.put("{{baseName}}", {{{paramName}}}); + {{/required}} + {{^required}} + if ({{{paramName}}} != null) { + formParams.put("{{baseName}}", {{{paramName}}}); + } + {{/required}} + {{/formParams}} + + {{/hasFormParams}} + {{^hasFormParams}}String requestBody = {{#bodyParam}}{{paramName}}.toJson(){{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};{{/hasFormParams}} Resource resource = new Resource(this, this.baseURL + "{{{path}}}", null); {{#returnType}} - String jsonResult = resource.request(requestBody, requestOptions, ApiConstants.HttpMethod.{{httpMethod}}, {{#hasPathParams}}pathParams{{/hasPathParams}}{{^hasPathParams}}null{{/hasPathParams}}{{#hasQueryParams}}, queryParams{{/hasQueryParams}}); + String jsonResult = {{#hasFormParams}}resource.requestMultipart(formParams, requestOptions, ApiConstants.HttpMethod.{{httpMethod}}, {{#hasPathParams}}pathParams{{/hasPathParams}}{{^hasPathParams}}null{{/hasPathParams}}{{#hasQueryParams}}, queryParams{{/hasQueryParams}}{{^hasQueryParams}}, null{{/hasQueryParams}}){{/hasFormParams}}{{^hasFormParams}}resource.request(requestBody, requestOptions, ApiConstants.HttpMethod.{{httpMethod}}, {{#hasPathParams}}pathParams{{/hasPathParams}}{{^hasPathParams}}null{{/hasPathParams}}{{#hasQueryParams}}, queryParams{{/hasQueryParams}}){{/hasFormParams}}; return {{#returnType}}{{{.}}}.fromJson(jsonResult){{/returnType}}; {{/returnType}} {{^returnType}} - resource.request(requestBody, requestOptions, ApiConstants.HttpMethod.{{httpMethod}}, {{#hasPathParams}}pathParams{{/hasPathParams}}{{^hasPathParams}}null{{/hasPathParams}}{{#hasQueryParams}}, queryParams{{/hasQueryParams}}); + {{#hasFormParams}}resource.requestMultipart(formParams, requestOptions, ApiConstants.HttpMethod.{{httpMethod}}, {{#hasPathParams}}pathParams{{/hasPathParams}}{{^hasPathParams}}null{{/hasPathParams}}{{#hasQueryParams}}, queryParams{{/hasQueryParams}}{{^hasQueryParams}}, null{{/hasQueryParams}});{{/hasFormParams}}{{^hasFormParams}}resource.request(requestBody, requestOptions, ApiConstants.HttpMethod.{{httpMethod}}, {{#hasPathParams}}pathParams{{/hasPathParams}}{{^hasPathParams}}null{{/hasPathParams}}{{#hasQueryParams}}, queryParams{{/hasQueryParams}});{{/hasFormParams}} {{/returnType}} } {{/operation}} diff --git a/templates-v7/libraries/jersey3/api_overload_invoke.mustache b/templates-v7/libraries/jersey3/api_overload_invoke.mustache index 52479b036..ae789a4c1 100644 --- a/templates-v7/libraries/jersey3/api_overload_invoke.mustache +++ b/templates-v7/libraries/jersey3/api_overload_invoke.mustache @@ -1,2 +1,2 @@ {{! Overload contains just required and body params, null on the remaining }} -{{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{#required}}{{paramName}}, {{/required}}{{^required}}null, {{/required}} {{/queryParams}}{{#bodyParams}}{{paramName}}, {{/bodyParams}}null \ No newline at end of file +{{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{#required}}{{paramName}}, {{/required}}{{^required}}null, {{/required}} {{/queryParams}}{{#bodyParams}}{{paramName}}, {{/bodyParams}}{{#formParams}}{{#required}}{{paramName}}, {{/required}}{{^required}}null, {{/required}}{{/formParams}}null \ No newline at end of file diff --git a/templates-v7/libraries/jersey3/api_parameters.mustache b/templates-v7/libraries/jersey3/api_parameters.mustache index 4214551d2..0ca26e894 100644 --- a/templates-v7/libraries/jersey3/api_parameters.mustache +++ b/templates-v7/libraries/jersey3/api_parameters.mustache @@ -1,2 +1,2 @@ -{{! Path and body are required, followed by optional query string and request options }} -{{#pathParams}}{{{dataType}}} {{paramName}}, {{/pathParams}}{{#queryParams}}{{{dataType}}} {{paramName}}, {{/queryParams}}{{#bodyParams}}{{{dataType}}} {{paramName}}, {{/bodyParams}}RequestOptions requestOptions \ No newline at end of file +{{! Path and body are required, followed by query, form, and request options }} +{{#pathParams}}{{{dataType}}} {{paramName}}, {{/pathParams}}{{#queryParams}}{{{dataType}}} {{paramName}}, {{/queryParams}}{{#bodyParams}}{{{dataType}}} {{paramName}}, {{/bodyParams}}{{#formParams}}{{{dataType}}} {{paramName}}, {{/formParams}}RequestOptions requestOptions \ No newline at end of file diff --git a/templates-v7/libraries/jersey3/api_summary.mustache b/templates-v7/libraries/jersey3/api_summary.mustache index 284fd96b4..7899c21bb 100644 --- a/templates-v7/libraries/jersey3/api_summary.mustache +++ b/templates-v7/libraries/jersey3/api_summary.mustache @@ -7,6 +7,9 @@ {{#bodyParams}} * @param {{paramName}} {{#isContainer}}{@code {{{dataType}}} }{{/isContainer}}{{^isContainer}}{@link {{dataType}} }{{/isContainer}} {{description}} (required) {{/bodyParams}} +{{#formParams}} + * @param {{paramName}} {{#isContainer}}{@code {{{dataType}}} }{{/isContainer}}{{^isContainer}}{@link {{dataType}} }{{/isContainer}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional){{/required}} +{{/formParams}} {{#queryParams}} * @param {{paramName}} {{#isContainer}}{@code {{{dataType}}} }{{/isContainer}}{{^isContainer}}{@link {{dataType}} }{{/isContainer}} Query: {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} {{/queryParams}} diff --git a/templates-v7/libraries/jersey3/api_summary_overload.mustache b/templates-v7/libraries/jersey3/api_summary_overload.mustache index a9b2b7156..b0b6e1679 100644 --- a/templates-v7/libraries/jersey3/api_summary_overload.mustache +++ b/templates-v7/libraries/jersey3/api_summary_overload.mustache @@ -7,6 +7,9 @@ {{#bodyParams}} * @param {{paramName}} {@link {{dataType}} } {{description}} (required) {{/bodyParams}} + {{#formParams}}{{#required}} + * @param {{paramName}} {@link {{dataType}} } {{description}} (required) + {{/required}}{{/formParams}} {{#queryParams}}{{#required}} * @param {{paramName}} {@link {{dataType}} } Query: {{description}} (required) {{/required}}{{/queryParams}}