FINERACT-2380: move retrofit client based test to feign

This commit is contained in:
Attila Budai
2025-11-26 10:47:29 +01:00
committed by Adam Saghy
parent 6204cde27c
commit 5d2a9c5390
77 changed files with 4908 additions and 5039 deletions
@@ -61,6 +61,7 @@ import org.apache.fineract.client.feign.services.EntityDataTableApi;
import org.apache.fineract.client.feign.services.EntityFieldConfigurationApi;
import org.apache.fineract.client.feign.services.ExternalAssetOwnerLoanProductAttributesApi;
import org.apache.fineract.client.feign.services.ExternalAssetOwnersApi;
import org.apache.fineract.client.feign.services.ExternalAssetOwnersApiExtension;
import org.apache.fineract.client.feign.services.ExternalEventConfigurationApi;
import org.apache.fineract.client.feign.services.ExternalServicesApi;
import org.apache.fineract.client.feign.services.FetchAuthenticatedUserDetailsApi;
@@ -95,6 +96,7 @@ import org.apache.fineract.client.feign.services.LoanCobCatchUpApi;
import org.apache.fineract.client.feign.services.LoanCollateralApi;
import org.apache.fineract.client.feign.services.LoanCollateralManagementApi;
import org.apache.fineract.client.feign.services.LoanDisbursementDetailsApi;
import org.apache.fineract.client.feign.services.LoanDisbursementDetailsApiExtension;
import org.apache.fineract.client.feign.services.LoanInterestPauseApi;
import org.apache.fineract.client.feign.services.LoanProductsApi;
import org.apache.fineract.client.feign.services.LoanReschedulingApi;
@@ -392,6 +394,10 @@ public final class FineractFeignClient {
return create(ExternalAssetOwnersApi.class);
}
public ExternalAssetOwnersApiExtension externalAssetOwnersExtension() {
return create(ExternalAssetOwnersApiExtension.class);
}
public ExternalEventConfigurationApi externalEventConfiguration() {
return create(ExternalEventConfigurationApi.class);
}
@@ -528,6 +534,10 @@ public final class FineractFeignClient {
return create(LoanDisbursementDetailsApi.class);
}
public LoanDisbursementDetailsApiExtension loanDisbursementDetailsExtension() {
return create(LoanDisbursementDetailsApiExtension.class);
}
public LoanInterestPauseApi loanInterestPause() {
return create(LoanInterestPauseApi.class);
}
@@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.fineract.client.feign.support.ApiResponseDecoder;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
@@ -95,7 +96,8 @@ public final class FineractFeignClientConfig {
Encoder multipartEncoder = new FineractMultipartEncoder(jacksonEncoder);
return Feign.builder().client(getOrCreateHttpClient()).encoder(multipartEncoder)
.decoder(new JacksonDecoder(ObjectMapperFactory.getShared())).errorDecoder(new FineractErrorDecoder())
.decoder(new ApiResponseDecoder(new JacksonDecoder(ObjectMapperFactory.getShared())))
.errorDecoder(new FineractErrorDecoder())
.options(new Request.Options(connectTimeout, TimeUnit.MILLISECONDS, readTimeout, TimeUnit.MILLISECONDS, true))
.retryer(Retryer.NEVER_RETRY).requestInterceptor(new BasicAuthRequestInterceptor(username, password))
.requestInterceptor(new TenantIdRequestInterceptor(tenantId)).logger(new Slf4jLogger(apiType))
@@ -0,0 +1,40 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.client.feign.services;
import feign.Headers;
import feign.Param;
import feign.QueryMap;
import feign.RequestLine;
import java.util.Map;
import org.apache.fineract.client.models.ExternalAssetOwnerRequest;
import org.apache.fineract.client.models.PostInitiateTransferResponse;
public interface ExternalAssetOwnersApiExtension {
@RequestLine("POST /v1/external-asset-owners/transfers/{id}")
@Headers("Content-Type: application/json")
PostInitiateTransferResponse transferRequestWithIdWithBody(@Param("id") Long id, ExternalAssetOwnerRequest body,
@QueryMap Map<String, Object> queryParams);
@RequestLine("POST /v1/external-asset-owners/transfers/external-id/{externalId}")
@Headers("Content-Type: application/json")
PostInitiateTransferResponse transferRequestWithId1WithBody(@Param("externalId") String externalId, ExternalAssetOwnerRequest body,
@QueryMap Map<String, Object> queryParams);
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.client.feign.services;
import feign.Param;
import feign.RequestLine;
import org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest;
public interface LoanDisbursementDetailsApiExtension {
@RequestLine("PUT /v1/loans/{loanId}/disbursements/editDisbursements")
PostAddAndDeleteDisbursementDetailRequest addAndDeleteDisbursementDetail(@Param("loanId") Long loanId,
PostAddAndDeleteDisbursementDetailRequest postAddAndDeleteDisbursementDetailRequest);
}
@@ -0,0 +1,81 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.client.feign.support;
import feign.Response;
import feign.codec.Decoder;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.apache.fineract.client.models.ApiResponse;
/**
* Custom Feign decoder that handles ApiResponse&lt;T&gt; return types for *WithHttpInfo methods.
*
* When a Feign client method returns ApiResponse&lt;T&gt;, the standard JacksonDecoder cannot handle it because:
* <ul>
* <li>The server returns just T (the body), not ApiResponse&lt;T&gt;</li>
* <li>ApiResponse wraps the body with HTTP status code and headers</li>
* </ul>
*
* This decoder:
* <ol>
* <li>Detects if the return type is ApiResponse&lt;T&gt;</li>
* <li>Extracts the inner type T and delegates body decoding to the underlying decoder</li>
* <li>Wraps the decoded body with status code and headers into ApiResponse&lt;T&gt;</li>
* </ol>
*/
public final class ApiResponseDecoder implements Decoder {
private final Decoder delegate;
public ApiResponseDecoder(Decoder delegate) {
this.delegate = delegate;
}
@Override
public Object decode(Response response, Type type) throws IOException {
if (isApiResponseType(type)) {
Type innerType = getApiResponseInnerType(type);
Object body = delegate.decode(response, innerType);
return new ApiResponse<>(response.status(), response.headers(), body);
}
return delegate.decode(response, type);
}
private boolean isApiResponseType(Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
Type rawType = paramType.getRawType();
return rawType == ApiResponse.class;
}
return type == ApiResponse.class;
}
private Type getApiResponseInnerType(Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
Type[] typeArgs = paramType.getActualTypeArguments();
if (typeArgs.length > 0) {
return typeArgs[0];
}
}
return Object.class;
}
}
@@ -18,13 +18,9 @@
*/
package org.apache.fineract.client.feign.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.FeignException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.feign.FeignException;
/**
* Exception thrown by {@link FeignCalls} utility when Feign calls fail.
@@ -49,7 +45,7 @@ public class CallFailedRuntimeException extends RuntimeException {
sb.append(", request=").append(e.request().url());
}
String contentString = e.contentUTF8();
String contentString = e.responseBodyAsString();
if (contentString != null && !contentString.isEmpty()) {
sb.append(", errorBody=").append(contentString);
}
@@ -58,34 +54,9 @@ public class CallFailedRuntimeException extends RuntimeException {
}
private static String extractDeveloperMessage(FeignException e) {
try {
byte[] content = e.content();
if (content == null || content.length == 0) {
return e.getMessage();
}
String contentString = new String(content, StandardCharsets.UTF_8);
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(contentString);
if (root.has("developerMessage")) {
return root.get("developerMessage").asText();
}
if (root.has("errors")) {
JsonNode errors = root.get("errors");
if (errors.isArray() && errors.size() > 0) {
JsonNode firstError = errors.get(0);
if (firstError.has("developerMessage")) {
return firstError.get("developerMessage").asText();
}
}
}
return contentString;
} catch (IOException ex) {
log.warn("Failed to extract developer message from error response", ex);
return e.getMessage();
if (e.getDeveloperMessage() != null) {
return e.getDeveloperMessage();
}
return e.getMessage();
}
}
@@ -18,8 +18,8 @@
*/
package org.apache.fineract.client.feign.util;
import feign.FeignException;
import java.util.function.Supplier;
import org.apache.fineract.client.feign.FeignException;
/**
* Extension methods for Feign calls. This class is recommended to be statically imported.
@@ -77,4 +77,41 @@ public final class FeignCalls {
public static <T> T execute(Supplier<T> feignCall) throws FeignException {
return feignCall.get();
}
/**
* Execute a Feign call expecting failure (for negative tests). Returns the exception with error details.
*
* @param feignCall
* the Feign call to execute
* @return CallFailedRuntimeException containing status code and error message
* @throws AssertionError
* if the call succeeds when failure was expected
*/
public static CallFailedRuntimeException fail(Supplier<?> feignCall) {
try {
Object result = feignCall.get();
throw new AssertionError("Expected call to fail, but it succeeded with result: " + result);
} catch (FeignException e) {
return new CallFailedRuntimeException(e);
}
}
/**
* Execute a Feign call expecting failure with void return (for negative tests). Returns the exception with error
* details.
*
* @param feignCall
* the Feign call to execute
* @return CallFailedRuntimeException containing status code and error message
* @throws AssertionError
* if the call succeeds when failure was expected
*/
public static CallFailedRuntimeException failVoid(Runnable feignCall) {
try {
feignCall.run();
throw new AssertionError("Expected call to fail, but it succeeded");
} catch (FeignException e) {
return new CallFailedRuntimeException(e);
}
}
}
@@ -1,47 +1,326 @@
package {{package}};
import feign.*;
import feign.Param;
import feign.RequestLine;
{{#legacyDates}}
import {{invokerPackage}}.ParamExpander;
{{/legacyDates}}
import {{modelPackage}}.ApiResponse;
{{#imports}}import {{import}};
{{/imports}}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
{{#useBeanValidation}}
import {{javaxPackage}}.validation.constraints.*;
import {{javaxPackage}}.validation.Valid;
{{/useBeanValidation}}
import feign.*;
{{>generatedAnnotation}}
public interface {{classname}} {
{{#operations}}
{{#operation}}
/**
* {{summary}}
* {{notes}}
{{#allParams}}
* @param {{paramName}} {{description}}
{{/allParams}}
* @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}
*/
@RequestLine("{{httpMethod}} {{path}}")
{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{operationId}}(
{{#pathParams}}
@Param("{{baseName}}") {{{dataType}}} {{paramName}}{{^-last}},{{/-last}}
{{/pathParams}}
{{#bodyParam}}
{{#hasPathParams}},{{/hasPathParams}}{{{dataType}}} {{paramName}}
{{/bodyParam}}
{{#hasQueryParams}}
{{#hasPathParams}}{{^bodyParam}},{{/bodyParam}}{{/hasPathParams}}{{#bodyParam}},{{/bodyParam}}
@QueryMap Map<String, Object> queryParams
{{/hasQueryParams}}
{{#hasHeaderParams}}
{{#hasPathParams}}{{^bodyParam}}{{^hasQueryParams}},{{/hasQueryParams}}{{/bodyParam}}{{/hasPathParams}}
{{#bodyParam}}{{^hasQueryParams}},{{/hasQueryParams}}{{/bodyParam}}
{{#hasQueryParams}},{{/hasQueryParams}}
@HeaderMap Map<String, Object> headerParams
{{/hasHeaderParams}}
);
{{#operations}}{{#operation}}
/**
* {{summary}}
* {{notes}}
{{#allParams}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/allParams}}
{{#returnType}}
* @return {{.}}
{{/returnType}}
{{#externalDocs}}
* {{description}}
* @see <a href="{{url}}">{{summary}} Documentation</a>
{{/externalDocs}}
{{#isDeprecated}}
* @deprecated
{{/isDeprecated}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
@RequestLine("{{httpMethod}} {{{path}}}{{#hasQueryParams}}?{{/hasQueryParams}}{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}")
@Headers({
{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}",
{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}}
"{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}},
{{/-last}}{{/headerParams}}
})
{{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/allParams}}@HeaderMap Map<String, String> headers);
{{/operation}}
/**
* {{summary}}
* {{notes}}
* Convenience method with empty headers.
{{#allParams}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/allParams}}
{{#returnType}}
* @return {{.}}
{{/returnType}}
{{#isDeprecated}}
* @deprecated
{{/isDeprecated}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
default {{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{nickname}}({{#allParams}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) {
{{#returnType}}return {{/returnType}}{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}Map.of());
}
/**
* {{summary}}
* Similar to <code>{{operationId}}</code> but it also returns the http response headers .
* {{notes}}
{{#allParams}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/allParams}}
{{#returnType}}
* @return A ApiResponse that wraps the response boyd and the http headers.
{{/returnType}}
{{#externalDocs}}
* {{description}}
* @see <a href="{{url}}">{{summary}} Documentation</a>
{{/externalDocs}}
{{#isDeprecated}}
* @deprecated
{{/isDeprecated}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
@RequestLine("{{httpMethod}} {{{path}}}{{#hasQueryParams}}?{{/hasQueryParams}}{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}")
@Headers({
{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}",
{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}}
"{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}},
{{/-last}}{{/headerParams}}
})
ApiResponse<{{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{nickname}}WithHttpInfo({{#allParams}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/allParams}}@HeaderMap Map<String, String> headers);
{{#hasQueryParams}}
/**
* {{summary}}
* {{notes}}
* Note, this is equivalent to the other <code>{{operationId}}</code> method,
* but with the query parameters collected into a single Map parameter. This
* is convenient for services with optional query parameters, especially when
* used with the {@link {{operationIdCamelCase}}QueryParams} class that allows for
* building up this map in a fluent style.
{{#allParams}}
{{^isQueryParam}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/isQueryParam}}
{{/allParams}}
* @param queryParams Map of query parameters as name-value pairs
* <p>The following elements may be specified in the query map:</p>
* <ul>
{{#queryParams}}
* <li>{{paramName}} - {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}</li>
{{/queryParams}}
* </ul>
{{#returnType}}
* @return {{.}}
{{/returnType}}
{{#externalDocs}}
* {{description}}
* @see <a href="{{url}}">{{summary}} Documentation</a>
{{/externalDocs}}
{{#isDeprecated}}
* @deprecated
{{/isDeprecated}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
@RequestLine("{{httpMethod}} {{{path}}}?{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}")
@Headers({
{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}",
{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}}
"{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}},
{{/-last}}{{/headerParams}}
})
{{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) {{operationIdCamelCase}}QueryParams queryParams, @HeaderMap Map<String, String> headers);
/**
* {{summary}}
* {{notes}}
* Convenience method that uses typed QueryParams and empty headers.
{{#allParams}}
{{^isQueryParam}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/isQueryParam}}
{{/allParams}}
* @param queryParams Query parameters as typed object
{{#returnType}}
* @return {{.}}
{{/returnType}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
default {{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{nickname}}({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) {{operationIdCamelCase}}QueryParams queryParams) {
{{#returnType}}return {{/returnType}}{{nickname}}({{#allParams}}{{^isQueryParam}}{{paramName}}, {{/isQueryParam}}{{/allParams}}queryParams, Map.of());
}
/**
* {{summary}}
* {{notes}}
* Convenience method that accepts generic query parameters map.
{{#allParams}}
{{^isQueryParam}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/isQueryParam}}
{{/allParams}}
* @param queryParams Query parameters as generic map
* @param headers Custom headers to include in the request
{{#returnType}}
* @return {{.}}
{{/returnType}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
@RequestLine("{{httpMethod}} {{{path}}}?{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}")
@Headers({
{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}",
{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}}
"{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}},
{{/-last}}{{/headerParams}}
})
{{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) Map<String, Object> queryParams, @HeaderMap Map<String, String> headers);
/**
* {{summary}}
* {{notes}}
* Convenience method that accepts generic query parameters map with empty headers.
{{#allParams}}
{{^isQueryParam}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/isQueryParam}}
{{/allParams}}
* @param queryParams Query parameters as generic map
{{#returnType}}
* @return {{.}}
{{/returnType}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
default {{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{nickname}}({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) Map<String, Object> queryParams) {
{{#returnType}}return {{/returnType}}{{nickname}}({{#allParams}}{{^isQueryParam}}{{paramName}}, {{/isQueryParam}}{{/allParams}}queryParams, Map.of());
}
/**
* {{summary}}
* {{notes}}
* Note, this is equivalent to the other <code>{{operationId}}</code> that receives the query parameters as a map,
* but this one also exposes the Http response headers
{{#allParams}}
{{^isQueryParam}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/isQueryParam}}
{{/allParams}}
* @param queryParams Map of query parameters as name-value pairs
* <p>The following elements may be specified in the query map:</p>
* <ul>
{{#queryParams}}
* <li>{{paramName}} - {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}</li>
{{/queryParams}}
* </ul>
{{#returnType}}
* @return {{.}}
{{/returnType}}
{{#externalDocs}}
* {{description}}
* @see <a href="{{url}}">{{summary}} Documentation</a>
{{/externalDocs}}
{{#isDeprecated}}
* @deprecated
{{/isDeprecated}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
@RequestLine("{{httpMethod}} {{{path}}}?{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{^-last}}&{{/-last}}{{/queryParams}}")
@Headers({
{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}",
{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}}
"{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}},
{{/-last}}{{/headerParams}}
})
ApiResponse<{{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{nickname}}WithHttpInfo({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) {{operationIdCamelCase}}QueryParams queryParams, @HeaderMap Map<String, String> headers);
/**
* A convenience class for generating query parameters for the
* <code>{{operationId}}</code> method in a fluent style.
*/
public static class {{operationIdCamelCase}}QueryParams extends HashMap<String, Object> {
{{#queryParams}}
public {{operationIdCamelCase}}QueryParams {{paramName}}(final {{{dataType}}} value) {
put("{{baseName}}", value);
return this;
}
{{/queryParams}}
}
{{/hasQueryParams}}
/**
* {{summary}}
* {{notes}}
* Universal method that accepts generic query parameters map with headers.
* Useful for passing undocumented or optional query parameters.
{{#allParams}}
{{^isQueryParam}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/isQueryParam}}
{{/allParams}}
* @param queryParams Query parameters as generic map (e.g., template=true, associations=all)
* @param headers Custom headers to include in the request
{{#returnType}}
* @return {{.}}
{{/returnType}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
@RequestLine("{{httpMethod}} {{{path}}}")
@Headers({
{{#vendorExtensions.x-content-type}} "Content-Type: {{{vendorExtensions.x-content-type}}}",
{{/vendorExtensions.x-content-type}} "Accept: {{#vendorExtensions.x-accepts}}{{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-accepts}}",{{#headerParams}}
"{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{^-last}},
{{/-last}}{{/headerParams}}
})
{{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}Universal({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) Map<String, Object> queryParams, @HeaderMap Map<String, String> headers);
/**
* {{summary}}
* {{notes}}
* Universal method that accepts generic query parameters map with empty headers.
{{#allParams}}
{{^isQueryParam}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
{{/isQueryParam}}
{{/allParams}}
* @param queryParams Query parameters as generic map
{{#returnType}}
* @return {{.}}
{{/returnType}}
*/
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
default {{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{nickname}}Universal({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^isFormParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isFormParam}}{{#isFormParam}}@Param("{{baseName}}") {{/isFormParam}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) Map<String, Object> queryParams) {
{{#returnType}}return {{/returnType}}{{nickname}}Universal({{#allParams}}{{^isQueryParam}}{{paramName}}, {{/isQueryParam}}{{/allParams}}queryParams, Map.of());
}
{{/operation}}
{{/operations}}
}