I started to work with GraphQL for one week and I could not find out yet how to catch "internal" GraphQL errors like CoercingParseValueException. Because our Frontend use this endpoint to receive some information about shipping. When the schema or a required field is missing then GraphQL itself send an error which just got a message with arbitrary strings which you have to parse at the frontend to understand this message and show the client the correct error.
For our project, we defined a error-model for custom errors. This error-model contains a code field with a self defined code for every situtation like NotFoundException, ValidationException etc.
But how can I catch error from GraphQL and modify them?
Apporaches:
#Component
public class GraphQLErrorHandler implements graphql.servlet.GraphQLErrorHandler {
#Override
public List<GraphQLError> processErrors(List<GraphQLError> list) {
return list.stream().map(this::getNested).collect(Collectors.toList());
}
private GraphQLError getNested(GraphQLError error) {
if (error instanceof ExceptionWhileDataFetching) {
ExceptionWhileDataFetching exceptionError = (ExceptionWhileDataFetching) error;
if (exceptionError.getException() instanceof GraphQLError) {
return (GraphQLError) exceptionError.getException();
}
}
return error;
}
}
Does not work for me. ProcessErrors it is never called. I am using Spring Boot (Kickstarter Version)
For Custom errors I am using the new feature which was released for 10 days.
#Component("CLASSPATH TO THIS CLASS")
public class GraphQLExceptionHandler {
#ExceptionHandler({NotFoundException.class})
GraphQLError handleNotFoundException(NotFoundException e) {
return e;
}
#ExceptionHandler(ValidationException.class)
GraphQLError handleValidationException(ValidationException e) {
return e;
}
}
This approach works perfectly with custom error messages. To use this feature I have to enable the graphql-servlet property exception-handlers-enabled and set it to true. Nevertheless this approach does not catch "internal" Apollo/GraphQL errors even if the ExceptionHandler annotation is defined with Exception.class.
Maybe can help me with this problem?
Many thanks
try this. you should return ThrowableGraphQLError type for general exception
#ExceptionHandler(Exception::class)
fun handleException(e: Exception): ThrowableGraphQLError {
log.error("{}", e)
return ThrowableGraphQLError(e, "Internal Server Error")
}
Related
I'm developing this Java RESTEASY application oriented to microservice pattern, i use methods like this to handle my exceptions:
#Path("/find/{id}")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response retrieveItemById(#PathParam("id") String id) {
int id;
try {
itemDTO result = service.getItem(id);
return Response.ok(result).build();
}catch(Exception e) {
return Response.status(404).build();
}
}
Somebody told me that this method to handle exception (in this case item not found exception) is wrong, but I can't figure out why.
What is the right way to handle exception such as item not found, creation failed, updated failed ... in a REST oriented application?
Thanks.
In my request handler, if the passed-in accountId cannot be converted to a valid ObjectId I want to catch the error and send back a meaningful message; however, doing so causes the return type to be incompatible, and I cannot figure out how to achieve this pretty trivial use case.
My code:
#GetMapping("/{accountId}")
public Mono<ResponseEntity<Account>> get(#PathVariable String accountId) {
log.debug(GETTING_DATA_FOR_ACCOUNT, accountId);
try {
ObjectId id = new ObjectId(accountId);
return repository.findById(id)
.map(ResponseEntity::ok)
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
} catch (IllegalArgumentException ex) {
log.error(MALFORMED_OBJECT_ID, accountId);
// TODO(marco): find a way to return the custom error message. This seems to be currently
// impossible with the Reactive API, as using body(message) changes the return type to
// be incompatible (and Mono<ResponseEntity<?>> does not seem to cut it).
return Mono.just(ResponseEntity.badRequest().build());
}
}
The body(T body) method changes the type of the returned Mono so that it is (assuming one just sends a String) a Mono<ResponseEntity<String>>; however, changing the method's return type to Mono<ResponseEntity<?>> does not work either:
...
return Mono.just(ResponseEntity.badRequest().body(
MALFORMED_OBJECT_ID.replace("{}", accountId)));
as it gives an "incompatible type" error on the other return statement:
error: incompatible types: Mono<ResponseEntity<Account>> cannot be converted to Mono<ResponseEntity<?>>
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
Obviously, changing the return type of the method to Mono<?> would work, but the response then is the serialized JSON of the ResponseEntity which is NOT what I want.
I have also tried using the onErrorXxxx() methods, but they do not work here either, as the conversion error happens even before the Flux is computed, and I just get a "vanilla" 400 error with an empty message.
The only way I can think of working around this would be to add a message field to my Account object and return that one, but it's genuinely a horrible hack.
#thomas-andolf's answer helped me figure out the actual solution.
For anyone stumbling upon this in future, here is how I actually solved the puzzle (and, yes, you still need the try/catch to intercept the error thrown by the ObjectId constructor):
#GetMapping("/{accountId}")
public Mono<ResponseEntity<Account>> get(#PathVariable String accountId) {
return Mono.just(accountId)
.map(acctId -> {
try {
return new ObjectId(accountId);
} catch (IllegalArgumentException ex) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
MALFORMED_OBJECT_ID));
}
})
.flatMap(repository::findById)
.map(ResponseEntity::ok)
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
}
To actually see the message in the returned body, you will need to add server.error.include-message=always in application.properties (see here).
Using onError() won't work here (I did try that, in all its variants) as it requires a Mono<ResponseEntity<Account>> and there is no way to generate one from the error status (when adding the message body).
So, I have been coding a microservice which uses GraphQL's java implementation for APIs. GraphQL enforces some level of validation for the supplied queries by client. However, in cases when the issue occurs inside resolving the query, I have seen graphql show messages which exposes the internals of the micr-service.
What do I need? A way to handle all exceptions/errors thrown from resolver functions in such a way that I can sanitize the exceptions/errors before GraphQL creates corresponding response.
I have looked up official documentation and many stack over flow questions, but failed to find any place it talks about handling. If I found any, they were for previous versions and are no more supported.
Some of the links I referred -
1. https://www.howtographql.com/graphql-java/7-error-handling/
2. GraphQL java send custom error in json format
3. https://www.graphql-java.com/documentation/v13/execution/
I have already done the below things like -
Creating custom handler
#Bean
public GraphQLErrorHandler errorHandler() {
return new CustomGraphQLErrorHandler();
}
public class CustomGraphQLErrorHandler implements GraphQLErrorHandler {
#Override
public List<GraphQLError> processErrors(List<GraphQLError> errors) {
List<GraphQLError> clientErrors = errors.stream()
.filter(this::isClientError)
.collect(Collectors.toList());
List<GraphQLError> serverErrors = errors.stream()
.filter(this::isSystemError)
.map(GraphQLErrorAdapter::new)
.collect(Collectors.toList());
List<GraphQLError> e = new ArrayList<>();
e.addAll(clientErrors);
e.addAll(serverErrors);
return e;
}
private boolean isSystemError(GraphQLError error) {
return !isClientError(error);
}
private boolean isClientError(GraphQLError error) {
return !(error instanceof ExceptionWhileDataFetching || error instanceof Throwable);
}
}```
Expected behavior - The control would reach to `processErrors` method. Actual - It doesn't reach there.
You need to override the errorsPresent method in GraphQLErrorHandler to return true when an error is passed into that method. Something like:
#Override
public boolean errorsPresent(List<GraphQLError> errors) {
return !CollectionUtils.isEmpty(errors);
}
Currently I use JSON for the messages in spring kafka and that is pretty easy and works with almost no coding:
#KafkaListener(topics = "foo.t",)
public void receive(Foo payload) {
LOG.info("received payload='{}'", payload);
doMagic(payload);
}
#KafkaListener(topics = "bar.t",)
public void receive(Bar payload) {
LOG.info("received payload='{}'", payload);
doMagic(payload);
}
and little config:
# Kafka Config
spring.kafka.bootstrap-servers=broker.kafka
spring.kafka.consumer.group-id=some-app
spring.kafka.consumer.properties.value.deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=com.example.someapp.dto
Which works (great) because the content/type information is encoded in the JSON and thus can be restored from the bytes alone. (There are issues with that as well, but it works)
However in protobuf I don't have these meta-information or at least I don't know where to find them.
Question:
Is there a way to declare a generic kafka MessageConverter that works for multiple types, without throwing all the nice abstraction/auto configuration from spring out of the window?
(I would also like to use this for JSON, as encoding the content/data type of the message i the message has both some security and compatibility issues)
I would like to avoid something like this solution: https://stackoverflow.com/a/46670801/4573065 .
Alternatives
Write a message converter/ Deserializer that tries all kafka classes.
#Override
public T deserialize(String topic, byte[] data) {
try {
return (T) Foo.parse(data);
} catch (Exception e) {
try {
return (T) Bar.parse(data);
} catch (Exception e1) {
throw e
}
}
}
However this will probably negate all performance gains I hope to get by using a binary format.
Another alternative would be to statically map topic->content type however this would still be something error prone or at least hard to make spring configure for you.
EDIT: My producer looks like this:
public void send(String message) {
kafkaTemplate.send("string.t", message);
}
I am developing the RESTful webservices using Jersey & Spring 3.2 along with Open CMIS.
I am not using MVC pattern of Spring and it's just Spring IOC & Jersey SpringServlet, the controller class is something like below code
#GET
#Path("/{objId:.+}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public statusMsg addObject(#PathParam("objId") String objId{
return repoService.addObject(objId);
}
In the repoService I am performing the business logic to add the object using CMIS, my question is that I am catching around 5 exceptions related to CMIS then the base exception i.e Exception but for every service method I have to repeat it which I don't want to do.
I was searching on Google and found that #ControllerAdvice is the best solution for such problem wheer you can define all the checked & unchecked exceptions and wherever remove all the try catch blocks from the application. But it only work with MVC pattern.
Question 1: Is there a way I can use this in above Jersey-Spring framework?
After more reserach I found that Jersey provides ExceptionMapper to handle customized exception but I want to catch more CMIS exception or default Exception or IO Exception etc.
Question 2: How can I do it with ExceptionMapper?
Question 3: Am I on the correct approach or do you suggest any better approach to handle such issues.
Thanks in advance.
I use jersey2.11 with Tomcat and almost exception handle with ExceptionMapper.
(In domain logic, only DB rollback process use try-catch code.)
I think ExceptionMapper with #Provider automatically choose correct ExceptionMapper. So I suppose this function is satisfied with "I want to catch more CMIS exception or default Exception or IO Exception etc."
This code is my handling ExceptionMapper design code.
1.Some Jersey Root Resource Class
#GET
#Produces("application/json")
public String getUser(#NotNull #QueryParam("id") String id,
#NotNull #QueryParam("token") String token) throws Exception { // This level throws exceptions handled by ExceptionMapper
someComplexMethod(id, token); // possible throw Exception, IOException or other exceptions.
return CLICHED_MESSAGE;
}
2.ExceptionMapper package. com.yourdomain.exceptionmapper
AbstractExceptionMapper.java (All ExceptionMapper class extends this Abstract class)
public abstract class AbstractExceptionMapper {
private static Logger logger = LogManager.getLogger(); // Example log4j2.
protected Response errorResponse(int status, ResponseEntity responseEntity) {
return customizeResponse(status, responseEntity);
}
protected Response errorResponse(int status, ResponseEntity responseEntity, Throwable t) {
logger.catching(t); // logging stack trace.
return customizeResponse(status, responseEntity);
}
private Response customizeResponse(int status, ResponseEntity responseEntity) {
return Response.status(status).entity(responseEntity).build();
}
}
ExceptionMapper.java (At least this mapper can catch any exception which is not define specify exception mapper.)
#Provider
public class ExceptionMapper extends AbstractExceptionMapper implements
javax.ws.rs.ext.ExceptionMapper<Exception> {
#Override
public Response toResponse(Exception e) {
// ResponseEntity class's Member Integer code, String message, Object data. For response format.
ResponseEntity re = new ResponseEntity(Code.ERROR_MISC);
return this.errorResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, re, e);
}
}
WebApplicationExceptionMapper.java (Specify WebApplicationException)
#Provider
public class WebApplicationExceptionMapper extends AbstractExceptionMapper implements
ExceptionMapper<WebApplicationException> {
#Override
public Response toResponse(WebApplicationException e) {
ResponseEntity re = new ResponseEntity(Code.ERROR_WEB_APPLICATION);
return this.errorResponse(e.getResponse().getStatus(), re, e);
}
}
ConstraintViolationExceptionMapper.java (Specify Hibernate Validator ConstraintViolationException)
#Provider
public class ConstraintViolationExceptionMapper extends AbstractExceptionMapper implements
ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException e) {
ResponseEntity re = new ResponseEntity(Code.ERROR_CONSTRAINT_VIOLATION);
List<Map<String, ?>> data = new ArrayList<>();
Map<String, String> errorMap;
for (final ConstraintViolation<?> error : e.getConstraintViolations()) {
errorMap = new HashMap<>();
errorMap.put("attribute", error.getPropertyPath().toString());
errorMap.put("message", error.getMessage());
data.add(errorMap);
}
re.setData(data);
return this.errorResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, re, e);
}
}
.. and other specify exception can create ExceptionMapper classes.
In my experience, Exception Mapper is high level idea for focus to domain logic. It could drive out boring scattered try-catch block code from domain logic.
So I hope that you feel the "Yes i am" at Question 3 to resolve the problem at your environment.
you have not used try catch and throw anywhere across the application.
My code design use throws at method like this and this make to manage by ExceptionMapper classes.
public String getUser(#NotNull #QueryParam("id") String id,
#NotNull #QueryParam("token") String token) throws Exception
So in above approach I have created just 1 class for all the exceptions which I could expect and for any unknown exception the base Exception will be there to catch.
Now wherever in my application if any exception occurs it comes to the CentralControllerException and appropriate response with http status code is sent back.
Q.2. Do you foresee any issue in above approach.
I think if simple project or never update/modify project (project lifecycle short time), your one class exception mapper approach ok.
But ... i never take this approach. Simply, if need to manage more exception, this method become big and complex, and hard to read and maintain becoming.
In my policy, OOP should use pleomorphism strategy any level code(class plan, DI plan) and this approach some part aim to drive out if/switch block in code. And this idea make each method short code and simple, clear to "domain logic" and code become to resistant to modify.
So i create implements ExceptionMapper and delegate to DI which ExceptionMapper class manage to exception.
(So DI manage replace your single class If block manage which exception handling, this is typically refactoring approach similar Extract xxx http://refactoring.com/catalog/extractClass.html.
In our discussion case, single class and one method too busy, so extract each ExceptionMapper class approaching and DI call suitable class & method strategy.)
Btw, system processing result is same at present point. But if need to reduce future development cost ,should not took approach one class exception handling plan. Because if give up simply code and refactor status, project code is dead faster.
This is my idea and why this.
regards.
thanks for your reply. I can see you have created multiple classes based on the exception type and behaviour.
Q1. In your services method are you throwing any exception like
public void addObject(String objId) throws WebApplicationException{
}
or you have not used try catch and throw anywhere across the application.
Actually, I have tried something where in my web application I am not using try, catch and throws anywhere and in my CentralControllerException I have mentioned like below:
public class CentralControllerHandler implements ExceptionMapper<Exception> {
#Override
#Produces(MediaType.APPLICATION_JSON)
public Response toResponse(Exception ex) {
if(ex instanceof CmisContentAlreadyExistsException){
log.error(ex);
// send Response status as 400
}
if(ex instanceof IOException){
log.error(ex);
// send Response status as 400
}
return Response;
}
}
So in above approach I have created just 1 class for all the exceptions which I could expect and for any unknown exception the base Exception will be there to catch.
Now wherever in my application if any exception occurs it comes to the CentralControllerException and appropriate response with http status code is sent back.
Q.2. Do you foresee any issue in above approach.