Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import dev.openfga.sdk.errors.FgaError;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;

public class ClientBatchCheckClientResponse extends CheckResponse {
Expand All @@ -18,19 +20,24 @@ public ClientBatchCheckClientResponse(
this.request = request;
this.throwable = throwable;

Throwable cause = throwable instanceof CompletionException || throwable instanceof ExecutionException
? throwable.getCause()
: throwable;

if (clientCheckResponse != null) {
this.statusCode = clientCheckResponse.getStatusCode();
this.headers = clientCheckResponse.getHeaders();
this.rawResponse = clientCheckResponse.getRawResponse();
this.setAllowed(clientCheckResponse.getAllowed());
this.setResolution(clientCheckResponse.getResolution());
} else if (throwable instanceof FgaError) {
FgaError error = (FgaError) throwable;
} else if (cause instanceof FgaError) {
FgaError error = (FgaError) cause;
this.statusCode = error.getStatusCode();
this.headers = error.getResponseHeaders().map();
var responseHeaders = error.getResponseHeaders();
this.headers = responseHeaders != null ? responseHeaders.map() : null;
this.rawResponse = error.getResponseData();
} else {
// Should be unreachable, but required for type completion
// no HTTP response available, e.g. the request never reached the server
this.statusCode = null;
this.headers = null;
this.rawResponse = null;
Comment thread
SoulPancake marked this conversation as resolved.
Expand Down Expand Up @@ -68,14 +75,30 @@ public Throwable getThrowable() {
return throwable;
}

public int getStatusCode() {
/**
* Returns the HTTP status code of the check response.
* <p>
* If no HTTP response was received — for example, the request never reached the server because of a
* network failure (connection refused, timeout, DNS failure) and all retries were exhausted — this
* returns {@code null}. In that case the underlying cause can be examined with
* {@link ClientBatchCheckClientResponse#getThrowable()}.
*
* @return the HTTP status code, or {@code null} if no HTTP response was received.
*/
public Integer getStatusCode() {
return statusCode;
}

/**
* @return the HTTP response headers, or {@code null} if no HTTP response was received.
*/
public Map<String, List<String>> getHeaders() {
return headers;
}

/**
* @return the raw HTTP response body, or {@code null} if no HTTP response was received.
*/
public String getRawResponse() {
return rawResponse;
}
Expand Down
52 changes: 52 additions & 0 deletions src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.http.Fault;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import com.pgssoft.httpclient.HttpClientMock;
Expand All @@ -18,6 +19,7 @@
import dev.openfga.sdk.api.model.*;
import dev.openfga.sdk.constants.FgaConstants;
import dev.openfga.sdk.errors.*;
import java.io.IOException;
import java.net.http.HttpClient;
import java.time.Duration;
import java.time.OffsetDateTime;
Expand Down Expand Up @@ -2066,6 +2068,11 @@ public void clientBatchCheck_400() throws Exception {
assertNotNull(response);
assertEquals(1, response.size());
assertNull(response.get(0).getAllowed());
assertEquals(400, response.get(0).getStatusCode());
assertEquals(
"{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}",
response.get(0).getRawResponse());
assertNotNull(response.get(0).getHeaders());
Throwable execException = response.get(0).getThrowable();
var exception = assertInstanceOf(FgaApiValidationError.class, execException.getCause());
assertEquals(400, exception.getStatusCode());
Expand All @@ -2092,6 +2099,11 @@ public void clientBatchCheck_404() throws Exception {
assertNotNull(response);
assertEquals(1, response.size());
assertNull(response.get(0).getAllowed());
assertEquals(404, response.get(0).getStatusCode());
assertEquals(
"{\"code\":\"undefined_endpoint\",\"message\":\"Endpoint not enabled\"}",
response.get(0).getRawResponse());
assertNotNull(response.get(0).getHeaders());
Throwable execException = response.get(0).getThrowable();
var exception = assertInstanceOf(FgaApiNotFoundError.class, execException.getCause());
assertEquals(404, exception.getStatusCode());
Expand All @@ -2118,13 +2130,53 @@ public void clientBatchCheck_500() throws Exception {
assertNotNull(response);
assertEquals(1, response.size());
assertNull(response.get(0).getAllowed());
assertEquals(500, response.get(0).getStatusCode());
assertEquals(
"{\"code\":\"internal_error\",\"message\":\"Internal Server Error\"}",
response.get(0).getRawResponse());
assertNotNull(response.get(0).getHeaders());
Throwable execException = response.get(0).getThrowable();
var exception = assertInstanceOf(FgaApiInternalError.class, execException.getCause());
assertEquals(500, exception.getStatusCode());
assertEquals(
"{\"code\":\"internal_error\",\"message\":\"Internal Server Error\"}", exception.getResponseData());
}

@Test
public void clientBatchCheck_networkError(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
// Given
String httpBaseUrl = wireMockRuntimeInfo.getHttpBaseUrl();
var fga = new OpenFgaClient(clientConfiguration.apiUrl(httpBaseUrl), new ApiClient());
String postUrl = String.format("/stores/%s/check", DEFAULT_STORE_ID);
WireMock.stubFor(
WireMock.post(postUrl).willReturn(WireMock.aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));

// When
List<ClientBatchCheckClientResponse> response = fga.clientBatchCheck(
List.of(new ClientCheckRequest()), new ClientBatchCheckClientOptions())
.join();

// Then
// Network errors are retried (1 initial + 3 retries = 4 total)
WireMock.verify(4, WireMock.postRequestedFor(WireMock.urlEqualTo(postUrl)));
assertNotNull(response);
assertEquals(1, response.size());
assertNull(response.get(0).getAllowed());
// No HTTP response was received, so status code, headers and body are null
assertNull(response.get(0).getStatusCode());
assertNull(response.get(0).getHeaders());
assertNull(response.get(0).getRawResponse());
Throwable execException = response.get(0).getThrowable();
assertNotNull(execException);
var exception = assertInstanceOf(ApiException.class, execException.getCause());
assertFalse(exception instanceof FgaError);
Throwable rootCause = exception;
while (rootCause.getCause() != null) {
rootCause = rootCause.getCause();
}
assertInstanceOf(IOException.class, rootCause);
}

@Test
public void shouldThrowExceptionWhenCorrelationIdsAreDuplicated() {
// Given
Expand Down
Loading