Skip to content

Commit aa53b0e

Browse files
committed
JCL-403: SolidClient builds ProblemDetails
`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.
1 parent 608e632 commit aa53b0e

File tree

2 files changed

+418
-37
lines changed

2 files changed

+418
-37
lines changed

solid/src/main/java/com/inrupt/client/solid/SolidClient.java

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,10 @@
2020
*/
2121
package com.inrupt.client.solid;
2222

23-
import static java.nio.charset.StandardCharsets.UTF_8;
24-
25-
import com.inrupt.client.Client;
26-
import com.inrupt.client.ClientProvider;
27-
import com.inrupt.client.Headers;
28-
import com.inrupt.client.RDFSource;
29-
import com.inrupt.client.Request;
30-
import com.inrupt.client.Resource;
31-
import com.inrupt.client.Response;
32-
import com.inrupt.client.ValidationResult;
23+
import com.inrupt.client.*;
3324
import com.inrupt.client.auth.Session;
25+
import com.inrupt.client.spi.JsonService;
26+
import com.inrupt.client.spi.ServiceProvider;
3427

3528
import java.io.ByteArrayInputStream;
3629
import java.io.IOException;
@@ -64,11 +57,22 @@ public class SolidClient {
6457
private final Client client;
6558
private final Headers defaultHeaders;
6659
private final boolean fetchAfterWrite;
60+
private final JsonService jsonService;
6761

6862
SolidClient(final Client client, final Headers headers, final boolean fetchAfterWrite) {
6963
this.client = Objects.requireNonNull(client, "Client may not be null!");
7064
this.defaultHeaders = Objects.requireNonNull(headers, "Headers may not be null!");
7165
this.fetchAfterWrite = fetchAfterWrite;
66+
67+
// It is acceptable for a SolidClient instance to be in a classpath without any implementation for
68+
// JsonService, in which case the ProblemDetails exceptions will fallback to default and not be parsed.
69+
JsonService js;
70+
try {
71+
js = ServiceProvider.getJsonService();
72+
} catch (IllegalStateException e) {
73+
js = null;
74+
}
75+
this.jsonService = js;
7276
}
7377

7478
/**
@@ -134,8 +138,19 @@ public <T extends Resource> CompletionStage<T> read(final URI identifier, final
134138
return client.send(request, Response.BodyHandlers.ofByteArray())
135139
.thenApply(response -> {
136140
if (response.statusCode() >= ERROR_STATUS) {
137-
throw SolidClientException.handle("Unable to read resource at " + request.uri(), request.uri(),
138-
response.statusCode(), response.headers(), new String(response.body()));
141+
final ProblemDetails pd = ProblemDetails.fromErrorResponse(
142+
response.statusCode(),
143+
response.headers(),
144+
response.body(),
145+
this.jsonService
146+
);
147+
throw SolidClientException.handle(
148+
"Unable to read resource at " + request.uri(),
149+
pd,
150+
response.uri(),
151+
response.headers(),
152+
new String(response.body())
153+
);
139154
} else {
140155
final String contentType = response.headers().firstValue(CONTENT_TYPE)
141156
.orElse("application/octet-stream");
@@ -284,8 +299,19 @@ public <T extends Resource> CompletionStage<Void> delete(final T resource, final
284299
if (isSuccess(res.statusCode())) {
285300
return null;
286301
} else {
287-
throw SolidClientException.handle("Unable to delete resource", resource.getIdentifier(),
288-
res.statusCode(), res.headers(), new String(res.body(), UTF_8));
302+
final ProblemDetails pd = ProblemDetails.fromErrorResponse(
303+
res.statusCode(),
304+
res.headers(),
305+
res.body(),
306+
this.jsonService
307+
);
308+
throw SolidClientException.handle(
309+
"Unable to delete resource",
310+
pd,
311+
resource.getIdentifier(),
312+
res.headers(),
313+
new String(res.body())
314+
);
289315
}
290316
});
291317
}
@@ -371,8 +397,19 @@ <T extends Resource> Function<Response<byte[]>, CompletionStage<T>> handleRespon
371397
final Headers headers, final String message) {
372398
return res -> {
373399
if (!isSuccess(res.statusCode())) {
374-
throw SolidClientException.handle(message, resource.getIdentifier(),
375-
res.statusCode(), res.headers(), new String(res.body(), UTF_8));
400+
final ProblemDetails pd = ProblemDetails.fromErrorResponse(
401+
res.statusCode(),
402+
res.headers(),
403+
res.body(),
404+
this.jsonService
405+
);
406+
throw SolidClientException.handle(
407+
message,
408+
pd,
409+
resource.getIdentifier(),
410+
res.headers(),
411+
new String(res.body())
412+
);
376413
}
377414

378415
if (!fetchAfterWrite) {
@@ -382,7 +419,6 @@ <T extends Resource> Function<Response<byte[]>, CompletionStage<T>> handleRespon
382419
@SuppressWarnings("unchecked")
383420
final Class<T> clazz = (Class<T>) resource.getClass();
384421
return read(resource.getIdentifier(), headers, clazz);
385-
386422
};
387423
}
388424

0 commit comments

Comments
 (0)