*`I have below schema definition to represent commission amount in my openapi contract.`*
"properties": {
"Amount": {
"type": "number",
"description": "The amount of tnx",
"pattern" : "^-?[0-9]*(\\.([0-9]{1,2}))?$",
"format": "big-decimal",
"minimum": 0.01,
"maximum": 49999.99,
"exclusiveMinimum": false,
"exclusiveMaximum": false,
"example" : "9999.99"
},
generated code:
#NotNull #Valid #DecimalMin("0.01") #DecimalMax("49999.99")
#Schema(name = "amount", example = "9999.99", description = "The amount of tnx", required = true)
public BigDecimal getAmount() {
return amount;
}
when i gave above 50000 or below 0.01 its given the right validation message through post man
{
"errors": [
{
"code": "DecimalMax",
"message": "must be less than or equal to 49999.99",
"field": "Amount"
}
]
}
{
"errors": [
{
"code": "DecimalMin",
"message": "must be greater than or equal to 0.01",
"field": "Amount"
}
]
}
but i want override the code with some some number to represent the error code like 100,101 etc
{
"errors": [
{
"code": "100",
"message": "must be less than or equal to 49999.99",
"field": "Amount"
}
]
}
{
"errors": [
{
"code": "101",
"message": "must be greater than or equal to 0.01",
"field": "Amount"
}
]
}
can i specify any annotation on swagger to achieve this
By Default, when a field fails validation, Spring will throw a MethodArgumentNotValidException. This is managed internally by Springs spring-boot-starter-validation. You can override this exception handling using #RestControllerAdvice or #ControllerAdvice.
First, create a basic model for your response. Here is an extremely simple one.
#Setter
#Getter
public class RestException {
String code;
String message;
}
Then, create your controller advice class.
#RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
RestException exception = new RestException();
exception.setCode(HttpStatus.BAD_REQUEST.toString());
var message = Optional.ofNullable(ex).map(MethodArgumentNotValidException::getBindingResult).map(BindingResult::getFieldError).orElse(null);
exception.setMessage(Objects.isNull(message) ? "unidentified error" : message.getField() + " " + message.getDefaultMessage());
return new ResponseEntity<>(exception, HttpStatus.BAD_REQUEST);
}
}
this very simple example will return a response of
{
"code": "400 BAD_REQUEST",
"message": "amount must be greater than or equal to 0"
}
Note
You are free to set the code returned to the user as whatever you want (aka, exception.setCode("99999 MY_STATUS_CODE"). However, this will have no meaning to the user, as it won't match the headers sent by the controller as part of the ResponseEntity. I recommend sticking with defined HttpStatus types as the code you display back to your users.
Also, unless you are specifically trying to get a BigDecimal as your type, setting the format of the number to float or double will result in the expected dataType. Otherwise, declaring big-decimal isn't necessary, since that is the default for a number with no format defined.
Likewise, the defaults for exclusiveMinimum and exclusiveMaximum is false by default, so there is no need to include them in your schema.
Finally, I recommend adjusting your schema to include to response description. Something along the lines of this
responses:
"200":
description: OK
"5XX":
description: Unknown error has occurred
"400":
description: A required parameter is missing or an invalid datatype or value was provided for property.
Check the error message to check the invalid information, adjust the provided schema, and try again.
That should cover all of your bases. Feel free to expand the RestException and RestExceptionHandler classes. As I said, these are basic examples. You can make them much more robust (i.e. multiple returns based upon the field or error message).
Related
I have a Swagger file (OAS 2) with an endpoint used to download a file. I'm using openapi-generator (through the Maven plugin) to generate Java / OpenFeign client code.
The problem is that this particular endpoint translates to:
#RequestLine("GET /files/{id}/content")
#Headers({
"Accept: application/octet-stream",
})
File downloadByIdUsingGET(#Param("id") String id);
which returns null when invoked.
According to this, the correct method profile should be:
Response downloadByIdUsingGET(#Param("id") String id);
And indeed if I write the interface myself, with this method I can use:
Response downloadResponse = api.downloadByIdUsingGET(id);
InputStream downloadInputStream = downloadResponse.body().asInputStream();
So the question is: how can I configure the generator to use the Response type for this endpoint?
I have tried:
<typeMappings>file=feign.Response</typeMappings>
but this substitutes File type with Response everywhere, including in other endpoints where I don't want it, particularly (guess what) the upload endpoint, which is generated as:
FileProperties uploadUsingPOST(#Param("file") File file);
and works fine like this.
Here's the relevant part of the Swagger file:
"/files/{id}/content": {
"get": {
"produces": [
"application/octet-stream"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string",
}
],
"responses": {
"200": {
"description": "Successful",
"schema": {
"type": "file"
}
}
}
}
}
As spring boot provides the ResponseEntity to represent a HTTP response for rest apis, including headers, body, and status.
My RestController contains getTodoById method like below-
#GetMapping("/todo/{id}")
public Todo getTodoById(#PathVariable String id) {
int todoId = Integer.parseInt(id);
Todo todoItem = todoRepository.findById(todoId);
ResponseEntity.ok(todoItem);
}
It gives the following api response on api hit(api/v1/todo/13).
{
"id": 13,
"title": "title13",
"status": "not started"
}
There is need to have a common customised response structure for all apis in the application as below-
{
"status": "success",
"data": {
"id": 13,
"title": "title13",
"status": "not started"
},
"error": null,
"statusCode": 200
}
{
"status": "failure",
"data": {},
"error": "bad request",
"statusCode": 400
}
How do i get the required JSON response structure using ResponseEntity?
I explored it but could not find the solution which solve the above problem.
Any help would be appreciated.
Thanks
Well, instead of returning
ResponseEntity.ok(todoItem);
you obviously need to return something like
ResponseEntity.ok(new Response(todoItem));
with
public class Response {
private String status = "success";
private Object data;
private String error;
private int statusCode = 200;
// Constructor, getters and setters omitted
}
This question already has answers here:
Spring3 #ExceptionHandler for ServletRequestBindingException
(4 answers)
Closed 4 years ago.
I would like to handle exceptions without override default spring handler for validation.
If i do not implement an exception handler by #ControllerAdvice, my validation erros will response like this:
{
"timestamp": "2018-09-25T13:15:30.037+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Null.notificationEntity.id",
"Null.id",
"Null.java.math.BigInteger",
"Null"
],
"arguments": [
{
"codes": [
"notificationEntity.id",
"id"
],
"arguments": null,
"defaultMessage": "id",
"code": "id"
}
],
"defaultMessage": "must be null",
"objectName": "notificationEntity",
"field": "id",
"rejectedValue": 15,
"bindingFailure": false,
"code": "Null"
}
],
"message": "Validation failed for object='notificationEntity'. Error count: 1",
"path": "/v1/notifications"
}
The repsonse above is nice and clear for me, but if i create an exception handler, to handle my own exceptions, i have to manually handle validation errors throwed by #Valid.
I have found here a sample about it: http://www.springboottutorial.com/spring-boot-validation-for-rest-services
But on this example i still have to implement manually, and my result is:
{
"timestamp": "2018-09-25T13:07:22.779+0000",
"status": 400,
"code": "BAD_REQUEST",
"error": "Bad Request",
"message": "org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'notificationEntity' on field 'id': rejected value [15]; codes [Null.notificationEntity.id,Null.id,Null.java.math.BigInteger,Null]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [notificationEntity.id,id]; arguments []; default message [id]]; default message [must be null]",
"path": "/v1/notifications"
}
My question is, how could i implement my own exception handler without override spring validation handler or how could i call spring validation handler to keep the same response for validation exceptions?
If you go with catching the exception in your ControllerAdvice, you will have access to the validation exception (which I assume is the one with the structure you want to replicate).
#ExceptionHandler(ValidationException.class)
#ResponseBody
public ResponseEntity<YourCustomResponse> handleValidationexception(ValidationException e) {
// probably do some logging
YourCustomResponse response = buildCustomResponse(e); // build the response payload however you see fit
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
In this way, you can set the body, the HTTP headers and the HTTP status that is returned to the client in case of a ValidationException.
I'm using the #Valid annotation on the #RequestBody argument which uses Hibernate Validation, to ensure it's validated:
public ResponseEntity<?> register(#RequestBody #Valid RegistrationModel registration) { ...
Following the recommendations of the Spring Rest book, I'm catching that exception and turning it into a nice JSON error message that looks like this:
{
"title": "Validation Failed",
"status": 400,
"detail": "Input validation failed",
"timeStamp": 1505345551757,
"developerMessage": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": {
"hashedPassword": [
{
"code": "NotNull",
"message": "must not be null"
}
],
"organization": [
{
"code": "NotBlank",
"message": "must not be blank"
}
],
"name": [
{
"code": "NotBlank",
"message": "must not be blank"
}
]
}
}
On the client side, I'm doing this to make the call:
Object result = restTemplate.postForObject("http://localhost:8080/v1/registration", registration, Object.class);
Using Object until I define exactly on the representation of the response. When there's a validation error though, I get a 400 error which throws a HttpClientErrorException.
HttpClientErrorException has a getResponseBodyAsString() that responds with the JSON I pasted above. I could manually parse it, but I was wondering what's the most correct way to parse it. RestTemplate clearly has a mechanism for automatically parsing responses when there's no errors. Does it have something for errors?
Spring, als always, provides very useful defaults to handle Validation errors. But sometimes it looks difficult to customize those. In my case I have a custom validation that uses a javascript function to validate a field in a domain object. The default validation error produces 4 message codes that use the object name, the field name, the field type and the validation type. So far so good. But I would like to add an additional code that contains the name of the js-function as a component. How could I do that?
Or more general my question is: where do I find a documentation of the way Spring builds the default error messages, and how they can be manipulated.
In my case I get an output like:
{
"timestamp": 1457092927829,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [
{
"codes": [
"JSValidated.order.validFrom",
"JSValidated.validFrom",
"JSValidated.java.time.ZonedDateTime",
"JSValidated"
],
"arguments": [
{
"codes": [
"order.validFrom",
"validFrom"
],
"arguments": null,
"defaultMessage": "validFrom",
"code": "validFrom"
},
"checkOrder",
"static/OrderValidator.js"
],
"defaultMessage": "validation checkValidFrom failed",
"objectName": "order",
"field": "validFrom",
"rejectedValue": 1196586930,
"bindingFailure": false,
"code": "JSValidated"
},
{
"codes": [
"NotEmpty.order.id",
"NotEmpty.id",
"NotEmpty.java.lang.String",
"NotEmpty"
],
"arguments": [
{
"codes": [
"order.id",
"id"
],
"arguments": null,
"defaultMessage": "id",
"code": "id"
}
],
"defaultMessage": "may not be empty",
"objectName": "order",
"field": "id",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotEmpty"
}
],
"message": "Validation failed for object='order'. Error count: 2",
"path": "/order"
}
How can I add or change the codes? How can I add or change the list of arguments? Where is all the stuff documented?
You can use a global exception handler using #ExceptionHandler
You can define which exceptions should be handled. You have access to the thrown exception, which contains also the validation errors.
Create your own error class, that contains the properties you want to return.
Map the validation error into your error object and return it along with the HTTP status of your choice.
BindingException is one Exception I got from validation and the handler looks like this :
#ExceptionHandler(BindException.class)
#ResponseBody
public ResponseEntity<Object> handle(HttpServletRequest req, BindException ex) {
ExceptionResponse response = new ExceptionResponse(ex);
return new ResponseEntity<>(response, HttpStatus.EXPECTATION_FAILED);
}
And the error class ExceptionResponse :
#JsonInclude(Include.NON_NULL)
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public static class ExceptionResponse{
String exception;
String message;
String trace;
public ExceptionResponse(Exception exception) {
super();
this.exception = exception.getClass().getName();
this.message = exception.getMessage();
this.trace = Arrays.toString(exception.getStackTrace());
}
This is a json serialization of the result of method getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) of class
org.springframework.boot.autoconfigure.web.DefaultErrorAttributes.
This class can be extended to add additional properties.
Codes and messages are added by validators, do if you'd like to change them you need to customize validators used.