From ecdc1971c29e05fc2afbdbb6cc2d8ab43793bf49 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 13 Apr 2016 12:00:56 +0100 Subject: [PATCH] Add default descriptions for Hibernate Validator's constraints Closes gh-151 --- .../docs/asciidoc/documenting-your-api.adoc | 15 + .../DefaultConstraintDescriptions.properties | 14 +- ...dleConstraintDescriptionResolverTests.java | 263 ++++++++++++++---- 3 files changed, 233 insertions(+), 59 deletions(-) diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 88313f97..3e409f6e 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -534,6 +534,21 @@ Default descriptions are provided for all of the Bean Validation 1.1's constrain * Pattern * Size +Default descriptions are also provided for constraints from Hibernate Validator: + +* CreditCardNumber +* EAN +* Email +* Length +* LuhnCheck +* Mod10Check +* Mod11Check +* NotBlank +* NotEmpty +* Range +* SafeHtml +* URL + To override the default descriptions, or to provide a new description, create a resource bundle with the base name `org.springframework.restdocs.constraints.ConstraintDescriptions`. The Spring diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/constraints/DefaultConstraintDescriptions.properties b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/constraints/DefaultConstraintDescriptions.properties index c945d486..1cd5320f 100644 --- a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/constraints/DefaultConstraintDescriptions.properties +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/constraints/DefaultConstraintDescriptions.properties @@ -10,4 +10,16 @@ javax.validation.constraints.NotNull.description=Must not be null javax.validation.constraints.Null.description=Must be null javax.validation.constraints.Past.description=Must be in the past javax.validation.constraints.Pattern.description=Must match the regular expression '${regexp}' -javax.validation.constraints.Size.description=Size must be between ${min} and ${max} inclusive \ No newline at end of file +javax.validation.constraints.Size.description=Size must be between ${min} and ${max} inclusive +org.hibernate.validator.constraints.CreditCardNumber.description=Must be a well-formed credit card number +org.hibernate.validator.constraints.EAN.description=Must be a well-formed ${type} number +org.hibernate.validator.constraints.Email.description=Must be a well-formed email address +org.hibernate.validator.constraints.Length.description=Length must be between ${min} and ${max} inclusive +org.hibernate.validator.constraints.LuhnCheck.description=Must pass the Luhn Modulo 10 checksum algorithm +org.hibernate.validator.constraints.Mod10Check.description=Must pass the Mod10 checksum algorithm +org.hibernate.validator.constraints.Mod11Check.description=Must pass the Mod11 checksum algorithm +org.hibernate.validator.constraints.NotBlank.description=Must not be blank +org.hibernate.validator.constraints.NotEmpty.description=Must not be empty +org.hibernate.validator.constraints.Range.description=Must be at least ${min} and at most ${max} +org.hibernate.validator.constraints.SafeHtml.description=Must be safe HTML +org.hibernate.validator.constraints.URL.description=Must be a well-formed URL \ No newline at end of file diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolverTests.java index 06334f51..f9a7f734 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ResourceBundleConstraintDescriptionResolverTests.java @@ -16,11 +16,13 @@ package org.springframework.restdocs.constraints; +import java.lang.annotation.Annotation; +import java.math.BigDecimal; import java.net.URL; import java.util.Collections; -import java.util.HashMap; +import java.util.Date; +import java.util.List; import java.util.ListResourceBundle; -import java.util.Map; import java.util.ResourceBundle; import javax.validation.constraints.AssertFalse; @@ -37,8 +39,23 @@ import javax.validation.constraints.Past; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; +import org.hibernate.validator.constraints.CreditCardNumber; +import org.hibernate.validator.constraints.EAN; +import org.hibernate.validator.constraints.Email; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.LuhnCheck; +import org.hibernate.validator.constraints.Mod10Check; +import org.hibernate.validator.constraints.Mod11Check; +import org.hibernate.validator.constraints.NotBlank; +import org.hibernate.validator.constraints.NotEmpty; +import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; import org.junit.Test; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -54,111 +71,151 @@ public class ResourceBundleConstraintDescriptionResolverTests { @Test public void defaultMessageAssertFalse() { - String description = this.resolver.resolveDescription(new Constraint( - AssertFalse.class.getName(), Collections.emptyMap())); - assertThat(description, is(equalTo("Must be false"))); + assertThat(constraintDescriptionForField("assertFalse"), + is(equalTo("Must be false"))); } @Test public void defaultMessageAssertTrue() { - String description = this.resolver.resolveDescription(new Constraint( - AssertTrue.class.getName(), Collections.emptyMap())); - assertThat(description, is(equalTo("Must be true"))); + assertThat(constraintDescriptionForField("assertTrue"), + is(equalTo("Must be true"))); } @Test public void defaultMessageDecimalMax() { - Map configuration = new HashMap<>(); - configuration.put("value", "9.875"); - String description = this.resolver.resolveDescription( - new Constraint(DecimalMax.class.getName(), configuration)); - assertThat(description, is(equalTo("Must be at most 9.875"))); + assertThat(constraintDescriptionForField("decimalMax"), + is(equalTo("Must be at most 9.875"))); } @Test public void defaultMessageDecimalMin() { - Map configuration = new HashMap<>(); - configuration.put("value", "1.5"); - String description = this.resolver.resolveDescription( - new Constraint(DecimalMin.class.getName(), configuration)); - assertThat(description, is(equalTo("Must be at least 1.5"))); + assertThat(constraintDescriptionForField("decimalMin"), + is(equalTo("Must be at least 1.5"))); } @Test public void defaultMessageDigits() { - Map configuration = new HashMap<>(); - configuration.put("integer", "2"); - configuration.put("fraction", "5"); - String description = this.resolver.resolveDescription( - new Constraint(Digits.class.getName(), configuration)); - assertThat(description, is(equalTo( - "Must have at most 2 integral digits and 5 " + "fractional digits"))); + assertThat(constraintDescriptionForField("digits"), is( + equalTo("Must have at most 2 integral digits and 5 fractional digits"))); } @Test public void defaultMessageFuture() { - String description = this.resolver.resolveDescription(new Constraint( - Future.class.getName(), Collections.emptyMap())); - assertThat(description, is(equalTo("Must be in the future"))); + assertThat(constraintDescriptionForField("future"), + is(equalTo("Must be in the future"))); } @Test public void defaultMessageMax() { - Map configuration = new HashMap<>(); - configuration.put("value", 10); - String description = this.resolver - .resolveDescription(new Constraint(Max.class.getName(), configuration)); - assertThat(description, is(equalTo("Must be at most 10"))); + assertThat(constraintDescriptionForField("max"), + is(equalTo("Must be at most 10"))); } @Test public void defaultMessageMin() { - Map configuration = new HashMap<>(); - configuration.put("value", 10); - String description = this.resolver - .resolveDescription(new Constraint(Min.class.getName(), configuration)); - assertThat(description, is(equalTo("Must be at least 10"))); + assertThat(constraintDescriptionForField("min"), + is(equalTo("Must be at least 10"))); } @Test public void defaultMessageNotNull() { - String description = this.resolver.resolveDescription(new Constraint( - NotNull.class.getName(), Collections.emptyMap())); - assertThat(description, is(equalTo("Must not be null"))); + assertThat(constraintDescriptionForField("notNull"), + is(equalTo("Must not be null"))); } @Test public void defaultMessageNull() { - String description = this.resolver.resolveDescription(new Constraint( - Null.class.getName(), Collections.emptyMap())); - assertThat(description, is(equalTo("Must be null"))); + assertThat(constraintDescriptionForField("nul"), is(equalTo("Must be null"))); } @Test public void defaultMessagePast() { - String description = this.resolver.resolveDescription(new Constraint( - Past.class.getName(), Collections.emptyMap())); - assertThat(description, is(equalTo("Must be in the past"))); + assertThat(constraintDescriptionForField("past"), + is(equalTo("Must be in the past"))); } @Test public void defaultMessagePattern() { - Map configuration = new HashMap<>(); - configuration.put("regexp", "[A-Z][a-z]+"); - String description = this.resolver.resolveDescription( - new Constraint(Pattern.class.getName(), configuration)); - assertThat(description, + assertThat(constraintDescriptionForField("pattern"), is(equalTo("Must match the regular expression '[A-Z][a-z]+'"))); } @Test public void defaultMessageSize() { - Map configuration = new HashMap<>(); - configuration.put("min", 2); - configuration.put("max", 10); - String description = this.resolver - .resolveDescription(new Constraint(Size.class.getName(), configuration)); - assertThat(description, is(equalTo("Size must be between 2 and 10 inclusive"))); + assertThat(constraintDescriptionForField("size"), + is(equalTo("Size must be between 2 and 10 inclusive"))); + } + + @Test + public void defaultMessageCreditCardNumber() { + assertThat(constraintDescriptionForField("creditCardNumber"), + is(equalTo("Must be a well-formed credit card number"))); + } + + @Test + public void defaultMessageEan() { + assertThat(constraintDescriptionForField("ean"), + is(equalTo("Must be a well-formed EAN13 number"))); + } + + @Test + public void defaultMessageEmail() { + assertThat(constraintDescriptionForField("email"), + is(equalTo("Must be a well-formed email address"))); + } + + @Test + public void defaultMessageLength() { + assertThat(constraintDescriptionForField("length"), + is(equalTo("Length must be between 2 and 10 inclusive"))); + } + + @Test + public void defaultMessageLuhnCheck() { + assertThat(constraintDescriptionForField("luhnCheck"), + is(equalTo("Must pass the Luhn Modulo 10 checksum algorithm"))); + } + + @Test + public void defaultMessageMod10Check() { + assertThat(constraintDescriptionForField("mod10Check"), + is(equalTo("Must pass the Mod10 checksum algorithm"))); + } + + @Test + public void defaultMessageMod11Check() { + assertThat(constraintDescriptionForField("mod11Check"), + is(equalTo("Must pass the Mod11 checksum algorithm"))); + } + + @Test + public void defaultMessageNotBlank() { + assertThat(constraintDescriptionForField("notBlank"), + is(equalTo("Must not be blank"))); + } + + @Test + public void defaultMessageNotEmpty() { + assertThat(constraintDescriptionForField("notEmpty"), + is(equalTo("Must not be empty"))); + } + + @Test + public void defaultMessageRange() { + assertThat(constraintDescriptionForField("range"), + is(equalTo("Must be at least 10 and at most 100"))); + } + + @Test + public void defaultMessageSafeHtml() { + assertThat(constraintDescriptionForField("safeHtml"), + is(equalTo("Must be safe HTML"))); + } + + @Test + public void defaultMessageUrl() { + assertThat(constraintDescriptionForField("url"), + is(equalTo("Must be a well-formed URL"))); } @Test @@ -206,4 +263,94 @@ public class ResourceBundleConstraintDescriptionResolverTests { assertThat(description, is(equalTo("Not null"))); } + private String constraintDescriptionForField(String name) { + return this.resolver.resolveDescription(getConstraintFromField(name)); + } + + private Constraint getConstraintFromField(String name) { + Annotation[] annotations = ReflectionUtils.findField(Constrained.class, name) + .getAnnotations(); + Assert.isTrue(annotations.length == 1); + return new Constraint(annotations[0].annotationType().getName(), + AnnotationUtils.getAnnotationAttributes(annotations[0])); + } + + private static class Constrained { + + @AssertFalse + private boolean assertFalse; + + @AssertTrue + private boolean assertTrue; + + @DecimalMax("9.875") + private BigDecimal decimalMax; + + @DecimalMin("1.5") + private BigDecimal decimalMin; + + @Digits(integer = 2, fraction = 5) + private String digits; + + @Future + private Date future; + + @Max(10) + private int max; + + @Min(10) + private int min; + + @NotNull + private String notNull; + + @Null + private String nul; + + @Past + private Date past; + + @Pattern(regexp = "[A-Z][a-z]+") + private String pattern; + + @Size(min = 2, max = 10) + private List size; + + @CreditCardNumber + private String creditCardNumber; + + @EAN + private String ean; + + @Email + private String email; + + @Length(min = 2, max = 10) + private String length; + + @LuhnCheck + private String luhnCheck; + + @Mod10Check + private String mod10Check; + + @Mod11Check + private String mod11Check; + + @NotBlank + private String notBlank; + + @NotEmpty + private String notEmpty; + + @Range(min = 10, max = 100) + private int range; + + @SafeHtml + private String safeHtml; + + @org.hibernate.validator.constraints.URL + private String url; + } + }