Does having a custom error response mean that you have to catch any exception in order to be consistent? - spring

In my spring boot project i have a User class and its' fields have annotation constrains like #Size, #Pattern #NotNull etc.
For example
#Id
#Column(name = "name", nullable = false, length = 16, unique = true)
#NotNull
#Size(max = 16, message = "Username should be less or equal than 16 characters")
#Pattern(regexp = "[^\s]*", message = "Username should not contain whitespaces")
#Pattern(regexp = "^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$", message = "Username should should start with a letter")
private String userName;
A post request with invalid userName returns the following error response
{
"timestamp":"2021-06-28T18:02:02.720+00:00",
"status":400,
"error":"Bad Request",
"message":"Validation failed for object='user'. Error count: 1",
"errors":[
{
"codes":[
"Pattern.user.userName",
"Pattern.userName",
"Pattern.java.lang.String",
"Pattern"
],
"arguments":[
{
"codes":[
"user.userName",
"userName"
],
"arguments":null,
"defaultMessage":"userName",
"code":"userName"
},
[
],
{
"defaultMessage":"^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$",
"arguments":null,
"codes":[
"^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$"
]
}
],
"defaultMessage":"Username should should start with a letter",
"objectName":"user",
"field":"userName",
"rejectedValue":"5",
"bindingFailure":false,
"code":"Pattern"
}
],
"path":"/signup"
}
Before questioning if this kind of error format is what i need, i didn't like it so i tried to make my own like in this guide Baeldung
I have a global controller now to deal with custom errors like when the username is taken.
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {...}
This is what i get now
{
"timestamp": "29-06-2021 05:05:03",
"status": "BAD_REQUEST",
"message": "Invalid input",
"path": "/signup",
"errors": [
{
"field": "userName",
"message": "Username should should start with a letter",
"rejectedValue": "5"
}
]
}
I suppose a good API means that you have to be consistent, that is we always have to return an error response with the same structure.
I have override some ResponseEntityExceptionHandler's method in order to catch other errors but there are still many methods to override. Here is a list of the remaining methods.
// handleBindException
// handleTypeMismatch
// handleMissingServletRequestPart
// handleMissingServletRequestParameter
// handleMethodArgumentTypeMismatch
// handleConstraintViolation
// handleHttpMediaTypeNotAcceptable
// handleMissingPathVariable
// handleServletRequestBindingException
// handleConversionNotSupported
// handleHttpMessageNotWritable
// handleAsyncRequestTimeoutException
My questions:
Do i have to catch all these exceptions? To be more specific, is it always possible to take all these exceptions no matter how your domains, controllers, services work?
Can you please write for each of these exceptions a bad request that will cause them to be thrown? Please, don't just tell me when they will be thrown. I'm new to spring and i won't be able to understand without an example.

How about overriding the below method from ResponseEntityExceptionHandler, as it is being invoked by all methods mentioned in the query.
protected ResponseEntity<Object> handleExceptionInternal()
And have your own logic to check the instance of exception and provide different kind of error response to client.

Related

Fluent Validation and ASP.NET Core 6 Web API

I am new to fluent validation and also a beginner in Web API. I have been working on a dummy project to learn and your advice will be much appreciated. After following the FluentValidation website, I was able to successfully implement fluent validation.
However, my response body looks very different and contains a lot of information. Is it possible to have a regular response body with validation errors?
I will put down the steps I took to implement fluent validation. your advice and help are much appreciated. I am using manual validation because based on the fluent validation website they are not supporting the auto validation anymore.
In the program file, I added
builder.Services.AddValidatorsFromAssemblyContaining<CityValidator>();
Then I added a class that validated my City class which has two properties Name and Description:
public class CityValidator : AbstractValidator<City>
{
public CityValidator()
{
RuleFor(x => x.Name)
.NotNull()
.NotEmpty()
.WithMessage("Please specify a name");
RuleFor(x => x.Description)
.NotNull()
.NotEmpty()
.WithMessage("Please specify a Description");
}
}
In my CitiesController constructor I injected Validator<City> validator; and in my action, I am using this code:
ValidationResult result = await _validator.ValidateAsync(city);
if (!result.IsValid)
{
result.AddToModelState(this.ModelState);
return BadRequest(result);
}
The AddToModelState is an extension method
public static void AddToModelState(this ValidationResult result, ModelStateDictionary modelState)
{
if (!result.IsValid)
{
foreach (var error in result.Errors)
{
modelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
}
}
On post, I am getting the response as
{
"isValid": false,
"errors": [
{
"propertyName": "Name",
"errorMessage": "Please specify a name",
"attemptedValue": "",
"customState": null,
"severity": 0,
"errorCode": "NotEmptyValidator",
"formattedMessagePlaceholderValues": {
"PropertyName": "Name",
"PropertyValue": ""
}
},
{
"propertyName": "Description",
"errorMessage": "Please specify a name",
"attemptedValue": "",
"customState": null,
"severity": 0,
"errorCode": "NotEmptyValidator",
"formattedMessagePlaceholderValues": {
"PropertyName": "Description",
"PropertyValue": ""
}
}
],
"ruleSetsExecuted": [
"default"
]
}
While the regular response without Fluent Validation looks like this:
{
"errors": {
"": [
"A non-empty request body is required."
],
"pointofInterest": [
"The pointofInterest field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-1a68c87bda2ffb8de50b7d2888b32d02-94d30c7679aec10b-00"
}
The question: is there a way from the use the fluent validation and get the response format like
{
"errors": {
"": [
"A non-empty request body is required."
],
"pointofInterest": [
"The pointofInterest field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-1a68c87bda2ffb8de50b7d2888b32d02-94d30c7679aec10b-00"
}
Thank you for your time.
Updated ans:
with your code, you can simply replace.
return BadRequest(result); // replace this line with below line.
return ValidationProblem(ModelState);
then you get same format as required.
------------------------*----------------------------------------
Please ignore this for manual validation.
You don't need explicit validation call.
this code is not required:
ValidationResult result = await _validator.ValidateAsync(city);
if (!result.IsValid)
{
result.AddToModelState(this.ModelState);
return BadRequest(result);
}
it will auto validate the model using your custom validator.
you simply need this
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
and it will give you errors in the require format.
if(!result.IsValid)
{
result.AddToModelState(this.ModelState);
return ValidationProblem(ModelState);
}

How I can return my custom json file instead of default json file that generates spring boot?

I have a rest controller for authorization:
#RestController
class AuthController {
#PostMapping("/sign-up")
fun signUp(#RequestBody signUpRequest: SignUpRequest): ResponseEntity<String> {
some code here..
}
}
The signUp method gets SignUpRequest model as a request body. SignUpRequest model is:
enum class Role {
#JsonProperty("Student")
STUDENT,
#JsonProperty("Tutor")
TUTOR
}
data class SignUpRequest(
val role: Role,
val email: String,
val password: String
)
When I make /sign-up post request with JSON:
{
"role": "asdf",
"email": "",
"password": ""
}
It returns me an answer that were generated by spring boot:
{
"timestamp": "2020-02-12T05:45:42.387+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]\n at [Source: (PushbackInputStream); line: 3, column: 10] (through reference chain: foo.bar.xyz.model.SignUpRequest[\"role\"])",
"path": "/sign-up"
}
Question is: How I can return my custom JSON instead of that default generated JSON?
I want to return my custom JSON, like:
{
"result": "Invalid user data are given",
"errors": [
{
"fieldName": "ROLE",
"text": "Given role does not exist"
},
{
"fieldName": "EMAIL",
"text": "EMAIL is empty"
}
]
}
I suggest you to create ErrorContrller that generates custom json map as response. Then when you will catch an error in sign-up method, call ErrorContrllers method.
You can find info from this link
Finally I found out a solution. You should create a class that annotates #ControllerAdvice, and make a method that annotates #ExceptionHandler.
#ControllerAdvice
class HttpMessageNotReadableExceptionController {
#ExceptionHandler(HttpMessageNotReadableException::class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleException(
exception: HttpMessageNotReadableException
): PostSignUpResponseError {
val errors = mutableListOf<PostSignUpResponseErrorItem>()
errors.add(
PostSignUpResponseErrorItem(
fieldNamePost = "Role",
text = "Given role does not exist"
)
)
return PostSignUpResponseError(
result = "Invalid user data are given",
errors = errors
)
}
}
where PostSignUpResponseErrorItem and PostSignUpResponseError are:
data class PostSignUpResponseError(
val result: String,
val errors: List<PostSignUpResponseErrorItem>
)
class PostSignUpResponseErrorItem(
val fieldNamePost: PostSignUpRequestFieldName,
val text: String
)
Anyway, I still don't know how to attach this thing to a certain PostMapping method.

Is there a better way to construct a controller with multiple parameters the might not be required?

I'm making a simple spring boot application and i have to get a list of objects, which i filter by parameters that aren't required. So basically the user can send the parameters, but doesn't have to. Best solution that i found is the use of #RequestParam annotation but it doesn't seem to work. If i put both parameters it works perfectly but if I set just one or none i get an error. Do i have to overload methods for every case or is there a smoother way to deal with this(if possible by still using the get request)?
My controller:
#RequestMapping(
value = "/fruits",
params = {"apple", "orange"},
method = GET)
public ResponseEntity<List<Fruit>> getFruits(
#RequestParam(value = "apple", required = false, defaultValue = "")
List<Apple> apple,
#RequestParam(value ="orange", required = false, defaultValue = "")
List<Orange> orange) {
List<Fruit> fruit = projectService.getFruits(apple, orange);
return ResponseEntity.ok().body(fruit);
}
Error:
{
"timestamp": "2019-01-20T21:26:52.287+0000",
"status": 400,
"error": "Bad Request",
"message": "Parameter conditions \"apple, orange\" not met for actual
request parameters: ",
"path": "/api/fruits"
}
Remove this line:
params ={"apple","orange"}
and it will work.
Because you don't need to call twice, it's enough with those #RequestParam annotations.

JsonInclude How to ignore default value boolean only when is not assigned

when I use Include.NON_NULL, the error response is wrong and if I use Include.NON_DEFAULT, the response with status 200 is wrong
This is what I'm looking for:
Status: 200 OK
{
"adultMovie": false,
"backdropPathMovie": "/2U3hyiVzzhYzS6j9fcdVW4mO4Uk.jpg",
"originalLanguageMovie": "en",
"originalTitleMovie": "Fire"
}
Status: 400
{
"errors": [
{
"typeId": "FIELD_VALIDATION_ERRORS",
"field": "idMovie",
"message": "Invalid value of `fff` provided"
}
]
}
This is what I tried so far:
My model:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Movie implements Serializable {
private static final long serialVersionUID = -3515253168981789136L;
private boolean adultMovie;
private String backdropPathMovie;
private String originalLanguageMovie;
private String originalTitleMovie;
private Set<Error> errors;
}
Status: 200 OK
{
"adultMovie": false,
"backdropPathMovie": "/2U3hyiVzzhYzS6j9fcdVW4mO4Uk.jpg",
"originalLanguageMovie": "en",
"originalTitleMovie": "Fire"
}
Status: 400. Validation error
{
"adultMovie": false, // It shouldn't show up
"errors": [
{
"typeId": "FIELD_VALIDATION_ERRORS",
"field": "idMovie",
"message": "Invalid value of `513f` provided"
}
]
}
If I try:
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class Movie implements Serializable {
Status: 200 OK
{
"backdropPathMovie": "/2U3hyiVzzhYzS6j9fcdVW4mO4Uk.jpg",
"originalLanguageMovie": "en",
"originalTitleMovie": "Fire"
} // Here should show up "adultMovie": false, as this field is set as false.
Status: 400. Validation error
{
"errors": [
{
"typeId": "FIELD_VALIDATION_ERRORS",
"field": "idMovie",
"message": "Invalid value of `fff` provided"
}
]
}
Instead on boolean you can use Boolean like below. boolean is having values either true or false and default value is false if not set. Therefore with #JsonInclude(JsonInclude.Include.NON_DEFAULT) even though you have set false value manually it will assume it's default value and will ignore this value even though you required this in response. On the other hand Boolean is having three values like true, false and null. So if you have set it to false it will also consider in in response. Only if you set to null it will not include in response.
private Boolean adultMovie;
Your question is not clear;
the question subject is the "subject" of the question,
it is not the text of the question.
Source of your problem: You must learn how to count.
Description:
A boolean may contain exactly two values; true and false.
Based on my imagination of what you wanted to ask,
it appears that you want something that contains three values;
true, false, and not-set.
Use Boolean as the type of the adultMovie field.
Boolean is a reference type;
therefore it can have three values;
Boolean.TRUE, Boolean.FALSE, and null.
In concert with the #JsonInclude(JsonInclude.Include.NON_NULL) annotation,
a null value for adultMovie will result in JSON that does not contain the "adultMovie" entry.

WebApi 2.1 Model validation works locally, but does not show missing fields when run on the server

We are using WebApi v2.1 and validating ModelState via a filter applied in the WebApiConfig class.
Fields specified as required are not listed on the error message when we run on the server (Win Server 2008R2), but they work perfectly when we run locally (on IISExpress).
The request is correctly rejected locally and on the server, but the server response does not show the missing fields.
For Example:
Given a Local request that lacks the required abbreviation and issuerName fields, the response shows as expected:
{
"message": "The request is invalid.",
"modelState": {
"value": [
"Required property 'abbreviation' not found in JSON. Path '', line 18, position 2.",
"Required property 'issuerName' not found in JSON. Path '', line 18, position 2."
]
}
When the same request is sent to the server, the response shows:
{
"message": "The request is invalid.",
"modelState": {
"value": [
"An error has occurred.",
"An error has occurred."
]
}
}
Our filter is the following:
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Our data model class is decorated with the DataContract attribute, and the required fields are attributed like so:
[DataMember(IsRequired=true)]
public string IssuerName
The server is more restrictive about sending errors down ot the client. Try setting the IncludeErrorDetails flag on your httpconfiguration to verify that this is the underlying issue.
In general though turning this flag on is not the best idea, and you will want to serialize the errors down differently.
For more info:
http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx

Resources