Skip to content

Commit

Permalink
Api Exception handling
Browse files Browse the repository at this point in the history
Changelog and Version
  • Loading branch information
ramsessanchez committed Nov 9, 2023
1 parent 877153c commit cbe580b
Show file tree
Hide file tree
Showing 12 changed files with 44 additions and 53 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

Changed OkHttpRequestAdapter dependency from OkHttpClient to Call.Factory (parent interface implemented by OkHttpClient).
## [1.0.0] - 2023-11-10

### Changed

- Kiota-Java has moved away from Async/Completable futures, thus Async components are no longer utilized and have been removed. Furthermore, requestAdapter methods no longer use the async suffix. [#175](https://github.com/microsoft/kiota-java/issues/175)
- ApiException class now extends RuntimeException instead of Exception.
- Changed OkHttpRequestAdapter dependency from OkHttpClient to Call.Factory (parent interface implemented by OkHttpClient).

## [0.8.0] - 2023-10-31

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import jakarta.annotation.Nonnull;

/** Parent type for exceptions thrown by the client when receiving failed responses to its requests. */
public class ApiException extends Exception {
public class ApiException extends RuntimeException {
/** {@inheritDoc} */
public ApiException() {
super();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public ApiExceptionBuilder(@Nonnull final Supplier<Parsable> builder) {
value = (ApiException) error;
} else {
value = new ApiExceptionBuilder()
.withMessage("\"unexpected error type \" + error.getClass().getName()")
.withMessage("\"unexpected error type \"" + error.getClass().getName())
.build();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

/** Base class for request configuration */
public abstract class BaseRequestConfiguration {
/**
* Default constructor
*/
public BaseRequestConfiguration() {
// default empty constructor
}
/** Request headers */
@Nullable
public RequestHeaders headers = new RequestHeaders();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
* A map that is case-insensitive on the keys
*/
public class CaseInsensitiveMap implements Map<String, Set<String>>{
/**
* Default constructor
*/
public CaseInsensitiveMap() {
// default constructor
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
* Represents a multipart body for a request or a response.
*/
public class MultipartBody implements Parsable {
/**
* Creates a new instance of the MultipartBody class.
*/
public MultipartBody() {
// default empty constructor
}
@Nonnull
private final String boundary = UUID.randomUUID().toString().replace("-", "");
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
* The {@code ResponseHandler} implementation to handle native response objects
*/
public class NativeResponseHandler implements ResponseHandler {
/**
* Default constructor
*/
public NativeResponseHandler() {
// default empty constructor
}
private Object value;
private HashMap<String, ParsableFactory<? extends Parsable>> errorMappings;
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public interface ResponseHandler {
* @param errorMappings the error mappings for the response to use when deserializing failed responses bodies. Where an error code like 401 applies specifically to that status code, a class code like 4XX applies to all status codes within the range if an the specific error code is not present.
* @param <NativeResponseType> The type of the native response object.
* @param <ModelType> The type of the response model object.
* @return The deserialized response model object.
*/
@Nullable
<NativeResponseType, ModelType> ModelType handleResponse(@Nonnull final NativeResponseType response, @Nullable final HashMap<String, ParsableFactory<? extends Parsable>> errorMappings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ private HeadersCompatibility() {
* @param headers the okhttp3 headers
* @return the ResponseHeaders object
*/
@Nonnull
public static ResponseHeaders getResponseHeaders(@Nonnull final okhttp3.Headers headers) {
Objects.requireNonNull(headers);
final ResponseHeaders responseHeaders = new ResponseHeaders();
Expand All @@ -29,13 +30,13 @@ public static ResponseHeaders getResponseHeaders(@Nonnull final okhttp3.Headers
});
return responseHeaders;
}

/**
* INTERNAL METHOD, DO NOT USE DIRECTLY
* Get the request headers from the okhttp3 headers and convert them to a RequestHeaders object
* @param headers the okhttp3 headers
* @return the RequestHeaders object
*/
@Nonnull
public static RequestHeaders getRequestHeaders(@Nonnull final okhttp3.Headers headers) {
Objects.requireNonNull(headers);
final RequestHeaders requestHeaders = new RequestHeaders();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,10 @@
import java.util.Set;
import java.util.UUID;

import com.microsoft.kiota.ApiExceptionBuilder;
import com.microsoft.kiota.*;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;

import com.microsoft.kiota.ApiClientBuilder;
import com.microsoft.kiota.ApiException;
import com.microsoft.kiota.HttpMethod;
import com.microsoft.kiota.RequestInformation;
import com.microsoft.kiota.RequestOption;
import com.microsoft.kiota.ResponseHandlerOption;
import com.microsoft.kiota.ResponseHeaders;
import com.microsoft.kiota.ResponseHandler;
import com.microsoft.kiota.PeriodAndDuration;
import com.microsoft.kiota.authentication.AuthenticationProvider;
import com.microsoft.kiota.http.middleware.ParametersNameDecodingHandler;
import com.microsoft.kiota.serialization.ParseNodeFactoryRegistry;
Expand Down Expand Up @@ -201,10 +192,6 @@ public <ModelType extends Parsable> List<ModelType> sendCollection(@Nonnull fina
span.addEvent(eventResponseHandlerInvokedKey);
return responseHandler.handleResponse(response, errorMappings);
}
} catch(ApiException ex) {
throw new RuntimeException(ex);
} catch(IOException ex) {
throw new RuntimeException("failed to read the response body", ex);
} finally {
span.end();
}
Expand Down Expand Up @@ -268,10 +255,6 @@ public <ModelType extends Parsable> ModelType send(@Nonnull final RequestInforma
span.addEvent(eventResponseHandlerInvokedKey);
return responseHandler.handleResponse(response, errorMappings);
}
} catch(ApiException ex) {
throw new RuntimeException(ex);
} catch(IOException ex) {
throw new RuntimeException("failed to read the response body", ex);
} finally {
span.end();
}
Expand Down Expand Up @@ -371,11 +354,6 @@ public <ModelType> ModelType sendPrimitive(@Nonnull final RequestInformation req
span.addEvent(eventResponseHandlerInvokedKey);
return responseHandler.handleResponse(response, errorMappings);
}

} catch(ApiException ex) {
throw new RuntimeException(ex);
} catch(IOException ex) {
throw new RuntimeException("failed to read the response body", ex);
} finally {
span.end();
}
Expand Down Expand Up @@ -415,10 +393,6 @@ public <ModelType extends Enum<ModelType>> ModelType sendEnum(@Nonnull final Req
span.addEvent(eventResponseHandlerInvokedKey);
return responseHandler.handleResponse(response, errorMappings);
}
} catch(ApiException ex) {
throw new RuntimeException(ex);
} catch(IOException ex) {
throw new RuntimeException("failed to read the response body", ex);
} finally {
span.end();
}
Expand Down Expand Up @@ -458,12 +432,6 @@ public <ModelType extends Enum<ModelType>> List<ModelType> sendEnumCollection(@N
span.addEvent(eventResponseHandlerInvokedKey);
return responseHandler.handleResponse(response, errorMappings);
}


} catch(ApiException ex) {
throw new RuntimeException(ex);
} catch(IOException ex) {
throw new RuntimeException("failed to read the response body", ex);
} finally {
span.end();
}
Expand Down Expand Up @@ -503,16 +471,12 @@ public <ModelType> List<ModelType> sendPrimitiveCollection(@Nonnull final Reques
span.addEvent(eventResponseHandlerInvokedKey);
return responseHandler.handleResponse(response, errorMappings);
}
} catch(ApiException ex) {
throw new RuntimeException(ex);
} catch(IOException ex) {
throw new RuntimeException("failed to read the response body", ex);
} finally {
span.end();
}
}
@Nullable
private ParseNode getRootParseNode(final Response response, final Span parentSpan, final Span spanForAttributes) throws IOException {
private ParseNode getRootParseNode(final Response response, final Span parentSpan, final Span spanForAttributes) {
final Span span = GlobalOpenTelemetry.getTracer(obsOptions.getTracerInstrumentationName()).spanBuilder("getRootParseNode").setParent(Context.current().with(parentSpan)).startSpan();
try(final Scope scope = span.makeCurrent()) {
final ResponseBody body = response.body(); // closing the response closes the body and stream https://square.github.io/okhttp/4.x/okhttp/okhttp3/-response-body/
Expand All @@ -539,14 +503,14 @@ private boolean shouldReturnNull(final Response response) {
/** Key used for the attribute when an error response body is found */
@Nonnull
public static final String errorBodyFoundAttributeName = "com.microsoft.kiota.error_body_found";
private Response throwIfFailedResponse(@Nonnull final Response response, @Nonnull final Span spanForAttributes, @Nullable final HashMap<String, ParsableFactory<? extends Parsable>> errorMappings) throws IOException, ApiException {
private Response throwIfFailedResponse(@Nonnull final Response response, @Nonnull final Span spanForAttributes, @Nullable final HashMap<String, ParsableFactory<? extends Parsable>> errorMappings) {
final Span span = GlobalOpenTelemetry.getTracer(obsOptions.getTracerInstrumentationName()).spanBuilder("throwIfFailedResponse").setParent(Context.current().with(spanForAttributes)).startSpan();
try(final Scope scope = span.makeCurrent()) {
if (response.isSuccessful()) return response;
spanForAttributes.setStatus(StatusCode.ERROR);

final String statusCodeAsString = Integer.toString(response.code());
final Integer statusCode = response.code();
final int statusCode = response.code();
final ResponseHeaders responseHeaders = HeadersCompatibility.getResponseHeaders(response.headers());
if (errorMappings == null ||
!errorMappings.containsKey(statusCodeAsString) &&
Expand Down Expand Up @@ -679,7 +643,7 @@ String getClaimsFromResponse(@Nonnull final Response response, @Nonnull final Re
(claims == null || claims.isEmpty()) && // we avoid infinite loops and retry only once
(requestInfo.content == null || requestInfo.content.markSupported())) {
final List<String> authenticateHeader = response.headers("WWW-Authenticate");
if(authenticateHeader != null && !authenticateHeader.isEmpty()) {
if(!authenticateHeader.isEmpty()) {
String rawHeaderValue = null;
for(final String authenticateEntry: authenticateHeader) {
final Matcher matcher = bearerPattern.matcher(authenticateEntry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.io.InputStream;
import java.io.IOException;
import java.net.URI;
Expand Down Expand Up @@ -184,11 +183,10 @@ public void throwsAPIException() throws Exception {
when(mockFactory.getParseNode(any(String.class), any(InputStream.class))).thenReturn(mockParseNode);
when(mockFactory.getValidContentType()).thenReturn("application/json");
final var requestAdapter = new OkHttpRequestAdapter(authenticationProviderMock, mockFactory, null, client);
final var exception = assertThrows(RuntimeException.class, ()->requestAdapter.send(requestInformation, (node) -> mockEntity, null)) ;
final var cause = exception.getCause();
assertTrue(cause instanceof ApiException);
assertEquals(404, ((ApiException)cause).getResponseStatusCode());
assertTrue(((ApiException)cause).getResponseHeaders().containsKey("request-id"));
final var exception = assertThrows(ApiException.class, ()->requestAdapter.send(requestInformation, (node) -> mockEntity, null)) ;
assertNotNull(exception);
assertEquals(404, exception.getResponseStatusCode());
assertTrue(exception.getResponseHeaders().containsKey("request-id"));
}
public static OkHttpClient getMockClient(final Response response) throws IOException {
final OkHttpClient mockClient = mock(OkHttpClient.class);
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ org.gradle.parallel=true
org.gradle.caching=true

mavenGroupId = com.microsoft.kiota
mavenMajorVersion = 0
mavenMinorVersion = 8
mavenMajorVersion = 1
mavenMinorVersion = 0
mavenPatchVersion = 0
mavenArtifactSuffix =

Expand Down

0 comments on commit cbe580b

Please sign in to comment.