From 490a1f35b63518a790cdf135466b4b738330dc91 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 4 Apr 2024 11:26:34 +0200 Subject: [PATCH 01/28] Data classes for HTTP Problem Details support Initial ClientHttpException class Create HTTP status enum Add some tests Add javadoc Refactor HttpStatus Add specialized builder to ProblemDetails Add missing header Make problem details parsing more resilient Add tests for problem details parsing The tests are in the solid-client module, because it will need the additional json service anyways in the test dependencies for testing the rest of this feature, and putting these tests in the api module creates a circular dependency. License header --- .../inrupt/client/ClientHttpException.java | 54 ++++++ .../java/com/inrupt/client/HttpStatus.java | 80 +++++++++ .../com/inrupt/client/ProblemDetails.java | 132 ++++++++++++++ .../com/inrupt/client/HttpStatusTest.java | 63 +++++++ solid/pom.xml | 6 + .../client/solid/ProblemDetailsTest.java | 164 ++++++++++++++++++ 6 files changed, 499 insertions(+) create mode 100644 api/src/main/java/com/inrupt/client/ClientHttpException.java create mode 100644 api/src/main/java/com/inrupt/client/HttpStatus.java create mode 100644 api/src/main/java/com/inrupt/client/ProblemDetails.java create mode 100644 api/src/test/java/com/inrupt/client/HttpStatusTest.java create mode 100644 solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java diff --git a/api/src/main/java/com/inrupt/client/ClientHttpException.java b/api/src/main/java/com/inrupt/client/ClientHttpException.java new file mode 100644 index 00000000000..d2401e3cbc2 --- /dev/null +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -0,0 +1,54 @@ +/* + * Copyright Inrupt Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.inrupt.client; + +/** + * A runtime exception representing an HTTP error response carrying a structured representation of the problem. The + * problem description is embedded in a {@link ProblemDetails} instance. + */ +public class ClientHttpException extends InruptClientException { + private final ProblemDetails problemDetails; + + /** + * Create a ClientHttpException. + * @param problemDetails the {@link ProblemDetails} instance + * @param message the exception message + */ + public ClientHttpException(final ProblemDetails problemDetails, final String message) { + super(message); + this.problemDetails = problemDetails; + } + + /** + * Create a ClientHttpException. + * @param problemDetails the {@link ProblemDetails} instance + * @param message the exception message + * @param cause a wrapped exception cause + */ + public ClientHttpException(final ProblemDetails problemDetails, final String message, final Exception cause) { + super(message, cause); + this.problemDetails = problemDetails; + } + + public ProblemDetails getProblemDetails() { + return this.problemDetails; + } +} diff --git a/api/src/main/java/com/inrupt/client/HttpStatus.java b/api/src/main/java/com/inrupt/client/HttpStatus.java new file mode 100644 index 00000000000..193c14edcf2 --- /dev/null +++ b/api/src/main/java/com/inrupt/client/HttpStatus.java @@ -0,0 +1,80 @@ +/* + * Copyright Inrupt Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.inrupt.client; + +import java.util.Arrays; + +public final class HttpStatus { + + public static final int BAD_REQUEST = 400; + public static final int UNAUTHORIZED = 401; + public static final int FORBIDDEN = 403; + public static final int NOT_FOUND = 404; + public static final int METHOD_NOT_ALLOWED = 405; + public static final int NOT_ACCEPTABLE = 406; + public static final int CONFLICT = 409; + public static final int GONE = 410; + public static final int PRECONDITION_FAILED = 412; + public static final int UNSUPPORTED_MEDIA_TYPE = 415; + public static final int TOO_MANY_REQUESTS = 429; + public static final int INTERNAL_SERVER_ERROR = 500; + + enum StatusMessages { + BAD_REQUEST(HttpStatus.BAD_REQUEST, "Bad Request"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "Unauthorized"), + FORBIDDEN(HttpStatus.FORBIDDEN, "Forbidden"), + NOT_FOUND(HttpStatus.NOT_FOUND, "Not Found"), + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "Method Not Allowed"), + NOT_ACCEPTABLE(HttpStatus.NOT_ACCEPTABLE, "Not Acceptable"), + CONFLICT(HttpStatus.CONFLICT, "Conflict"), + GONE(HttpStatus.GONE, "Gone"), + PRECONDITION_FAILED(HttpStatus.PRECONDITION_FAILED, "Precondition Failed"), + UNSUPPORTED_MEDIA_TYPE(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"), + TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS, "Too Many Requests"), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error"); + + private final int code; + final String message; + + StatusMessages(final int code, final String message) { + this.code = code; + this.message = message; + } + + static String getStatusMessage(final int statusCode) { + return Arrays.stream(StatusMessages.values()) + .filter(status -> status.code == statusCode) + .findFirst() + .map(knownStatus -> knownStatus.message) + .orElseGet(() -> { + // If the status is unknown, default to 400 for client errors and 500 for server errors + if (statusCode >= 400 && statusCode <= 499) { + return BAD_REQUEST.message; + } + return INTERNAL_SERVER_ERROR.message; + }); + } + } + + private HttpStatus() { + // noop + } +} diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java new file mode 100644 index 00000000000..becf9edc2da --- /dev/null +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -0,0 +1,132 @@ +/* + * Copyright Inrupt Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.inrupt.client; + +import com.inrupt.client.spi.JsonService; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A data class representing a structured problem description sent by the server on error response. + * + * @see RFC 9457 Problem Details for HTTP APIs + */ +public class ProblemDetails { + public static final String MIME_TYPE = "application/problem+json"; + public static final String DEFAULT_TYPE = "about:blank"; + private final URI type; + private final String title; + private final String details; + private final int status; + private final URI instance; + + public ProblemDetails( + final URI type, + final String title, + final String details, + final int status, + final URI instance + ) { + // The `type` is not mandatory in RFC9457, so we want to set + // a default value here even when deserializing from JSON. + if (type != null) { + this.type = type; + } else { + this.type = URI.create(DEFAULT_TYPE); + } + this.title = title; + this.details = details; + this.status = status; + this.instance = instance; + } + + public URI getType() { + return this.type; + }; + + public String getTitle() { + return this.title; + }; + + public String getDetails() { + return this.details; + }; + + public int getStatus() { + return this.status; + }; + + public URI getInstance() { + return this.instance; + }; + + public static ProblemDetails fromErrorResponse( + final int statusCode, + final Headers headers, + final byte[] body, + final JsonService jsonService + ) { + if (jsonService == null + || (headers != null && !headers.allValues("Content-Type").contains(ProblemDetails.MIME_TYPE))) { + return new ProblemDetails( + null, + HttpStatus.StatusMessages.getStatusMessage(statusCode), + null, + statusCode, + null + ); + } + try { + // ProblemDetails doesn't have a default constructor, and we can't use JSON mapping annotations because + // the JSON service is an abstraction over JSON-B and Jackson, so we deserialize the JSON object in a Map + // and build the ProblemDetails from the Map values. + final Map pdData = jsonService.fromJson( + new ByteArrayInputStream(body), + new HashMap(){}.getClass().getGenericSuperclass() + ); + final String title = Optional.ofNullable((String) pdData.get("title")) + .orElse(HttpStatus.StatusMessages.getStatusMessage(statusCode)); + final String details = (String) pdData.get("details"); + final URI type = Optional.ofNullable((String) pdData.get("type")) + .map(URI::create) + .orElse(null); + final URI instance = Optional.ofNullable((String) pdData.get("instance")) + .map(URI::create) + .orElse(null); + // Note that the status code is disregarded from the body, and reused from the HTTP response directly, + // as they must be the same as per https://www.rfc-editor.org/rfc/rfc9457.html#name-status. + return new ProblemDetails(type, title, details, statusCode, instance); + } catch (IOException e) { + return new ProblemDetails( + null, + HttpStatus.StatusMessages.getStatusMessage(statusCode), + null, + statusCode, + null + ); + } + } +} diff --git a/api/src/test/java/com/inrupt/client/HttpStatusTest.java b/api/src/test/java/com/inrupt/client/HttpStatusTest.java new file mode 100644 index 00000000000..35126cb23c0 --- /dev/null +++ b/api/src/test/java/com/inrupt/client/HttpStatusTest.java @@ -0,0 +1,63 @@ +/* + * Copyright Inrupt Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.inrupt.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class HttpStatusTest { + @Test + void checkHttpStatusSearchKnownStatus() { + assertEquals( + HttpStatus.StatusMessages.getStatusMessage(HttpStatus.NOT_FOUND), + HttpStatus.StatusMessages.NOT_FOUND.message + ); + } + + @Test + void checkHttpStatusSearchUnknownClientError () { + assertEquals( + HttpStatus.StatusMessages.getStatusMessage(418), + HttpStatus.StatusMessages.BAD_REQUEST.message + ); + } + + @Test + void checkHttpStatusSearchUnknownServerError () { + assertEquals( + HttpStatus.StatusMessages.getStatusMessage(555), + HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + ); + assertEquals( + HttpStatus.StatusMessages.getStatusMessage(999), + HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + ); + assertEquals( + HttpStatus.StatusMessages.getStatusMessage(-1), + HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + ); + assertEquals( + HttpStatus.StatusMessages.getStatusMessage(15), + HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + ); + } +} diff --git a/solid/pom.xml b/solid/pom.xml index 15163ab2e65..3be4e20184b 100644 --- a/solid/pom.xml +++ b/solid/pom.xml @@ -79,6 +79,12 @@ ${project.version} test + + com.inrupt.client + inrupt-client-jackson + ${project.version} + test + org.slf4j slf4j-api diff --git a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java new file mode 100644 index 00000000000..b013b2999fa --- /dev/null +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -0,0 +1,164 @@ +/* + * Copyright Inrupt Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.inrupt.client.solid; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.inrupt.client.Headers; +import com.inrupt.client.ProblemDetails; +import com.inrupt.client.spi.JsonService; +import com.inrupt.client.spi.ServiceProvider; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +// Ideally, this class should be in the api module, but it creates +// a circular dependency with the JSON module implementation. +public class ProblemDetailsTest { + Headers mockProblemDetailsHeader() { + final List headerValues = new ArrayList<>(); + headerValues.add("application/problem+json"); + final Map> headerMap = new HashMap<>(); + headerMap.put("Content-Type", headerValues); + return Headers.of(headerMap); + } + + final JsonService jsonService = ServiceProvider.getJsonService(); + @Test + void testEmptyProblemDetails() { + final int statusCode = 400; + final ProblemDetails pd = ProblemDetails.fromErrorResponse( + statusCode, + mockProblemDetailsHeader(), + "{}".getBytes(), + jsonService + ); + assertEquals(ProblemDetails.DEFAULT_TYPE, pd.getType().toString()); + assertEquals(statusCode, pd.getStatus()); + Assertions.assertEquals("Bad Request", pd.getTitle()); + assertNull(pd.getDetails()); + assertNull(pd.getInstance()); + } + @Test + void testCompleteProblemDetails() { + final int statusCode = 400; + final ProblemDetails pd = ProblemDetails.fromErrorResponse( + statusCode, + mockProblemDetailsHeader(), + ("{" + + "\"title\":\"Some title\"," + + "\"status\":400," + + "\"details\":\"Some details\"," + + "\"instance\":\"https://example.org/instance\"," + + "\"type\":\"https://example.org/type\"" + + "}").getBytes(), + jsonService + ); + assertEquals("https://example.org/type", pd.getType().toString()); + assertEquals(statusCode, pd.getStatus()); + Assertions.assertEquals("Some title", pd.getTitle()); + assertEquals("Some details", pd.getDetails()); + assertEquals("https://example.org/instance", pd.getInstance().toString()); + } + + @Test + void testIgnoreUnknownProblemDetails() { + final int statusCode = 400; + final ProblemDetails pd = ProblemDetails.fromErrorResponse( + statusCode, + mockProblemDetailsHeader(), + ("{" + + "\"title\":\"Some title\"," + + "\"status\":400," + + "\"details\":\"Some details\"," + + "\"instance\":\"https://example.org/instance\"," + + "\"type\":\"https://example.org/type\"," + + "\"unknown\":\"Some unknown property\"" + + "}").getBytes(), + jsonService + ); + assertEquals("https://example.org/type", pd.getType().toString()); + assertEquals(statusCode, pd.getStatus()); + Assertions.assertEquals("Some title", pd.getTitle()); + assertEquals("Some details", pd.getDetails()); + assertEquals("https://example.org/instance", pd.getInstance().toString()); + } + + @Test + void testInvalidStatusProblemDetails() { + final int statusCode = 400; + final ProblemDetails pd = ProblemDetails.fromErrorResponse( + statusCode, + mockProblemDetailsHeader(), + ("{" + + "\"status\":\"Some invalid status\"," + + "}").getBytes(), + jsonService + ); + assertEquals(statusCode, pd.getStatus()); + } + + @Test + void testMismatchingStatusProblemDetails() { + final int statusCode = 400; + final ProblemDetails pd = ProblemDetails.fromErrorResponse( + statusCode, + mockProblemDetailsHeader(), + ("{" + + "\"status\":500," + + "}").getBytes(), + jsonService + ); + assertEquals(statusCode, pd.getStatus()); + } + + @Test + void testInvalidTypeProblemDetails() { + final ProblemDetails pd = ProblemDetails.fromErrorResponse( + 400, + mockProblemDetailsHeader(), + ("{" + + "\"type\":\"Some invalid type\"," + + "}").getBytes(), + jsonService + ); + assertEquals(ProblemDetails.DEFAULT_TYPE, pd.getType().toString()); + } + + @Test + void testInvalidInstanceProblemDetails() { + final ProblemDetails pd = ProblemDetails.fromErrorResponse( + 400, + mockProblemDetailsHeader(), + ("{" + + "\"instance\":\"Some invalid instance\"," + + "}").getBytes(), + jsonService + ); + assertNull(pd.getInstance()); + } +} From 1147b3bd0d7a69663d19c31fdeb5fef1ffe42068 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Mon, 15 Apr 2024 22:45:41 +0200 Subject: [PATCH 02/28] Use typed JSON deserialization --- .../com/inrupt/client/ProblemDetails.java | 43 ++++++--------- .../com/inrupt/client/ProblemDetailsData.java | 55 +++++++++++++++++++ 2 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 api/src/main/java/com/inrupt/client/ProblemDetailsData.java diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index becf9edc2da..65d512db343 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -24,6 +24,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.HashMap; import java.util.Map; @@ -50,13 +51,7 @@ public ProblemDetails( final int status, final URI instance ) { - // The `type` is not mandatory in RFC9457, so we want to set - // a default value here even when deserializing from JSON. - if (type != null) { - this.type = type; - } else { - this.type = URI.create(DEFAULT_TYPE); - } + this.type = type; this.title = title; this.details = details; this.status = status; @@ -92,7 +87,7 @@ public static ProblemDetails fromErrorResponse( if (jsonService == null || (headers != null && !headers.allValues("Content-Type").contains(ProblemDetails.MIME_TYPE))) { return new ProblemDetails( - null, + URI.create(ProblemDetails.DEFAULT_TYPE), HttpStatus.StatusMessages.getStatusMessage(statusCode), null, statusCode, @@ -100,28 +95,26 @@ public static ProblemDetails fromErrorResponse( ); } try { - // ProblemDetails doesn't have a default constructor, and we can't use JSON mapping annotations because - // the JSON service is an abstraction over JSON-B and Jackson, so we deserialize the JSON object in a Map - // and build the ProblemDetails from the Map values. - final Map pdData = jsonService.fromJson( + final ProblemDetailsData pdData = jsonService.fromJson( new ByteArrayInputStream(body), - new HashMap(){}.getClass().getGenericSuperclass() + ProblemDetailsData.class ); - final String title = Optional.ofNullable((String) pdData.get("title")) + final URI type = Optional.ofNullable(pdData.getType()) + .orElse(URI.create(ProblemDetails.DEFAULT_TYPE)); + final String title = Optional.ofNullable(pdData.getTitle()) .orElse(HttpStatus.StatusMessages.getStatusMessage(statusCode)); - final String details = (String) pdData.get("details"); - final URI type = Optional.ofNullable((String) pdData.get("type")) - .map(URI::create) - .orElse(null); - final URI instance = Optional.ofNullable((String) pdData.get("instance")) - .map(URI::create) - .orElse(null); - // Note that the status code is disregarded from the body, and reused from the HTTP response directly, - // as they must be the same as per https://www.rfc-editor.org/rfc/rfc9457.html#name-status. - return new ProblemDetails(type, title, details, statusCode, instance); + // JSON mappers map invalid integers to 0, which is an invalid value in our case anyway. + final int status = Optional.of(pdData.getStatus()).filter(s -> s != 0).orElse(statusCode); + return new ProblemDetails( + type, + title, + pdData.getDetails(), + status, + pdData.getInstance() + ); } catch (IOException e) { return new ProblemDetails( - null, + URI.create(ProblemDetails.DEFAULT_TYPE), HttpStatus.StatusMessages.getStatusMessage(statusCode), null, statusCode, diff --git a/api/src/main/java/com/inrupt/client/ProblemDetailsData.java b/api/src/main/java/com/inrupt/client/ProblemDetailsData.java new file mode 100644 index 00000000000..fde3f633764 --- /dev/null +++ b/api/src/main/java/com/inrupt/client/ProblemDetailsData.java @@ -0,0 +1,55 @@ +package com.inrupt.client; + +import java.net.URI; + +/** + * This package-private mutable class is used for JSON deserialization. + * Once instantiated, it is used to build an immutable {@link ProblemDetails}. + */ +class ProblemDetailsData { + private URI type; + private String title; + private String details; + private int status; + private URI instance; + + public URI getType() { + return this.type; + }; + + public String getTitle() { + return this.title; + }; + + public String getDetails() { + return this.details; + }; + + public int getStatus() { + return this.status; + }; + + public URI getInstance() { + return this.instance; + }; + + public void setType(URI type) { + this.type = type; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setDetails(String details) { + this.details = details; + } + + public void setStatus(int status) { + this.status = status; + } + + public void setInstance(URI instance) { + this.instance = instance; + } +} From 050f4a697cd783670307ff8830cf4f84a7cb82bb Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Mon, 15 Apr 2024 22:57:08 +0200 Subject: [PATCH 03/28] =?UTF-8?q?Move=20JSON=20service=20discovery=20in=20?= =?UTF-8?q?ProblemDetails=C2=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/inrupt/client/ProblemDetails.java | 14 +++++++++---- .../client/solid/ProblemDetailsTest.java | 21 +++++++------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 65d512db343..0af2f4cf338 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -21,6 +21,7 @@ package com.inrupt.client; import com.inrupt.client.spi.JsonService; +import com.inrupt.client.spi.ServiceProvider; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -43,6 +44,7 @@ public class ProblemDetails { private final String details; private final int status; private final URI instance; + private static JsonService jsonService; public ProblemDetails( final URI type, @@ -81,10 +83,14 @@ public URI getInstance() { public static ProblemDetails fromErrorResponse( final int statusCode, final Headers headers, - final byte[] body, - final JsonService jsonService + final byte[] body ) { - if (jsonService == null + try { + ProblemDetails.jsonService = ServiceProvider.getJsonService(); + } catch (IllegalStateException e) { + ProblemDetails.jsonService = null; + } + if (ProblemDetails.jsonService == null || (headers != null && !headers.allValues("Content-Type").contains(ProblemDetails.MIME_TYPE))) { return new ProblemDetails( URI.create(ProblemDetails.DEFAULT_TYPE), @@ -95,7 +101,7 @@ public static ProblemDetails fromErrorResponse( ); } try { - final ProblemDetailsData pdData = jsonService.fromJson( + final ProblemDetailsData pdData = ProblemDetails.jsonService.fromJson( new ByteArrayInputStream(body), ProblemDetailsData.class ); diff --git a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java index b013b2999fa..63bfeac46f2 100644 --- a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -54,8 +54,7 @@ void testEmptyProblemDetails() { final ProblemDetails pd = ProblemDetails.fromErrorResponse( statusCode, mockProblemDetailsHeader(), - "{}".getBytes(), - jsonService + "{}".getBytes() ); assertEquals(ProblemDetails.DEFAULT_TYPE, pd.getType().toString()); assertEquals(statusCode, pd.getStatus()); @@ -75,8 +74,7 @@ void testCompleteProblemDetails() { "\"details\":\"Some details\"," + "\"instance\":\"https://example.org/instance\"," + "\"type\":\"https://example.org/type\"" + - "}").getBytes(), - jsonService + "}").getBytes() ); assertEquals("https://example.org/type", pd.getType().toString()); assertEquals(statusCode, pd.getStatus()); @@ -98,8 +96,7 @@ void testIgnoreUnknownProblemDetails() { "\"instance\":\"https://example.org/instance\"," + "\"type\":\"https://example.org/type\"," + "\"unknown\":\"Some unknown property\"" + - "}").getBytes(), - jsonService + "}").getBytes() ); assertEquals("https://example.org/type", pd.getType().toString()); assertEquals(statusCode, pd.getStatus()); @@ -116,8 +113,7 @@ void testInvalidStatusProblemDetails() { mockProblemDetailsHeader(), ("{" + "\"status\":\"Some invalid status\"," + - "}").getBytes(), - jsonService + "}").getBytes() ); assertEquals(statusCode, pd.getStatus()); } @@ -130,8 +126,7 @@ void testMismatchingStatusProblemDetails() { mockProblemDetailsHeader(), ("{" + "\"status\":500," + - "}").getBytes(), - jsonService + "}").getBytes() ); assertEquals(statusCode, pd.getStatus()); } @@ -143,8 +138,7 @@ void testInvalidTypeProblemDetails() { mockProblemDetailsHeader(), ("{" + "\"type\":\"Some invalid type\"," + - "}").getBytes(), - jsonService + "}").getBytes() ); assertEquals(ProblemDetails.DEFAULT_TYPE, pd.getType().toString()); } @@ -156,8 +150,7 @@ void testInvalidInstanceProblemDetails() { mockProblemDetailsHeader(), ("{" + "\"instance\":\"Some invalid instance\"," + - "}").getBytes(), - jsonService + "}").getBytes() ); assertNull(pd.getInstance()); } From 374880eabfb46358f727f6855def43500616396a Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Mon, 15 Apr 2024 23:01:57 +0200 Subject: [PATCH 04/28] Make JSON service singleton --- .../com/inrupt/client/ProblemDetails.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 0af2f4cf338..e88d7ba870e 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -45,6 +45,7 @@ public class ProblemDetails { private final int status; private final URI instance; private static JsonService jsonService; + private static boolean isJsonServiceInitialized; public ProblemDetails( final URI type, @@ -80,17 +81,28 @@ public URI getInstance() { return this.instance; }; - public static ProblemDetails fromErrorResponse( - final int statusCode, - final Headers headers, - final byte[] body - ) { + private static JsonService getJsonService() { + if (ProblemDetails.isJsonServiceInitialized) { + return ProblemDetails.jsonService; + } + // It is acceptable for a JenaBodyHandlers 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. try { ProblemDetails.jsonService = ServiceProvider.getJsonService(); } catch (IllegalStateException e) { ProblemDetails.jsonService = null; } - if (ProblemDetails.jsonService == null + ProblemDetails.isJsonServiceInitialized = true; + return ProblemDetails.jsonService; + } + + public static ProblemDetails fromErrorResponse( + final int statusCode, + final Headers headers, + final byte[] body + ) { + final JsonService jsonService = getJsonService(); + if (jsonService == null || (headers != null && !headers.allValues("Content-Type").contains(ProblemDetails.MIME_TYPE))) { return new ProblemDetails( URI.create(ProblemDetails.DEFAULT_TYPE), @@ -101,7 +113,7 @@ public static ProblemDetails fromErrorResponse( ); } try { - final ProblemDetailsData pdData = ProblemDetails.jsonService.fromJson( + final ProblemDetailsData pdData = jsonService.fromJson( new ByteArrayInputStream(body), ProblemDetailsData.class ); From 914c153b0b000afaf6bd2522abda0c5e206d65fb Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Mon, 15 Apr 2024 23:11:47 +0200 Subject: [PATCH 05/28] =?UTF-8?q?fixup!=20Move=20JSON=20service=20discover?= =?UTF-8?q?y=20in=20ProblemDetails=C2=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/com/inrupt/client/solid/ProblemDetailsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java index 63bfeac46f2..3e98b098905 100644 --- a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -47,7 +47,6 @@ Headers mockProblemDetailsHeader() { return Headers.of(headerMap); } - final JsonService jsonService = ServiceProvider.getJsonService(); @Test void testEmptyProblemDetails() { final int statusCode = 400; From f753ff2806eaf7d057dc4f174541e6b6e9897488 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Mon, 15 Apr 2024 23:15:48 +0200 Subject: [PATCH 06/28] Change default ProblemDetails titles --- api/src/main/java/com/inrupt/client/HttpStatus.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/HttpStatus.java b/api/src/main/java/com/inrupt/client/HttpStatus.java index 193c14edcf2..85a71151313 100644 --- a/api/src/main/java/com/inrupt/client/HttpStatus.java +++ b/api/src/main/java/com/inrupt/client/HttpStatus.java @@ -67,9 +67,9 @@ static String getStatusMessage(final int statusCode) { .orElseGet(() -> { // If the status is unknown, default to 400 for client errors and 500 for server errors if (statusCode >= 400 && statusCode <= 499) { - return BAD_REQUEST.message; + return "Unknown Client Error"; } - return INTERNAL_SERVER_ERROR.message; + return "unknown Server Error"; }); } } From 67f612472c86d868e9287a1bbfacc18e5f0ae8f5 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Mon, 15 Apr 2024 23:19:49 +0200 Subject: [PATCH 07/28] Fix ProblemDetails parsing tests the JSON was invalid, causing the intended code path not to be used. --- .../com/inrupt/client/solid/ProblemDetailsTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java index 3e98b098905..5ee2cafad83 100644 --- a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -111,7 +111,7 @@ void testInvalidStatusProblemDetails() { statusCode, mockProblemDetailsHeader(), ("{" + - "\"status\":\"Some invalid status\"," + + "\"status\":\"Some invalid status\"" + "}").getBytes() ); assertEquals(statusCode, pd.getStatus()); @@ -124,10 +124,10 @@ void testMismatchingStatusProblemDetails() { statusCode, mockProblemDetailsHeader(), ("{" + - "\"status\":500," + + "\"status\":500" + "}").getBytes() ); - assertEquals(statusCode, pd.getStatus()); + assertEquals(500, pd.getStatus()); } @Test @@ -136,7 +136,7 @@ void testInvalidTypeProblemDetails() { 400, mockProblemDetailsHeader(), ("{" + - "\"type\":\"Some invalid type\"," + + "\"type\":\"Some invalid type\"" + "}").getBytes() ); assertEquals(ProblemDetails.DEFAULT_TYPE, pd.getType().toString()); @@ -148,7 +148,7 @@ void testInvalidInstanceProblemDetails() { 400, mockProblemDetailsHeader(), ("{" + - "\"instance\":\"Some invalid instance\"," + + "\"instance\":\"Some invalid instance\"" + "}").getBytes() ); assertNull(pd.getInstance()); From 766eb950833f44b58244d0eaa0625b6fa1396ad5 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 09:23:28 +0200 Subject: [PATCH 08/28] Lift SolidClientExceptions members to parent --- .../inrupt/client/ClientHttpException.java | 63 ++++++++++++++++--- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ClientHttpException.java b/api/src/main/java/com/inrupt/client/ClientHttpException.java index d2401e3cbc2..214f672ba11 100644 --- a/api/src/main/java/com/inrupt/client/ClientHttpException.java +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -20,35 +20,78 @@ */ package com.inrupt.client; +import java.net.URI; + /** * A runtime exception representing an HTTP error response carrying a structured representation of the problem. The * problem description is embedded in a {@link ProblemDetails} instance. */ public class ClientHttpException extends InruptClientException { private final ProblemDetails problemDetails; + private final URI uri; + private final int statusCode; + private final String body; + private final transient Headers headers; /** * Create a ClientHttpException. - * @param problemDetails the {@link ProblemDetails} instance * @param message the exception message + * @param uri the error response URI + * @param statusCode the error response status code + * @param headers the error response headers */ - public ClientHttpException(final ProblemDetails problemDetails, final String message) { + public ClientHttpException(final String message, final URI uri, final int statusCode, + final Headers headers, final String body) { super(message); - this.problemDetails = problemDetails; + this.uri = uri; + this.statusCode = statusCode; + this.headers = headers; + this.body = body; + this.problemDetails = ProblemDetails.fromErrorResponse(statusCode, headers, body.getBytes()); } /** - * Create a ClientHttpException. - * @param problemDetails the {@link ProblemDetails} instance - * @param message the exception message - * @param cause a wrapped exception cause + * Retrieve the URI associated with this exception. + * + * @return the uri + */ + public URI getUri() { + return uri; + } + + /** + * Retrieve the status code associated with this exception. + * + * @return the status code + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Retrieve the headers associated with this exception. + * + * @return the headers */ - public ClientHttpException(final ProblemDetails problemDetails, final String message, final Exception cause) { - super(message, cause); - this.problemDetails = problemDetails; + public Headers getHeaders() { + return headers; } + /** + * Retrieve the body associated with this exception. + * + * @return the body + */ + public String getBody() { + return body; + } + + /** + * Retrieve the {@link ProblemDetails} instance describing the HTTP error response. + * @return the problem details object + */ public ProblemDetails getProblemDetails() { return this.problemDetails; } + } From 9cbf5e3398b9a6021a1b0fe8643231b299cb1e35 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 09:30:26 +0200 Subject: [PATCH 09/28] Fix typo in default HTTP status --- api/src/main/java/com/inrupt/client/HttpStatus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/inrupt/client/HttpStatus.java b/api/src/main/java/com/inrupt/client/HttpStatus.java index 85a71151313..6d7654eeaca 100644 --- a/api/src/main/java/com/inrupt/client/HttpStatus.java +++ b/api/src/main/java/com/inrupt/client/HttpStatus.java @@ -69,7 +69,7 @@ static String getStatusMessage(final int statusCode) { if (statusCode >= 400 && statusCode <= 499) { return "Unknown Client Error"; } - return "unknown Server Error"; + return "Unknown Server Error"; }); } } From ba883b75317ba5736d93e0ba8e65c35642e39944 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 10:03:47 +0200 Subject: [PATCH 10/28] Lint --- .../java/com/inrupt/client/ClientHttpException.java | 1 + .../main/java/com/inrupt/client/ProblemDetails.java | 3 --- .../java/com/inrupt/client/ProblemDetailsData.java | 10 +++++----- .../com/inrupt/client/solid/ProblemDetailsTest.java | 2 -- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ClientHttpException.java b/api/src/main/java/com/inrupt/client/ClientHttpException.java index 214f672ba11..38313f0d465 100644 --- a/api/src/main/java/com/inrupt/client/ClientHttpException.java +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -39,6 +39,7 @@ public class ClientHttpException extends InruptClientException { * @param uri the error response URI * @param statusCode the error response status code * @param headers the error response headers + * @param body the error response body */ public ClientHttpException(final String message, final URI uri, final int statusCode, final Headers headers, final String body) { diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index e88d7ba870e..43215292359 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -25,10 +25,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URI; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; /** diff --git a/api/src/main/java/com/inrupt/client/ProblemDetailsData.java b/api/src/main/java/com/inrupt/client/ProblemDetailsData.java index fde3f633764..876967eb1dc 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetailsData.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetailsData.java @@ -33,23 +33,23 @@ public URI getInstance() { return this.instance; }; - public void setType(URI type) { + public void setType(final URI type) { this.type = type; } - public void setTitle(String title) { + public void setTitle(final String title) { this.title = title; } - public void setDetails(String details) { + public void setDetails(final String details) { this.details = details; } - public void setStatus(int status) { + public void setStatus(final int status) { this.status = status; } - public void setInstance(URI instance) { + public void setInstance(final URI instance) { this.instance = instance; } } diff --git a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java index 5ee2cafad83..227ea50d4ae 100644 --- a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -25,8 +25,6 @@ import com.inrupt.client.Headers; import com.inrupt.client.ProblemDetails; -import com.inrupt.client.spi.JsonService; -import com.inrupt.client.spi.ServiceProvider; import java.util.ArrayList; import java.util.HashMap; From 17b8ce21791127c60f27e66ba824f18879752312 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Wed, 10 Apr 2024 14:50:44 +0200 Subject: [PATCH 11/28] Refactor specialized exception constructors The exception are now built taking a ProblemDetails in. Use HttpStatus from API module as reference Lint --- .../client/solid/BadRequestException.java | 5 +- .../client/solid/ConflictException.java | 3 +- .../client/solid/ForbiddenException.java | 3 +- .../inrupt/client/solid/GoneException.java | 3 +- .../solid/InternalServerErrorException.java | 3 +- .../solid/MethodNotAllowedException.java | 3 +- .../client/solid/NotAcceptableException.java | 3 +- .../client/solid/NotFoundException.java | 3 +- .../solid/PreconditionFailedException.java | 3 +- .../client/solid/SolidClientException.java | 54 ++++--------------- .../solid/TooManyRequestsException.java | 3 +- .../client/solid/UnauthorizedException.java | 3 +- .../solid/UnsupportedMediaTypeException.java | 3 +- .../client/solid/SolidExceptionTest.java | 12 +++++ 14 files changed, 46 insertions(+), 58 deletions(-) diff --git a/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java b/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java index 3fc0ad547f1..0f380e5632c 100644 --- a/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java +++ b/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class BadRequestException extends SolidClientException { private static final long serialVersionUID = -3379457428921025570L; - public static final int STATUS_CODE = 400; + public static final int STATUS_CODE = HttpStatus.BAD_REQUEST; /** * Create a BadRequestException exception. @@ -47,6 +48,6 @@ public BadRequestException( final URI uri, final Headers headers, final String body) { - super(message, uri, STATUS_CODE, headers, body); + super(message, uri, HttpStatus.BAD_REQUEST, headers, body); } } diff --git a/solid/src/main/java/com/inrupt/client/solid/ConflictException.java b/solid/src/main/java/com/inrupt/client/solid/ConflictException.java index d88386eb724..fe2a15385f5 100644 --- a/solid/src/main/java/com/inrupt/client/solid/ConflictException.java +++ b/solid/src/main/java/com/inrupt/client/solid/ConflictException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class ConflictException extends SolidClientException { private static final long serialVersionUID = -203198307847520748L; - public static final int STATUS_CODE = 409; + public static final int STATUS_CODE = HttpStatus.CONFLICT; /** * Create a ConflictException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/ForbiddenException.java b/solid/src/main/java/com/inrupt/client/solid/ForbiddenException.java index 7aeb3219bec..20bf1fd32dc 100644 --- a/solid/src/main/java/com/inrupt/client/solid/ForbiddenException.java +++ b/solid/src/main/java/com/inrupt/client/solid/ForbiddenException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class ForbiddenException extends SolidClientException { private static final long serialVersionUID = 3299286274724874244L; - public static final int STATUS_CODE = 403; + public static final int STATUS_CODE = HttpStatus.FORBIDDEN; /** * Create a ForbiddenException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/GoneException.java b/solid/src/main/java/com/inrupt/client/solid/GoneException.java index 0882302e27b..2d247db4c87 100644 --- a/solid/src/main/java/com/inrupt/client/solid/GoneException.java +++ b/solid/src/main/java/com/inrupt/client/solid/GoneException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class GoneException extends SolidClientException { private static final long serialVersionUID = -6892345582498100242L; - public static final int STATUS_CODE = 410; + public static final int STATUS_CODE = HttpStatus.GONE; /** * Create a GoneException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/InternalServerErrorException.java b/solid/src/main/java/com/inrupt/client/solid/InternalServerErrorException.java index ea9acb1c56e..d57ac187d86 100644 --- a/solid/src/main/java/com/inrupt/client/solid/InternalServerErrorException.java +++ b/solid/src/main/java/com/inrupt/client/solid/InternalServerErrorException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class InternalServerErrorException extends SolidClientException { private static final long serialVersionUID = -6672490715281719330L; - public static final int STATUS_CODE = 500; + public static final int STATUS_CODE = HttpStatus.INTERNAL_SERVER_ERROR; /** * Create an InternalServerErrorException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/MethodNotAllowedException.java b/solid/src/main/java/com/inrupt/client/solid/MethodNotAllowedException.java index d472b5fcbd3..dbba0b6003c 100644 --- a/solid/src/main/java/com/inrupt/client/solid/MethodNotAllowedException.java +++ b/solid/src/main/java/com/inrupt/client/solid/MethodNotAllowedException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class MethodNotAllowedException extends SolidClientException { private static final long serialVersionUID = -9125437562813923030L; - public static final int STATUS_CODE = 405; + public static final int STATUS_CODE = HttpStatus.METHOD_NOT_ALLOWED; /** * Create a MethodNotAllowedException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/NotAcceptableException.java b/solid/src/main/java/com/inrupt/client/solid/NotAcceptableException.java index 53744c0b2e8..18b7bf929e5 100644 --- a/solid/src/main/java/com/inrupt/client/solid/NotAcceptableException.java +++ b/solid/src/main/java/com/inrupt/client/solid/NotAcceptableException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class NotAcceptableException extends SolidClientException { private static final long serialVersionUID = 6594993822477388733L; - public static final int STATUS_CODE = 406; + public static final int STATUS_CODE = HttpStatus.NOT_ACCEPTABLE; /** * Create a NotAcceptableException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/NotFoundException.java b/solid/src/main/java/com/inrupt/client/solid/NotFoundException.java index 466bed74894..f0cf9ef7164 100644 --- a/solid/src/main/java/com/inrupt/client/solid/NotFoundException.java +++ b/solid/src/main/java/com/inrupt/client/solid/NotFoundException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class NotFoundException extends SolidClientException { private static final long serialVersionUID = -2256628528500739683L; - public static final int STATUS_CODE = 404; + public static final int STATUS_CODE = HttpStatus.NOT_FOUND; /** * Create a NotFoundException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java b/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java index 461c937a0eb..4584bac47b9 100644 --- a/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java +++ b/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class PreconditionFailedException extends SolidClientException { private static final long serialVersionUID = 4205761003697773528L; - public static final int STATUS_CODE = 412; + public static final int STATUS_CODE = HttpStatus.PRECONDITION_FAILED; /** * Create a PreconditionFailedException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/SolidClientException.java b/solid/src/main/java/com/inrupt/client/solid/SolidClientException.java index 668df265d3d..bf6decd11ee 100644 --- a/solid/src/main/java/com/inrupt/client/solid/SolidClientException.java +++ b/solid/src/main/java/com/inrupt/client/solid/SolidClientException.java @@ -20,23 +20,18 @@ */ package com.inrupt.client.solid; +import com.inrupt.client.ClientHttpException; import com.inrupt.client.Headers; -import com.inrupt.client.InruptClientException; import java.net.URI; /** * A runtime exception for use with SolidClient HTTP operations. */ -public class SolidClientException extends InruptClientException { +public class SolidClientException extends ClientHttpException { private static final long serialVersionUID = 2868432164225689934L; - private final URI uri; - private final int statusCode; - private final String body; - private final transient Headers headers; - /** * Create a SolidClient exception. * @@ -48,49 +43,18 @@ public class SolidClientException extends InruptClientException { */ public SolidClientException(final String message, final URI uri, final int statusCode, final Headers headers, final String body) { - super(message); - this.uri = uri; - this.statusCode = statusCode; - this.headers = headers; - this.body = body; - } - - /** - * Retrieve the URI associated with this exception. - * - * @return the uri - */ - public URI getUri() { - return uri; - } - - /** - * Retrieve the status code associated with this exception. - * - * @return the status code - */ - public int getStatusCode() { - return statusCode; + super(message, uri, statusCode, headers, body); } /** - * Retrieve the headers associated with this exception. * - * @return the headers + * @param message the resulting exception message + * @param uri the request URL + * @param statusCode the response status code + * @param headers the response {@link Headers} + * @param body the response body + * @return an appropriate exception based on the status code. */ - public Headers getHeaders() { - return headers; - } - - /** - * Retrieve the body associated with this exception. - * - * @return the body - */ - public String getBody() { - return body; - } - public static SolidClientException handle( final String message, final URI uri, diff --git a/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java b/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java index 4f299fc9492..018a95e1e8f 100644 --- a/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java +++ b/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class TooManyRequestsException extends SolidClientException { private static final long serialVersionUID = -1798491190232642824L; - public static final int STATUS_CODE = 429; + public static final int STATUS_CODE = HttpStatus.TOO_MANY_REQUESTS; /** * Create a TooManyRequestsException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/UnauthorizedException.java b/solid/src/main/java/com/inrupt/client/solid/UnauthorizedException.java index db8615df958..7ad2d425137 100644 --- a/solid/src/main/java/com/inrupt/client/solid/UnauthorizedException.java +++ b/solid/src/main/java/com/inrupt/client/solid/UnauthorizedException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class UnauthorizedException extends SolidClientException { private static final long serialVersionUID = -3219668517323678497L; - public static final int STATUS_CODE = 401; + public static final int STATUS_CODE = HttpStatus.UNAUTHORIZED; /** * Create an UnauthorizedException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/UnsupportedMediaTypeException.java b/solid/src/main/java/com/inrupt/client/solid/UnsupportedMediaTypeException.java index 27e9c96953d..866fe70cf7d 100644 --- a/solid/src/main/java/com/inrupt/client/solid/UnsupportedMediaTypeException.java +++ b/solid/src/main/java/com/inrupt/client/solid/UnsupportedMediaTypeException.java @@ -21,6 +21,7 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; +import com.inrupt.client.HttpStatus; import java.net.URI; @@ -32,7 +33,7 @@ public class UnsupportedMediaTypeException extends SolidClientException { private static final long serialVersionUID = 1312856145838280673L; - public static final int STATUS_CODE = 415; + public static final int STATUS_CODE = HttpStatus.UNSUPPORTED_MEDIA_TYPE; /** * Create an UnsupportedMediaTypeException exception. diff --git a/solid/src/test/java/com/inrupt/client/solid/SolidExceptionTest.java b/solid/src/test/java/com/inrupt/client/solid/SolidExceptionTest.java index 15220345d68..147b48fb094 100644 --- a/solid/src/test/java/com/inrupt/client/solid/SolidExceptionTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/SolidExceptionTest.java @@ -22,6 +22,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.net.URI; + import org.junit.jupiter.api.Test; class SolidExceptionTest { @@ -41,4 +43,14 @@ void checkSolidWrappedException() { assertEquals(upstream, err.getCause()); assertEquals(msg, err.getMessage()); } + + @Test + void checkSolidClientException() { + final String msg = "Error"; + final SolidClientException err = new SolidClientException( + msg, URI.create("https://example.org/request"), 123, null, "some body" + ); + assertEquals(msg, err.getMessage()); + assertEquals(123, err.getProblemDetails().getStatus()); + } } From 6c8c766a60fbc55094e9a3bbb71c81390fffeeb5 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 10:29:01 +0200 Subject: [PATCH 12/28] fixup! Change default ProblemDetails titles --- api/src/main/java/com/inrupt/client/HttpStatus.java | 7 +++++-- .../test/java/com/inrupt/client/HttpStatusTest.java | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/HttpStatus.java b/api/src/main/java/com/inrupt/client/HttpStatus.java index 6d7654eeaca..4ed555ae6ca 100644 --- a/api/src/main/java/com/inrupt/client/HttpStatus.java +++ b/api/src/main/java/com/inrupt/client/HttpStatus.java @@ -51,6 +51,9 @@ enum StatusMessages { TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS, "Too Many Requests"), INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error"); + static final String UNKNOWN_CLIENT_ERROR = "Unknown Client Error"; + static final String UNKNOWN_SERVER_ERROR = "Unknown Server Error"; + private final int code; final String message; @@ -67,9 +70,9 @@ static String getStatusMessage(final int statusCode) { .orElseGet(() -> { // If the status is unknown, default to 400 for client errors and 500 for server errors if (statusCode >= 400 && statusCode <= 499) { - return "Unknown Client Error"; + return HttpStatus.StatusMessages.UNKNOWN_CLIENT_ERROR; } - return "Unknown Server Error"; + return HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR; }); } } diff --git a/api/src/test/java/com/inrupt/client/HttpStatusTest.java b/api/src/test/java/com/inrupt/client/HttpStatusTest.java index 35126cb23c0..3d885ef0e22 100644 --- a/api/src/test/java/com/inrupt/client/HttpStatusTest.java +++ b/api/src/test/java/com/inrupt/client/HttpStatusTest.java @@ -37,7 +37,7 @@ void checkHttpStatusSearchKnownStatus() { void checkHttpStatusSearchUnknownClientError () { assertEquals( HttpStatus.StatusMessages.getStatusMessage(418), - HttpStatus.StatusMessages.BAD_REQUEST.message + HttpStatus.StatusMessages.UNKNOWN_CLIENT_ERROR ); } @@ -45,19 +45,19 @@ void checkHttpStatusSearchUnknownClientError () { void checkHttpStatusSearchUnknownServerError () { assertEquals( HttpStatus.StatusMessages.getStatusMessage(555), - HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR ); assertEquals( HttpStatus.StatusMessages.getStatusMessage(999), - HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR ); assertEquals( HttpStatus.StatusMessages.getStatusMessage(-1), - HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR ); assertEquals( HttpStatus.StatusMessages.getStatusMessage(15), - HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR ); } } From 97f39c7726674ec8cc24bb2cb5910f7dc328d308 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 13:37:15 +0200 Subject: [PATCH 13/28] Remove default ProblemDetails title --- .../java/com/inrupt/client/HttpStatus.java | 83 ------------------- .../com/inrupt/client/ProblemDetails.java | 4 +- .../com/inrupt/client/HttpStatusTest.java | 63 -------------- .../client/solid/BadRequestException.java | 5 +- .../client/solid/ConflictException.java | 3 +- .../client/solid/ForbiddenException.java | 3 +- .../inrupt/client/solid/GoneException.java | 3 +- .../solid/InternalServerErrorException.java | 3 +- .../solid/MethodNotAllowedException.java | 3 +- .../client/solid/NotAcceptableException.java | 3 +- .../client/solid/NotFoundException.java | 3 +- .../solid/PreconditionFailedException.java | 3 +- .../solid/TooManyRequestsException.java | 3 +- .../client/solid/UnauthorizedException.java | 3 +- .../solid/UnsupportedMediaTypeException.java | 3 +- 15 files changed, 15 insertions(+), 173 deletions(-) delete mode 100644 api/src/main/java/com/inrupt/client/HttpStatus.java delete mode 100644 api/src/test/java/com/inrupt/client/HttpStatusTest.java diff --git a/api/src/main/java/com/inrupt/client/HttpStatus.java b/api/src/main/java/com/inrupt/client/HttpStatus.java deleted file mode 100644 index 4ed555ae6ca..00000000000 --- a/api/src/main/java/com/inrupt/client/HttpStatus.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Inrupt Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.inrupt.client; - -import java.util.Arrays; - -public final class HttpStatus { - - public static final int BAD_REQUEST = 400; - public static final int UNAUTHORIZED = 401; - public static final int FORBIDDEN = 403; - public static final int NOT_FOUND = 404; - public static final int METHOD_NOT_ALLOWED = 405; - public static final int NOT_ACCEPTABLE = 406; - public static final int CONFLICT = 409; - public static final int GONE = 410; - public static final int PRECONDITION_FAILED = 412; - public static final int UNSUPPORTED_MEDIA_TYPE = 415; - public static final int TOO_MANY_REQUESTS = 429; - public static final int INTERNAL_SERVER_ERROR = 500; - - enum StatusMessages { - BAD_REQUEST(HttpStatus.BAD_REQUEST, "Bad Request"), - UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "Unauthorized"), - FORBIDDEN(HttpStatus.FORBIDDEN, "Forbidden"), - NOT_FOUND(HttpStatus.NOT_FOUND, "Not Found"), - METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "Method Not Allowed"), - NOT_ACCEPTABLE(HttpStatus.NOT_ACCEPTABLE, "Not Acceptable"), - CONFLICT(HttpStatus.CONFLICT, "Conflict"), - GONE(HttpStatus.GONE, "Gone"), - PRECONDITION_FAILED(HttpStatus.PRECONDITION_FAILED, "Precondition Failed"), - UNSUPPORTED_MEDIA_TYPE(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"), - TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS, "Too Many Requests"), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error"); - - static final String UNKNOWN_CLIENT_ERROR = "Unknown Client Error"; - static final String UNKNOWN_SERVER_ERROR = "Unknown Server Error"; - - private final int code; - final String message; - - StatusMessages(final int code, final String message) { - this.code = code; - this.message = message; - } - - static String getStatusMessage(final int statusCode) { - return Arrays.stream(StatusMessages.values()) - .filter(status -> status.code == statusCode) - .findFirst() - .map(knownStatus -> knownStatus.message) - .orElseGet(() -> { - // If the status is unknown, default to 400 for client errors and 500 for server errors - if (statusCode >= 400 && statusCode <= 499) { - return HttpStatus.StatusMessages.UNKNOWN_CLIENT_ERROR; - } - return HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR; - }); - } - } - - private HttpStatus() { - // noop - } -} diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 43215292359..bbbeefda7d5 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -103,7 +103,7 @@ public static ProblemDetails fromErrorResponse( || (headers != null && !headers.allValues("Content-Type").contains(ProblemDetails.MIME_TYPE))) { return new ProblemDetails( URI.create(ProblemDetails.DEFAULT_TYPE), - HttpStatus.StatusMessages.getStatusMessage(statusCode), + null, null, statusCode, null @@ -130,7 +130,7 @@ public static ProblemDetails fromErrorResponse( } catch (IOException e) { return new ProblemDetails( URI.create(ProblemDetails.DEFAULT_TYPE), - HttpStatus.StatusMessages.getStatusMessage(statusCode), + null, null, statusCode, null diff --git a/api/src/test/java/com/inrupt/client/HttpStatusTest.java b/api/src/test/java/com/inrupt/client/HttpStatusTest.java deleted file mode 100644 index 3d885ef0e22..00000000000 --- a/api/src/test/java/com/inrupt/client/HttpStatusTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Inrupt Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.inrupt.client; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -public class HttpStatusTest { - @Test - void checkHttpStatusSearchKnownStatus() { - assertEquals( - HttpStatus.StatusMessages.getStatusMessage(HttpStatus.NOT_FOUND), - HttpStatus.StatusMessages.NOT_FOUND.message - ); - } - - @Test - void checkHttpStatusSearchUnknownClientError () { - assertEquals( - HttpStatus.StatusMessages.getStatusMessage(418), - HttpStatus.StatusMessages.UNKNOWN_CLIENT_ERROR - ); - } - - @Test - void checkHttpStatusSearchUnknownServerError () { - assertEquals( - HttpStatus.StatusMessages.getStatusMessage(555), - HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR - ); - assertEquals( - HttpStatus.StatusMessages.getStatusMessage(999), - HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR - ); - assertEquals( - HttpStatus.StatusMessages.getStatusMessage(-1), - HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR - ); - assertEquals( - HttpStatus.StatusMessages.getStatusMessage(15), - HttpStatus.StatusMessages.UNKNOWN_SERVER_ERROR - ); - } -} diff --git a/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java b/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java index 0f380e5632c..2ff7d587952 100644 --- a/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java +++ b/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class BadRequestException extends SolidClientException { private static final long serialVersionUID = -3379457428921025570L; - public static final int STATUS_CODE = HttpStatus.BAD_REQUEST; + public static final int STATUS_CODE = 400; /** * Create a BadRequestException exception. @@ -48,6 +47,6 @@ public BadRequestException( final URI uri, final Headers headers, final String body) { - super(message, uri, HttpStatus.BAD_REQUEST, headers, body); + super(message, uri, 400, headers, body); } } diff --git a/solid/src/main/java/com/inrupt/client/solid/ConflictException.java b/solid/src/main/java/com/inrupt/client/solid/ConflictException.java index fe2a15385f5..561dfcbff65 100644 --- a/solid/src/main/java/com/inrupt/client/solid/ConflictException.java +++ b/solid/src/main/java/com/inrupt/client/solid/ConflictException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class ConflictException extends SolidClientException { private static final long serialVersionUID = -203198307847520748L; - public static final int STATUS_CODE = HttpStatus.CONFLICT; + public static final int STATUS_CODE = 412; /** * Create a ConflictException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/ForbiddenException.java b/solid/src/main/java/com/inrupt/client/solid/ForbiddenException.java index 20bf1fd32dc..7aeb3219bec 100644 --- a/solid/src/main/java/com/inrupt/client/solid/ForbiddenException.java +++ b/solid/src/main/java/com/inrupt/client/solid/ForbiddenException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class ForbiddenException extends SolidClientException { private static final long serialVersionUID = 3299286274724874244L; - public static final int STATUS_CODE = HttpStatus.FORBIDDEN; + public static final int STATUS_CODE = 403; /** * Create a ForbiddenException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/GoneException.java b/solid/src/main/java/com/inrupt/client/solid/GoneException.java index 2d247db4c87..0882302e27b 100644 --- a/solid/src/main/java/com/inrupt/client/solid/GoneException.java +++ b/solid/src/main/java/com/inrupt/client/solid/GoneException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class GoneException extends SolidClientException { private static final long serialVersionUID = -6892345582498100242L; - public static final int STATUS_CODE = HttpStatus.GONE; + public static final int STATUS_CODE = 410; /** * Create a GoneException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/InternalServerErrorException.java b/solid/src/main/java/com/inrupt/client/solid/InternalServerErrorException.java index d57ac187d86..ea9acb1c56e 100644 --- a/solid/src/main/java/com/inrupt/client/solid/InternalServerErrorException.java +++ b/solid/src/main/java/com/inrupt/client/solid/InternalServerErrorException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class InternalServerErrorException extends SolidClientException { private static final long serialVersionUID = -6672490715281719330L; - public static final int STATUS_CODE = HttpStatus.INTERNAL_SERVER_ERROR; + public static final int STATUS_CODE = 500; /** * Create an InternalServerErrorException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/MethodNotAllowedException.java b/solid/src/main/java/com/inrupt/client/solid/MethodNotAllowedException.java index dbba0b6003c..d472b5fcbd3 100644 --- a/solid/src/main/java/com/inrupt/client/solid/MethodNotAllowedException.java +++ b/solid/src/main/java/com/inrupt/client/solid/MethodNotAllowedException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class MethodNotAllowedException extends SolidClientException { private static final long serialVersionUID = -9125437562813923030L; - public static final int STATUS_CODE = HttpStatus.METHOD_NOT_ALLOWED; + public static final int STATUS_CODE = 405; /** * Create a MethodNotAllowedException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/NotAcceptableException.java b/solid/src/main/java/com/inrupt/client/solid/NotAcceptableException.java index 18b7bf929e5..53744c0b2e8 100644 --- a/solid/src/main/java/com/inrupt/client/solid/NotAcceptableException.java +++ b/solid/src/main/java/com/inrupt/client/solid/NotAcceptableException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class NotAcceptableException extends SolidClientException { private static final long serialVersionUID = 6594993822477388733L; - public static final int STATUS_CODE = HttpStatus.NOT_ACCEPTABLE; + public static final int STATUS_CODE = 406; /** * Create a NotAcceptableException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/NotFoundException.java b/solid/src/main/java/com/inrupt/client/solid/NotFoundException.java index f0cf9ef7164..466bed74894 100644 --- a/solid/src/main/java/com/inrupt/client/solid/NotFoundException.java +++ b/solid/src/main/java/com/inrupt/client/solid/NotFoundException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class NotFoundException extends SolidClientException { private static final long serialVersionUID = -2256628528500739683L; - public static final int STATUS_CODE = HttpStatus.NOT_FOUND; + public static final int STATUS_CODE = 404; /** * Create a NotFoundException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java b/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java index 4584bac47b9..9859a6aa5ad 100644 --- a/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java +++ b/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class PreconditionFailedException extends SolidClientException { private static final long serialVersionUID = 4205761003697773528L; - public static final int STATUS_CODE = HttpStatus.PRECONDITION_FAILED; + public static final int STATUS_CODE = 419; /** * Create a PreconditionFailedException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java b/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java index 018a95e1e8f..4f299fc9492 100644 --- a/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java +++ b/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class TooManyRequestsException extends SolidClientException { private static final long serialVersionUID = -1798491190232642824L; - public static final int STATUS_CODE = HttpStatus.TOO_MANY_REQUESTS; + public static final int STATUS_CODE = 429; /** * Create a TooManyRequestsException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/UnauthorizedException.java b/solid/src/main/java/com/inrupt/client/solid/UnauthorizedException.java index 7ad2d425137..db8615df958 100644 --- a/solid/src/main/java/com/inrupt/client/solid/UnauthorizedException.java +++ b/solid/src/main/java/com/inrupt/client/solid/UnauthorizedException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class UnauthorizedException extends SolidClientException { private static final long serialVersionUID = -3219668517323678497L; - public static final int STATUS_CODE = HttpStatus.UNAUTHORIZED; + public static final int STATUS_CODE = 401; /** * Create an UnauthorizedException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/UnsupportedMediaTypeException.java b/solid/src/main/java/com/inrupt/client/solid/UnsupportedMediaTypeException.java index 866fe70cf7d..27e9c96953d 100644 --- a/solid/src/main/java/com/inrupt/client/solid/UnsupportedMediaTypeException.java +++ b/solid/src/main/java/com/inrupt/client/solid/UnsupportedMediaTypeException.java @@ -21,7 +21,6 @@ package com.inrupt.client.solid; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import java.net.URI; @@ -33,7 +32,7 @@ public class UnsupportedMediaTypeException extends SolidClientException { private static final long serialVersionUID = 1312856145838280673L; - public static final int STATUS_CODE = HttpStatus.UNSUPPORTED_MEDIA_TYPE; + public static final int STATUS_CODE = 415; /** * Create an UnsupportedMediaTypeException exception. From 662ad838bb3ad70bb0aae283656a8eb5d6a8a183 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 14:35:29 +0200 Subject: [PATCH 14/28] fixup! Remove default ProblemDetails title --- .../main/java/com/inrupt/client/solid/ConflictException.java | 2 +- .../com/inrupt/client/solid/PreconditionFailedException.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/solid/src/main/java/com/inrupt/client/solid/ConflictException.java b/solid/src/main/java/com/inrupt/client/solid/ConflictException.java index 561dfcbff65..d88386eb724 100644 --- a/solid/src/main/java/com/inrupt/client/solid/ConflictException.java +++ b/solid/src/main/java/com/inrupt/client/solid/ConflictException.java @@ -32,7 +32,7 @@ public class ConflictException extends SolidClientException { private static final long serialVersionUID = -203198307847520748L; - public static final int STATUS_CODE = 412; + public static final int STATUS_CODE = 409; /** * Create a ConflictException exception. diff --git a/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java b/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java index 9859a6aa5ad..461c937a0eb 100644 --- a/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java +++ b/solid/src/main/java/com/inrupt/client/solid/PreconditionFailedException.java @@ -32,7 +32,7 @@ public class PreconditionFailedException extends SolidClientException { private static final long serialVersionUID = 4205761003697773528L; - public static final int STATUS_CODE = 419; + public static final int STATUS_CODE = 412; /** * Create a PreconditionFailedException exception. From 540fe127e94afa4f8c087631b79a07a70829072e Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 14:36:29 +0200 Subject: [PATCH 15/28] fixup! fixup! Remove default ProblemDetails title --- api/src/main/java/com/inrupt/client/ProblemDetails.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index bbbeefda7d5..d171dd0251c 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -116,13 +116,11 @@ public static ProblemDetails fromErrorResponse( ); final URI type = Optional.ofNullable(pdData.getType()) .orElse(URI.create(ProblemDetails.DEFAULT_TYPE)); - final String title = Optional.ofNullable(pdData.getTitle()) - .orElse(HttpStatus.StatusMessages.getStatusMessage(statusCode)); // JSON mappers map invalid integers to 0, which is an invalid value in our case anyway. final int status = Optional.of(pdData.getStatus()).filter(s -> s != 0).orElse(statusCode); return new ProblemDetails( type, - title, + pdData.getTitle(), pdData.getDetails(), status, pdData.getInstance() From 2452b49c25ed4ee8f98d376841564208a226dd45 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 14:40:22 +0200 Subject: [PATCH 16/28] fixup! fixup! fixup! Remove default ProblemDetails title --- .../test/java/com/inrupt/client/solid/ProblemDetailsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java index 227ea50d4ae..daa83242803 100644 --- a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -55,7 +55,7 @@ void testEmptyProblemDetails() { ); assertEquals(ProblemDetails.DEFAULT_TYPE, pd.getType().toString()); assertEquals(statusCode, pd.getStatus()); - Assertions.assertEquals("Bad Request", pd.getTitle()); + assertNull(pd.getTitle()); assertNull(pd.getDetails()); assertNull(pd.getInstance()); } From 80be1aca5322afe1162587ad9af70d05cbb21baa Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 14:51:09 +0200 Subject: [PATCH 17/28] Replace ProblemDetailsData with private setters --- .../com/inrupt/client/ProblemDetails.java | 44 ++++++++++++--- .../com/inrupt/client/ProblemDetailsData.java | 55 ------------------- 2 files changed, 37 insertions(+), 62 deletions(-) delete mode 100644 api/src/main/java/com/inrupt/client/ProblemDetailsData.java diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index d171dd0251c..3b770b8c6b0 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -36,11 +36,11 @@ public class ProblemDetails { public static final String MIME_TYPE = "application/problem+json"; public static final String DEFAULT_TYPE = "about:blank"; - private final URI type; - private final String title; - private final String details; - private final int status; - private final URI instance; + private URI type; + private String title; + private String details; + private int status; + private URI instance; private static JsonService jsonService; private static boolean isJsonServiceInitialized; @@ -78,6 +78,36 @@ public URI getInstance() { return this.instance; }; + // Default constructor required for JSON serialization, but package private. + ProblemDetails() { + /* noop */ + } + + // The setters are package-private so that the class is publicly immutable. + void setType(URI type) { + this.type = type; + } + + // The setters are package-private so that the class is publicly immutable. + void setTitle(String title) { + this.title = title; + } + + // The setters are package-private so that the class is publicly immutable but can be deserialized from JSON. + void setDetails(String details) { + this.details = details; + } + + // The setters are package-private so that the class is publicly immutable but can be deserialized from JSON. + void setStatus(int status) { + this.status = status; + } + + // The setters are package-private so that the class is publicly immutable but can be deserialized from JSON. + void setInstance(URI instance) { + this.instance = instance; + } + private static JsonService getJsonService() { if (ProblemDetails.isJsonServiceInitialized) { return ProblemDetails.jsonService; @@ -110,9 +140,9 @@ public static ProblemDetails fromErrorResponse( ); } try { - final ProblemDetailsData pdData = jsonService.fromJson( + final ProblemDetails pdData = jsonService.fromJson( new ByteArrayInputStream(body), - ProblemDetailsData.class + ProblemDetails.class ); final URI type = Optional.ofNullable(pdData.getType()) .orElse(URI.create(ProblemDetails.DEFAULT_TYPE)); diff --git a/api/src/main/java/com/inrupt/client/ProblemDetailsData.java b/api/src/main/java/com/inrupt/client/ProblemDetailsData.java deleted file mode 100644 index 876967eb1dc..00000000000 --- a/api/src/main/java/com/inrupt/client/ProblemDetailsData.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.inrupt.client; - -import java.net.URI; - -/** - * This package-private mutable class is used for JSON deserialization. - * Once instantiated, it is used to build an immutable {@link ProblemDetails}. - */ -class ProblemDetailsData { - private URI type; - private String title; - private String details; - private int status; - private URI instance; - - public URI getType() { - return this.type; - }; - - public String getTitle() { - return this.title; - }; - - public String getDetails() { - return this.details; - }; - - public int getStatus() { - return this.status; - }; - - public URI getInstance() { - return this.instance; - }; - - public void setType(final URI type) { - this.type = type; - } - - public void setTitle(final String title) { - this.title = title; - } - - public void setDetails(final String details) { - this.details = details; - } - - public void setStatus(final int status) { - this.status = status; - } - - public void setInstance(final URI instance) { - this.instance = instance; - } -} From ca829b2baa5967c015cdd409e88f067cd2d93dbf Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 14:54:36 +0200 Subject: [PATCH 18/28] Revert "Replace ProblemDetailsData w prv. setters" This reverts commit 499164d6d61338e0ffcc4b724f5793e295f6c2cf. --- .../com/inrupt/client/ProblemDetails.java | 44 +++------------ .../com/inrupt/client/ProblemDetailsData.java | 55 +++++++++++++++++++ 2 files changed, 62 insertions(+), 37 deletions(-) create mode 100644 api/src/main/java/com/inrupt/client/ProblemDetailsData.java diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 3b770b8c6b0..d171dd0251c 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -36,11 +36,11 @@ public class ProblemDetails { public static final String MIME_TYPE = "application/problem+json"; public static final String DEFAULT_TYPE = "about:blank"; - private URI type; - private String title; - private String details; - private int status; - private URI instance; + private final URI type; + private final String title; + private final String details; + private final int status; + private final URI instance; private static JsonService jsonService; private static boolean isJsonServiceInitialized; @@ -78,36 +78,6 @@ public URI getInstance() { return this.instance; }; - // Default constructor required for JSON serialization, but package private. - ProblemDetails() { - /* noop */ - } - - // The setters are package-private so that the class is publicly immutable. - void setType(URI type) { - this.type = type; - } - - // The setters are package-private so that the class is publicly immutable. - void setTitle(String title) { - this.title = title; - } - - // The setters are package-private so that the class is publicly immutable but can be deserialized from JSON. - void setDetails(String details) { - this.details = details; - } - - // The setters are package-private so that the class is publicly immutable but can be deserialized from JSON. - void setStatus(int status) { - this.status = status; - } - - // The setters are package-private so that the class is publicly immutable but can be deserialized from JSON. - void setInstance(URI instance) { - this.instance = instance; - } - private static JsonService getJsonService() { if (ProblemDetails.isJsonServiceInitialized) { return ProblemDetails.jsonService; @@ -140,9 +110,9 @@ public static ProblemDetails fromErrorResponse( ); } try { - final ProblemDetails pdData = jsonService.fromJson( + final ProblemDetailsData pdData = jsonService.fromJson( new ByteArrayInputStream(body), - ProblemDetails.class + ProblemDetailsData.class ); final URI type = Optional.ofNullable(pdData.getType()) .orElse(URI.create(ProblemDetails.DEFAULT_TYPE)); diff --git a/api/src/main/java/com/inrupt/client/ProblemDetailsData.java b/api/src/main/java/com/inrupt/client/ProblemDetailsData.java new file mode 100644 index 00000000000..876967eb1dc --- /dev/null +++ b/api/src/main/java/com/inrupt/client/ProblemDetailsData.java @@ -0,0 +1,55 @@ +package com.inrupt.client; + +import java.net.URI; + +/** + * This package-private mutable class is used for JSON deserialization. + * Once instantiated, it is used to build an immutable {@link ProblemDetails}. + */ +class ProblemDetailsData { + private URI type; + private String title; + private String details; + private int status; + private URI instance; + + public URI getType() { + return this.type; + }; + + public String getTitle() { + return this.title; + }; + + public String getDetails() { + return this.details; + }; + + public int getStatus() { + return this.status; + }; + + public URI getInstance() { + return this.instance; + }; + + public void setType(final URI type) { + this.type = type; + } + + public void setTitle(final String title) { + this.title = title; + } + + public void setDetails(final String details) { + this.details = details; + } + + public void setStatus(final int status) { + this.status = status; + } + + public void setInstance(final URI instance) { + this.instance = instance; + } +} From 183289843a723d710f89daf9f771ab47fc89d785 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 15:16:40 +0200 Subject: [PATCH 19/28] Make data class public That is required by jsonb --- .../com/inrupt/client/ProblemDetails.java | 18 ++++-- .../com/inrupt/client/ProblemDetailsData.java | 55 ------------------- .../client/solid/ProblemDetailsTest.java | 15 +++++ 3 files changed, 28 insertions(+), 60 deletions(-) delete mode 100644 api/src/main/java/com/inrupt/client/ProblemDetailsData.java diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index d171dd0251c..172a87eaf20 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -78,6 +78,14 @@ public URI getInstance() { return this.instance; }; + public static class ProblemDetailsData { + public URI type; + public String title; + public String details; + public int status; + public URI instance; + } + private static JsonService getJsonService() { if (ProblemDetails.isJsonServiceInitialized) { return ProblemDetails.jsonService; @@ -114,16 +122,16 @@ public static ProblemDetails fromErrorResponse( new ByteArrayInputStream(body), ProblemDetailsData.class ); - final URI type = Optional.ofNullable(pdData.getType()) + final URI type = Optional.ofNullable(pdData.type) .orElse(URI.create(ProblemDetails.DEFAULT_TYPE)); // JSON mappers map invalid integers to 0, which is an invalid value in our case anyway. - final int status = Optional.of(pdData.getStatus()).filter(s -> s != 0).orElse(statusCode); + final int status = Optional.of(pdData.status).filter(s -> s != 0).orElse(statusCode); return new ProblemDetails( type, - pdData.getTitle(), - pdData.getDetails(), + pdData.title, + pdData.details, status, - pdData.getInstance() + pdData.instance ); } catch (IOException e) { return new ProblemDetails( diff --git a/api/src/main/java/com/inrupt/client/ProblemDetailsData.java b/api/src/main/java/com/inrupt/client/ProblemDetailsData.java deleted file mode 100644 index 876967eb1dc..00000000000 --- a/api/src/main/java/com/inrupt/client/ProblemDetailsData.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.inrupt.client; - -import java.net.URI; - -/** - * This package-private mutable class is used for JSON deserialization. - * Once instantiated, it is used to build an immutable {@link ProblemDetails}. - */ -class ProblemDetailsData { - private URI type; - private String title; - private String details; - private int status; - private URI instance; - - public URI getType() { - return this.type; - }; - - public String getTitle() { - return this.title; - }; - - public String getDetails() { - return this.details; - }; - - public int getStatus() { - return this.status; - }; - - public URI getInstance() { - return this.instance; - }; - - public void setType(final URI type) { - this.type = type; - } - - public void setTitle(final String title) { - this.title = title; - } - - public void setDetails(final String details) { - this.details = details; - } - - public void setStatus(final int status) { - this.status = status; - } - - public void setInstance(final URI instance) { - this.instance = instance; - } -} diff --git a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java index daa83242803..68e007a76e7 100644 --- a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -151,4 +151,19 @@ void testInvalidInstanceProblemDetails() { ); assertNull(pd.getInstance()); } + + @Test + void testInvalidProblemDetails() { + final int statusCode = 400; + final ProblemDetails pd = ProblemDetails.fromErrorResponse( + statusCode, + mockProblemDetailsHeader(), + "Not valid application/problem+json".getBytes() + ); + assertEquals(ProblemDetails.DEFAULT_TYPE, pd.getType().toString()); + assertEquals(statusCode, pd.getStatus()); + assertNull(pd.getTitle()); + assertNull(pd.getDetails()); + assertNull(pd.getInstance()); + } } From 4638882804bc0e972ae940637989d07cbac635bb Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 15:51:28 +0200 Subject: [PATCH 20/28] Add body handler to throw on HTTP error --- .../main/java/com/inrupt/client/Response.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/api/src/main/java/com/inrupt/client/Response.java b/api/src/main/java/com/inrupt/client/Response.java index c8398c04c45..bbbfda47098 100644 --- a/api/src/main/java/com/inrupt/client/Response.java +++ b/api/src/main/java/com/inrupt/client/Response.java @@ -26,6 +26,7 @@ import java.io.InputStream; import java.net.URI; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; /** * An HTTP Response. @@ -152,6 +153,24 @@ public static BodyHandler discarding() { return responseInfo -> null; } + /** + * Throws on HTTP error, or apply the provided body handler. + * @param handler the body handler to apply on non-error HTTP responses + * @return the body handler + * @param the type of the body handler + */ + public static Response.BodyHandler throwOnError(Response.BodyHandler handler) { + return responseinfo -> { + if (responseinfo.statusCode() > 399) { + throw new ClientHttpException( + "An HTTP error has been returned from "+responseinfo.uri()+" with status code "+responseinfo.statusCode(), + responseinfo.uri(), responseinfo.statusCode(), responseinfo.headers(), new String(responseinfo.body().array(), StandardCharsets.UTF_8) + ); + } + return handler.apply(responseinfo); + }; + } + private BodyHandlers() { // Prevent instantiation } From 641289543b784f6cd8b96c4d07219ba8cf64c8f0 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 16:55:30 +0200 Subject: [PATCH 21/28] fixup! Add body handler to throw on HTTP error --- api/src/main/java/com/inrupt/client/Response.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/Response.java b/api/src/main/java/com/inrupt/client/Response.java index bbbfda47098..baddf0532d8 100644 --- a/api/src/main/java/com/inrupt/client/Response.java +++ b/api/src/main/java/com/inrupt/client/Response.java @@ -27,6 +27,7 @@ import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.function.Function; /** * An HTTP Response. @@ -159,9 +160,12 @@ public static BodyHandler discarding() { * @return the body handler * @param the type of the body handler */ - public static Response.BodyHandler throwOnError(Response.BodyHandler handler) { + public static Response.BodyHandler throwOnError( + Response.BodyHandler handler, + Function isSuccess + ) { return responseinfo -> { - if (responseinfo.statusCode() > 399) { + if (isSuccess.apply(responseinfo)) { throw new ClientHttpException( "An HTTP error has been returned from "+responseinfo.uri()+" with status code "+responseinfo.statusCode(), responseinfo.uri(), responseinfo.statusCode(), responseinfo.headers(), new String(responseinfo.body().array(), StandardCharsets.UTF_8) From 71d63028e09b246e03457f401188b7940479ba7c Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 17:01:23 +0200 Subject: [PATCH 22/28] Lint --- api/src/main/java/com/inrupt/client/Response.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/Response.java b/api/src/main/java/com/inrupt/client/Response.java index baddf0532d8..50f74b0a94e 100644 --- a/api/src/main/java/com/inrupt/client/Response.java +++ b/api/src/main/java/com/inrupt/client/Response.java @@ -157,18 +157,25 @@ public static BodyHandler discarding() { /** * Throws on HTTP error, or apply the provided body handler. * @param handler the body handler to apply on non-error HTTP responses + * @param isSuccess a callback determining error cases * @return the body handler * @param the type of the body handler */ public static Response.BodyHandler throwOnError( - Response.BodyHandler handler, - Function isSuccess + final Response.BodyHandler handler, + final Function isSuccess ) { return responseinfo -> { if (isSuccess.apply(responseinfo)) { throw new ClientHttpException( - "An HTTP error has been returned from "+responseinfo.uri()+" with status code "+responseinfo.statusCode(), - responseinfo.uri(), responseinfo.statusCode(), responseinfo.headers(), new String(responseinfo.body().array(), StandardCharsets.UTF_8) + "An HTTP error has been returned from " + + responseinfo.uri() + + " with status code " + + responseinfo.statusCode(), + responseinfo.uri(), + responseinfo.statusCode(), + responseinfo.headers(), + new String(responseinfo.body().array(), StandardCharsets.UTF_8) ); } return handler.apply(responseinfo); From d75f6e5c76c8835a7e44e6819d25bd84e6f9ae54 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 22:53:57 +0200 Subject: [PATCH 23/28] fixup! fixup! Add body handler to throw on HTTP error --- api/src/main/java/com/inrupt/client/Response.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/inrupt/client/Response.java b/api/src/main/java/com/inrupt/client/Response.java index 50f74b0a94e..f612db1c516 100644 --- a/api/src/main/java/com/inrupt/client/Response.java +++ b/api/src/main/java/com/inrupt/client/Response.java @@ -166,7 +166,7 @@ public static Response.BodyHandler throwOnError( final Function isSuccess ) { return responseinfo -> { - if (isSuccess.apply(responseinfo)) { + if (!isSuccess.apply(responseinfo)) { throw new ClientHttpException( "An HTTP error has been returned from " + responseinfo.uri() From d91de68e1ee81a31da0a6fc9e73f871046ffc4f1 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 22:54:04 +0200 Subject: [PATCH 24/28] Rephrase comment --- api/src/main/java/com/inrupt/client/ProblemDetails.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 172a87eaf20..34a7aa0e344 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -90,8 +90,8 @@ private static JsonService getJsonService() { if (ProblemDetails.isJsonServiceInitialized) { return ProblemDetails.jsonService; } - // It is acceptable for a JenaBodyHandlers 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. + // It is a legitimate use case that this is loaded in a context where no implementation of the JSON service is + // available. On SPI lookup failure, the ProblemDetails exceptions will fall back to default and not be parsed. try { ProblemDetails.jsonService = ServiceProvider.getJsonService(); } catch (IllegalStateException e) { From 600bfdceea225a64c061c458ff6b2b20bed5fd22 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 16 Apr 2024 22:55:54 +0200 Subject: [PATCH 25/28] fixup! fixup! fixup! Remove default ProblemDetails title --- .../main/java/com/inrupt/client/solid/BadRequestException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java b/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java index 2ff7d587952..3fc0ad547f1 100644 --- a/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java +++ b/solid/src/main/java/com/inrupt/client/solid/BadRequestException.java @@ -47,6 +47,6 @@ public BadRequestException( final URI uri, final Headers headers, final String body) { - super(message, uri, 400, headers, body); + super(message, uri, STATUS_CODE, headers, body); } } From 5a1bd37c5b19d5ec9f463c33c26da2135548f423 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Wed, 17 Apr 2024 09:05:15 +0200 Subject: [PATCH 26/28] Add exception mapper to throwing body handler --- .../main/java/com/inrupt/client/Response.java | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/Response.java b/api/src/main/java/com/inrupt/client/Response.java index f612db1c516..5fa2e3cf812 100644 --- a/api/src/main/java/com/inrupt/client/Response.java +++ b/api/src/main/java/com/inrupt/client/Response.java @@ -155,33 +155,53 @@ public static BodyHandler discarding() { } /** - * Throws on HTTP error, or apply the provided body handler. + * Throws on HTTP error using the provided mapper, or apply the provided body handler. * @param handler the body handler to apply on non-error HTTP responses * @param isSuccess a callback determining error cases + * @param exceptionMapper the exception mapper * @return the body handler * @param the type of the body handler */ public static Response.BodyHandler throwOnError( final Response.BodyHandler handler, - final Function isSuccess + final Function isSuccess, + final Function exceptionMapper ) { return responseinfo -> { if (!isSuccess.apply(responseinfo)) { - throw new ClientHttpException( - "An HTTP error has been returned from " - + responseinfo.uri() - + " with status code " - + responseinfo.statusCode(), - responseinfo.uri(), - responseinfo.statusCode(), - responseinfo.headers(), - new String(responseinfo.body().array(), StandardCharsets.UTF_8) - ); + throw exceptionMapper.apply(responseinfo); } return handler.apply(responseinfo); }; } + /** + * Throws on HTTP error, or apply the provided body handler. + * @param handler the body handler to apply on non-error HTTP responses + * @param isSuccess a callback determining error cases + * @return the body handler + * @param the type of the body handler + */ + public static Response.BodyHandler throwOnError( + final Response.BodyHandler handler, + final Function isSuccess + ) { + final Function defaultMapper = responseInfo -> + new ClientHttpException( + "An HTTP error has been returned from " + + responseInfo.uri() + + " with status code " + + responseInfo.statusCode(), + responseInfo.uri(), + responseInfo.statusCode(), + responseInfo.headers(), + new String(responseInfo.body().array(), StandardCharsets.UTF_8) + ); + return throwOnError(handler, isSuccess, defaultMapper); + } + + + private BodyHandlers() { // Prevent instantiation } From aff5c70be1e3e075356eb31b03846f5aca07f61d Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Wed, 17 Apr 2024 15:54:29 +0200 Subject: [PATCH 27/28] Add Javadoc to inner ProblemDetails.Data class --- api/src/main/java/com/inrupt/client/ProblemDetails.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 34a7aa0e344..998dd534980 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -78,7 +78,10 @@ public URI getInstance() { return this.instance; }; - public static class ProblemDetailsData { + /** + * This inner class is only ever used for JSON deserialization. Please do not use in any other context. + */ + public static class Data { public URI type; public String title; public String details; @@ -118,9 +121,9 @@ public static ProblemDetails fromErrorResponse( ); } try { - final ProblemDetailsData pdData = jsonService.fromJson( + final Data pdData = jsonService.fromJson( new ByteArrayInputStream(body), - ProblemDetailsData.class + Data.class ); final URI type = Optional.ofNullable(pdData.type) .orElse(URI.create(ProblemDetails.DEFAULT_TYPE)); From 3a5f9815e08ff2128206ce62f61f6506aeea2d36 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Wed, 17 Apr 2024 15:57:56 +0200 Subject: [PATCH 28/28] Remove response URI from exception message --- api/src/main/java/com/inrupt/client/Response.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/Response.java b/api/src/main/java/com/inrupt/client/Response.java index 5fa2e3cf812..30acaffa1d7 100644 --- a/api/src/main/java/com/inrupt/client/Response.java +++ b/api/src/main/java/com/inrupt/client/Response.java @@ -188,10 +188,7 @@ public static Response.BodyHandler throwOnError( ) { final Function defaultMapper = responseInfo -> new ClientHttpException( - "An HTTP error has been returned from " - + responseInfo.uri() - + " with status code " - + responseInfo.statusCode(), + "An HTTP error has been returned, with status code " + responseInfo.statusCode(), responseInfo.uri(), responseInfo.statusCode(), responseInfo.headers(),