Skip to content
Open
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
25 changes: 25 additions & 0 deletions datamodel/openapi/openapi-api-apache-sample/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,31 @@
<artifactId>wiremock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
Expand All @@ -75,6 +95,11 @@
<artifactId>jackson-datatype-jsr310</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.sap.cloud.sdk.services.openapi.apache;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.util.function.UnaryOperator;

import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.junit.jupiter.api.Test;

import com.sap.cloud.sdk.datamodel.openapi.apache.petstore.api.DefaultApi;
import com.sap.cloud.sdk.services.openapi.apache.apiclient.ApiClient;

import io.vavr.control.Try;
import lombok.SneakyThrows;

public class ApiClientWithRequestCustomizerTest
{
@Test
@SneakyThrows
void testRequestCustomizer()
{
final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);

// Use the withRequestCustomizer API to add a custom header
final UnaryOperator<ClassicRequestBuilder> customizer =
builder -> builder.setPath("/custom-path").addHeader("X-Custom", "custom");

final var apiClient = ApiClient.fromHttpClient(httpClient).withRequestCustomizer(customizer);
new DefaultApi(apiClient).findPets();

verify(httpClient).execute(argThat(req -> Try.run(() -> {
assertThat(req.getRequestUri()).isEqualTo("/custom-path");
assertThat(req.getMethod()).isEqualTo("GET");
assertThat(req.getHeaders()).extracting(Header::getName).containsExactlyInAnyOrder("Accept", "X-Custom");
assertThat(req.getFirstHeader("X-Custom").getValue()).isEqualTo("custom");
assertThat(req.getFirstHeader("Accept").getValue()).isEqualTo("application/json");
}).isSuccess()), any(HttpContext.class), any());
}

@Test
@SneakyThrows
void testRequestCustomizerWithEntity()
{
final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);

final var apiClient = ApiClient.fromHttpClient(httpClient).withRequestCustomizer(b -> b.setEntity("foo"));
new DefaultApi(apiClient).findPets();

verify(httpClient).execute(argThat(req -> Try.run(() -> {
assertThat(EntityUtils.toString(req.getEntity())).isEqualTo("foo");
}).isSuccess()), any(HttpContext.class), any());
}

@Test
@SneakyThrows
void testBasicApiCall()
{
final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);

final var apiClient = ApiClient.fromHttpClient(httpClient);
new DefaultApi(apiClient).findPets();

verify(httpClient).execute(argThat(req -> Try.run(() -> {
assertThat(req.getRequestUri()).isEqualTo("/pets");
assertThat(req.getMethod()).isEqualTo("GET");
assertThat(req.getFirstHeader("Accept").getValue()).isEqualTo("application/json");
}).isSuccess()), any(HttpContext.class), any());
}

@Test
@SneakyThrows
void testWithBasePath()
{
final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);

final var apiClient = ApiClient.fromHttpClient(httpClient).withBasePath("https://api.example.com");
new DefaultApi(apiClient).findPets();

verify(httpClient).execute(argThat(req -> Try.run(() -> {
assertThat(req.getUri().toString()).isEqualTo("https://api.example.com/pets");
}).isSuccess()), any(HttpContext.class), any());
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
/*
* Prompt Registry API
* Prompt Storage service for Design time & Runtime prompt templates.
*
* The version of the OpenAPI document: 0.0.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package com.sap.cloud.sdk.services.openapi.apache.apiclient;

import static com.sap.cloud.sdk.services.openapi.apache.apiclient.DefaultApiResponseHandler.isJsonMime;
Expand All @@ -30,6 +18,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.zip.GZIPOutputStream;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -98,13 +87,28 @@ public class ApiClient
private final String tempFolderPath;

@With( onMethod_ = @Beta )
@Nullable
@Nonnull
private final OpenApiResponseListener openApiResponseListener;

// Methods that can have a request body
private static final Set<Method> BODY_METHODS = Set.of(Method.POST, Method.PUT, Method.PATCH, Method.DELETE);

// At runtime "localhost" will be replaced as basepath by the Destination's URI.
private static final String DEFAULT_BASE_PATH = "http://localhost";

/**
* Http request customizer that can be used to manipulate OpenAPI requests before invocation.
*
* @since 5.28.0
*/
@With( onMethod_ = @Beta )
@Nonnull
private final UnaryOperator<ClassicRequestBuilder> requestCustomizer;

private static final OpenApiResponseListener EMPTY_RESPONSE_LISTENER = r -> {
};
private static final UnaryOperator<ClassicRequestBuilder> EMPTY_REQUEST_CUSTOMIZER = r -> r;

/**
* Creates an ApiClient instance from an existing HttpClient.
*
Expand All @@ -115,7 +119,13 @@ public class ApiClient
@Nonnull
public static ApiClient fromHttpClient( @Nonnull final CloseableHttpClient httpClient )
{
return new ApiClient(httpClient, DEFAULT_BASE_PATH, createDefaultObjectMapper(), null, null);
return new ApiClient(
httpClient,
DEFAULT_BASE_PATH,
createDefaultObjectMapper(),
null,
EMPTY_RESPONSE_LISTENER,
EMPTY_REQUEST_CUSTOMIZER);
}

/**
Expand All @@ -130,7 +140,6 @@ public static ApiClient create( @Nonnull final Destination destination )
{
return fromHttpClient((CloseableHttpClient) ApacheHttpClient5Accessor.getHttpClient(destination))
.withBasePath(destination.asHttp().getUri().toString());

}

/**
Expand Down Expand Up @@ -184,7 +193,7 @@ public static String parameterToString( @Nullable final Object param )
{
if( param == null ) {
return "";
} else if( param instanceof Date date ) {
} else if( param instanceof final Date date ) {
return formatDate(date);
} else if( param instanceof Collection ) {
final StringBuilder b = new StringBuilder();
Expand Down Expand Up @@ -469,7 +478,66 @@ public <T> T invokeAPI(
@Nullable final String accept,
@Nonnull final String contentType,
@Nonnull final TypeReference<T> returnType )
throws OpenApiRequestException
{
final ClassicRequestBuilder builder =
buildClassicRequest(
path,
method,
queryParams,
collectionQueryParams,
urlQueryDeepObject,
body,
headerParams,
formParams,
accept,
contentType);

return invokeAPI(builder, returnType);
}

/**
* Invoke API by sending HTTP request with the given request builder.
*
* @param <T>
* Type
* @param requestBuilder
* The request builder with all parameters configured
* @param resultType
* Return type
* @return The response body
* @throws OpenApiRequestException
* API exception
*/
@Nonnull
protected <T> T invokeAPI(
@Nonnull final ClassicRequestBuilder requestBuilder,
@Nonnull final TypeReference<T> resultType )
{
final ClassicRequestBuilder finalBuilder = requestCustomizer.apply(requestBuilder);

final HttpClientContext context = HttpClientContext.create();
try {
final HttpClientResponseHandler<T> responseHandler =
new DefaultApiResponseHandler<>(objectMapper, tempFolderPath, resultType, openApiResponseListener);
return httpClient.execute(finalBuilder.build(), context, responseHandler);
}
catch( final IOException e ) {
throw new OpenApiRequestException(e);
}
}

@Nonnull
private ClassicRequestBuilder buildClassicRequest(
@Nonnull final String path,
@Nonnull final String method,
@Nullable final List<Pair> queryParams,
@Nullable final List<Pair> collectionQueryParams,
@Nullable final String urlQueryDeepObject,
@Nullable final Object body,
@Nonnull final Map<String, String> headerParams,
@Nonnull final Map<String, Object> formParams,
@Nullable final String accept,
@Nonnull final String contentType )
{
if( body != null && !formParams.isEmpty() ) {
throw new OpenApiRequestException("Cannot have body and form params");
Expand All @@ -486,9 +554,6 @@ public <T> T invokeAPI(
for( final Entry<String, String> keyValue : headerParams.entrySet() ) {
builder.addHeader(keyValue.getKey(), keyValue.getValue());
}

final HttpClientContext context = HttpClientContext.create();

final ContentType contentTypeObj = getContentType(contentType);
if( body != null || !formParams.isEmpty() ) {
if( isBodyAllowed(Method.valueOf(method)) ) {
Expand All @@ -501,15 +566,7 @@ public <T> T invokeAPI(
// for empty body
builder.setEntity(new StringEntity("", contentTypeObj));
}

try {
final HttpClientResponseHandler<T> responseHandler =
new DefaultApiResponseHandler<>(objectMapper, tempFolderPath, returnType, openApiResponseListener);
return httpClient.execute(builder.build(), context, responseHandler);
}
catch( IOException e ) {
throw new OpenApiRequestException(e);
}
return builder;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ class DefaultApiResponseHandler<T> implements HttpClientResponseHandler<T>
@Nonnull
private final TypeReference<T> returnType;

/** Optional listener for OpenAPI response including status code and headers */
@Nullable
/** Listener for OpenAPI response including status code and headers */
@Nonnull
private final OpenApiResponseListener openApiResponseListener;

@Nullable
Expand Down Expand Up @@ -109,9 +109,7 @@ private T processResponse( @Nonnull final ClassicHttpResponse response )
{
final int statusCode = response.getCode();
final Map<String, List<String>> headers = transformResponseHeaders(response.getHeaders());
if( openApiResponseListener != null ) {
openApiResponseListener.onResponse(new OpenApiResponse(statusCode, headers));
}
openApiResponseListener.onResponse(new OpenApiResponse(statusCode, headers));

if( statusCode == HttpStatus.SC_NO_CONTENT ) {
if( returnType.getType().equals(OpenApiResponse.class) ) {
Expand Down
Loading