Skip to content

Commit 2c0f12b

Browse files
authored
Merge pull request #36 from avaje/feature/jakarta-decimalMax
Support Jakarta DecimalMax
2 parents a35b9a2 + 531b51f commit 2c0f12b

File tree

9 files changed

+298
-19
lines changed

9 files changed

+298
-19
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package example.jakarta;
2+
3+
import jakarta.validation.Valid;
4+
import jakarta.validation.constraints.DecimalMax;
5+
import jakarta.validation.constraints.DecimalMin;
6+
import jakarta.validation.constraints.Digits;
7+
8+
import java.math.BigDecimal;
9+
10+
@Valid
11+
public class JMyMinNumbers {
12+
13+
@DecimalMin("10.50")
14+
final BigDecimal price;
15+
16+
@DecimalMin(value = "9.30", inclusive = false)
17+
final BigDecimal priceInc;
18+
19+
public JMyMinNumbers(BigDecimal price, BigDecimal priceInc) {
20+
this.price = price;
21+
this.priceInc = priceInc;
22+
}
23+
24+
public BigDecimal price() {
25+
return price;
26+
}
27+
28+
public BigDecimal priceInc() {
29+
return priceInc;
30+
}
31+
32+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package example.jakarta;
2+
3+
import io.avaje.validation.ConstraintViolation;
4+
import io.avaje.validation.ConstraintViolationException;
5+
import io.avaje.validation.Validator;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.math.BigDecimal;
9+
import java.util.ArrayList;
10+
import java.util.Locale;
11+
12+
import static java.math.BigDecimal.ONE;
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.assertj.core.api.Assertions.fail;
15+
16+
class JMyMinNumbersTest {
17+
18+
final Validator validator = Validator.builder().build();
19+
20+
final BigDecimal valid = new BigDecimal("20");
21+
22+
@Test
23+
void valid() {
24+
var bean = new JMyMinNumbers(valid, valid);
25+
validator.validate(bean);
26+
}
27+
28+
@Test
29+
void decimalMin() {
30+
var violation = one(new JMyMinNumbers(ONE, valid));
31+
assertThat(violation.message()).isEqualTo("must be greater than or equal to 10.50");
32+
}
33+
34+
@Test
35+
void decimalMinDE() {
36+
var violation = one(new JMyMinNumbers(ONE, valid), Locale.GERMAN);
37+
assertThat(violation.message()).isEqualTo("muss größer oder gleich 10.50 sein");
38+
}
39+
40+
@Test
41+
void decimalMinExclusive() {
42+
var violation = one(new JMyMinNumbers(valid, ONE));
43+
assertThat(violation.message()).isEqualTo("must be greater than 9.30");
44+
}
45+
46+
@Test
47+
void decimalMaxExclusiveDE() {
48+
var violation = one(new JMyMinNumbers(valid, ONE), Locale.GERMAN);
49+
assertThat(violation.message()).isEqualTo("muss größer 9.30 sein");
50+
}
51+
52+
ConstraintViolation one(Object any) {
53+
return one(any, Locale.ENGLISH);
54+
}
55+
56+
ConstraintViolation one(Object any, Locale locale) {
57+
try {
58+
validator.validate(any, locale);
59+
fail("not expected");
60+
return null;
61+
} catch (ConstraintViolationException e) {
62+
var violations = new ArrayList<>(e.violations());
63+
assertThat(violations).hasSize(1);
64+
return violations.get(0);
65+
}
66+
}
67+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package example.jakarta;
2+
3+
import jakarta.validation.Valid;
4+
import jakarta.validation.constraints.DecimalMax;
5+
import jakarta.validation.constraints.Digits;
6+
7+
import java.math.BigDecimal;
8+
9+
@Valid
10+
public class JMyNumbers {
11+
12+
@DecimalMax("10.50")
13+
final BigDecimal price;
14+
15+
@DecimalMax(value = "9.30", inclusive = false)
16+
final BigDecimal priceInc;
17+
18+
@Digits(integer = 5, fraction = 3)
19+
final String someDigits;
20+
21+
public JMyNumbers(BigDecimal price, BigDecimal priceInc, String someDigits) {
22+
this.price = price;
23+
this.priceInc = priceInc;
24+
this.someDigits = someDigits;
25+
}
26+
27+
public BigDecimal price() {
28+
return price;
29+
}
30+
31+
public BigDecimal priceInc() {
32+
return priceInc;
33+
}
34+
35+
public String someDigits() {
36+
return someDigits;
37+
}
38+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package example.jakarta;
2+
3+
import io.avaje.validation.ConstraintViolation;
4+
import io.avaje.validation.ConstraintViolationException;
5+
import io.avaje.validation.Validator;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.math.BigDecimal;
9+
import java.util.ArrayList;
10+
import java.util.Locale;
11+
12+
import static java.math.BigDecimal.ONE;
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.assertj.core.api.Assertions.fail;
15+
16+
class JMyNumbersTest {
17+
18+
final Validator validator = Validator.builder().build();
19+
20+
@Test
21+
void valid() {
22+
var bean = new JMyNumbers(ONE, ONE, "12345.123");
23+
validator.validate(bean);
24+
}
25+
26+
@Test
27+
void decimalMax() {
28+
var violation = one(new JMyNumbers(new BigDecimal("11"), ONE, "12345.123"));
29+
assertThat(violation.message()).isEqualTo("must be less than or equal to 10.50");
30+
}
31+
32+
@Test
33+
void decimalMaxDE() {
34+
var violation = one(new JMyNumbers(new BigDecimal("11"), ONE, "12345.123"), Locale.GERMAN);
35+
assertThat(violation.message()).isEqualTo("muss kleiner oder gleich 10.50 sein");
36+
}
37+
38+
@Test
39+
void decimalMaxExclusive() {
40+
var violation = one(new JMyNumbers(ONE, new BigDecimal("11"), "12345.123"));
41+
assertThat(violation.message()).isEqualTo("must be less than 9.30");
42+
}
43+
44+
@Test
45+
void decimalMaxExclusiveDE() {
46+
var violation = one(new JMyNumbers(ONE, new BigDecimal("11"), "12345.123"), Locale.GERMAN);
47+
assertThat(violation.message()).isEqualTo("muss kleiner 9.30 sein");
48+
}
49+
50+
ConstraintViolation one(Object any) {
51+
return one(any, Locale.ENGLISH);
52+
}
53+
54+
ConstraintViolation one(Object any, Locale locale) {
55+
try {
56+
validator.validate(any, locale);
57+
fail("not expected");
58+
return null;
59+
} catch (ConstraintViolationException e) {
60+
var violations = new ArrayList<>(e.violations());
61+
assertThat(violations).hasSize(1);
62+
return violations.get(0);
63+
}
64+
}
65+
}

validator-generator/src/main/java/io/avaje/validation/generator/AnnotationUtil.java

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ interface Handler {
1818
handlers.put("avaje.Pattern", pattern);
1919
handlers.put("jakarta.validation.constraints.Pattern", pattern);
2020

21-
Handler jakartaHandler = new JakartaHandler();
21+
final var jakartaHandler = new JakartaHandler();
2222
handlers.put("jakarta.validation.constraints.NotBlank", jakartaHandler);
2323
handlers.put("jakarta.validation.constraints.Size", jakartaHandler);
24+
25+
final var jakartaDecimal = new JakartaDecimal();
26+
handlers.put("jakarta.validation.constraints.DecimalMax", jakartaDecimal);
27+
handlers.put("jakarta.validation.constraints.DecimalMin", jakartaDecimal);
2428
}
2529

2630
private AnnotationUtil() {}
@@ -90,12 +94,26 @@ private static void pattern(StringBuilder sb, PatternPrism prism) {
9094

9195
static class StandardHandler extends BaseHandler {
9296

97+
protected final AnnotationMirror annotationMirror;
98+
protected final Element element;
99+
100+
/** Prototype factory */
101+
StandardHandler() {
102+
this.annotationMirror = null;
103+
this.element = null;
104+
}
105+
106+
StandardHandler(AnnotationMirror annotationMirror, Element element) {
107+
this.annotationMirror = annotationMirror;
108+
this.element = element;
109+
}
110+
93111
@Override
94112
public String attributes(AnnotationMirror annotationMirror, Element element) {
95-
return new StandardHandler().writeAttributes(annotationMirror, element);
113+
return new StandardHandler(annotationMirror, element).writeAttributes();
96114
}
97115

98-
String writeAttributes(AnnotationMirror annotationMirror, Element element) {
116+
String writeAttributes() {
99117
for (final ExecutableElement member : ElementFilter.methodsIn(element.getEnclosedElements())) {
100118
final AnnotationValue value = annotationMirror.getElementValues().get(member);
101119
final AnnotationValue defaultValue = member.getDefaultValue();
@@ -124,30 +142,78 @@ void writeAttributeKey(String name) {
124142
first = false;
125143
sb.append("\"").append(name).append("\",");
126144
}
145+
146+
AnnotationValue memberValue(String nameMatch) {
147+
for (final ExecutableElement member : ElementFilter.methodsIn(element.getEnclosedElements())) {
148+
if (nameMatch.equals(member.getSimpleName().toString())) {
149+
return annotationMirror.getElementValues().get(member);
150+
}
151+
}
152+
return null;
153+
}
127154
}
128155

129156
static class JakartaHandler extends StandardHandler {
130157

158+
/** Prototype factory only */
159+
JakartaHandler() {
160+
super();
161+
}
162+
163+
JakartaHandler(AnnotationMirror annotationMirror, Element element) {
164+
super(annotationMirror, element);
165+
}
166+
131167
@Override
132168
public String attributes(AnnotationMirror annotationMirror, Element element) {
133-
return new JakartaHandler().writeAttributes(annotationMirror, element);
169+
return new JakartaHandler(annotationMirror, element).writeAttributes();
134170
}
135171

136172
@Override
137173
void writeAttribute(Name simpleName, AnnotationValue value, AnnotationValue defaultValue) {
138174
final String name = simpleName.toString();
139175
if (value == null) {
140176
if ("message".equals(name)) {
141-
final String msgKey = defaultValue.toString().replace("{jakarta.validation.constraints.", "{avaje.");
142177
writeAttributeKey("message");
143-
sb.append(msgKey);
178+
sb.append(messageKey(defaultValue));
144179
} else if (!name.equals("payload") && !name.equals("groups")) {
145180
super.writeAttribute(simpleName, null, defaultValue);
146181
}
147182
} else {
148183
super.writeAttribute(simpleName, value, defaultValue);
149184
}
150185
}
186+
187+
String messageKey(AnnotationValue defaultValue) {
188+
return defaultValue.toString().replace("{jakarta.validation.constraints.", "{avaje.");
189+
}
190+
}
191+
192+
static class JakartaDecimal extends JakartaHandler {
193+
194+
/** Prototype factory only */
195+
JakartaDecimal() {
196+
super();
197+
}
198+
199+
@Override
200+
public String attributes(AnnotationMirror annotationMirror, Element element) {
201+
return new JakartaDecimal(annotationMirror, element).writeAttributes();
202+
}
203+
204+
JakartaDecimal(AnnotationMirror annotationMirror, Element element) {
205+
super(annotationMirror, element);
206+
}
207+
208+
String messageKey(AnnotationValue defaultValue) {
209+
final AnnotationValue inclusiveValue = memberValue("inclusive");
210+
final boolean inclusive = (inclusiveValue == null || "true".equals(inclusiveValue.toString()));
211+
String messageKey = super.messageKey(defaultValue);
212+
if (!inclusive) {
213+
messageKey = messageKey.replace(".message", ".exclusive.message");
214+
}
215+
return messageKey;
216+
}
151217
}
152218

153219
}

validator-generator/src/main/java/io/avaje/validation/generator/FieldReader.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static java.util.stream.Collectors.toMap;
44

5+
import java.util.HashSet;
56
import java.util.List;
67
import java.util.Map;
78
import java.util.Set;
@@ -12,6 +13,12 @@
1213

1314
final class FieldReader {
1415

16+
static final Set<String> BASIC_TYPES = new HashSet<>();
17+
static {
18+
BASIC_TYPES.add("java.lang.String");
19+
BASIC_TYPES.add("java.math.BigDecimal");
20+
}
21+
1522
private final List<String> genericTypeParams;
1623
private final boolean publicField;
1724
private final String rawType;
@@ -241,6 +248,6 @@ public void writeConstructor(Append writer) {
241248
}
242249

243250
private boolean isBasicType(final String topType) {
244-
return "java.lang.String".equals(topType) || GenericTypeMap.typeOfRaw(topType) != null;
251+
return BASIC_TYPES.contains(topType) || GenericTypeMap.typeOfRaw(topType) != null;
245252
}
246253
}

0 commit comments

Comments
 (0)