With java validation constraints, i.e.
#NotNull, #Size, etc
You can add a message field that your api can return to the client. Is there anyway to add additional fields such as a custom code?
The problem I have is that every possible error needs it's own message and code return to the client. (By code I mean a custom one in the response body, not a http status code).
I.e.
{
message: foo can not be null,
code: 10001
}
The only thing I can think of is to use custom validator classes on every single field which would be quite a lot of work, or have a giant if/else block that sets the code based on the message.
Can anyone think of a nicer solution?
Thanks in advance for any help :)
You can use the payload() parameter defined by all constraint types.
You'd have to declare a class type for each one of your error codes:
public interface Error_01 {}
And then:
public class SomeValidatedClass {
#NotNull(payload=Error_01.class)
private String someField;
}
Related
I have a class ActivitiesModel which uses Lombok's SuperBuilder.
import jakarta.validation.NotBlank;
// other imports and statements omitted for brevity.
#Data
#SuperBuilder
#NoArgsConstructor
public class ActivitiesModel {
public static final String ACTIVITIES_NOT_NULL_MESSAGE = "Activities cannot be null";
public static final String ACTIVITY_NOT_BLANK_MESSAGE = "Activity cannot be blank";
#NotNull(message = ACTIVITIES_NOT_NULL_MESSAGE)
private List<#NotBlank(message = ACTIVITY_NOT_BLANK_MESSAGE) String> activities;
}
I am using this builder to create an object of ActivitiesModel, and then validating it using Hibernate's Validator interface:
// Somewhere else in the application.
// Create an object using the builder method.
ActivitiesModel activitiesModel = ActivitiesModel.builder()
.activities(List.of("hello", "world")) // <----- Point A
.build();
// Validate the object using Hibernate's validator.
validator.validate(activitiesModel);
However, running this code gives me the following error:
java.lang.Error:
Unresolved compilation problem:
Type mismatch: cannot convert from List<String> to List<E>
The stack trace seems to be pointing at Point A.
I have tried the following approaches:
Replacing the #SuperBuilder with #Builder and #AllArgsConstructor.
Replacing the message attribute with a string literal instead of a static final variable, i.e:
private List<#NotBlank(message = "Activity cannot be blank") String> activities;
1st approach seems to fix this error, however, it's not something I can use as I need to extend the builder functionality to a subclass of ActivitiesModel. Also, this issue is also present in another abstract class, so the super builder functionality for parent classes is definitely required.
2nd approach also works in solving the error. However, going with it is a bit problematic because I then need to have the same message string in the validation test for this model class, which is something I would like to avoid as it duplicates the string.
Another thing to note is that this error only seems to occur in the presence of an annotation on the generic type parameter of the container, which is NotBlank in this case. It is not influenced by any annotations which are present directly on the field itself (NotNull in this case).
So, all in all, these are the questions that I would like to get some answers to:
Somehow, Lombok is able to figure out the types in case of a string literal but not in case of a static final String. Why is that?
Am I going about this totally wrong? The problem occurs because I'm trying to store the message string in a variable, and I'm trying to re-use the same variable at two places: the annotation's message attribute, and in the validation test for the model class. Should I not be checking for the presence of the message in my validation tests, but be checking for something else instead?
For anyone who comes across this later on, the research for this issue has led me to believe that comparing message strings in tests is not the way to go about writing validation test cases. Another downside to this approach is that you might have different validation messages for different locales. In that case, the message string itself might be a template e.g. my.message.key with its values in a ResourceBundle provided to Hibernate, i.e. files such as ValidationMessages.properties and ValidationMessages_de.properties.
In such a scenario, you could compare message for one locale in your validation test case, however, a better approach might be to check the annotation and the field for which the validation has failed. We can get both of these pieces of information via the ConstraintViolation and subsequently the ConstraintDescriptor types, provided by Hibernate. This way we can circumvent checking the message itself, but rely on the actual validation annotation which has failed.
As for the solution to this question, it seems it was a build cache issue. Cleaning maven's build cache results in this code working perfectly fine, but VSCode still seems to have an issue. For now, I will choose to ignore that.
In my project,we want manage all REST APIs errorcodes in a Enum type,like the following code:
package com.example.util
public enum ErrorType{
SUCCESS("0000", "success")
PARAMS_EMPTY("3001", "params cannot be empty")
}
The problem we encounter is if we put the class into util package,everytime we add a new error type in business spring boot app,we'll need to modify,publish and recompile the app and util project.That would be hard to maintance the util package.Basically,we prefer to maintance a relatively stable utility package.
So we are considering if there is a way that we can generate Enum type dynamiclly,we can comfigure the error information in mysql in advance,then we can load them into enum type in application boot procedure.
I'm not sure is this a good idea to dynamic generate enum type in Java,or if there is a better solution for this problem.
You can't add or remove values from an enum. Enums are complete static enumerations.
If you need to handle variable values you need to work with a standard class.
For example you can have something like the following:
public Error {
public static Error getByName(String name) {
....
}
public static Error getByCode(int code) {
....
}
}
and use it as follow:
Error myError404 = Error.getByCode(404);
Obviously this code gives you a lot of flexibility, but you cannot know in advance if a particular error code exists or not. And you can't use ide facilities related to enums.
Generating an enum would not be so useful I think, since part of the power of enums is that you can use them statically in your code - so then you have to update your code anyway.
How about adding a an exception handler to your util library that can be populated with error codes / description mappings from the database that can then translate errors/exceptions to response codes / error messages for your API? (just guessing you have an api here :-) )
Thanks for your reply,we finally decide to give up this idea.Dynamic generate an enum would not help,indeed it will add more complexity to our project,it's not a common use of enum.
Instead of this,we predefine our main error type likes user_error,system_error and db_error,etc.The specific error information will be processed in the business service.
i have a little kont in my brain about structuring our code. We have a REST Backend based on SpringBoot. To handle requests regarding to security checks we use HandlerInterceptors. In some specific cases we need a specific interceptor and not our default one. The default one is registered in a 3rd party lib that no one can forget it. But i want all coders to think about this specific interceptor.
Actually, i just said it to them to achieve this.
Here's my question: Is there an option to create required (or necessary) interfaces which must be implemented? This would be a way to provide our security code by lib and to have the security that every coder implemented our specific interface (also if he just does nothing with it).
pseudo code:
public interface thinkForIt(){
Object SecBean specificSecBean;
public void methodToThinkOn();
}
public SecImpl implements thinkForIt(){
#Override
public void methodToThinkOn(){
return null; // i thought about it but i do not need to do anyting!
}
If the interface thinkForIt would have any annotations like #required, users could get warning or error if they did not implement it...
Looking for a solution and thanks for your comments in advance!
Your overall design is questionable; you are reinventing security code, which is always a red flag. Use Spring Security instead.
However, there's a simple way to ensure that "some bean of type Foo" has been registered with the context:
#Component
#RequiredArgsConstructor
public class ContextConfigurationVerifier {
final Foo required;
}
I have a class which takes enum values like Male,Female #POST . when I sent a wrong value like 'male' instead of 'Male' it shows me 400 Bad Request with this message in rest client : Can not construct instance of constants.Constants$GenderEnum from String value 'male': value not one of declared Enum instance names
at [Source: org.apache.catalina.connector.CoyoteInputStream#718a453d; line: 7, column: 23] (through reference chain: valueobjects.ConsumerValueObject["gender"])
My Rest End Point Looks like below :
#Consumes("application/json")
#Produces("application/json")
#POST
public Response addConsumer(ConsumerValueObject consumerVO)
Here ConsumerValueObject holds the enum.
How to suppress that error message in Rest client? I tried with ExceptionMapper but it did not help!I need to suppress the message due to security issues!
This is the Jackson response from either JsonParseExceptionMapper or JsonMappingExceptionMapper. These classes come with the dependency
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${2.x.version}</version>
</dependency>
Whether you have this explicit dependency or you have the resteasy-jackson2-provider (which uses the above under the hood), most likely the mappers are registered implicitly through classpath scanning. For instance you have an empty Application class.
#ApplicationPath("/")
public class ResteasyApplication extends Application {}
This will cause disovery/registration through classpath scanning. If you don't have either of those dependencies, and if you are in Wildfly, I am not exactly sure how they are registered, but that is what's happening.
You could write/register your own ExceptionMappers for the JsonParseException and JsonMappingException
#Provider
public class JsonMappingExceptionMapper
implements ExceptionMapper<JsonMappingException> {
#Override
public Response toResponse(JsonMappingException e) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
but from what I have tested, it's a tossup as to which one will be registered, yours or Jackson's. The mappers are put into a Set (so unordered), then pushed into a Map, so only one get's pushed in. The order in which they are pushed in like I said is a tossup.
I guess this is really only a partial answer, as I have not been able to find a solution that is guaranteed to use your mapper, aside from registering all your classes explicitly (ultimately disabling the classpath scanning), but that is a hassle.
But now the fight has been narrowed down. I will try again some more if I get a chance later
UPDATE
So this is not a solution, just a semi-proof-of-concept to show how we can get it to use our ExceptionMapper.
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.my.pkg.JsonMappingExceptionMapper;
#Path("/init")
public class InitResource {
#GET
public Response init() {
ResteasyProviderFactory factory = ResteasyProviderFactory.getInstance();
factory.getExceptionMappers().put(JsonMappingException.class,
new JsonMappingExceptionMapper());
return Response.ok("Done!").build();
}
}
Once we hit the init endpoint for first time, our JsonMappingExcpetionMapper will register, and override the existing one, whether it is Jackson's or ours.
Of course we would not want to do this for real, it's just showing how to override the mapper. The thing I can't figure out is where to put this code. I've tried a ServletContextListener, in the Application constructor, in a Feature with a low priority. I can't figure it out. None of the above occur before RESTeasy does its final registration.
Do you really want to supress the error message or do you want to fix the actual probelm?
You can actually catch all thrown exception with a custom exception mapper like
#Provider
public class CustomExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable t) {
return Response.ok().build();
}
}
though, this will handle all caught exceptions and return a 200 OK which tricks clients to think that the request actually succeeded - which was not the case! Instead of Throwable you should be able to catch the concrete exception (even if it is a RuntimeException) as well - maybe you have not declared it as provider or did not specify the correct exception class?
Though, as already mentioned returning a different status code for an exception is generally bad practice and should be avoided. Fixing the actual problem is probably more suitable in that case.
JAX-RS provides MessageBodyReader and MessageBodyWriter interfaces which you can declare to un/marshall an inputstream to an object or an object to return to an output-stream. The official documentation on MessageBodyReader has more detailed information on that regard.
One implementation therefore could be the following steps:
Read the input-stream to f.e. string
Replace all "male" or "female" tokens with their upper-case version
Parse the string to a json-representation (using org.json.JSONObject f.e)
Use ObjectMapper to convert the JSON representation to a Java object
return the mapped object
This works if the input failure is just a simple upper/lower case issue. If there are typos or semantically alternative available, which are not yet in your enum, you need to put in a bit more effort.
If you, however, fail to create a proper object representation, you should return a user-failure (something in the 400 range) to the client to inform the client that something went wrong.
I have a dto object which keeps an IP Range using first and last fields. Simple CRUD operations are made with this class using dropwizard (jersey-jackson-hibernate validator)
public class IpRangeDto {
#JsonCreator
public static IpRangeDto fromCidr(#JsonProperty("cidr") String cidr) {
//Resolve CIDR and assign first and last fields
}
#NotNull
#IpAddress // My custom validator
private String first;
#NotNull
#IpAddress
private String last;
}
For the sake of user-friendliness I had decided to add an alternative way to create this object, which is by using CIDR. So the client could send either first and last fields in JSON or only the cidr field. So the way to do it is as above, using #JsonCreator. And it works just fine.
"ipRange":{
"first": "15.0.0.1",
"last": "15.0.0.255",
}
"ipRange":{
"cidr": "15.0.0.0/24"
}
I want to validate this CIDR value that it's the right format so I can return 422 with proper error message. If I throw exception in the constructor/factory method then jersey-jackson returns 400 directly (even if I throw ConstraintViolationException, it's encapsulated by JsonProcessingException).
I could simply ignore the exceptions, and leave the fields empty which will return 422 because of #NotNull constraints but then the error message will not be as clear as it should be.
I tried adding my #Cidr validator next to the #JsonProperty parameter but that doesn't seem to be effective. My understanding is that validation occurs after Jackson is finished with creating Dtos, so with my #JsonCreator approach there might not be any solution to this problem. So I'm open to refactoring suggestions as well.
I am not an expert on the exact integration of Bean Validation into jackson, but I think it is just doing actual property validation. This means as you already pointed out, the entities are created first and then the properties are validated.
Bean Validation (as of version 1.1) also offers so called method validation, in which case you could place your Cidr constraint onto the string parameter of the method, but as said, I don't think that there is an integration in jackson for that.
And one more thing ;-) - static methods and properties are generally excluded from validation in Bean Validation (see also http://beanvalidation.org/1.1/spec/#d0e2815).
Regarding a workaround, one thing comes to mind (even though it feels a bit complicated). Write a custom class level IpRange constraint. In a class constraint you would get passed a IpRangeDto instance and it is up to you to validate the whole object and select the right error message for any violations. Provided you would add a cidr property to the dto which gets set when fromCidr is called, you would have then all information you need for the validation and selection of a proper error message.