Skip to content

Commit

Permalink
JCL-403: SolidClient builds ProblemDetails
Browse files Browse the repository at this point in the history
`SolidClient` now parses HTTP error responses if they are compliant with
RFC9457 in order to throw a structured exception. In case when no JSON
parser is available in the classpath, the default behavior is to build
a ProblemDetails object that only relies on the data available in the
HTTP response status code.
  • Loading branch information
NSeydoux committed Apr 14, 2024
1 parent 608e632 commit aa53b0e
Show file tree
Hide file tree
Showing 2 changed files with 418 additions and 37 deletions.
70 changes: 53 additions & 17 deletions solid/src/main/java/com/inrupt/client/solid/SolidClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,10 @@
*/
package com.inrupt.client.solid;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.inrupt.client.Client;
import com.inrupt.client.ClientProvider;
import com.inrupt.client.Headers;
import com.inrupt.client.RDFSource;
import com.inrupt.client.Request;
import com.inrupt.client.Resource;
import com.inrupt.client.Response;
import com.inrupt.client.ValidationResult;
import com.inrupt.client.*;
import com.inrupt.client.auth.Session;
import com.inrupt.client.spi.JsonService;
import com.inrupt.client.spi.ServiceProvider;

import java.io.ByteArrayInputStream;
import java.io.IOException;
Expand Down Expand Up @@ -64,11 +57,22 @@ public class SolidClient {
private final Client client;
private final Headers defaultHeaders;
private final boolean fetchAfterWrite;
private final JsonService jsonService;

SolidClient(final Client client, final Headers headers, final boolean fetchAfterWrite) {
this.client = Objects.requireNonNull(client, "Client may not be null!");
this.defaultHeaders = Objects.requireNonNull(headers, "Headers may not be null!");
this.fetchAfterWrite = fetchAfterWrite;

// It is acceptable for a SolidClient instance to be in a classpath without any implementation for
// JsonService, in which case the ProblemDetails exceptions will fallback to default and not be parsed.
JsonService js;
try {
js = ServiceProvider.getJsonService();
} catch (IllegalStateException e) {
js = null;
}
this.jsonService = js;
}

/**
Expand Down Expand Up @@ -134,8 +138,19 @@ public <T extends Resource> CompletionStage<T> read(final URI identifier, final
return client.send(request, Response.BodyHandlers.ofByteArray())
.thenApply(response -> {
if (response.statusCode() >= ERROR_STATUS) {
throw SolidClientException.handle("Unable to read resource at " + request.uri(), request.uri(),
response.statusCode(), response.headers(), new String(response.body()));
final ProblemDetails pd = ProblemDetails.fromErrorResponse(
response.statusCode(),
response.headers(),
response.body(),
this.jsonService
);
throw SolidClientException.handle(
"Unable to read resource at " + request.uri(),
pd,
response.uri(),
response.headers(),
new String(response.body())
);
} else {
final String contentType = response.headers().firstValue(CONTENT_TYPE)
.orElse("application/octet-stream");
Expand Down Expand Up @@ -284,8 +299,19 @@ public <T extends Resource> CompletionStage<Void> delete(final T resource, final
if (isSuccess(res.statusCode())) {
return null;
} else {
throw SolidClientException.handle("Unable to delete resource", resource.getIdentifier(),
res.statusCode(), res.headers(), new String(res.body(), UTF_8));
final ProblemDetails pd = ProblemDetails.fromErrorResponse(
res.statusCode(),
res.headers(),
res.body(),
this.jsonService
);
throw SolidClientException.handle(
"Unable to delete resource",
pd,
resource.getIdentifier(),
res.headers(),
new String(res.body())
);
}
});
}
Expand Down Expand Up @@ -371,8 +397,19 @@ <T extends Resource> Function<Response<byte[]>, CompletionStage<T>> handleRespon
final Headers headers, final String message) {
return res -> {
if (!isSuccess(res.statusCode())) {
throw SolidClientException.handle(message, resource.getIdentifier(),
res.statusCode(), res.headers(), new String(res.body(), UTF_8));
final ProblemDetails pd = ProblemDetails.fromErrorResponse(
res.statusCode(),
res.headers(),
res.body(),
this.jsonService
);
throw SolidClientException.handle(
message,
pd,
resource.getIdentifier(),
res.headers(),
new String(res.body())
);
}

if (!fetchAfterWrite) {
Expand All @@ -382,7 +419,6 @@ <T extends Resource> Function<Response<byte[]>, CompletionStage<T>> handleRespon
@SuppressWarnings("unchecked")
final Class<T> clazz = (Class<T>) resource.getClass();
return read(resource.getIdentifier(), headers, clazz);

};
}

Expand Down
Loading

0 comments on commit aa53b0e

Please sign in to comment.