Skip to content

Commit

Permalink
feat: adds the ability to pass options to default interceptors
Browse files Browse the repository at this point in the history
* Fix default handler overide , when user passed in interceptor should overide any default implementations

* fix code based on comments

* Update components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java

Co-authored-by: Vincent Biret <vincentbiret@hotmail.com>

* Update components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java

Co-authored-by: Vincent Biret <vincentbiret@hotmail.com>

* Update components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java

Co-authored-by: Vincent Biret <vincentbiret@hotmail.com>

* fix comments

* chore: linting

* fix: makes options non nullable

Signed-off-by: Vincent Biret <vibiret@microsoft.com>

* chore: linting

Signed-off-by: Vincent Biret <vibiret@microsoft.com>

* chore: linting

Signed-off-by: Vincent Biret <vibiret@microsoft.com>

* fix: adds missing doc comment
fix: deprecates extraneous method

Signed-off-by: Vincent Biret <vibiret@microsoft.com>

* fix: misaliagments between methods API surface

* feat: adds create method for options

Signed-off-by: Vincent Biret <vibiret@microsoft.com>

* fix: aligns API surface parameter types

Signed-off-by: Vincent Biret <vibiret@microsoft.com>

* fix; interceptors order

Signed-off-by: Vincent Biret <vibiret@microsoft.com>

---------

Signed-off-by: Vincent Biret <vibiret@microsoft.com>
Co-authored-by: Raghu Sammeta <rsammeta@proofpoint.com>
Co-authored-by: Vincent Biret <vincentbiret@hotmail.com>
Co-authored-by: Vincent Biret <vibiret@microsoft.com>
  • Loading branch information
4 people authored Oct 11, 2024
1 parent 731286c commit d1c97c1
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.microsoft.kiota.http;

import com.microsoft.kiota.RequestOption;
import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider;
import com.microsoft.kiota.http.middleware.AuthorizationHandler;
import com.microsoft.kiota.http.middleware.HeadersInspectionHandler;
import com.microsoft.kiota.http.middleware.ParametersNameDecodingHandler;
import com.microsoft.kiota.http.middleware.RedirectHandler;
import com.microsoft.kiota.http.middleware.RetryHandler;
import com.microsoft.kiota.http.middleware.UrlReplaceHandler;
import com.microsoft.kiota.http.middleware.UserAgentHandler;
import com.microsoft.kiota.http.middleware.options.HeadersInspectionOption;
import com.microsoft.kiota.http.middleware.options.ParametersNameDecodingOption;
import com.microsoft.kiota.http.middleware.options.RedirectHandlerOption;
import com.microsoft.kiota.http.middleware.options.RetryHandlerOption;
import com.microsoft.kiota.http.middleware.options.UrlReplaceHandlerOption;
import com.microsoft.kiota.http.middleware.options.UserAgentHandlerOption;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
Expand All @@ -18,6 +25,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/** This class is used to build the HttpClient instance used by the core service. */
public class KiotaClientFactory {
Expand All @@ -31,12 +39,23 @@ private KiotaClientFactory() {}
return create(createDefaultInterceptors());
}

/**
* Creates an OkHttpClient Builder with the default configuration and middleware options.
* @param requestOptions The request options to use for the interceptors.
* @return an OkHttpClient Builder instance.
*/
@Nonnull public static OkHttpClient.Builder create(@Nonnull final RequestOption[] requestOptions) {
Objects.requireNonNull(requestOptions, "parameter requestOptions cannot be null");
return create(createDefaultInterceptors(requestOptions));
}

/**
* Creates an OkHttpClient Builder with the default configuration and middleware.
* @param interceptors The interceptors to add to the client. Will default to createDefaultInterceptors() if null.
* @return an OkHttpClient Builder instance.
*/
@Nonnull public static OkHttpClient.Builder create(@Nullable final Interceptor[] interceptors) {
@Nonnull public static OkHttpClient.Builder create(@Nonnull final Interceptor[] interceptors) {
Objects.requireNonNull(interceptors, "parameter interceptors cannot be null");
final OkHttpClient.Builder builder =
new OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(100))
Expand All @@ -45,9 +64,7 @@ private KiotaClientFactory() {}
Duration.ofSeconds(
100)); // TODO configure the default client options.

final Interceptor[] interceptorsOrDefault =
interceptors != null ? interceptors : createDefaultInterceptors();
for (final Interceptor interceptor : interceptorsOrDefault) {
for (final Interceptor interceptor : interceptors) {
builder.addInterceptor(interceptor);
}
return builder;
Expand All @@ -58,12 +75,9 @@ private KiotaClientFactory() {}
* @param interceptors The interceptors to add to the client. Will default to createDefaultInterceptors() if null.
* @return an OkHttpClient Builder instance.
*/
@Nonnull public static OkHttpClient.Builder create(@Nullable final List<Interceptor> interceptors) {
if (interceptors == null) {
return create();
}
return create(
(new ArrayList<>(interceptors)).toArray(new Interceptor[interceptors.size()]));
@Nonnull public static OkHttpClient.Builder create(@Nonnull final List<Interceptor> interceptors) {
Objects.requireNonNull(interceptors, "parameter interceptors cannot be null");
return create((new ArrayList<>(interceptors)).toArray(new Interceptor[0]));
}

/**
Expand All @@ -73,7 +87,8 @@ private KiotaClientFactory() {}
*/
@Nonnull public static OkHttpClient.Builder create(
@Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) {
ArrayList<Interceptor> interceptors = new ArrayList<>(createDefaultInterceptorsAsList());
ArrayList<Interceptor> interceptors =
new ArrayList<>(Arrays.asList(createDefaultInterceptors()));
interceptors.add(new AuthorizationHandler(authenticationProvider));
return create(interceptors);
}
Expand All @@ -83,19 +98,81 @@ private KiotaClientFactory() {}
* @return an array of interceptors.
*/
@Nonnull public static Interceptor[] createDefaultInterceptors() {
return new Interceptor[] {
new RedirectHandler(),
new RetryHandler(),
new ParametersNameDecodingHandler(),
new UserAgentHandler(),
new HeadersInspectionHandler()
};
return createDefaultInterceptors(new RequestOption[0]);
}

/**
* Creates the default interceptors for the client.
* @param requestOptions The request options to use for the interceptors.
* @return an array of interceptors.
*/
@Nonnull public static Interceptor[] createDefaultInterceptors(
@Nonnull final RequestOption[] requestOptions) {
Objects.requireNonNull(requestOptions, "parameter requestOptions cannot be null");

UrlReplaceHandlerOption uriReplacementOption = null;
UserAgentHandlerOption userAgentHandlerOption = null;
RetryHandlerOption retryHandlerOption = null;
RedirectHandlerOption redirectHandlerOption = null;
ParametersNameDecodingOption parametersNameDecodingOption = null;
HeadersInspectionOption headersInspectionHandlerOption = null;

for (final RequestOption option : requestOptions) {
if (uriReplacementOption == null && option instanceof UrlReplaceHandlerOption) {
uriReplacementOption = (UrlReplaceHandlerOption) option;
} else if (retryHandlerOption == null && option instanceof RetryHandlerOption) {
retryHandlerOption = (RetryHandlerOption) option;
} else if (redirectHandlerOption == null && option instanceof RedirectHandlerOption) {
redirectHandlerOption = (RedirectHandlerOption) option;
} else if (parametersNameDecodingOption == null
&& option instanceof ParametersNameDecodingOption) {
parametersNameDecodingOption = (ParametersNameDecodingOption) option;
} else if (userAgentHandlerOption == null && option instanceof UserAgentHandlerOption) {
userAgentHandlerOption = (UserAgentHandlerOption) option;
} else if (headersInspectionHandlerOption == null
&& option instanceof HeadersInspectionOption) {
headersInspectionHandlerOption = (HeadersInspectionOption) option;
}
}

final List<Interceptor> handlers = new ArrayList<>();
// orders matter as they are executed in a chain
// interceptors that only modify the request should be added first
// interceptors that read the response should be added last
handlers.add(
userAgentHandlerOption != null
? new UserAgentHandler(userAgentHandlerOption)
: new UserAgentHandler());
handlers.add(
parametersNameDecodingOption != null
? new ParametersNameDecodingHandler(parametersNameDecodingOption)
: new ParametersNameDecodingHandler());
handlers.add(
uriReplacementOption != null
? new UrlReplaceHandler(uriReplacementOption)
: new UrlReplaceHandler());
handlers.add(
headersInspectionHandlerOption != null
? new HeadersInspectionHandler(headersInspectionHandlerOption)
: new HeadersInspectionHandler());
handlers.add(
redirectHandlerOption != null
? new RedirectHandler(redirectHandlerOption)
: new RedirectHandler());
handlers.add(
retryHandlerOption != null
? new RetryHandler(retryHandlerOption)
: new RetryHandler());

return handlers.toArray(new Interceptor[0]);
}

/**
* Creates the default interceptors for the client.
* @return an array of interceptors.
* @deprecated Use {@link #createDefaultInterceptors()} instead.
*/
@Deprecated
@Nonnull public static List<Interceptor> createDefaultInterceptorsAsList() {
return new ArrayList<>(Arrays.asList(createDefaultInterceptors()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.microsoft.kiota.http;

import static org.junit.jupiter.api.Assertions.*;

import com.microsoft.kiota.RequestOption;
import com.microsoft.kiota.http.middleware.ChaosHandler;
import com.microsoft.kiota.http.middleware.HeadersInspectionHandler;
import com.microsoft.kiota.http.middleware.ParametersNameDecodingHandler;
import com.microsoft.kiota.http.middleware.RedirectHandler;
import com.microsoft.kiota.http.middleware.RetryHandler;
import com.microsoft.kiota.http.middleware.UrlReplaceHandler;
import com.microsoft.kiota.http.middleware.UserAgentHandler;
import com.microsoft.kiota.http.middleware.options.RetryHandlerOption;
import com.microsoft.kiota.http.middleware.options.UrlReplaceHandlerOption;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class KiotaClientFactoryTest {

@Test
void testCreatesDefaultInterceptors() throws IOException {
OkHttpClient client = KiotaClientFactory.create().build();
assertNotNull(client.interceptors());
assertEquals(6, client.interceptors().size());
}

@Test
void testDefaultInterceptorsWhenPassedIn() throws IOException {
OkHttpClient client =
KiotaClientFactory.create(
new Interceptor[] {getDisabledRetryHandler(), new ChaosHandler()})
.build();
List<Interceptor> interceptors = client.interceptors();
assertNotNull(interceptors);
assertEquals(2, interceptors.size());
for (Interceptor interceptor : interceptors) {
if (interceptor instanceof RetryHandler) {
RetryHandlerOption handlerOption = ((RetryHandler) interceptor).getRetryOptions();
assertEquals(0, handlerOption.delay());
assertEquals(0, handlerOption.maxRetries());
}

assertTrue(
interceptor instanceof RetryHandler || interceptor instanceof ChaosHandler,
"Array should contain instances of RetryHandler and ChaosHandler");
}
}

@Test
void testDefaultInterceptorsWhenRequestOptionsPassedIn() throws IOException {
RetryHandlerOption retryHandlerOption =
new RetryHandlerOption((delay, executionCount, request, response) -> false, 0, 0);
UrlReplaceHandlerOption urlReplaceHandlerOption =
new UrlReplaceHandlerOption(new HashMap<>(), false);

final ArrayList<RequestOption> options = new ArrayList<>();
options.add(urlReplaceHandlerOption);
options.add(retryHandlerOption);

Interceptor[] interceptors =
KiotaClientFactory.createDefaultInterceptors(options.toArray(new RequestOption[0]));
OkHttpClient client = KiotaClientFactory.create(interceptors).build();
List<Interceptor> clientInterceptors = client.interceptors();
assertNotNull(interceptors);
assertEquals(6, clientInterceptors.size());
for (Interceptor interceptor : clientInterceptors) {
if (interceptor instanceof RetryHandler) {
RetryHandlerOption handlerOption = ((RetryHandler) interceptor).getRetryOptions();
assertEquals(0, handlerOption.delay());
assertEquals(0, handlerOption.maxRetries());
}

if (interceptor instanceof UrlReplaceHandler) {
UrlReplaceHandlerOption handlerOption =
((UrlReplaceHandler) interceptor).getUrlReplaceHandlerOption();
assertTrue(handlerOption.getReplacementPairs().isEmpty());
assertFalse(handlerOption.isEnabled());
}

assertTrue(
interceptor instanceof UrlReplaceHandler
|| interceptor instanceof RedirectHandler
|| interceptor instanceof RetryHandler
|| interceptor instanceof ParametersNameDecodingHandler
|| interceptor instanceof UserAgentHandler
|| interceptor instanceof HeadersInspectionHandler
|| interceptor instanceof ChaosHandler,
"Array should contain instances of"
+ " UrlReplaceHandler,RedirectHandler,RetryHandler,ParametersNameDecodingHandler,UserAgentHandler,"
+ " HeadersInspectionHandler, and ChaosHandler");
}
}

private static RetryHandler getDisabledRetryHandler() {
RetryHandlerOption retryHandlerOption =
new RetryHandlerOption((delay, executionCount, request, response) -> false, 0, 0);
RetryHandler retryHandler = new RetryHandler(retryHandlerOption);
return retryHandler;
}
}

0 comments on commit d1c97c1

Please sign in to comment.