How to improve error responses when using #RepositoryRestResource - spring-boot

I'm using spring's #RepositoryRestResource annotation on a PagingAndSortingRepository.
When I send an erroneous payload to the corresponding endpoint, the error responses that are sent back are hard to parse, e.g.
{
"cause": {
"cause": {
"cause": null,
"message": "ERROR: duplicate key value violates unique constraint \"uk_bawli8xm92f30ei6x9p3h8eju\"\n Detail: Key (email)=(jhunstone0#netlog.com) already exists."
},
"message": "could not execute statement"
},
"message": "could not execute statement; SQL [n/a]; constraint [uk_bawli8xm92f30ei6x9p3h8eju]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"
}
Is there any way to configure the messages, so it is clear which field (here: email) caused the error?

Regarding the error handling - you can implement a custom exception handler for such exceptions, extract the constraint name from the root cause, analyze it and create a corresponding message for the user.
Some error handling examples: 1, 2.
UPDATED
You should check the app log to determine which exception you have to handle. If I'm not mistaken for constraint violation we must handle org.springframework.dao.DataIntegrityViolationException, for example:
#ControllerAdvice
public class CommonExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(DataIntegrityViolationException.class)
ResponseEntity<?> handleDataIntegrityViolation(DataIntegrityViolationException ex, HttpServletRequest req) {
String causeMessage = NestedExceptionUtils.getMostSpecificCause(ex).getMessage(); // determine the root cause message
String reqPath = req.getServletPath(); // get the request path
String userMessage = ... // Decide what the message you will show to users
HttpStatus status = HttpStatus... // Decide what the status your response will be have, for example HttpStatus.CONFLICT
ApiErrorMessage message = new ApiErrorMessage(userMessage, status, reqPath); // Create your custom error message
return new ResponseEntity<>(message, status); // return response to users
}
// other handlers
}
Or you can implement this handler easier as in the official example.

Related

MSAL4J with Extensions error: com.microsoft.aad.msal4jextensions.CrossProcessCacheFileLock - null

I try to authenticate to Azure AD throught MSAL4J Java library (https://github.com/AzureAD/microsoft-authentication-library-for-java) with extention (https://github.com/AzureAD/microsoft-authentication-extensions-for-java) for cross platform cache serialization.
I am using the "Desktop app that calls web APIs: Acquire a token interactively" flow (https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token-interactive?tabs=dotnet)
Here is my code:
private static IAuthenticationResult acquireTokenInteractive(final boolean onlyFromCache) throws Exception {
scopeSet.add(API_SCOPE);
final PersistenceTokenCacheAccessAspect perSettings = new PersistenceTokenCacheAccessAspect(createPersistenceSettings());
pca = PublicClientApplication.builder(CLIENT_ID).authority(AUTHORITY).setTokenCacheAccessAspect(perSettings).build();
final Set<IAccount> accountsInCache = pca.getAccounts().join();
IAuthenticationResult result;
try {
// Take first account in the cache. In a production application, you would filter
// accountsInCache to get the right account for the user authenticating.
final IAccount account = accountsInCache.iterator().next();
final SilentParameters silentParameters = SilentParameters.builder(scopeSet, account).build();
// try to acquire token silently. This call will fail since the token cache
// does not have any data for the user you are trying to acquire a token for
result = pca.acquireTokenSilently(silentParameters).join();
} catch (final Exception ex) {
if ((ex.getCause() instanceof MsalException) || (ex instanceof NoSuchElementException)) {
if (!onlyFromCache) {
final InteractiveRequestParameters parameters = InteractiveRequestParameters.builder(new URI("http://localhost"))
.scopes(scopeSet).build();
// Try to acquire a token interactively with system browser. If successful, you should see
// the token and account information printed out to console
result = pca.acquireToken(parameters).join();
} else {
return null;
}
} else {
// Handle other exceptions accordingly
throw ex;
}
}
return result;
}
I get an error while accessing cache:
[ForkJoinPool.commonPool-worker-19] ERROR com.microsoft.aad.msal4jextensions.CrossProcessCacheFileLock - null
and when I try to acquire token, an exception is thrown after a valid login in the browser (Firefox) :
java.util.concurrent.CompletionException:
java.lang.AbstractMethodError: Receiver class
com.sun.jna.platform.win32.WinCrypt$DATA_BLOB does not define or
inherit an implementation of the resolved method 'abstract
java.util.List getFieldOrder()' of abstract class
com.sun.jna.Structure. at
java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at
java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at
java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
at
java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
at
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at
java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at
java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at
java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.AbstractMethodError: Receiver class
com.sun.jna.platform.win32.WinCrypt$DATA_BLOB does not define or
inherit an implementation of the resolved method 'abstract
java.util.List getFieldOrder()' of abstract class
com.sun.jna.Structure. at
com.sun.jna.Structure.fieldOrder(Structure.java:952) at
com.sun.jna.Structure.getFields(Structure.java:1006) at
com.sun.jna.Structure.deriveLayout(Structure.java:1172) at
com.sun.jna.Structure.calculateSize(Structure.java:1097) at
com.sun.jna.Structure.calculateSize(Structure.java:1049) at
com.sun.jna.Structure.allocateMemory(Structure.java:403) at
com.sun.jna.Structure.(Structure.java:194) at
com.sun.jna.Structure.(Structure.java:182) at
com.sun.jna.Structure.(Structure.java:169) at
com.sun.jna.Structure.(Structure.java:161) at
com.sun.jna.platform.win32.WinCrypt$DATA_BLOB.(WinCrypt.java:74)
at
com.sun.jna.platform.win32.Crypt32Util.cryptProtectData(Crypt32Util.java:80)
at
com.sun.jna.platform.win32.Crypt32Util.cryptProtectData(Crypt32Util.java:60)
at
com.sun.jna.platform.win32.Crypt32Util.cryptProtectData(Crypt32Util.java:47)
at
com.microsoft.aad.msal4jextensions.persistence.CacheFileAccessor.write(CacheFileAccessor.java:56)
at
com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect.afterCacheAccess(PersistenceTokenCacheAccessAspect.java:144)
at
com.microsoft.aad.msal4j.TokenCache$CacheAspect.close(TokenCache.java:171)
at com.microsoft.aad.msal4j.TokenCache.saveTokens(TokenCache.java:218)
at
com.microsoft.aad.msal4j.AbstractClientApplicationBase.acquireTokenCommon(AbstractClientApplicationBase.java:131)
at
com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:63)
at
com.microsoft.aad.msal4j.AcquireTokenByInteractiveFlowSupplier.acquireTokenWithAuthorizationCode(AcquireTokenByInteractiveFlowSupplier.java:193)
at
com.microsoft.aad.msal4j.AcquireTokenByInteractiveFlowSupplier.execute(AcquireTokenByInteractiveFlowSupplier.java:39)
at
com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:71)
at
com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:20)
at
java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
... 6 more
What am I doing wrong ?
Thanks by advance !

How to document internal error with microprofile

Assume following code written with Quarkus. But can as well be with micronaut.
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#APIResponses(
value = {
#APIResponse(
responseCode = "201",
description = "Customer Created"),
#APIResponse(
responseCode = "400",
description = "Customer already exists for customerId")
}
)
public Response post(#Valid Customer customer) {
final Customer saved = customerService.save(customer);
return Response.status(Response.Status.CREATED).entity(saved).build();
}
The Customer definition includes a field pictureUrl. CustomerService is responsible to validate the the URL is a valid URL and that the image really exists.
This means that following exception will be processed by the service: MalformedURLException and IOException. The CustomerService catches these errors and throws an application specific exception to report that the image does not exist or the path is not correct: ApplicationException.
How do you document this error case with microprofile?
My research suggests that I have to implement an exception mapper of the form:
public class ApplicationExceptionMapper implements ExceptionMapper<NotFoundException> {
#Override
#APIResponse(responseCode = "404", description = "Image not Found",
content = #Content(
schema = #Schema(implementation = Customer.class)
)
)
public Response toResponse(NotFoundException t) {
return Response.status(404, t.getMessage()).build();
}
}
And once I have a such mapper, the framework would know how to convert my exception into Response. Is my analysis correct? What is the best practice?
You are more or less pointed in the right direction, your question can be divided in two, let me answer separately both:
How to document an error using microprofile openapi: Using the api responses and the operation description is the correct way as you are doing, you can include a extended description of your errors and the specific Http error code associated with each if you want. This annotations should be present in the Rest Resource not in the ExceptionMapper.
Handling custom errors with micro profile or just the Jax-RS way of dealing with exceptions: A endpoint implemented with Jax-RS Apis knows how to hande WebApplicationExceptions automatically, By launching a custom exception which has this one us parent, the Jax-RS implementation automatically will know how to map your Exception to a Response.
Dealing with Unexpected exceptions or customizing responses for certain exceptions: By implementing the interface ExceptionMapper, you can further customize the response generation for specific exceptions if you want but this is not generally required.
A common good strategy for dealing with the scenario you have explained is to have a family of custom exceptions that extend the WebApplicationException where you can specify the response code and message, Or just using the ones provided by jax-rs. And if you need further customization with i18n support or its important to provide a response entity with details associated to the error, then implement the ExceptionMapper.
For the actual error handling and conversion of exceptions to corresponding HTTP responses you would use the JaxRS exception mapper as you already started. But the exception mapper itself, is not considered at all during the creation of the OpenAPI schema, as the OpenAPI extension has no way of obtaining the information about the actual error responses produced by your exception mapper. So you need to declare the possible error cases on the actual endpoint methods -which can be a bit tedious, especially if your endpoint methods can result in multiple error responses (400, 500, 409..). But you can reduce the amount of duplicated code by creating a shared error response definitions.
A common pattern for error handling on API layer is to explicitly define the models for your error responses e.g. I want all my endpoints to return error responses in a form of json document that looks sth like this:
{
"errorCode" : "MY_API_123",
"errorDescription" : "This operation is not allowed"
//anything else, details, links etc
}
So I create a POJO model for the response:
#Data
#AllArgsConstructor
public class ErrorResponse {
private String errorCode;
private String errorDescription;
}
And in my exception mapper I can convert my exception to the error response above:
public Response toResponse(Exception e) {
// you can define a mapper for a more general type of exception and then create specific error responses based on actual type, or you could define your own application level exception with custom error info, keys, maybe even i18n, that you catch here, you get the idea
if (e instanceOf NotFoundException) {
return Response.status(404, new ErrorResponse("MY_API_400", "Object not found")).build();
}
if (e instanceOf IllegalArgumentException) {
return Response.status(400, new ErrorResponse("MY_API_400", "Invalid request")).build();
}
// fallback if we dont have any better error info
return Response.status(500, new ErrorResponse("MY_API_100", "Internal server error")).build();
}
You can then document the possible error responses using the OpenAPi annotations:
#APIResponses({
#APIResponse(responseCode = "201", description = "Customer Created"),
#APIResponse(responseCode = "400", description = "Other object exists for customerId", content = #Content(schema = #Schema(implementation = ErrorResponse.class))),
#APIResponse(responseCode = "500", description = "Internal error", content = #Content(schema = #Schema(implementation = ErrorResponse.class)))
})
public Response yourMethod()
Or if you have some responses that are repeated often (such as generic internal server error, unathorized/unathenticated) you can document them on your class extending JaxRS Application and then reference them in your endpoints like this:
#Authenticated
#APIResponses({
#APIResponse(responseCode = "200", description = "Ok"),
#APIResponse(responseCode = "401", ref = "#/components/responses/Unauthorized"),
#APIResponse(responseCode = "500", ref = "#/components/responses/ServerError")
})
public Response someAPIMethod() {
Example JaxRS application class(useful for other common top level openapi schema attributes)
#OpenAPIDefinition(
info = #Info(title = "My cool API", version = "1.0.0"),
components = #Components(
schemas = {#Schema(name = "ErrorResponse", implementation = ErrorResponse.class)},
responses = {
#APIResponse(name = "ServerError", description = "Server side error", content = #Content(mediaType = MediaType.APPLICATION_JSON, schema = #Schema(ref = "#/components/schemas/ErrorResponse")), responseCode = "500"),
#APIResponse(name = "NotFound", description = "Requested object not found", content = #Content(schema = #Schema(ref = "#/components/schemas/ErrorResponse")), responseCode = "404"),
#APIResponse(name = "Forbidden", description = "Authorization error", responseCode = "403"),
#APIResponse(name = "BadRequest", description = "Bad Request", responseCode = "400"),
#APIResponse(name = "Unauthorized", description = "Authorization error", responseCode = "401")})
)
public class RESTApplication extends Application {
}

WebClient's bodyToMono on Empty Body Expected Behavior

What is the expected behavior when WebClient bodyToMono encounters an empty body? In my specific example we are checking the status returned from a post call, and if it's an error converting it to our custom error format. If the conversion to the custom error format fails, we create a new error in our custom format stating that. But when a response came in that was an error with an empty body, it failed to send any error back at all because bodyToMono didn't fail as I had expected. See the below code block:
.retrieve()
.onStatus(HttpStatus::isError) { response ->
response.bodyToMono(ErrorResponse::class.java)
.doOnError {
throw APIException(
code = UNEXPECTED_RESPONSE_CODE,
reason = it.message ?: "Could not parse error response from Inventory Availability",
httpStatus = response.statusCode()
)
}
.map {
throw APIException(
reason = it.errors.reason,
code = it.errors.code,
httpStatus = response.statusCode()
)
}
}
To fix this we added the switchIfEmpty.
.retrieve()
.onStatus(HttpStatus::isError) { response ->
response.bodyToMono(ErrorResponse::class.java)
.switchIfEmpty { throw RuntimeException("Received Empty Response Body") }
.doOnError {
throw APIException(
code = UNEXPECTED_RESPONSE_CODE,
reason = it.message ?: "Could not parse error response from Inventory Availability",
httpStatus = response.statusCode()
)
}
.map {
throw APIException(
reason = it.errors.reason,
code = it.errors.code,
httpStatus = response.statusCode()
)
}
}
My question is this: Is this expected behavior from bodyToMono? Since I'm explicitly asking to map the response body to my object of ErrorResponse, I would expect an empty body to error and then hit the doOnError block, but instead it just "succeeds"/returns an empty mono...hence us adding the switchIfEmpty block. If the object we were mapping to had nullable fields, I could see it not having an error, but since all fields are required why does bodyToMono not throw an error when trying to map "nothing" to my object?
This is correct. A Mono can produce 0..1 elements and an empty body just produces a mono that completes without emitting a value, much like Mono.empty().
If no body is an error in your use case you can call .single() on your mono.

How to deal with ContraintViolationException and other Exceptions

I have a camel route like this:
this.from(uri).process(e -> {
String json = e.getIn().getBody(String.class);
JsonObject message = gson.fromJson(json, JsonObject.class);
Status status = gson.fromJson(message.get("data"), Status.class);
e.getIn().setHeader("JSON_OBJECT", message);
e.getIn().setBody(status);
}).to(jpaStatusUri).process(e -> {
JsonObject message = (JsonObject) e.getIn().getHeader("JSON_OBJECT");
StatusDetails details = gson.fromJson(message.get("data"), StatusDetails.class);
// If the first route was successfull i can get the id of the inserted entity here (this works)
details.setStatusId(e.getIn().getBody(Status.class).getId());
e.getIn().setBody(details);
}).to(jpaDetailsUri);
I send the entity Status to a jpa endpoint jpaStatusUri. The entity is inserted, and in the following process() method i create a corresponding StatusDetails entity, set it's statusId to the id of the previously saved Status and send it to the jpaDetailsUri endpoint.
This works as expected in case the Status entity has been saved successfully. In case of a ConstraintViolationException i.e. a unique key for the Status entity already exists this will throw the exception, i will not have a correct statusId and i will not be able to update the corresponding StatusDetails entity anymore.
Of course i could all handle this in the process() method, but what would be the "camel-way" to handle this ?
A camel-way to handle such things would be to use an onException :
onException(ConstraintViolationException.class)
.process(e -> {e.getIn().setBody(new ErrorObjectJson());})
.handled(true)
check out the documentation
There is also the try-catch way of handling exception that is provided by camel.

Webapi CreateErrorResponse throws ArgumentException

I'm trying to call to the extension method CreateErrorResponse with a status code and string.
The problem that the function throws ArgumentException with additional information that the value dose not fall within the expected range.
I tried to send different values of arguments, explicitly cast the arguments and using different overloads.
Code
private HttpResponseException GetItemsException(IEnumerable<Guid> ids, string errorMessage, HttpStatusCode status)
{
HttpResponseMessage error = Request.CreateErrorResponse(status, errorMessage);
error.Headers.Add("ids", ids.Select(id => id.ToString()));
return new HttpResponseException(error);
}

Resources