From 6fc35db001073d371ab944cf58201f2f913a2dff Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 4 Apr 2024 11:26:34 +0200 Subject: [PATCH 01/18] Basic structure of the ProblemDetails class --- .../inrupt/client/ClientHttpException.java | 4 ++ .../com/inrupt/client/ProblemDetails.java | 63 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 api/src/main/java/com/inrupt/client/ClientHttpException.java create mode 100644 api/src/main/java/com/inrupt/client/ProblemDetails.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..f2a99f68ab7 --- /dev/null +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -0,0 +1,4 @@ +package com.inrupt.client; + +public class ClientHttpException { +} 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..ee5fe0c9890 --- /dev/null +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -0,0 +1,63 @@ +package com.inrupt.client; + +import java.net.URI; + +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 fromDefaultResponse(int code, String status) { + return new ProblemDetails( + URI.create(DEFAULT_TYPE), + status, + null, + code, + null + ); + } +} From f689e52ef4fdcc3dbe265666a1468e4f72b72bbb Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 4 Apr 2024 11:43:44 +0200 Subject: [PATCH 02/18] Initial ClientHttpException class --- .../inrupt/client/ClientHttpException.java | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/inrupt/client/ClientHttpException.java b/api/src/main/java/com/inrupt/client/ClientHttpException.java index f2a99f68ab7..f38f87121cb 100644 --- a/api/src/main/java/com/inrupt/client/ClientHttpException.java +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -1,4 +1,53 @@ package com.inrupt.client; -public class ClientHttpException { +import com.inrupt.client.spi.JsonService; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; + +public class ClientHttpException extends InruptClientException { + private ProblemDetails problemDetails; + + /** + * Create a ClientHttpException. + * @param message the exception message + */ + public ClientHttpException(ProblemDetails problemDetails, String message) { + super(message); + this.problemDetails = problemDetails; + } + + /** + * Create a ClientHttpException. + * @param message the exception message + * @param cause a wrapped exception cause + */ + public ClientHttpException(ProblemDetails problemDetails, String message, Exception cause) { + super(message, cause); + this.problemDetails = problemDetails; + } + + public ProblemDetails getProblemDetails() { + return this.problemDetails; + } + + private static ClientHttpException buildDefaultException(String message, int code, String status) { + return new ClientHttpException( + ProblemDetails.fromDefaultResponse(code, status), + message + ); + } + + public static ClientHttpException fromResponse(String message, int code, String status, Headers headers, InputStream body, JsonService jsonService) { + if (!headers.allValues("Content-Type").contains(ProblemDetails.MIME_TYPE)) { + return buildDefaultException(message, code, status); + } + try { + ProblemDetails pd = jsonService.fromJson(body, ProblemDetails.class); + return new ClientHttpException(pd, message); + } catch (IOException e) { + return buildDefaultException(message, code, status); + } + } } From de55a3dfc5ec9465d3b18e7ffb1903544794f96d Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Wed, 10 Apr 2024 14:50:44 +0200 Subject: [PATCH 03/18] Refactor specialized exception constructors The exception are now built taking a ProblemDetails in. --- .../inrupt/client/ClientHttpException.java | 27 +------- .../com/inrupt/client/ProblemDetails.java | 10 --- .../client/solid/BadRequestException.java | 20 ++++++ .../client/solid/ConflictException.java | 20 ++++++ .../client/solid/ForbiddenException.java | 20 ++++++ .../inrupt/client/solid/GoneException.java | 20 ++++++ .../solid/InternalServerErrorException.java | 20 ++++++ .../solid/MethodNotAllowedException.java | 20 ++++++ .../client/solid/NotAcceptableException.java | 20 ++++++ .../client/solid/NotFoundException.java | 20 ++++++ .../solid/PreconditionFailedException.java | 20 ++++++ .../client/solid/SolidClientException.java | 64 ++++++++++++++++++- .../solid/TooManyRequestsException.java | 20 ++++++ .../client/solid/UnauthorizedException.java | 20 ++++++ .../solid/UnsupportedMediaTypeException.java | 20 ++++++ 15 files changed, 303 insertions(+), 38 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ClientHttpException.java b/api/src/main/java/com/inrupt/client/ClientHttpException.java index f38f87121cb..262fef49645 100644 --- a/api/src/main/java/com/inrupt/client/ClientHttpException.java +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -1,13 +1,7 @@ package com.inrupt.client; -import com.inrupt.client.spi.JsonService; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; - public class ClientHttpException extends InruptClientException { - private ProblemDetails problemDetails; + private final ProblemDetails problemDetails; /** * Create a ClientHttpException. @@ -31,23 +25,4 @@ public ClientHttpException(ProblemDetails problemDetails, String message, Except public ProblemDetails getProblemDetails() { return this.problemDetails; } - - private static ClientHttpException buildDefaultException(String message, int code, String status) { - return new ClientHttpException( - ProblemDetails.fromDefaultResponse(code, status), - message - ); - } - - public static ClientHttpException fromResponse(String message, int code, String status, Headers headers, InputStream body, JsonService jsonService) { - if (!headers.allValues("Content-Type").contains(ProblemDetails.MIME_TYPE)) { - return buildDefaultException(message, code, status); - } - try { - ProblemDetails pd = jsonService.fromJson(body, ProblemDetails.class); - return new ClientHttpException(pd, message); - } catch (IOException e) { - return buildDefaultException(message, code, status); - } - } } diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index ee5fe0c9890..f4bd3a0a810 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -50,14 +50,4 @@ public int getStatus() { public URI getInstance() { return this.instance; }; - - public static ProblemDetails fromDefaultResponse(int code, String status) { - return new ProblemDetails( - URI.create(DEFAULT_TYPE), - status, - null, - code, - null - ); - } } 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..4b107d638cc 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class BadRequestException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public BadRequestException( final String message, @@ -49,4 +51,22 @@ public BadRequestException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a BadRequestException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public BadRequestException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, 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..154f66a85a5 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class ConflictException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public ConflictException( final String message, @@ -49,4 +51,22 @@ public ConflictException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a ConflictException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public ConflictException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..97d4f73ebd6 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class ForbiddenException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public ForbiddenException( final String message, @@ -49,4 +51,22 @@ public ForbiddenException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a ForbiddenException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public ForbiddenException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..cfbce185429 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class GoneException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public GoneException( final String message, @@ -49,4 +51,22 @@ public GoneException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a GoneException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public GoneException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..b30f8ddf1e1 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class InternalServerErrorException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public InternalServerErrorException( final String message, @@ -49,4 +51,22 @@ public InternalServerErrorException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create an InternalServerErrorException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public InternalServerErrorException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..f69183d60b9 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class MethodNotAllowedException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public MethodNotAllowedException( final String message, @@ -49,4 +51,22 @@ public MethodNotAllowedException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a MethodNotAllowedException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public MethodNotAllowedException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..1738e86d2c6 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class NotAcceptableException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public NotAcceptableException( final String message, @@ -49,4 +51,22 @@ public NotAcceptableException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a NotAcceptableException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public NotAcceptableException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..f2d06a95d60 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class NotFoundException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public NotFoundException( final String message, @@ -49,4 +51,22 @@ public NotFoundException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a NotFoundException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public NotFoundException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..2bc1916a243 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class PreconditionFailedException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public PreconditionFailedException( final String message, @@ -49,4 +51,22 @@ public PreconditionFailedException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a PreconditionFailedException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public PreconditionFailedException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..7cf74536f04 100644 --- a/solid/src/main/java/com/inrupt/client/solid/SolidClientException.java +++ b/solid/src/main/java/com/inrupt/client/solid/SolidClientException.java @@ -20,15 +20,20 @@ */ package com.inrupt.client.solid; +import com.inrupt.client.ClientHttpException; import com.inrupt.client.Headers; import com.inrupt.client.InruptClientException; +import com.inrupt.client.ProblemDetails; +import com.inrupt.client.spi.JsonService; +import java.io.IOException; +import java.io.InputStream; 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; @@ -45,16 +50,31 @@ public class SolidClientException extends InruptClientException { * @param statusCode the HTTP status code * @param headers the response headers * @param body the body + * @deprecated */ public SolidClientException(final String message, final URI uri, final int statusCode, final Headers headers, final String body) { - super(message); + super(null, message); this.uri = uri; this.statusCode = statusCode; this.headers = headers; this.body = body; } + public SolidClientException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body + ) { + super(pd, message); + this.uri = uri; + this.statusCode = pd.getStatus(); + this.headers = headers; + this.body = body; + } + /** * Retrieve the URI associated with this exception. * @@ -91,6 +111,9 @@ public String getBody() { return body; } + /** + * @deprecated + */ public static SolidClientException handle( final String message, final URI uri, @@ -126,5 +149,42 @@ public static SolidClientException handle( return new SolidClientException(message, uri, statusCode, headers, body); } } + + public static SolidClientException handle( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body + ) { + switch (pd.getStatus()) { + case BadRequestException.STATUS_CODE: + return new BadRequestException(message, pd, uri, headers, body); + case UnauthorizedException.STATUS_CODE: + return new UnauthorizedException(message, pd, uri, headers, body); + case ForbiddenException.STATUS_CODE: + return new ForbiddenException(message, pd, uri, headers, body); + case NotFoundException.STATUS_CODE: + return new NotFoundException(message, pd, uri, headers, body); + case MethodNotAllowedException.STATUS_CODE: + return new MethodNotAllowedException(message, pd, uri, headers, body); + case NotAcceptableException.STATUS_CODE: + return new NotAcceptableException(message, pd, uri, headers, body); + case ConflictException.STATUS_CODE: + return new ConflictException(message, pd, uri, headers, body); + case GoneException.STATUS_CODE: + return new GoneException(message, pd, uri, headers, body); + case PreconditionFailedException.STATUS_CODE: + return new PreconditionFailedException(message, pd, uri, headers, body); + case UnsupportedMediaTypeException.STATUS_CODE: + return new UnsupportedMediaTypeException(message, pd, uri, headers, body); + case TooManyRequestsException.STATUS_CODE: + return new TooManyRequestsException(message, pd, uri, headers, body); + case InternalServerErrorException.STATUS_CODE: + return new InternalServerErrorException(message, pd, uri, headers, body); + default: + return new SolidClientException(message, pd, uri, headers, body); + } + } } 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..f753732094b 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class TooManyRequestsException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public TooManyRequestsException( final String message, @@ -49,4 +51,22 @@ public TooManyRequestsException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a TooManyRequestsException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public TooManyRequestsException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..371269a6649 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class UnauthorizedException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public UnauthorizedException( final String message, @@ -49,4 +51,22 @@ public UnauthorizedException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a UnauthorizedException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public UnauthorizedException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } 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..bbbb3be0c35 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.ProblemDetails; import java.net.URI; @@ -41,6 +42,7 @@ public class UnsupportedMediaTypeException extends SolidClientException { * @param uri the uri * @param headers the response headers * @param body the body + * @deprecated */ public UnsupportedMediaTypeException( final String message, @@ -49,4 +51,22 @@ public UnsupportedMediaTypeException( final String body) { super(message, uri, STATUS_CODE, headers, body); } + + /** + * Create a UnsupportedMediaTypeException exception. + * + * @param message the message + * @param pd the ProblemDetails instance + * @param uri the uri + * @param headers the response headers + * @param body the body + */ + public UnsupportedMediaTypeException( + final String message, + final ProblemDetails pd, + final URI uri, + final Headers headers, + final String body) { + super(message, pd, uri, headers, body); + } } From 74c3b35c3c61046651f6c9ba83d1843361877690 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 11 Apr 2024 10:03:16 +0200 Subject: [PATCH 04/18] Create HTTP status enum --- .../com/inrupt/client/solid/HttpStatus.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 solid/src/main/java/com/inrupt/client/solid/HttpStatus.java diff --git a/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java b/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java new file mode 100644 index 00000000000..5847cbecb7a --- /dev/null +++ b/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java @@ -0,0 +1,59 @@ +/* + * 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 java.util.Arrays; +import java.util.Optional; + +enum HttpStatus { + BAD_REQUEST(BadRequestException.STATUS_CODE, "Bad Request"), + UNAUTHORIZED(UnauthorizedException.STATUS_CODE, "Unauthorized"), + FORBIDDEN(ForbiddenException.STATUS_CODE, "Forbidden"), + NOT_FOUND(NotFoundException.STATUS_CODE, "Not Found"), + METHOD_NOT_ALLOWED(MethodNotAllowedException.STATUS_CODE, "Method Not Allowed"), + NOT_ACCEPTABLE(NotAcceptableException.STATUS_CODE, "Not Acceptable"), + CONFLICT(ConflictException.STATUS_CODE, "Conflict"), + GONE(GoneException.STATUS_CODE, "Gone"), + PRECONDITION_FAILED(PreconditionFailedException.STATUS_CODE, "Precondition Failed"), + UNSUPPORTED_MEDIA_TYPE(UnsupportedMediaTypeException.STATUS_CODE, "Unsupported Media Type"), + TOO_MANY_REQUESTS(TooManyRequestsException.STATUS_CODE, "Too Many Requests"), + INTERNAL_SERVER_ERROR(InternalServerErrorException.STATUS_CODE, "Internal Server Error"); + + final int code; + final String message; + + HttpStatus(int code, String message) { + this.code = code; + this.message = message; + } + + static String getStatusMessage(int statusCode) { + Optional knownStatus = Arrays.stream(HttpStatus.values()).filter(status -> status.code == statusCode).findFirst(); + if (knownStatus.isPresent()) { + return knownStatus.get().message; + } + // If the status is unknown, default to 400 for client errors and 500 for server errors + if (statusCode > 499) { + return INTERNAL_SERVER_ERROR.message; + } + return BAD_REQUEST.message; + } +} From 6af53950b92f08ad594f9f8b06779291c9ca9c3d Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 11 Apr 2024 11:11:56 +0200 Subject: [PATCH 05/18] Lint --- .../inrupt/client/ClientHttpException.java | 6 ++++-- .../com/inrupt/client/solid/HttpStatus.java | 8 +++++--- .../client/solid/SolidClientException.java | 20 +++++++++++++++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ClientHttpException.java b/api/src/main/java/com/inrupt/client/ClientHttpException.java index 262fef49645..37f4c7abc23 100644 --- a/api/src/main/java/com/inrupt/client/ClientHttpException.java +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -5,19 +5,21 @@ public class ClientHttpException extends InruptClientException { /** * Create a ClientHttpException. + * @param problemDetails the {@link ProblemDetails} instance * @param message the exception message */ - public ClientHttpException(ProblemDetails problemDetails, String 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(ProblemDetails problemDetails, String message, Exception cause) { + public ClientHttpException(final ProblemDetails problemDetails, final String message, final Exception cause) { super(message, cause); this.problemDetails = problemDetails; } diff --git a/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java b/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java index 5847cbecb7a..8de7a792770 100644 --- a/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java +++ b/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java @@ -40,13 +40,15 @@ enum HttpStatus { final int code; final String message; - HttpStatus(int code, String message) { + HttpStatus(final int code, final String message) { this.code = code; this.message = message; } - static String getStatusMessage(int statusCode) { - Optional knownStatus = Arrays.stream(HttpStatus.values()).filter(status -> status.code == statusCode).findFirst(); + static String getStatusMessage(final int statusCode) { + final Optional knownStatus = Arrays.stream(HttpStatus.values()) + .filter(status -> status.code == statusCode) + .findFirst(); if (knownStatus.isPresent()) { return knownStatus.get().message; } 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 7cf74536f04..f6c67683626 100644 --- a/solid/src/main/java/com/inrupt/client/solid/SolidClientException.java +++ b/solid/src/main/java/com/inrupt/client/solid/SolidClientException.java @@ -22,12 +22,8 @@ import com.inrupt.client.ClientHttpException; import com.inrupt.client.Headers; -import com.inrupt.client.InruptClientException; import com.inrupt.client.ProblemDetails; -import com.inrupt.client.spi.JsonService; -import java.io.IOException; -import java.io.InputStream; import java.net.URI; /** @@ -112,6 +108,13 @@ public String getBody() { } /** + * + * @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. * @deprecated */ public static SolidClientException handle( @@ -150,6 +153,15 @@ public static SolidClientException handle( } } + /** + * + * @param message the resulting exception message + * @param pd the {@link ProblemDetails} instance + * @param uri the request URL + * @param headers the response {@link Headers} + * @param body the response body + * @return an appropriate exception based on the status code. + */ public static SolidClientException handle( final String message, final ProblemDetails pd, From fe614296e0db7a2acaf44975dea82f5ebbe06b90 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 11 Apr 2024 11:42:23 +0200 Subject: [PATCH 06/18] Add some tests --- .../inrupt/client/solid/HttpStatusTest.java | 31 +++++++++++++++++++ .../client/solid/SolidExceptionTest.java | 21 +++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java diff --git a/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java b/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java new file mode 100644 index 00000000000..16c812f3848 --- /dev/null +++ b/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java @@ -0,0 +1,31 @@ +package com.inrupt.client.solid; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class HttpStatusTest { + @Test + void checkHttpStatusSearchKnownStatus() { + assertEquals( + HttpStatus.getStatusMessage(HttpStatus.NOT_FOUND.code), HttpStatus.NOT_FOUND.message + ); + } + + @Test + void checkHttpStatusSearchUnknownClientError () { + assertEquals( + HttpStatus.getStatusMessage(418), HttpStatus.BAD_REQUEST.message + ); + } + + @Test + void checkHttpStatusSearchUnknownServerError () { + assertEquals( + HttpStatus.getStatusMessage(555), HttpStatus.INTERNAL_SERVER_ERROR.message + ); + assertEquals( + HttpStatus.getStatusMessage(999), HttpStatus.INTERNAL_SERVER_ERROR.message + ); + } +} 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..bfab701eb77 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,10 @@ import static org.junit.jupiter.api.Assertions.*; +import com.inrupt.client.ProblemDetails; + +import java.net.URI; + import org.junit.jupiter.api.Test; class SolidExceptionTest { @@ -41,4 +45,21 @@ void checkSolidWrappedException() { assertEquals(upstream, err.getCause()); assertEquals(msg, err.getMessage()); } + + @Test + void checkSolidClientException() { + final String msg = "Error"; + final ProblemDetails pd = new ProblemDetails( + URI.create("https://example.org/problem"), + "Some title", + "Some details", + 123, + URI.create("https://example.org/instance") + ); + final SolidClientException err = new SolidClientException( + msg, pd, URI.create("https://example.org/request"), null, "some body" + ); + assertEquals(msg, err.getMessage()); + assertEquals(pd, err.getProblemDetails()); + } } From 0eec4798d18a4111f11466560c66ca46611d672b Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 11 Apr 2024 11:48:42 +0200 Subject: [PATCH 07/18] Add header --- .../inrupt/client/ClientHttpException.java | 20 +++++++++++++++++++ .../com/inrupt/client/ProblemDetails.java | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/api/src/main/java/com/inrupt/client/ClientHttpException.java b/api/src/main/java/com/inrupt/client/ClientHttpException.java index 37f4c7abc23..8e38bf0c101 100644 --- a/api/src/main/java/com/inrupt/client/ClientHttpException.java +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -1,3 +1,23 @@ +/* + * 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; public class ClientHttpException extends InruptClientException { diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index f4bd3a0a810..32a8962ed7b 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -1,3 +1,23 @@ +/* + * 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.net.URI; From 1f47530c79da2b88ad59025353133dc10075f893 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 11 Apr 2024 11:51:53 +0200 Subject: [PATCH 08/18] fixup! Add header --- .../inrupt/client/solid/HttpStatusTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java b/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java index 16c812f3848..e16ca0115a8 100644 --- a/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java @@ -1,3 +1,23 @@ +/* + * 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.*; From 90274b7b23049e41609341a9cc6fc8730f891552 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 11 Apr 2024 11:58:23 +0200 Subject: [PATCH 09/18] Add javadoc --- api/src/main/java/com/inrupt/client/ClientHttpException.java | 4 ++++ api/src/main/java/com/inrupt/client/ProblemDetails.java | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/api/src/main/java/com/inrupt/client/ClientHttpException.java b/api/src/main/java/com/inrupt/client/ClientHttpException.java index 8e38bf0c101..abe1fbbc505 100644 --- a/api/src/main/java/com/inrupt/client/ClientHttpException.java +++ b/api/src/main/java/com/inrupt/client/ClientHttpException.java @@ -20,6 +20,10 @@ */ 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; diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 32a8962ed7b..9b7fb6d89fa 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -22,6 +22,11 @@ import java.net.URI; +/** + * 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"; From 5b0cb4d0f0eb634997e9636bf1c99d4114563ed4 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Fri, 12 Apr 2024 22:28:28 +0200 Subject: [PATCH 10/18] Refactor HttpStatus --- .../java/com/inrupt/client/HttpStatus.java | 56 +++++++++++++++++++ .../com/inrupt/client}/HttpStatusTest.java | 18 ++++-- 2 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 api/src/main/java/com/inrupt/client/HttpStatus.java rename {solid/src/test/java/com/inrupt/client/solid => api/src/test/java/com/inrupt/client}/HttpStatusTest.java (63%) 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..706a0d80bf6 --- /dev/null +++ b/api/src/main/java/com/inrupt/client/HttpStatus.java @@ -0,0 +1,56 @@ +package com.inrupt.client; + +import java.util.Arrays; + +public 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; + + static 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; + }); + } + } +} diff --git a/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java b/api/src/test/java/com/inrupt/client/HttpStatusTest.java similarity index 63% rename from solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java rename to api/src/test/java/com/inrupt/client/HttpStatusTest.java index e16ca0115a8..c5376c5bce2 100644 --- a/solid/src/test/java/com/inrupt/client/solid/HttpStatusTest.java +++ b/api/src/test/java/com/inrupt/client/HttpStatusTest.java @@ -18,9 +18,9 @@ * 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; +package com.inrupt.client; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; @@ -28,24 +28,30 @@ public class HttpStatusTest { @Test void checkHttpStatusSearchKnownStatus() { assertEquals( - HttpStatus.getStatusMessage(HttpStatus.NOT_FOUND.code), HttpStatus.NOT_FOUND.message + HttpStatus.StatusMessages.getStatusMessage(HttpStatus.NOT_FOUND), HttpStatus.StatusMessages.NOT_FOUND.message ); } @Test void checkHttpStatusSearchUnknownClientError () { assertEquals( - HttpStatus.getStatusMessage(418), HttpStatus.BAD_REQUEST.message + HttpStatus.StatusMessages.getStatusMessage(418), HttpStatus.StatusMessages.BAD_REQUEST.message ); } @Test void checkHttpStatusSearchUnknownServerError () { assertEquals( - HttpStatus.getStatusMessage(555), HttpStatus.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.getStatusMessage(555), HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message ); assertEquals( - HttpStatus.getStatusMessage(999), HttpStatus.INTERNAL_SERVER_ERROR.message + 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 ); } } From fbe5ffd72f7513cfdc4af53c7e0ab69859f53e56 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Fri, 12 Apr 2024 22:30:53 +0200 Subject: [PATCH 11/18] Add specialized builder to ProblemDetails --- .../com/inrupt/client/ProblemDetails.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 9b7fb6d89fa..0ddd36a5bb4 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -20,7 +20,13 @@ */ 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; /** * A data class representing a structured problem description sent by the server on error response. @@ -75,4 +81,45 @@ public int getStatus() { 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 = (String) pdData.get("title"); + final String details = (String) pdData.get("details"); + final URI type = URI.create((String) pdData.get("type")); + final URI instance = URI.create((String) pdData.get("instance")); + final int status = (int) pdData.get("status"); + return new ProblemDetails(type, title, details, status, instance); + } catch (IOException e) { + return new ProblemDetails( + null, + HttpStatus.StatusMessages.getStatusMessage(statusCode), + null, + statusCode, + null + ); + } + } } From 74c14fa88312d324dd8f842d87f3d783625bd85e Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Fri, 12 Apr 2024 22:46:53 +0200 Subject: [PATCH 12/18] Use HttpStatus from API module as reference --- .../client/solid/BadRequestException.java | 3 +- .../client/solid/ConflictException.java | 3 +- .../client/solid/ForbiddenException.java | 3 +- .../inrupt/client/solid/GoneException.java | 3 +- .../com/inrupt/client/solid/HttpStatus.java | 61 ------------------- .../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 +- 13 files changed, 24 insertions(+), 73 deletions(-) delete mode 100644 solid/src/main/java/com/inrupt/client/solid/HttpStatus.java 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 4b107d638cc..3e86a0ec5b4 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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. 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 154f66a85a5..80b63b7decf 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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 97d4f73ebd6..9fd94a4fb9e 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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 cfbce185429..511b5ca947a 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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/HttpStatus.java b/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java deleted file mode 100644 index 8de7a792770..00000000000 --- a/solid/src/main/java/com/inrupt/client/solid/HttpStatus.java +++ /dev/null @@ -1,61 +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.solid; - -import java.util.Arrays; -import java.util.Optional; - -enum HttpStatus { - BAD_REQUEST(BadRequestException.STATUS_CODE, "Bad Request"), - UNAUTHORIZED(UnauthorizedException.STATUS_CODE, "Unauthorized"), - FORBIDDEN(ForbiddenException.STATUS_CODE, "Forbidden"), - NOT_FOUND(NotFoundException.STATUS_CODE, "Not Found"), - METHOD_NOT_ALLOWED(MethodNotAllowedException.STATUS_CODE, "Method Not Allowed"), - NOT_ACCEPTABLE(NotAcceptableException.STATUS_CODE, "Not Acceptable"), - CONFLICT(ConflictException.STATUS_CODE, "Conflict"), - GONE(GoneException.STATUS_CODE, "Gone"), - PRECONDITION_FAILED(PreconditionFailedException.STATUS_CODE, "Precondition Failed"), - UNSUPPORTED_MEDIA_TYPE(UnsupportedMediaTypeException.STATUS_CODE, "Unsupported Media Type"), - TOO_MANY_REQUESTS(TooManyRequestsException.STATUS_CODE, "Too Many Requests"), - INTERNAL_SERVER_ERROR(InternalServerErrorException.STATUS_CODE, "Internal Server Error"); - - final int code; - final String message; - - HttpStatus(final int code, final String message) { - this.code = code; - this.message = message; - } - - static String getStatusMessage(final int statusCode) { - final Optional knownStatus = Arrays.stream(HttpStatus.values()) - .filter(status -> status.code == statusCode) - .findFirst(); - if (knownStatus.isPresent()) { - return knownStatus.get().message; - } - // If the status is unknown, default to 400 for client errors and 500 for server errors - if (statusCode > 499) { - return INTERNAL_SERVER_ERROR.message; - } - return BAD_REQUEST.message; - } -} 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 b30f8ddf1e1..43a216fd307 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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 f69183d60b9..378e166b2a0 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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 1738e86d2c6..f1509073ef0 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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 f2d06a95d60..71e4dc16361 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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 2bc1916a243..4c1d30f0065 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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/TooManyRequestsException.java b/solid/src/main/java/com/inrupt/client/solid/TooManyRequestsException.java index f753732094b..d05b2f255fd 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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 371269a6649..e9d2d98571d 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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 bbbb3be0c35..4d90c434efc 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 com.inrupt.client.ProblemDetails; import java.net.URI; @@ -33,7 +34,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. From 971cd6af4bd59b84bf2430eb645b5eca26682bd2 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Fri, 12 Apr 2024 22:50:31 +0200 Subject: [PATCH 13/18] Lint --- .../java/com/inrupt/client/HttpStatus.java | 9 +++++++-- .../java/com/inrupt/client/HttpStatusTest.java | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/HttpStatus.java b/api/src/main/java/com/inrupt/client/HttpStatus.java index 706a0d80bf6..fe2143f16ed 100644 --- a/api/src/main/java/com/inrupt/client/HttpStatus.java +++ b/api/src/main/java/com/inrupt/client/HttpStatus.java @@ -2,7 +2,7 @@ import java.util.Arrays; -public class HttpStatus { +public final class HttpStatus { public static final int BAD_REQUEST = 400; public static final int UNAUTHORIZED = 401; @@ -17,7 +17,7 @@ public class HttpStatus { public static final int TOO_MANY_REQUESTS = 429; public static final int INTERNAL_SERVER_ERROR = 500; - static enum StatusMessages { + enum StatusMessages { BAD_REQUEST(HttpStatus.BAD_REQUEST, "Bad Request"), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "Unauthorized"), FORBIDDEN(HttpStatus.FORBIDDEN, "Forbidden"), @@ -53,4 +53,9 @@ static String getStatusMessage(final int statusCode) { }); } } + + // Prevents instantiation. + private HttpStatus() { + /* no-op */ + } } diff --git a/api/src/test/java/com/inrupt/client/HttpStatusTest.java b/api/src/test/java/com/inrupt/client/HttpStatusTest.java index c5376c5bce2..35126cb23c0 100644 --- a/api/src/test/java/com/inrupt/client/HttpStatusTest.java +++ b/api/src/test/java/com/inrupt/client/HttpStatusTest.java @@ -28,30 +28,36 @@ public class HttpStatusTest { @Test void checkHttpStatusSearchKnownStatus() { assertEquals( - HttpStatus.StatusMessages.getStatusMessage(HttpStatus.NOT_FOUND), HttpStatus.StatusMessages.NOT_FOUND.message + HttpStatus.StatusMessages.getStatusMessage(HttpStatus.NOT_FOUND), + HttpStatus.StatusMessages.NOT_FOUND.message ); } @Test void checkHttpStatusSearchUnknownClientError () { assertEquals( - HttpStatus.StatusMessages.getStatusMessage(418), HttpStatus.StatusMessages.BAD_REQUEST.message + HttpStatus.StatusMessages.getStatusMessage(418), + HttpStatus.StatusMessages.BAD_REQUEST.message ); } @Test void checkHttpStatusSearchUnknownServerError () { assertEquals( - HttpStatus.StatusMessages.getStatusMessage(555), HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.getStatusMessage(555), + HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message ); assertEquals( - HttpStatus.StatusMessages.getStatusMessage(999), HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.getStatusMessage(999), + HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message ); assertEquals( - HttpStatus.StatusMessages.getStatusMessage(-1), HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.getStatusMessage(-1), + HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message ); assertEquals( - HttpStatus.StatusMessages.getStatusMessage(15), HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message + HttpStatus.StatusMessages.getStatusMessage(15), + HttpStatus.StatusMessages.INTERNAL_SERVER_ERROR.message ); } } From 208424d879db1e4427959c617606b9e67b71da8f Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Fri, 12 Apr 2024 22:54:40 +0200 Subject: [PATCH 14/18] Add missing header --- .../java/com/inrupt/client/HttpStatus.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/api/src/main/java/com/inrupt/client/HttpStatus.java b/api/src/main/java/com/inrupt/client/HttpStatus.java index fe2143f16ed..52f72c14464 100644 --- a/api/src/main/java/com/inrupt/client/HttpStatus.java +++ b/api/src/main/java/com/inrupt/client/HttpStatus.java @@ -1,3 +1,23 @@ +/* + * 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; From d86fae152ab428e820cdfeff0217eb520aac9c4b Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Sun, 14 Apr 2024 14:35:36 +0200 Subject: [PATCH 15/18] Make problem details parsing more resilient --- .../java/com/inrupt/client/ProblemDetails.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/com/inrupt/client/ProblemDetails.java b/api/src/main/java/com/inrupt/client/ProblemDetails.java index 0ddd36a5bb4..2e613ad3db9 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -27,6 +27,8 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import java.util.Optional; /** * A data class representing a structured problem description sent by the server on error response. @@ -106,12 +108,18 @@ public static ProblemDetails fromErrorResponse( new ByteArrayInputStream(body), new HashMap(){}.getClass().getGenericSuperclass() ); - final String title = (String) pdData.get("title"); + final String title = Optional.ofNullable((String) pdData.get("title")) + .orElse(HttpStatus.StatusMessages.getStatusMessage(statusCode)); final String details = (String) pdData.get("details"); - final URI type = URI.create((String) pdData.get("type")); - final URI instance = URI.create((String) pdData.get("instance")); - final int status = (int) pdData.get("status"); - return new ProblemDetails(type, title, details, status, instance); + final URI type = Optional.ofNullable(pdData.get("type")) + .map(t -> URI.create((String) t)) + .orElse(null); + final URI instance = Optional.ofNullable(pdData.get("instance")) + .map(i -> URI.create((String) i)) + .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, From e1159b3e1ed75b120cd8edaac7a1a5132e36bc94 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Sun, 14 Apr 2024 15:08:03 +0200 Subject: [PATCH 16/18] 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. --- solid/pom.xml | 6 + .../client/solid/ProblemDetailsTest.java | 144 ++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java 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..3e5c4f52e01 --- /dev/null +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -0,0 +1,144 @@ +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.HttpStatus; +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 bb99a0c401a61f125dc5b15afdc9ae45738592fe Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Sun, 14 Apr 2024 16:02:14 +0200 Subject: [PATCH 17/18] Lint --- .../com/inrupt/client/ProblemDetails.java | 1 - .../client/solid/ProblemDetailsTest.java | 4 ++-- .../inrupt/client/test/RdfMockService.java | 22 +++++++++++++++---- 3 files changed, 20 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 2e613ad3db9..d804755ebee 100644 --- a/api/src/main/java/com/inrupt/client/ProblemDetails.java +++ b/api/src/main/java/com/inrupt/client/ProblemDetails.java @@ -27,7 +27,6 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Optional; /** 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 3e5c4f52e01..350b5acd6e1 100644 --- a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertNull; import com.inrupt.client.Headers; -import com.inrupt.client.HttpStatus; import com.inrupt.client.ProblemDetails; import com.inrupt.client.spi.JsonService; import com.inrupt.client.spi.ServiceProvider; @@ -17,7 +16,8 @@ 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. +// 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<>(); diff --git a/test/src/main/java/com/inrupt/client/test/RdfMockService.java b/test/src/main/java/com/inrupt/client/test/RdfMockService.java index 768a2ba1aec..619814b1f16 100644 --- a/test/src/main/java/com/inrupt/client/test/RdfMockService.java +++ b/test/src/main/java/com/inrupt/client/test/RdfMockService.java @@ -24,6 +24,7 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.inrupt.client.ProblemDetails; import java.util.Collections; import java.util.Map; @@ -44,35 +45,48 @@ public int getPort() { return wireMockServer.port(); } + private static final String CONTENT_TYPE = "Content-Type"; + private void setupMocks() { wireMockServer.stubFor(get(urlEqualTo("/oneTriple")) .willReturn(aResponse() .withStatus(200) - .withHeader("Content-Type", "text/turtle") + .withHeader(CONTENT_TYPE, "text/turtle") .withBody(" ."))); wireMockServer.stubFor(post(urlEqualTo("/postOneTriple")) .withRequestBody(matching( ".*\\s+" + "\\s+\"object\"\\s+\\..*")) - .withHeader("Content-Type", containing("text/turtle")) + .withHeader(CONTENT_TYPE, containing("text/turtle")) .willReturn(aResponse() .withStatus(204))); wireMockServer.stubFor(get(urlEqualTo("/example")) .willReturn(aResponse() .withStatus(200) - .withHeader("Content-Type", "text/turtle") + .withHeader(CONTENT_TYPE, "text/turtle") .withBody(getExampleTTL()))); wireMockServer.stubFor(patch(urlEqualTo("/sparqlUpdate")) - .withHeader("Content-Type", containing("application/sparql-update")) + .withHeader(CONTENT_TYPE, containing("application/sparql-update")) .withRequestBody(matching( "INSERT DATA\\s+\\{\\s*\\s+" + "\\s+\\s*\\.\\s*\\}\\s*")) .willReturn(aResponse() .withStatus(204))); + + wireMockServer.stubFor(get(urlEqualTo("/error")) + .willReturn(aResponse() + .withStatus(429) + .withHeader(CONTENT_TYPE, ProblemDetails.MIME_TYPE) + .withBody("{" + + "\"title\":\"Too Many Requests\"," + + "\"status\":429," + + "\"details\":\"Some details\"," + + "\"instance\":\"https://example.org/instance\"," + + "\"type\":\"https://example.org/type\"}"))); } private String getExampleTTL() { From fb0322c12e65499638e5587f32cadfdff4fd3247 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Sun, 14 Apr 2024 17:25:12 +0200 Subject: [PATCH 18/18] License header --- .../client/solid/ProblemDetailsTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 350b5acd6e1..2cd6f500a86 100644 --- a/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java +++ b/solid/src/test/java/com/inrupt/client/solid/ProblemDetailsTest.java @@ -1,3 +1,23 @@ +/* + * 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;