From dff74124e52b138907449dab523465a32121c99a Mon Sep 17 00:00:00 2001 From: Thomas BOUSSELIN Date: Thu, 14 Nov 2024 18:55:11 +0100 Subject: [PATCH 1/4] refacto: simplify ErrorHandling --- .../web/EntityAccessControlHandler.kt | 1 - .../web/ContextSourceRegistrationHandler.kt | 1 - .../search/discovery/web/AttributeHandler.kt | 1 - .../search/discovery/web/EntityTypeHandler.kt | 1 - .../search/entity/web/EntityHandler.kt | 1 - .../entity/web/EntityOperationHandler.kt | 1 - .../temporal/web/TemporalEntityHandler.kt | 1 - .../web/TemporalEntityOperationsHandler.kt | 1 - .../search/entity/web/EntityHandlerTests.kt | 2 +- .../egm/stellio/shared/model/ApiExceptions.kt | 189 ++++++++++++++++-- .../egm/stellio/shared/model/ErrorResponse.kt | 119 ----------- .../egm/stellio/shared/util/ApiResponses.kt | 54 ----- .../stellio/shared/web/ExceptionHandler.kt | 105 ++-------- .../egm/stellio/shared/web/TenantWebFilter.kt | 15 +- .../subscription/web/SubscriptionHandler.kt | 1 - .../web/SubscriptionHandlerTests.kt | 2 +- 16 files changed, 194 insertions(+), 301 deletions(-) delete mode 100644 shared/src/main/kotlin/com/egm/stellio/shared/model/ErrorResponse.kt diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandler.kt index ca8b224b6..09d307d2f 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandler.kt @@ -36,7 +36,6 @@ import com.egm.stellio.shared.util.getAuthzContextFromLinkHeaderOrDefault import com.egm.stellio.shared.util.getSubFromSecurityContext import com.egm.stellio.shared.util.parseRepresentations import com.egm.stellio.shared.util.replaceDefaultContextToAuthzContext -import com.egm.stellio.shared.util.toErrorResponse import com.egm.stellio.shared.web.BaseHandler import kotlinx.coroutines.reactive.awaitFirst import org.springframework.http.HttpHeaders diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt index b52097e2a..e9dc6cb8c 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt @@ -25,7 +25,6 @@ import com.egm.stellio.shared.util.getContextFromLinkHeaderOrDefault import com.egm.stellio.shared.util.getSubFromSecurityContext import com.egm.stellio.shared.util.parsePaginationParameters import com.egm.stellio.shared.util.prepareGetSuccessResponseHeaders -import com.egm.stellio.shared.util.toErrorResponse import com.egm.stellio.shared.web.BaseHandler import kotlinx.coroutines.reactive.awaitFirst import org.springframework.http.HttpHeaders diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/AttributeHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/AttributeHandler.kt index a6eb37a1f..b4d1a37fd 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/AttributeHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/AttributeHandler.kt @@ -10,7 +10,6 @@ import com.egm.stellio.shared.util.decode import com.egm.stellio.shared.util.getApplicableMediaType import com.egm.stellio.shared.util.getContextFromLinkHeaderOrDefault import com.egm.stellio.shared.util.prepareGetSuccessResponseHeaders -import com.egm.stellio.shared.util.toErrorResponse import org.springframework.http.HttpHeaders import org.springframework.http.MediaType import org.springframework.http.ResponseEntity diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandler.kt index 2581a02c1..1cf4ebb9c 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandler.kt @@ -10,7 +10,6 @@ import com.egm.stellio.shared.util.decode import com.egm.stellio.shared.util.getApplicableMediaType import com.egm.stellio.shared.util.getContextFromLinkHeaderOrDefault import com.egm.stellio.shared.util.prepareGetSuccessResponseHeaders -import com.egm.stellio.shared.util.toErrorResponse import org.springframework.http.HttpHeaders import org.springframework.http.MediaType import org.springframework.http.ResponseEntity diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityHandler.kt index 4c2ae9fe4..dc88acc99 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityHandler.kt @@ -44,7 +44,6 @@ import com.egm.stellio.shared.util.missingPathErrorResponse import com.egm.stellio.shared.util.parseRepresentations import com.egm.stellio.shared.util.parseTimeParameter import com.egm.stellio.shared.util.prepareGetSuccessResponseHeaders -import com.egm.stellio.shared.util.toErrorResponse import com.egm.stellio.shared.util.toUri import com.egm.stellio.shared.web.BaseHandler import org.springframework.http.HttpHeaders diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityOperationHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityOperationHandler.kt index 33d2d6e79..1fa5f2d3f 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityOperationHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityOperationHandler.kt @@ -36,7 +36,6 @@ import com.egm.stellio.shared.util.getContextFromLinkHeader import com.egm.stellio.shared.util.getContextFromLinkHeaderOrDefault import com.egm.stellio.shared.util.getSubFromSecurityContext import com.egm.stellio.shared.util.parseRepresentations -import com.egm.stellio.shared.util.toErrorResponse import com.egm.stellio.shared.util.toListOfUri import kotlinx.coroutines.reactive.awaitFirst import org.springframework.http.HttpHeaders diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandler.kt index a88585357..b2d084260 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandler.kt @@ -35,7 +35,6 @@ import com.egm.stellio.shared.util.getSubFromSecurityContext import com.egm.stellio.shared.util.invalidTemporalInstanceMessage import com.egm.stellio.shared.util.missingPathErrorResponse import com.egm.stellio.shared.util.parseRepresentations -import com.egm.stellio.shared.util.toErrorResponse import com.egm.stellio.shared.util.toUri import com.egm.stellio.shared.web.BaseHandler import org.springframework.http.HttpHeaders diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityOperationsHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityOperationsHandler.kt index 40a37bcc8..0a7065922 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityOperationsHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityOperationsHandler.kt @@ -11,7 +11,6 @@ import com.egm.stellio.shared.util.JsonLdUtils.compactEntities import com.egm.stellio.shared.util.getApplicableMediaType import com.egm.stellio.shared.util.getContextFromLinkHeaderOrDefault import com.egm.stellio.shared.util.getSubFromSecurityContext -import com.egm.stellio.shared.util.toErrorResponse import kotlinx.coroutines.reactive.awaitFirst import org.springframework.http.HttpHeaders import org.springframework.http.MediaType diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt index 028a5c5b9..0845daf93 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt @@ -203,7 +203,7 @@ class EntityHandlerTests { { "type":"https://uri.etsi.org/ngsi-ld/errors/InternalError", "title":"There has been an error during the operation execution", - "detail":"InternalErrorException(message=Internal Server Exception)" + "detail":"Internal Server Exception" } """ ) diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt index 6a9269335..1a0db0b0a 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt @@ -2,24 +2,164 @@ package com.egm.stellio.shared.model import com.apicatalog.jsonld.JsonLdError import com.apicatalog.jsonld.JsonLdErrorCode +import com.egm.stellio.shared.util.JsonUtils.serializeObject +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonProperty +import org.slf4j.LoggerFactory +import org.springframework.core.io.buffer.DefaultDataBufferFactory +import org.springframework.http.HttpHeaders.CONTENT_TYPE +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.ProblemDetail +import org.springframework.http.ResponseEntity +import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import java.net.URI + +const val DEFAULT_DETAIL = "If you have difficulty finding the issue" + + "you can check common issues here https://stellio.readthedocs.io/en/latest/TROUBLESHOOT.html" + + "If you think this is a bug of the broker" + + "you can create an issue on https://github.com/stellio-hub/stellio-context-broker" sealed class APIException( - override val message: String -) : Exception(message) - -data class InvalidRequestException(override val message: String) : APIException(message) -data class BadRequestDataException(override val message: String) : APIException(message) -data class AlreadyExistsException(override val message: String) : APIException(message) -data class OperationNotSupportedException(override val message: String) : APIException(message) -data class ResourceNotFoundException(override val message: String) : APIException(message) -data class InternalErrorException(override val message: String) : APIException(message) -data class TooComplexQueryException(override val message: String) : APIException(message) -data class TooManyResultsException(override val message: String) : APIException(message) -data class AccessDeniedException(override val message: String) : APIException(message) -data class NotImplementedException(override val message: String) : APIException(message) -data class LdContextNotAvailableException(override val message: String) : APIException(message) -data class NonexistentTenantException(override val message: String) : APIException(message) -data class NotAcceptableException(override val message: String) : APIException(message) + val type: URI, + @JsonIgnore + val status: HttpStatus, + open val title: String, + @JsonProperty("detail") + override val message: String = DEFAULT_DETAIL +) : Exception(message) { + private val logger = LoggerFactory.getLogger(javaClass) + + fun toErrorResponse(): ResponseEntity { + logger.info("Returning error ${this.type} (${this.message})") + return ResponseEntity.status(status) + .contentType(MediaType.APPLICATION_JSON) + .body( + ProblemDetail.forStatusAndDetail(status, this.message).also { + it.title = this.title + it.type = this.type + } + ) + } + + fun toServerWebExchange(exchange: ServerWebExchange): Mono { + logger.info("Returning server web exchange error ${this.type} (${this.message})") + exchange.response.statusCode = this.status + exchange.response.headers[CONTENT_TYPE] = MediaType.APPLICATION_JSON_VALUE + val body = serializeObject(this) + return exchange.response.writeWith( + Flux.just(DefaultDataBufferFactory().wrap(body.toByteArray())) + ) + } +} + +data class AlreadyExistsException(override val message: String) : APIException( + ErrorType.ALREADY_EXISTS.type, + HttpStatus.CONFLICT, + "The referred element already exists", + message +) + +data class InvalidRequestException(override val message: String) : APIException( // todo check + ErrorType.INVALID_REQUEST.type, + HttpStatus.BAD_REQUEST, + "The request associated to the operation is syntactically invalid or includes wrong content", + message +) + +data class BadRequestDataException(override val message: String) : APIException( + ErrorType.BAD_REQUEST_DATA.type, + HttpStatus.BAD_REQUEST, + "The request includes input data which does not meet the requirements of the operation", + message +) +data class OperationNotSupportedException(override val message: String) : APIException( + ErrorType.OPERATION_NOT_SUPPORTED.type, + HttpStatus.BAD_REQUEST, + "The operation is not supported", + message +) +data class ResourceNotFoundException(override val message: String) : APIException( + ErrorType.RESOURCE_NOT_FOUND.type, + HttpStatus.NOT_FOUND, + "The referred resource has not been found", + message +) +data class InternalErrorException(override val message: String) : APIException( + ErrorType.INTERNAL_ERROR.type, + HttpStatus.INTERNAL_SERVER_ERROR, + "There has been an error during the operation execution", + message +) + +data class TooManyResultsException(override val message: String) : APIException( + ErrorType.TOO_MANY_RESULTS.type, + HttpStatus.FORBIDDEN, + "The query associated to the operation is producing so many results " + + "that can exhaust client or server resources. " + + "It should be made more restrictive", + message +) +data class AccessDeniedException(override val message: String) : APIException( + ErrorType.ACCESS_DENIED.type, + HttpStatus.FORBIDDEN, + "The request tried to access an unauthorized resource", + message +) +data class NotImplementedException(override val message: String) : APIException( + ErrorType.NOT_IMPLEMENTED.type, + HttpStatus.NOT_IMPLEMENTED, + "The requested functionality is not yet implemented", + message +) +data class LdContextNotAvailableException(override val message: String) : APIException( + ErrorType.LD_CONTEXT_NOT_AVAILABLE.type, + HttpStatus.SERVICE_UNAVAILABLE, + "A remote JSON-LD @context referenced in a request cannot be retrieved by the NGSI-LD Broker and " + + "expansion or compaction cannot be performed", + message +) +data class NonexistentTenantException(override val message: String) : APIException( + ErrorType.NONEXISTENT_TENANT.type, + HttpStatus.NOT_FOUND, + "The addressed tenant does not exist", + message +) +data class TooComplexQueryException(override val message: String) : APIException( // todo check + ErrorType.TOO_COMPLEX_QUERY.type, + HttpStatus.INTERNAL_SERVER_ERROR, // todo check + "The query associated to the operation is too complex and cannot be resolved", + message +) +data class NotAcceptableException(override val message: String) : APIException( + ErrorType.NOT_ACCEPTABLE.type, + HttpStatus.NOT_ACCEPTABLE, + "The media type provided in Accept header is not supported", + message +) + +data class JsonParseApiException(override val message: String) : APIException( + ErrorType.INVALID_REQUEST.type, + HttpStatus.BAD_REQUEST, + "The request includes invalid input data, an error occurred during JSON parsing", + message +) + +data class UnsupportedMediaTypeStatusApiException(override val message: String) : APIException( + ErrorType.UNSUPPORTED_MEDIA_TYPE.type, + HttpStatus.UNSUPPORTED_MEDIA_TYPE, + "The content type of the request is not supported", + message +) + +data class JsonLdErrorApiResponse(override val title: String, val detail: String) : APIException( + ErrorType.BAD_REQUEST_DATA.type, // todo check + HttpStatus.BAD_REQUEST, + title, + detail +) fun Throwable.toAPIException(specificMessage: String? = null): APIException = when (this) { @@ -32,3 +172,20 @@ fun Throwable.toAPIException(specificMessage: String? = null): APIException = else BadRequestDataException("Unexpected error while parsing payload (cause was: $this)") else -> BadRequestDataException(specificMessage ?: this.localizedMessage) } + +enum class ErrorType(val type: URI) { + INVALID_REQUEST(URI("https://uri.etsi.org/ngsi-ld/errors/InvalidRequest")), + BAD_REQUEST_DATA(URI("https://uri.etsi.org/ngsi-ld/errors/BadRequestData")), + ALREADY_EXISTS(URI("https://uri.etsi.org/ngsi-ld/errors/AlreadyExists")), + OPERATION_NOT_SUPPORTED(URI("https://uri.etsi.org/ngsi-ld/errors/OperationNotSupported")), + RESOURCE_NOT_FOUND(URI("https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound")), + INTERNAL_ERROR(URI("https://uri.etsi.org/ngsi-ld/errors/InternalError")), + TOO_COMPLEX_QUERY(URI("https://uri.etsi.org/ngsi-ld/errors/TooComplexQuery")), + TOO_MANY_RESULTS(URI("https://uri.etsi.org/ngsi-ld/errors/TooManyResults")), + LD_CONTEXT_NOT_AVAILABLE(URI("https://uri.etsi.org/ngsi-ld/errors/LdContextNotAvailable")), + ACCESS_DENIED(URI("https://uri.etsi.org/ngsi-ld/errors/AccessDenied")), + NOT_IMPLEMENTED(URI("https://uri.etsi.org/ngsi-ld/errors/NotImplemented")), + UNSUPPORTED_MEDIA_TYPE(URI("https://uri.etsi.org/ngsi-ld/errors/UnsupportedMediaType")), + NOT_ACCEPTABLE(URI("https://uri.etsi.org/ngsi-ld/errors/NotAcceptable")), + NONEXISTENT_TENANT(URI("https://uri.etsi.org/ngsi-ld/errors/NonexistentTenant")) +} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/ErrorResponse.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/ErrorResponse.kt deleted file mode 100644 index 3e7820cb7..000000000 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/ErrorResponse.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.egm.stellio.shared.model - -import java.net.URI - -sealed class ErrorResponse( - val type: URI, - open val title: String, - open val detail: String -) - -data class InvalidRequestResponse(override val detail: String) : ErrorResponse( - ErrorType.INVALID_REQUEST.type, - "The request associated to the operation is syntactically invalid or includes wrong content", - detail -) - -data class BadRequestDataResponse(override val detail: String) : ErrorResponse( - ErrorType.BAD_REQUEST_DATA.type, - "The request includes input data which does not meet the requirements of the operation", - detail -) - -data class AlreadyExistsResponse(override val detail: String) : - ErrorResponse(ErrorType.ALREADY_EXISTS.type, "The referred element already exists", detail) - -data class OperationNotSupportedResponse(override val detail: String) : - ErrorResponse(ErrorType.OPERATION_NOT_SUPPORTED.type, "The operation is not supported", detail) - -data class ResourceNotFoundResponse(override val detail: String) : - ErrorResponse(ErrorType.RESOURCE_NOT_FOUND.type, "The referred resource has not been found", detail) - -data class InternalErrorResponse(override val detail: String) : - ErrorResponse(ErrorType.INTERNAL_ERROR.type, "There has been an error during the operation execution", detail) - -data class TooComplexQueryResponse(override val detail: String) : ErrorResponse( - ErrorType.TOO_COMPLEX_QUERY.type, - "The query associated to the operation is too complex and cannot be resolved", - detail -) - -data class TooManyResultsResponse(override val detail: String) : ErrorResponse( - ErrorType.TOO_MANY_RESULTS.type, - "The query associated to the operation is producing so many results " + - "that can exhaust client or server resources. " + - "It should be made more restrictive", - detail -) - -data class LdContextNotAvailableResponse(override val detail: String) : ErrorResponse( - ErrorType.LD_CONTEXT_NOT_AVAILABLE.type, - "A remote JSON-LD @context referenced in a request cannot be retrieved by the NGSI-LD Broker and " + - "expansion or compaction cannot be performed", - detail -) - -data class JsonLdErrorResponse(override val title: String, override val detail: String) : - ErrorResponse( - ErrorType.BAD_REQUEST_DATA.type, - title, - detail - ) - -data class JsonParseErrorResponse(override val detail: String) : ErrorResponse( - ErrorType.INVALID_REQUEST.type, - "The request includes invalid input data, an error occurred during JSON parsing", - detail -) - -data class AccessDeniedResponse(override val detail: String) : - ErrorResponse( - ErrorType.ACCESS_DENIED.type, - "The request tried to access an unauthorized resource", - detail - ) - -data class NotImplementedResponse(override val detail: String) : - ErrorResponse( - ErrorType.NOT_IMPLEMENTED.type, - "The requested functionality is not yet implemented", - detail - ) - -data class UnsupportedMediaTypeResponse(override val detail: String) : - ErrorResponse( - ErrorType.UNSUPPORTED_MEDIA_TYPE.type, - "The content type of the request is not supported", - detail - ) - -data class NotAcceptableResponse(override val detail: String) : - ErrorResponse( - ErrorType.NOT_ACCEPTABLE.type, - "The media type provided in Accept header is not supported", - detail - ) - -data class NonexistentTenantResponse(override val detail: String) : - ErrorResponse( - ErrorType.NONEXISTENT_TENANT.type, - "The addressed tenant does not exist", - detail - ) - -enum class ErrorType(val type: URI) { - INVALID_REQUEST(URI("https://uri.etsi.org/ngsi-ld/errors/InvalidRequest")), - BAD_REQUEST_DATA(URI("https://uri.etsi.org/ngsi-ld/errors/BadRequestData")), - ALREADY_EXISTS(URI("https://uri.etsi.org/ngsi-ld/errors/AlreadyExists")), - OPERATION_NOT_SUPPORTED(URI("https://uri.etsi.org/ngsi-ld/errors/OperationNotSupported")), - RESOURCE_NOT_FOUND(URI("https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound")), - INTERNAL_ERROR(URI("https://uri.etsi.org/ngsi-ld/errors/InternalError")), - TOO_COMPLEX_QUERY(URI("https://uri.etsi.org/ngsi-ld/errors/TooComplexQuery")), - TOO_MANY_RESULTS(URI("https://uri.etsi.org/ngsi-ld/errors/TooManyResults")), - LD_CONTEXT_NOT_AVAILABLE(URI("https://uri.etsi.org/ngsi-ld/errors/LdContextNotAvailable")), - ACCESS_DENIED(URI("https://uri.etsi.org/ngsi-ld/errors/AccessDenied")), - NOT_IMPLEMENTED(URI("https://uri.etsi.org/ngsi-ld/errors/NotImplemented")), - UNSUPPORTED_MEDIA_TYPE(URI("https://uri.etsi.org/ngsi-ld/errors/UnsupportedMediaType")), - NOT_ACCEPTABLE(URI("https://uri.etsi.org/ngsi-ld/errors/NotAcceptable")), - NONEXISTENT_TENANT(URI("https://uri.etsi.org/ngsi-ld/errors/NonexistentTenant")) -} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiResponses.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiResponses.kt index 5c595e4c7..dba87e30b 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiResponses.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiResponses.kt @@ -1,27 +1,7 @@ package com.egm.stellio.shared.util -import com.egm.stellio.shared.model.APIException -import com.egm.stellio.shared.model.AccessDeniedException -import com.egm.stellio.shared.model.AccessDeniedResponse -import com.egm.stellio.shared.model.AlreadyExistsException -import com.egm.stellio.shared.model.AlreadyExistsResponse import com.egm.stellio.shared.model.BadRequestDataException -import com.egm.stellio.shared.model.BadRequestDataResponse -import com.egm.stellio.shared.model.ErrorResponse -import com.egm.stellio.shared.model.InternalErrorResponse -import com.egm.stellio.shared.model.InvalidRequestException -import com.egm.stellio.shared.model.InvalidRequestResponse -import com.egm.stellio.shared.model.LdContextNotAvailableException -import com.egm.stellio.shared.model.LdContextNotAvailableResponse -import com.egm.stellio.shared.model.NotImplementedException -import com.egm.stellio.shared.model.NotImplementedResponse -import com.egm.stellio.shared.model.OperationNotSupportedException -import com.egm.stellio.shared.model.OperationNotSupportedResponse import com.egm.stellio.shared.model.PaginationQuery -import com.egm.stellio.shared.model.ResourceNotFoundException -import com.egm.stellio.shared.model.ResourceNotFoundResponse -import com.egm.stellio.shared.model.TooManyResultsException -import com.egm.stellio.shared.model.TooManyResultsResponse import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY import com.egm.stellio.shared.util.JsonUtils.serializeObject import org.slf4j.LoggerFactory @@ -84,40 +64,6 @@ const val ENTITY_DOES_NOT_EXIST_MESSAGE = "Entity does not exist" private val logger = LoggerFactory.getLogger("com.egm.stellio.shared.util.ApiResponses") -/** - * this is globally duplicating what is in ExceptionHandler#transformErrorResponse() - * but main code there should move here when we no longer raise business exceptions - */ -fun APIException.toErrorResponse(): ResponseEntity<*> = - when (this) { - is AlreadyExistsException -> - generateErrorResponse(HttpStatus.CONFLICT, AlreadyExistsResponse(this.message)) - is ResourceNotFoundException -> - generateErrorResponse(HttpStatus.NOT_FOUND, ResourceNotFoundResponse(this.message)) - is InvalidRequestException -> - generateErrorResponse(HttpStatus.BAD_REQUEST, InvalidRequestResponse(this.message)) - is BadRequestDataException -> - generateErrorResponse(HttpStatus.BAD_REQUEST, BadRequestDataResponse(this.message)) - is OperationNotSupportedException -> - generateErrorResponse(HttpStatus.BAD_REQUEST, OperationNotSupportedResponse(this.message)) - is AccessDeniedException -> - generateErrorResponse(HttpStatus.FORBIDDEN, AccessDeniedResponse(this.message)) - is NotImplementedException -> - generateErrorResponse(HttpStatus.NOT_IMPLEMENTED, NotImplementedResponse(this.message)) - is LdContextNotAvailableException -> - generateErrorResponse(HttpStatus.SERVICE_UNAVAILABLE, LdContextNotAvailableResponse(this.message)) - is TooManyResultsException -> - generateErrorResponse(HttpStatus.FORBIDDEN, TooManyResultsResponse(this.message)) - else -> generateErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, InternalErrorResponse("$cause")) - } - -fun generateErrorResponse(status: HttpStatus, exception: ErrorResponse): ResponseEntity<*> { - logger.info("Returning error ${exception.type} (${exception.detail})") - return ResponseEntity.status(status) - .contentType(MediaType.APPLICATION_JSON) - .body(serializeObject(exception)) -} - fun missingPathErrorResponse(errorMessage: String): ResponseEntity<*> { logger.info("Bad Request: $errorMessage") return BadRequestDataException(errorMessage).toErrorResponse() diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/web/ExceptionHandler.kt b/shared/src/main/kotlin/com/egm/stellio/shared/web/ExceptionHandler.kt index da1904d88..7b808ed48 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/web/ExceptionHandler.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/web/ExceptionHandler.kt @@ -1,30 +1,13 @@ package com.egm.stellio.shared.web import com.apicatalog.jsonld.JsonLdError -import com.egm.stellio.shared.model.AccessDeniedException -import com.egm.stellio.shared.model.AccessDeniedResponse -import com.egm.stellio.shared.model.AlreadyExistsException -import com.egm.stellio.shared.model.AlreadyExistsResponse -import com.egm.stellio.shared.model.BadRequestDataException -import com.egm.stellio.shared.model.BadRequestDataResponse -import com.egm.stellio.shared.model.ErrorResponse -import com.egm.stellio.shared.model.InternalErrorResponse -import com.egm.stellio.shared.model.InvalidRequestException -import com.egm.stellio.shared.model.InvalidRequestResponse -import com.egm.stellio.shared.model.JsonLdErrorResponse -import com.egm.stellio.shared.model.JsonParseErrorResponse -import com.egm.stellio.shared.model.LdContextNotAvailableException -import com.egm.stellio.shared.model.LdContextNotAvailableResponse -import com.egm.stellio.shared.model.NonexistentTenantException -import com.egm.stellio.shared.model.NonexistentTenantResponse -import com.egm.stellio.shared.model.NotAcceptableResponse -import com.egm.stellio.shared.model.NotImplementedException -import com.egm.stellio.shared.model.NotImplementedResponse -import com.egm.stellio.shared.model.ResourceNotFoundException -import com.egm.stellio.shared.model.ResourceNotFoundResponse -import com.egm.stellio.shared.model.UnsupportedMediaTypeResponse +import com.egm.stellio.shared.model.APIException +import com.egm.stellio.shared.model.InternalErrorException +import com.egm.stellio.shared.model.JsonLdErrorApiResponse +import com.egm.stellio.shared.model.JsonParseApiException +import com.egm.stellio.shared.model.NotAcceptableException +import com.egm.stellio.shared.model.UnsupportedMediaTypeStatusApiException import com.fasterxml.jackson.core.JsonParseException -import org.slf4j.LoggerFactory import org.springframework.core.codec.CodecException import org.springframework.http.HttpStatus import org.springframework.http.ProblemDetail @@ -38,75 +21,21 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException @RestControllerAdvice class ExceptionHandler { - private val logger = LoggerFactory.getLogger(javaClass) - @ExceptionHandler fun transformErrorResponse(throwable: Throwable): ResponseEntity = when (val cause = throwable.cause ?: throwable) { - is AlreadyExistsException -> generateErrorResponse( - HttpStatus.CONFLICT, - AlreadyExistsResponse(cause.message) - ) - is ResourceNotFoundException -> generateErrorResponse( - HttpStatus.NOT_FOUND, - ResourceNotFoundResponse(cause.message) - ) - is InvalidRequestException -> generateErrorResponse( - HttpStatus.BAD_REQUEST, - InvalidRequestResponse(cause.message) - ) - is BadRequestDataException -> generateErrorResponse( - HttpStatus.BAD_REQUEST, - BadRequestDataResponse(cause.message) - ) - is JsonLdError -> generateErrorResponse( - HttpStatus.BAD_REQUEST, - JsonLdErrorResponse(cause.code.toString(), cause.message.orEmpty()) - ) - is JsonParseException, is CodecException -> generateErrorResponse( - HttpStatus.BAD_REQUEST, - JsonParseErrorResponse(cause.message ?: "There has been a problem during JSON parsing") - ) - is AccessDeniedException -> generateErrorResponse( - HttpStatus.FORBIDDEN, - AccessDeniedResponse(cause.message) - ) - is NotImplementedException -> generateErrorResponse( - HttpStatus.NOT_IMPLEMENTED, - NotImplementedResponse(cause.message) - ) - is LdContextNotAvailableException -> generateErrorResponse( - HttpStatus.SERVICE_UNAVAILABLE, - LdContextNotAvailableResponse(cause.message) - ) - is UnsupportedMediaTypeStatusException -> generateErrorResponse( - HttpStatus.UNSUPPORTED_MEDIA_TYPE, - UnsupportedMediaTypeResponse(cause.message) - ) - is NotAcceptableStatusException -> generateErrorResponse( - HttpStatus.NOT_ACCEPTABLE, - NotAcceptableResponse(cause.message) - ) + is APIException -> cause.toErrorResponse() + is JsonLdError -> + JsonLdErrorApiResponse(cause.code.toString(), cause.message.orEmpty()).toErrorResponse() + is JsonParseException, is CodecException -> + JsonParseApiException(cause.message ?: "There has been a problem during JSON parsing").toErrorResponse() + is UnsupportedMediaTypeStatusException -> + UnsupportedMediaTypeStatusApiException(cause.message).toErrorResponse() + is NotAcceptableStatusException -> + NotAcceptableException(cause.message).toErrorResponse() is MethodNotAllowedException -> ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(cause.body) - is NonexistentTenantException -> generateErrorResponse( - HttpStatus.NOT_FOUND, - NonexistentTenantResponse(cause.message) - ) - else -> generateErrorResponse( - HttpStatus.INTERNAL_SERVER_ERROR, - InternalErrorResponse("$cause") - ) - } - private fun generateErrorResponse(status: HttpStatus, exception: ErrorResponse): ResponseEntity { - logger.info("Returning error ${exception.type} (${exception.detail})") - return ResponseEntity.status(status) - .body( - ProblemDetail.forStatusAndDetail(status, exception.detail).also { - it.title = exception.title - it.type = exception.type - } - ) - } + else -> InternalErrorException("$cause").toErrorResponse() + } } diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/web/TenantWebFilter.kt b/shared/src/main/kotlin/com/egm/stellio/shared/web/TenantWebFilter.kt index 5ffc7db7d..6f6cf8843 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/web/TenantWebFilter.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/web/TenantWebFilter.kt @@ -1,21 +1,15 @@ package com.egm.stellio.shared.web import com.egm.stellio.shared.config.ApplicationProperties -import com.egm.stellio.shared.model.NonexistentTenantResponse -import com.egm.stellio.shared.util.JsonUtils.serializeObject +import com.egm.stellio.shared.model.NonexistentTenantException import jakarta.annotation.PostConstruct import org.slf4j.LoggerFactory import org.springframework.core.Ordered import org.springframework.core.annotation.Order -import org.springframework.core.io.buffer.DefaultDataBufferFactory -import org.springframework.http.HttpHeaders.CONTENT_TYPE -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.WebFilter import org.springframework.web.server.WebFilterChain -import reactor.core.publisher.Flux import reactor.core.publisher.Mono const val NGSILD_TENANT_HEADER = "NGSILD-Tenant" @@ -44,12 +38,7 @@ class TenantWebFilter( val tenantName = tenantNameFromHeader ?: DEFAULT_TENANT_NAME if (!tenantsNames.contains(tenantName)) { logger.error("Unknown tenant requested: $tenantName") - exchange.response.setStatusCode(HttpStatus.NOT_FOUND) - exchange.response.headers[CONTENT_TYPE] = MediaType.APPLICATION_JSON_VALUE - val errorResponse = serializeObject(NonexistentTenantResponse("Tenant $tenantName does not exist")) - return exchange.response.writeWith( - Flux.just(DefaultDataBufferFactory().wrap(errorResponse.toByteArray())) - ) + return NonexistentTenantException("Tenant $tenantName does not exist").toServerWebExchange(exchange) } return chain diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/SubscriptionHandler.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/SubscriptionHandler.kt index 3411b0d96..c073a4945 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/SubscriptionHandler.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/SubscriptionHandler.kt @@ -26,7 +26,6 @@ import com.egm.stellio.shared.util.getContextFromLinkHeaderOrDefault import com.egm.stellio.shared.util.getSubFromSecurityContext import com.egm.stellio.shared.util.parsePaginationParameters import com.egm.stellio.shared.util.prepareGetSuccessResponseHeaders -import com.egm.stellio.shared.util.toErrorResponse import com.egm.stellio.shared.web.BaseHandler import com.egm.stellio.subscription.model.Subscription import com.egm.stellio.subscription.model.serialize diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt index a5265142a..7e2ec795c 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt @@ -236,7 +236,7 @@ class SubscriptionHandlerTests { { "type":"https://uri.etsi.org/ngsi-ld/errors/InternalError", "title":"There has been an error during the operation execution", - "detail":"InternalErrorException(message=Internal Server Exception)" + "detail":"Internal Server Exception" } """ ) From d21718475fbfadf8a2348ef2921a5fcf055177a6 Mon Sep 17 00:00:00 2001 From: Thomas BOUSSELIN Date: Mon, 18 Nov 2024 15:38:38 +0100 Subject: [PATCH 2/4] fix: PR comment and test --- .../egm/stellio/shared/model/ApiExceptions.kt | 29 ++++--------------- .../egm/stellio/shared/web/TenantWebFilter.kt | 14 ++++++++- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt index 1a0db0b0a..9c844aa7d 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt @@ -2,19 +2,13 @@ package com.egm.stellio.shared.model import com.apicatalog.jsonld.JsonLdError import com.apicatalog.jsonld.JsonLdErrorCode -import com.egm.stellio.shared.util.JsonUtils.serializeObject import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import org.slf4j.LoggerFactory -import org.springframework.core.io.buffer.DefaultDataBufferFactory -import org.springframework.http.HttpHeaders.CONTENT_TYPE import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ProblemDetail import org.springframework.http.ResponseEntity -import org.springframework.web.server.ServerWebExchange -import reactor.core.publisher.Flux -import reactor.core.publisher.Mono import java.net.URI const val DEFAULT_DETAIL = "If you have difficulty finding the issue" + @@ -32,26 +26,15 @@ sealed class APIException( ) : Exception(message) { private val logger = LoggerFactory.getLogger(javaClass) + fun toProblemDetail(): ProblemDetail = ProblemDetail.forStatusAndDetail(status, this.message).also { + it.title = this.title + it.type = this.type + } fun toErrorResponse(): ResponseEntity { logger.info("Returning error ${this.type} (${this.message})") return ResponseEntity.status(status) .contentType(MediaType.APPLICATION_JSON) - .body( - ProblemDetail.forStatusAndDetail(status, this.message).also { - it.title = this.title - it.type = this.type - } - ) - } - - fun toServerWebExchange(exchange: ServerWebExchange): Mono { - logger.info("Returning server web exchange error ${this.type} (${this.message})") - exchange.response.statusCode = this.status - exchange.response.headers[CONTENT_TYPE] = MediaType.APPLICATION_JSON_VALUE - val body = serializeObject(this) - return exchange.response.writeWith( - Flux.just(DefaultDataBufferFactory().wrap(body.toByteArray())) - ) + .body(toProblemDetail()) } } @@ -62,7 +45,7 @@ data class AlreadyExistsException(override val message: String) : APIException( message ) -data class InvalidRequestException(override val message: String) : APIException( // todo check +data class InvalidRequestException(override val message: String) : APIException( ErrorType.INVALID_REQUEST.type, HttpStatus.BAD_REQUEST, "The request associated to the operation is syntactically invalid or includes wrong content", diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/web/TenantWebFilter.kt b/shared/src/main/kotlin/com/egm/stellio/shared/web/TenantWebFilter.kt index 6f6cf8843..3ea419780 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/web/TenantWebFilter.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/web/TenantWebFilter.kt @@ -2,14 +2,20 @@ package com.egm.stellio.shared.web import com.egm.stellio.shared.config.ApplicationProperties import com.egm.stellio.shared.model.NonexistentTenantException +import com.egm.stellio.shared.util.JsonUtils.serializeObject import jakarta.annotation.PostConstruct import org.slf4j.LoggerFactory import org.springframework.core.Ordered import org.springframework.core.annotation.Order +import org.springframework.core.io.buffer.DefaultDataBufferFactory +import org.springframework.http.HttpHeaders.CONTENT_TYPE +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.WebFilter import org.springframework.web.server.WebFilterChain +import reactor.core.publisher.Flux import reactor.core.publisher.Mono const val NGSILD_TENANT_HEADER = "NGSILD-Tenant" @@ -38,7 +44,13 @@ class TenantWebFilter( val tenantName = tenantNameFromHeader ?: DEFAULT_TENANT_NAME if (!tenantsNames.contains(tenantName)) { logger.error("Unknown tenant requested: $tenantName") - return NonexistentTenantException("Tenant $tenantName does not exist").toServerWebExchange(exchange) + exchange.response.setStatusCode(HttpStatus.NOT_FOUND) + exchange.response.headers[CONTENT_TYPE] = MediaType.APPLICATION_JSON_VALUE + val errorResponse = + serializeObject(NonexistentTenantException("Tenant $tenantName does not exist").toProblemDetail()) + return exchange.response.writeWith( + Flux.just(DefaultDataBufferFactory().wrap(errorResponse.toByteArray())) + ) } return chain From b7674a1e7b094a6f1e5835ac36623b4c478efee4 Mon Sep 17 00:00:00 2001 From: Thomas BOUSSELIN Date: Mon, 18 Nov 2024 16:49:14 +0100 Subject: [PATCH 3/4] feat: remove title and replace it by detail. Detail is now used to redirect people to troubleshoot. --- .../web/EntityAccessControlHandlerTests.kt | 13 +- .../discovery/web/AttributeHandlerTests.kt | 5 +- .../discovery/web/EntityTypeHandlerTests.kt | 7 +- .../search/entity/web/EntityHandlerTests.kt | 207 +++++++++--------- .../entity/web/EntityOperationHandlerTests.kt | 14 +- .../web/TemporalEntityHandlerTests.kt | 189 ++++++++-------- .../TemporalEntityOperationsHandlerTests.kt | 5 +- .../egm/stellio/shared/model/ApiExceptions.kt | 30 +-- .../shared/web/TenantWebFilterTests.kt | 2 +- 9 files changed, 231 insertions(+), 241 deletions(-) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandlerTests.kt index ba34646ab..a633bde93 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandlerTests.kt @@ -10,6 +10,7 @@ import com.egm.stellio.search.common.config.SearchProperties import com.egm.stellio.shared.config.ApplicationProperties import com.egm.stellio.shared.model.AccessDeniedException import com.egm.stellio.shared.model.BadRequestDataException +import com.egm.stellio.shared.model.DEFAULT_DETAIL import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.ExpandedTerm import com.egm.stellio.shared.model.NgsiLdPropertyInstance @@ -357,9 +358,9 @@ class EntityAccessControlHandlerTests { .expectBody().json( """ { - "detail": "User forbidden to remove ownership of entity", + "title": "User forbidden to remove ownership of entity", "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource" + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -400,9 +401,9 @@ class EntityAccessControlHandlerTests { .expectBody().json( """ { - "detail": "No right found for urn:ngsi-ld:User:0123 on urn:ngsi-ld:Entity:entityId1", + "detail": "$DEFAULT_DETAIL", "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title": "The referred resource has not been found" + "title": "No right found for urn:ngsi-ld:User:0123 on urn:ngsi-ld:Entity:entityId1" } """.trimIndent() ) @@ -503,9 +504,9 @@ class EntityAccessControlHandlerTests { .expectBody().json( """ { - "detail": "$expectedAttr is not authorized property name", + "title": "$expectedAttr is not authorized property name", "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation" + "detail":"$DEFAULT_DETAIL" } """.trimIndent() ) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/discovery/web/AttributeHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/discovery/web/AttributeHandlerTests.kt index eaf2bdd5f..c866a9f90 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/discovery/web/AttributeHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/discovery/web/AttributeHandlerTests.kt @@ -9,6 +9,7 @@ import com.egm.stellio.search.discovery.model.AttributeType import com.egm.stellio.search.discovery.model.AttributeTypeInfo import com.egm.stellio.search.discovery.service.AttributeService import com.egm.stellio.shared.config.ApplicationProperties +import com.egm.stellio.shared.model.DEFAULT_DETAIL import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.util.APIC_COMPOUND_CONTEXTS import com.egm.stellio.shared.util.APIC_HEADER_LINK @@ -257,8 +258,8 @@ class AttributeHandlerTests { """ { "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"${attributeNotFoundMessage(OUTGOING_PROPERTY)}" + "title":"${attributeNotFoundMessage(OUTGOING_PROPERTY)}", + "detail":"$DEFAULT_DETAIL" } """ ) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandlerTests.kt index 760d75706..f92de9e58 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandlerTests.kt @@ -10,6 +10,7 @@ import com.egm.stellio.search.discovery.model.EntityTypeInfo import com.egm.stellio.search.discovery.model.EntityTypeList import com.egm.stellio.search.discovery.service.EntityTypeService import com.egm.stellio.shared.config.ApplicationProperties +import com.egm.stellio.shared.model.DEFAULT_DETAIL import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.util.APIC_COMPOUND_CONTEXTS import com.egm.stellio.shared.util.APIC_HEADER_LINK @@ -309,9 +310,9 @@ class EntityTypeHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"${typeNotFoundMessage(BEEHIVE_TYPE)}" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${typeNotFoundMessage(BEEHIVE_TYPE)}", + "detail": "$DEFAULT_DETAIL" } """ ) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt index 0845daf93..ff32f873c 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt @@ -19,6 +19,7 @@ import com.egm.stellio.shared.config.ApplicationProperties import com.egm.stellio.shared.model.AccessDeniedException import com.egm.stellio.shared.model.AlreadyExistsException import com.egm.stellio.shared.model.BadRequestDataException +import com.egm.stellio.shared.model.DEFAULT_DETAIL import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.InternalErrorException import com.egm.stellio.shared.model.NgsiLdEntity @@ -177,9 +178,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/AlreadyExists", - "title":"The referred element already exists", - "detail":"Already Exists" + "type": "https://uri.etsi.org/ngsi-ld/errors/AlreadyExists", + "title": "Already Exists", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -201,9 +202,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/InternalError", - "title":"There has been an error during the operation execution", - "detail":"Internal Server Exception" + "type": "https://uri.etsi.org/ngsi-ld/errors/InternalError", + "title": "Internal Server Exception", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -273,8 +274,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title": "The request includes input data which does not meet the requirements of the operation", - "detail": "Target entity does not exist" + "title": "Target entity does not exist", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -297,8 +298,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden to create entities" + "title": "User forbidden to create entities", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -440,9 +441,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"$expectedMessage" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "$expectedMessage", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -508,9 +509,9 @@ class EntityHandlerTests { { "createdAt":"2015-10-18T11:20:30.000001Z", "testedAt":{ - "type":"Property", + "type": "Property", "value":{ - "type":"DateTime", + "type": "DateTime", "@value":"2015-10-18T11:20:30.000001Z" }, "createdAt":"2015-10-18T11:20:30.000001Z", @@ -547,9 +548,9 @@ class EntityHandlerTests { """ { "testedAt":{ - "type":"Property", + "type": "Property", "value":{ - "type":"Date", + "type": "Date", "@value":"2015-10-18" } }, @@ -584,9 +585,9 @@ class EntityHandlerTests { """ { "testedAt":{ - "type":"Property", + "type": "Property", "value":{ - "type":"Time", + "type": "Time", "@value":"11:20:30" } }, @@ -622,8 +623,8 @@ class EntityHandlerTests { """ { "id":"urn:ngsi-ld:Beehive:4567", - "type":"Beehive", - "name":{"type":"Property","datasetId":"urn:ngsi-ld:Property:french-name","value":"ruche"}, + "type": "Beehive", + "name":{"type": "Property","datasetId":"urn:ngsi-ld:Property:french-name","value":"ruche"}, "@context": "${applicationProperties.contexts.core}" } """.trimIndent() @@ -665,13 +666,13 @@ class EntityHandlerTests { """ { "id":"urn:ngsi-ld:Beehive:4567", - "type":"Beehive", + "type": "Beehive", "name":[ { - "type":"Property","datasetId":"urn:ngsi-ld:Property:english-name","value":"beehive" + "type": "Property","datasetId":"urn:ngsi-ld:Property:english-name","value":"beehive" }, { - "type":"Property","datasetId":"urn:ngsi-ld:Property:french-name","value":"ruche" + "type": "Property","datasetId":"urn:ngsi-ld:Property:french-name","value":"ruche" } ], "@context": "${applicationProperties.contexts.core}" @@ -708,9 +709,9 @@ class EntityHandlerTests { """ { "id":"urn:ngsi-ld:Beehive:4567", - "type":"Beehive", + "type": "Beehive", "managedBy": { - "type":"Relationship", + "type": "Relationship", "datasetId":"urn:ngsi-ld:Dataset:managedBy:0215", "object":"urn:ngsi-ld:Beekeeper:1230" }, @@ -795,14 +796,14 @@ class EntityHandlerTests { """ { "id":"urn:ngsi-ld:Beehive:4567", - "type":"Beehive", + "type": "Beehive", "managedBy":[ { - "type":"Relationship", + "type": "Relationship", "object":"urn:ngsi-ld:Beekeeper:1229" }, { - "type":"Relationship", + "type": "Relationship", "datasetId":"urn:ngsi-ld:Dataset:managedBy:0215", "object":"urn:ngsi-ld:Beekeeper:1230" } @@ -827,9 +828,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"${entityNotFoundMessage("urn:ngsi-ld:BeeHive:TEST")}" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${entityNotFoundMessage("urn:ngsi-ld:BeeHive:TEST")}", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -850,8 +851,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden read access to entity urn:ngsi-ld:BeeHive:TEST" + "title": "User forbidden read access to entity urn:ngsi-ld:BeeHive:TEST", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1037,9 +1038,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Offset must be greater than zero and limit must be strictly greater than zero" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Offset must be greater than zero and limit must be strictly greater than zero", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1054,9 +1055,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/TooManyResults", - "title":"The query associated to the operation is producing so many results that can exhaust client or server resources. It should be made more restrictive", - "detail":"You asked for 200 results, but the supported maximum limit is 100" + "type": "https://uri.etsi.org/ngsi-ld/errors/TooManyResults", + "title": "You asked for 200 results, but the supported maximum limit is 100", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1128,9 +1129,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Offset and limit must be greater than zero" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Offset and limit must be greater than zero", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1159,9 +1160,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1212,8 +1213,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden to modify entity" + "title": "User forbidden to modify entity", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1248,9 +1249,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"The id contained in the body is not the same as the one provided in the URL" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "The id contained in the body is not the same as the one provided in the URL", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1265,9 +1266,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Missing entity id when trying to replace an entity" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Missing entity id when trying to replace an entity", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1442,9 +1443,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"Entity urn:ngsi-ld:BreedingService:0214 was not found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "Entity urn:ngsi-ld:BreedingService:0214 was not found", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1458,7 +1459,7 @@ class EntityHandlerTests { """ { "connectsTo":{ - "type":"Relationship" + "type": "Relationship" } } """.trimIndent() @@ -1480,8 +1481,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title": "The request includes input data which does not meet the requirements of the operation", - "detail": "Relationship https://ontology.eglobalmark.com/egm#connectsTo does not have an object field" + "title": "Relationship https://ontology.eglobalmark.com/egm#connectsTo does not have an object field", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1507,8 +1508,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden write access to entity urn:ngsi-ld:BreedingService:0214" + "title": "User forbidden write access to entity urn:ngsi-ld:BreedingService:0214", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1614,8 +1615,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden write access to entity urn:ngsi-ld:DeadFishes:019BN" + "title": "User forbidden write access to entity urn:ngsi-ld:DeadFishes:019BN", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1714,9 +1715,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'observedAt' parameter is not a valid date" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'observedAt' parameter is not a valid date", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1740,9 +1741,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"${entityNotFoundMessage(entityId.toString())}" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${entityNotFoundMessage(entityId.toString())}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1768,8 +1769,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden write access to entity urn:ngsi-ld:DeadFishes:019BN" + "title": "User forbidden write access to entity urn:ngsi-ld:DeadFishes:019BN", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1789,9 +1790,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Missing entity id when trying to merge an entity" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Missing entity id when trying to merge an entity", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1906,8 +1907,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/LdContextNotAvailable", - "title": "A remote JSON-LD @context referenced in a request cannot be retrieved by the NGSI-LD Broker and expansion or compaction cannot be performed", - "detail": "Unable to load remote context (cause was: JsonLdError[code=There was a problem encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED]., message=There was a problem encountered loading a remote context [https://easyglobalmarket.com/contexts/diat.jsonld]])" + "title": "Unable to load remote context (cause was: JsonLdError[code=There was a problem encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED]., message=There was a problem encountered loading a remote context [https://easyglobalmarket.com/contexts/diat.jsonld]])", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1937,9 +1938,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"Entity $beehiveId was not found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "Entity $beehiveId was not found", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1965,8 +1966,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden write access to entity urn:ngsi-ld:Sensor:0022CCC" + "title": "User forbidden write access to entity urn:ngsi-ld:Sensor:0022CCC", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -2000,9 +2001,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"${entityNotFoundMessage(beehiveId.toString())}" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${entityNotFoundMessage(beehiveId.toString())}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -2021,9 +2022,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/InternalError", - "title":"There has been an error during the operation execution", - "detail":"java.lang.RuntimeException: Unexpected server error" + "type": "https://uri.etsi.org/ngsi-ld/errors/InternalError", + "title": "java.lang.RuntimeException: Unexpected server error", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -2044,8 +2045,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden admin access to entity $beehiveId" + "title": "User forbidden admin access to entity $beehiveId", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -2142,9 +2143,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"Entity urn:ngsi-ld:BeeHive:TESTC was not found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "Entity urn:ngsi-ld:BeeHive:TESTC was not found", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -2165,9 +2166,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"Attribute Not Found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "Attribute Not Found", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -2189,8 +2190,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title": "The request includes input data which does not meet the requirements of the operation", - "detail": "Something is wrong with the request" + "title": "Something is wrong with the request", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -2212,8 +2213,8 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden write access to entity urn:ngsi-ld:BeeHive:TESTC" + "title": "User forbidden write access to entity urn:ngsi-ld:BeeHive:TESTC", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -2275,9 +2276,9 @@ class EntityHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"Unknown attribute $INCOMING_PROPERTY in entity $beehiveId" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "Unknown attribute $INCOMING_PROPERTY in entity $beehiveId", + "detail": "$DEFAULT_DETAIL" } """ ) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityOperationHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityOperationHandlerTests.kt index efebbdfc0..2a995cec3 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityOperationHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityOperationHandlerTests.kt @@ -8,6 +8,7 @@ import com.egm.stellio.search.entity.model.UpdateResult import com.egm.stellio.search.entity.service.EntityOperationService import com.egm.stellio.search.entity.service.EntityQueryService import com.egm.stellio.shared.config.ApplicationProperties +import com.egm.stellio.shared.model.DEFAULT_DETAIL import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.NgsiLdEntity import com.egm.stellio.shared.util.BEEHIVE_TYPE @@ -305,9 +306,9 @@ class EntityOperationHandlerTests { """ { "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail": - "Request payload must contain @context term for a request having an application/ld+json content type" + "title": + "Request payload must contain @context term for a request having an application/ld+json content type", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -406,9 +407,10 @@ class EntityOperationHandlerTests { """ { "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail": - "Request payload must contain @context term for a request having an application/ld+json content type" + "title": + "Request payload must contain @context term for a request having an application/ld+json content type", + "detail": "$DEFAULT_DETAIL", + } """.trimIndent() ) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandlerTests.kt index eb2910f5d..24b3a4126 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandlerTests.kt @@ -11,6 +11,7 @@ import com.egm.stellio.search.temporal.service.TemporalService.CreateOrUpdateRes import com.egm.stellio.shared.config.ApplicationProperties import com.egm.stellio.shared.model.AccessDeniedException import com.egm.stellio.shared.model.BadRequestDataException +import com.egm.stellio.shared.model.DEFAULT_DETAIL import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.model.TooManyResultsException @@ -220,9 +221,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Unable to expand input payload" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Unable to expand input payload", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -255,9 +256,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'timerel' and 'time' must be used in conjunction" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'timerel' and 'time' must be used in conjunction", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -272,9 +273,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'timerel' and 'time' must be used in conjunction" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'timerel' and 'time' must be used in conjunction", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -302,9 +303,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'endTimeAt' request parameter is mandatory if 'timerel' is 'between'" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'endTimeAt' request parameter is mandatory if 'timerel' is 'between'", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -319,9 +320,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'timeAt' parameter is not a valid date" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'timeAt' parameter is not a valid date", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -336,9 +337,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'timerel' is not valid, it should be one of 'before', 'between', or 'after'" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'timerel' is not valid, it should be one of 'before', 'between', or 'after'", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -356,9 +357,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'endTimeAt' parameter is not a valid date" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'endTimeAt' parameter is not a valid date", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -376,9 +377,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'aggrMethods' is mandatory if 'aggregatedValues' option is specified" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'aggrMethods' is mandatory if 'aggregatedValues' option is specified", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -396,9 +397,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'unknown' is not a recognized aggregation method for 'aggrMethods' parameter" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'unknown' is not a recognized aggregation method for 'aggrMethods' parameter", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -416,9 +417,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'aggrMethods' is mandatory if 'aggregatedValues' option is specified" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'aggrMethods' is mandatory if 'aggregatedValues' option is specified", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -440,9 +441,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"${entityNotFoundMessage(entityUri.toString())}" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${entityNotFoundMessage(entityUri.toString())}", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -564,9 +565,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'timerel' and 'time' must be used in conjunction" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "'timerel' and 'time' must be used in conjunction", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -797,9 +798,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Offset must be greater than zero and limit must be strictly greater than zero" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Offset must be greater than zero and limit must be strictly greater than zero", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -824,9 +825,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Offset must be greater than zero and limit must be strictly greater than zero" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Offset must be greater than zero and limit must be strictly greater than zero", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -851,9 +852,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/TooManyResults", - "title":"The query associated to the operation is producing so many results that can exhaust client or server resources. It should be made more restrictive", - "detail":"You asked for 200 results, but the supported maximum limit is 100" + "type": "https://uri.etsi.org/ngsi-ld/errors/TooManyResults", + "title": "You asked for 200 results, but the supported maximum limit is 100", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -905,8 +906,8 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden write access to entity $entityUri" + "title": "User forbidden write access to entity $entityUri", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -931,9 +932,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"Entity urn:ngsi-ld:BeeHive:TESTC was not found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "Entity urn:ngsi-ld:BeeHive:TESTC was not found", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -961,9 +962,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "detail":"${attributeOrInstanceNotFoundMessage(expandedAttr, attributeInstanceId.toString())}", - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${attributeOrInstanceNotFoundMessage(expandedAttr, attributeInstanceId.toString())}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -997,9 +998,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"${entityNotFoundMessage(entityUri.toString())}" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${entityNotFoundMessage(entityUri.toString())}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1014,9 +1015,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"The supplied identifier was expected to be an URI but it is not: beehive" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "The supplied identifier was expected to be an URI but it is not: beehive", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1043,9 +1044,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/InternalError", - "title":"There has been an error during the operation execution", - "detail":"java.lang.RuntimeException: Unexpected server error" + "type": "https://uri.etsi.org/ngsi-ld/errors/InternalError", + "title": "java.lang.RuntimeException: Unexpected server error", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -1066,8 +1067,8 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden admin access to entity $entityUri" + "title": "User forbidden admin access to entity $entityUri", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1164,9 +1165,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"Entity urn:ngsi-ld:BeeHive:TESTC was not found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "Entity urn:ngsi-ld:BeeHive:TESTC was not found", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1187,9 +1188,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"Attribute Not Found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "Attribute Not Found", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1211,8 +1212,8 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title": "The request includes input data which does not meet the requirements of the operation", - "detail": "Something is wrong with the request" + "title": "Something is wrong with the request", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1227,9 +1228,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"The supplied identifier was expected to be an URI but it is not: beehive" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "The supplied identifier was expected to be an URI but it is not: beehive", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1244,9 +1245,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Missing entity id or attribute id when trying to delete an attribute temporal" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Missing entity id or attribute id when trying to delete an attribute temporal", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1261,9 +1262,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Missing entity id or attribute id when trying to delete an attribute temporal" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Missing entity id or attribute id when trying to delete an attribute temporal", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1285,8 +1286,8 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden write access to entity urn:ngsi-ld:BeeHive:TESTC" + "title": "User forbidden write access to entity urn:ngsi-ld:BeeHive:TESTC", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1327,9 +1328,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "detail":"${entityNotFoundMessage(entityUri.toString())}", - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${entityNotFoundMessage(entityUri.toString())}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1363,9 +1364,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "detail":"${attributeOrInstanceNotFoundMessage(expandedAttr, attributeInstanceId.toString())}", - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${attributeOrInstanceNotFoundMessage(expandedAttr, attributeInstanceId.toString())}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1391,8 +1392,8 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "The request tried to access an unauthorized resource", - "detail": "User forbidden write access to entity $entityUri" + "title": "User forbidden write access to entity $entityUri", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -1409,9 +1410,9 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityOperationsHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityOperationsHandlerTests.kt index 4b3af2a95..7f4ff8e9b 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityOperationsHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityOperationsHandlerTests.kt @@ -7,6 +7,7 @@ import com.egm.stellio.search.support.buildDefaultTestTemporalQuery import com.egm.stellio.search.temporal.model.TemporalQuery import com.egm.stellio.search.temporal.service.TemporalQueryService import com.egm.stellio.shared.config.ApplicationProperties +import com.egm.stellio.shared.model.DEFAULT_DETAIL import com.egm.stellio.shared.util.APIARY_COMPACT_TYPE import com.egm.stellio.shared.util.APIARY_TYPE import com.egm.stellio.shared.util.APIC_HEADER_LINK @@ -186,8 +187,8 @@ class TemporalEntityOperationsHandlerTests { """ { "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"'timerel' and 'time' must be used in conjunction" + "title":"'timerel' and 'time' must be used in conjunction", + "detail":"$DEFAULT_DETAIL" } """ ) diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt index 9c844aa7d..567c88373 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt @@ -20,14 +20,14 @@ sealed class APIException( val type: URI, @JsonIgnore val status: HttpStatus, - open val title: String, @JsonProperty("detail") - override val message: String = DEFAULT_DETAIL + override val message: String, + open val detail: String = DEFAULT_DETAIL ) : Exception(message) { private val logger = LoggerFactory.getLogger(javaClass) - fun toProblemDetail(): ProblemDetail = ProblemDetail.forStatusAndDetail(status, this.message).also { - it.title = this.title + fun toProblemDetail(): ProblemDetail = ProblemDetail.forStatusAndDetail(status, this.detail).also { + it.title = this.message it.type = this.type } fun toErrorResponse(): ResponseEntity { @@ -41,106 +41,88 @@ sealed class APIException( data class AlreadyExistsException(override val message: String) : APIException( ErrorType.ALREADY_EXISTS.type, HttpStatus.CONFLICT, - "The referred element already exists", message ) data class InvalidRequestException(override val message: String) : APIException( ErrorType.INVALID_REQUEST.type, HttpStatus.BAD_REQUEST, - "The request associated to the operation is syntactically invalid or includes wrong content", message ) data class BadRequestDataException(override val message: String) : APIException( ErrorType.BAD_REQUEST_DATA.type, HttpStatus.BAD_REQUEST, - "The request includes input data which does not meet the requirements of the operation", message ) data class OperationNotSupportedException(override val message: String) : APIException( ErrorType.OPERATION_NOT_SUPPORTED.type, HttpStatus.BAD_REQUEST, - "The operation is not supported", message ) data class ResourceNotFoundException(override val message: String) : APIException( ErrorType.RESOURCE_NOT_FOUND.type, HttpStatus.NOT_FOUND, - "The referred resource has not been found", message ) data class InternalErrorException(override val message: String) : APIException( ErrorType.INTERNAL_ERROR.type, HttpStatus.INTERNAL_SERVER_ERROR, - "There has been an error during the operation execution", message ) data class TooManyResultsException(override val message: String) : APIException( ErrorType.TOO_MANY_RESULTS.type, HttpStatus.FORBIDDEN, - "The query associated to the operation is producing so many results " + - "that can exhaust client or server resources. " + - "It should be made more restrictive", message ) data class AccessDeniedException(override val message: String) : APIException( ErrorType.ACCESS_DENIED.type, HttpStatus.FORBIDDEN, - "The request tried to access an unauthorized resource", message ) data class NotImplementedException(override val message: String) : APIException( ErrorType.NOT_IMPLEMENTED.type, HttpStatus.NOT_IMPLEMENTED, - "The requested functionality is not yet implemented", message ) data class LdContextNotAvailableException(override val message: String) : APIException( ErrorType.LD_CONTEXT_NOT_AVAILABLE.type, HttpStatus.SERVICE_UNAVAILABLE, - "A remote JSON-LD @context referenced in a request cannot be retrieved by the NGSI-LD Broker and " + - "expansion or compaction cannot be performed", message ) data class NonexistentTenantException(override val message: String) : APIException( ErrorType.NONEXISTENT_TENANT.type, HttpStatus.NOT_FOUND, - "The addressed tenant does not exist", message ) data class TooComplexQueryException(override val message: String) : APIException( // todo check ErrorType.TOO_COMPLEX_QUERY.type, HttpStatus.INTERNAL_SERVER_ERROR, // todo check - "The query associated to the operation is too complex and cannot be resolved", message ) data class NotAcceptableException(override val message: String) : APIException( ErrorType.NOT_ACCEPTABLE.type, HttpStatus.NOT_ACCEPTABLE, - "The media type provided in Accept header is not supported", message ) data class JsonParseApiException(override val message: String) : APIException( ErrorType.INVALID_REQUEST.type, HttpStatus.BAD_REQUEST, - "The request includes invalid input data, an error occurred during JSON parsing", message ) data class UnsupportedMediaTypeStatusApiException(override val message: String) : APIException( ErrorType.UNSUPPORTED_MEDIA_TYPE.type, HttpStatus.UNSUPPORTED_MEDIA_TYPE, - "The content type of the request is not supported", message ) -data class JsonLdErrorApiResponse(override val title: String, val detail: String) : APIException( +data class JsonLdErrorApiResponse(override val message: String, override val detail: String) : APIException( ErrorType.BAD_REQUEST_DATA.type, // todo check HttpStatus.BAD_REQUEST, - title, + message, detail ) diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/web/TenantWebFilterTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/web/TenantWebFilterTests.kt index 8dc570440..64fe177cd 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/web/TenantWebFilterTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/web/TenantWebFilterTests.kt @@ -42,7 +42,7 @@ class TenantWebFilterTests { .expectStatus().isEqualTo(HttpStatus.NOT_FOUND) .expectBody() .jsonPath("$..type").isEqualTo("https://uri.etsi.org/ngsi-ld/errors/NonexistentTenant") - .jsonPath("$..detail") + .jsonPath("$..title") .isEqualTo("Tenant urn:ngsi-ld:tenant:02 does not exist") } From 33c90bbf68aba6a03766733bf76b2a315a81627e Mon Sep 17 00:00:00 2001 From: Thomas BOUSSELIN Date: Mon, 18 Nov 2024 17:17:17 +0100 Subject: [PATCH 4/4] feat: new message + fix subscription service tests --- .../egm/stellio/shared/model/ApiExceptions.kt | 12 +-- .../web/SubscriptionHandlerTests.kt | 98 ++++++++++--------- 2 files changed, 56 insertions(+), 54 deletions(-) diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt index 567c88373..88cc5c956 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/ApiExceptions.kt @@ -11,10 +11,10 @@ import org.springframework.http.ProblemDetail import org.springframework.http.ResponseEntity import java.net.URI -const val DEFAULT_DETAIL = "If you have difficulty finding the issue" + - "you can check common issues here https://stellio.readthedocs.io/en/latest/TROUBLESHOOT.html" + - "If you think this is a bug of the broker" + - "you can create an issue on https://github.com/stellio-hub/stellio-context-broker" +const val DEFAULT_DETAIL = "If you have difficulty identifying the issue," + + "you can check common problem on https://stellio.readthedocs.io/en/latest/TROUBLESHOOT.html" + + "If you believe this is a bug, you can report it by creating an issue on" + + "https://github.com/stellio-hub/stellio-context-broker" sealed class APIException( val type: URI, @@ -96,9 +96,9 @@ data class NonexistentTenantException(override val message: String) : APIExcepti HttpStatus.NOT_FOUND, message ) -data class TooComplexQueryException(override val message: String) : APIException( // todo check +data class TooComplexQueryException(override val message: String) : APIException( ErrorType.TOO_COMPLEX_QUERY.type, - HttpStatus.INTERNAL_SERVER_ERROR, // todo check + HttpStatus.FORBIDDEN, message ) data class NotAcceptableException(override val message: String) : APIException( diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt index 7e2ec795c..5d828381b 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt @@ -5,6 +5,7 @@ import arrow.core.left import arrow.core.right import com.egm.stellio.shared.config.ApplicationProperties import com.egm.stellio.shared.model.BadRequestDataException +import com.egm.stellio.shared.model.DEFAULT_DETAIL import com.egm.stellio.shared.model.InternalErrorException import com.egm.stellio.shared.util.APIC_COMPOUND_CONTEXT import com.egm.stellio.shared.util.APIC_COMPOUND_CONTEXTS @@ -116,9 +117,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail": "${subscriptionNotFoundMessage(subscriptionId)}" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${subscriptionNotFoundMessage(subscriptionId)}", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -138,9 +139,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "detail":"${subscriptionUnauthorizedMessage(subscriptionId)}", - "type":"https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title":"The request tried to access an unauthorized resource" + "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", + "title": "${subscriptionUnauthorizedMessage(subscriptionId)}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -210,9 +211,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/AlreadyExists", - "title":"The referred element already exists", - "detail":"${subscriptionAlreadyExistsMessage(subscriptionId)}" + "type": "https://uri.etsi.org/ngsi-ld/errors/AlreadyExists", + "title": "${subscriptionAlreadyExistsMessage(subscriptionId)}", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -234,9 +235,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/InternalError", - "title":"There has been an error during the operation execution", - "detail":"Internal Server Exception" + "type": "https://uri.etsi.org/ngsi-ld/errors/InternalError", + "title": "Internal Server Exception", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -255,9 +256,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Request payload must contain @context term for a request having an application/ld+json content type" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Request payload must contain @context term for a request having an application/ld+json content type", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -295,9 +296,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"You can't use 'timeInterval' in conjunction with 'watchedAttributes'" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "You can't use 'timeInterval' in conjunction with 'watchedAttributes'", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -448,9 +449,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Offset must be greater than zero and limit must be strictly greater than zero" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Offset must be greater than zero and limit must be strictly greater than zero", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -470,9 +471,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Offset must be greater than zero and limit must be strictly greater than zero" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Offset must be greater than zero and limit must be strictly greater than zero", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -487,9 +488,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/TooManyResults", - "title":"The query associated to the operation is producing so many results that can exhaust client or server resources. It should be made more restrictive", - "detail":"You asked for 200 results, but the supported maximum limit is 100" + "type": "https://uri.etsi.org/ngsi-ld/errors/TooManyResults", + "title": "You asked for 200 results, but the supported maximum limit is 100", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -536,9 +537,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/InternalError", - "title":"There has been an error during the operation execution", - "detail":"java.lang.RuntimeException: Update failed" + "type": "https://uri.etsi.org/ngsi-ld/errors/InternalError", + "title": "java.lang.RuntimeException: Update failed", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -565,9 +566,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found", - "detail":"${subscriptionNotFoundMessage(subscriptionId)}" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${subscriptionNotFoundMessage(subscriptionId)}", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -591,9 +592,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title":"The request includes input data which does not meet the requirements of the operation", - "detail":"Request payload must contain @context term for a request having an application/ld+json content type" + "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", + "title": "Request payload must contain @context term for a request having an application/ld+json content type", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -618,9 +619,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "detail":"${subscriptionUnauthorizedMessage(subscriptionId)}", - "type":"https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title":"The request tried to access an unauthorized resource" + "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", + "title": "${subscriptionUnauthorizedMessage(subscriptionId)}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() ) @@ -662,9 +663,10 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "detail":"${subscriptionNotFoundMessage(subscriptionId)}", - "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title":"The referred resource has not been found" + "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", + "title": "${subscriptionNotFoundMessage(subscriptionId)}", + "detail": "$DEFAULT_DETAIL" + } """.trimIndent() ) @@ -688,9 +690,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "type":"https://uri.etsi.org/ngsi-ld/errors/InternalError", - "title":"There has been an error during the operation execution", - "detail":"java.lang.RuntimeException: Unexpected server error" + "type": "https://uri.etsi.org/ngsi-ld/errors/InternalError", + "title": "java.lang.RuntimeException: Unexpected server error", + "detail": "$DEFAULT_DETAIL" } """ ) @@ -712,9 +714,9 @@ class SubscriptionHandlerTests { .expectBody().json( """ { - "detail":"${subscriptionUnauthorizedMessage(subscriptionId)}", - "type":"https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title":"The request tried to access an unauthorized resource" + "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", + "title": "${subscriptionUnauthorizedMessage(subscriptionId)}", + "detail": "$DEFAULT_DETAIL" } """.trimIndent() )