我开发了一个具有各种Rest功能的Web服务。我想使用标准@Valid
注释来验证我的bean。但我想修改输出json错误。
验证的错误消息目前的格式如下:
{
"errors": [
"someString size must be between 0 and 140",
"anotherString cannot contain numbers"
]
}
但我希望错误消息的格式如下:
{
"errors": [{
"someString": "size must be between 0 and 140"
},
{
"anotherString": "cannot contain numbers"
}
]
}
或
{
"errors": [{
"field": "someString"
"error": "size must be between 0 and 140"
},
{
"field": "anotherString"
"error": "cannot contain numbers"
}
]
}
我知道如何通过向验证注释提供message="some message about strings"
来改变错误消息,甚至将ValidationMEssages.properties
用作所有错误消息的公共位置。但是,如果出现错误,我无法改变输出格式。
我已阅读以下文档,但我需要更多指导。 http://www.dropwizard.io/1.0.0/docs/manual/validation.html
这是我的第一个DropWizard项目,我习惯于在Spring中开发。
提前致谢。
答案 0 :(得分:2)
我为自己的问题找到了解决方案。如果有人应该像我一样遇到同样的问题,我决定发布它。
这适用于DropWizard 1.0,我还没有测试它是否适用于早期版本或更高版本,所以请记住这一点。我无法为您提供完整的解决方案,但我已发布 我的解决方案是代码片段,所以不要指望你可以简单地复制/粘贴和编译。
解决方案实际上很简单,你只需要为ConstraintViolationException实现自己的ExceptionMapper并使用DropWizard重新编译它。
通过提供模板或常规文本,您可以轻松地为bean验证指定自己的消息。即
@NotNull(message="God damn it, Morty. I ask you to do one thing!")
或
@NotNull(message="{morty.error}")
模板位于ValidationMessages.properties
,您必须自己创建并放置在src/main/resources/
<强> ValidationMessages.properties 强>
morty.error=God damn it, Morty. I ask you to do one thing!
无论如何,这是我的解决方案。
<强> SomeApplication.class 强>
//Class where you setup DropWizard
public class SomeApplication extends Application<SomeConfiguration> {
@Override
public void run(SomeConfiguration conf, Environment environment) throws Exception {
//Remove all default ExceptionMappers
((DefaultServerFactory)conf.getServerFactory()).setRegisterDefaultExceptionMappers(false);
//Register your custom ExceptionMapper for ConstraintViolationException
environment.jersey().register(new CustomExceptionMapper());
//Restore the default ExceptionsMappers that you just removed
environment.jersey().register(new LoggingExceptionMapper<Throwable>() {});
environment.jersey().register(new JsonProcessingExceptionMapper());
environment.jersey().register(new EarlyEofExceptionMapper());
}
}
<强> CustomExceptionMapper.class 强>
//This is where the magic happens.
//This is your custom ExceptionMapper for ConstraintViolationException
@Provider
public class CustomExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
@Override
public Response toResponse(ConstraintViolationException cve) {
//List to store all the exceptions that you whish to output
//ValidationErrors is a custom object, which you can see further down
ValidationErrors validationErrors = new ValidationErrors();
//Loop through all the ConstraintViolations
for(ConstraintViolation<?> c : cve.getConstraintViolations()){
//We retrieve the variable name or method name where the annotation was called from.
//This will be your property name for your JSON output.
String field = ((PathImpl)c.getPropertyPath()).getLeafNode().getName();
//If field is null, then the notation is probably at a class level.
//Set field to class name
if(field == null){
field = c.getLeafBean().getClass().getSimpleName();
}
//c.getMessage() is the error message for your annotation.
validationErrors.add(field, c.getMessage());
}
//Return a response with proper http status.
return Response.status(422).entity(validationErrors).build();
}
}
<强> ValidationErrors 强>
//There is not really any magic happening here.
//This class is just a wrapper for a List with the type ValidationObject.
public class ValidationErrors {
public List<ValidationObject> errors = new ArrayList<ValidationObject>();
public void add(String field, String error){
errors.add(new ValidationObject(field, error));
}
}
<强> ValidationObject.class 强>
//Once again, no magic
public class ValidationObject {
//This will be your property names
private String field, error;
public ValidationObject(String field, String error){
this.field = field;
this.error = error;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
<强> TestClass.class 强>
//This is just a class to showcase the functionality
//I have not posted any codesnippers for this @CustomClassConstaint,
//it is a custom annotaiton.
//I only included this annotation to show how
//the CustomExceptionMapper handles annotations on a class level
@CustomClassConstaint
public class TestClass {
@NotNull
@Size(min = 2, max = 5)
public String testString1;
@NotNull
@Size(min = 2, max = 5)
public String testString2;
@Min(10)
@Max(20)
public int testInt1;
@Min(10)
@Max(20)
public int testInt2;
}
用于测试的休息功能
//Some rest function to showcase
@POST
@Path("/path/to/test")
//Remember @Valid or annotations will not be validated
public Response callRestTestMethod(@Valid TestClass testObject){
return Response.ok().build();
}
测试输入:
POST /path/to/test
{
"testString1": null,
"testString2": "",
"testInt1": 9,
"testInt2": 21
}
测试输出:
订单有点随机,每次调用callRestTestMethod(...)
时都会更改。验证是逐个触发的,因为它们在组件树中,我不知道是否可以控制订单。
{
"errors": [
{
"field": "TestClass",
"error": "custom error msg"
},
{
"field": "testInt1",
"error": "must be greater than or equal to 10"
},
{
"field": "testString1",
"error": "may not be null"
},
{
"field": "testInt2",
"error": "must be less than or equal to 20"
},
{
"field": "testString2",
"error": "size must be between 2 and 5"
}
]
}