Skip to content

Commit a8bad59

Browse files
authored
include current value in exception text (#65)
* introducing "%s" on jakarta.validation.constraints.Min.message * initial support for format-placeholder * added "%s" on jakarta.validation.constraints.Max.message * added "%s" on Pattern and Email * added format placeholders to all remaining properties * format placeholder support on custom annotations * introducing FormatMessageInjector --------- Co-authored-by: Peter Fichtner (pfichtner) <pfichtner@users.noreply.github.com>
1 parent 49cbee8 commit a8bad59

File tree

69 files changed

+581
-468
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+581
-468
lines changed

vaadoo-bytebuddy/src/main/java/com/github/pfichtner/vaadoo/CustomAnnotations.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.github.pfichtner.vaadoo;
1717

18+
import static com.github.pfichtner.vaadoo.FormatMessageInjector.injectFormatMessage;
1819
import static java.lang.String.format;
1920
import static java.util.Objects.requireNonNull;
2021
import static java.util.stream.Collectors.joining;
@@ -89,6 +90,7 @@ public static void addCustomAnnotations(MethodVisitor mv, Parameter parameter, T
8990
mv.visitInsn(DUP);
9091
var message = parameter.annotationValue(getObjectType(annotation.getInternalName()), "message");
9192
mv.visitLdcInsn(getMessage(parameter, annotation, message));
93+
injectFormatMessage(mv, parameter);
9294
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V",
9395
false);
9496
mv.visitInsn(ATHROW);
@@ -138,11 +140,14 @@ private static TypeDescription typeThatGetsValidated(TypeDefinition validatorCla
138140
format("Expect %s to have 2 generic types but found %s", validatorClass, typeArguments));
139141
}
140142

141-
private static Object defaultMessage(TypeDescription type) {
142-
MethodList<InDefinedShape> list = type.getDeclaredMethods() //
143-
.filter(named("message")) //
144-
.filter(m -> m.getParameters().isEmpty());
145-
return list.isEmpty() ? null : requireNonNull(list.getOnly().getDefaultValue()).resolve();
143+
private static InDefinedShape defaultMessageMethod(TypeDescription annotation) {
144+
MethodList<InDefinedShape> messageMethod = annotation.getDeclaredMethods().filter(named("message"));
145+
return messageMethod.size() == 1 ? requireNonNull(messageMethod.getOnly()) : null;
146+
}
147+
148+
private static Object defaultMessage(TypeDescription annotation) {
149+
InDefinedShape messageMethod = defaultMessageMethod(annotation);
150+
return messageMethod == null ? null : messageMethod.getDefaultValue().resolve();
146151
}
147152

148153
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.pfichtner.vaadoo;
17+
18+
import static net.bytebuddy.jar.asm.Opcodes.AASTORE;
19+
import static net.bytebuddy.jar.asm.Opcodes.ALOAD;
20+
import static net.bytebuddy.jar.asm.Opcodes.ANEWARRAY;
21+
import static net.bytebuddy.jar.asm.Opcodes.DLOAD;
22+
import static net.bytebuddy.jar.asm.Opcodes.DUP;
23+
import static net.bytebuddy.jar.asm.Opcodes.FLOAD;
24+
import static net.bytebuddy.jar.asm.Opcodes.ICONST_0;
25+
import static net.bytebuddy.jar.asm.Opcodes.ICONST_1;
26+
import static net.bytebuddy.jar.asm.Opcodes.ILOAD;
27+
import static net.bytebuddy.jar.asm.Opcodes.INVOKESTATIC;
28+
import static net.bytebuddy.jar.asm.Opcodes.LLOAD;
29+
30+
import com.github.pfichtner.vaadoo.Parameters.Parameter;
31+
32+
import net.bytebuddy.jar.asm.MethodVisitor;
33+
import net.bytebuddy.jar.asm.Type;
34+
35+
public final class FormatMessageInjector {
36+
37+
private FormatMessageInjector() {
38+
super();
39+
}
40+
41+
public static void injectFormatMessage(MethodVisitor mv, Parameter parameter) {
42+
mv.visitInsn(ICONST_1);
43+
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
44+
mv.visitInsn(DUP);
45+
mv.visitInsn(ICONST_0);
46+
loadParameterValue(mv, Type.getType(parameter.type().asErasure().getDescriptor()), parameter.offset());
47+
mv.visitInsn(AASTORE);
48+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "format",
49+
"(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false);
50+
}
51+
52+
private static void loadParameterValue(MethodVisitor mv, Type paramType, int varIndex) {
53+
switch (paramType.getSort()) {
54+
case Type.BOOLEAN:
55+
mv.visitVarInsn(ILOAD, varIndex);
56+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
57+
break;
58+
case Type.BYTE:
59+
mv.visitVarInsn(ILOAD, varIndex);
60+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
61+
break;
62+
case Type.CHAR:
63+
mv.visitVarInsn(ILOAD, varIndex);
64+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
65+
break;
66+
case Type.SHORT:
67+
mv.visitVarInsn(ILOAD, varIndex);
68+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
69+
break;
70+
case Type.INT:
71+
mv.visitVarInsn(ILOAD, varIndex);
72+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
73+
break;
74+
case Type.LONG:
75+
mv.visitVarInsn(LLOAD, varIndex);
76+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
77+
break;
78+
case Type.FLOAT:
79+
mv.visitVarInsn(FLOAD, varIndex);
80+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
81+
break;
82+
case Type.DOUBLE:
83+
mv.visitVarInsn(DLOAD, varIndex);
84+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
85+
break;
86+
default:
87+
mv.visitVarInsn(ALOAD, varIndex);
88+
break;
89+
}
90+
}
91+
92+
}

vaadoo-bytebuddy/src/main/java/com/github/pfichtner/vaadoo/ValidationCodeInjector.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static com.github.pfichtner.vaadoo.AsmUtil.isReturnOpcode;
2323
import static com.github.pfichtner.vaadoo.AsmUtil.isStoreOpcode;
2424
import static com.github.pfichtner.vaadoo.AsmUtil.sizeOf;
25+
import static com.github.pfichtner.vaadoo.FormatMessageInjector.injectFormatMessage;
2526
import static java.lang.String.format;
2627
import static java.lang.reflect.Modifier.isStatic;
2728
import static java.util.Arrays.stream;
@@ -328,10 +329,20 @@ public void visitLdcInsn(Object value) {
328329
if (value instanceof String) {
329330
String replaced = NamedPlaceholders.replace((String) value, resolver);
330331
Map<String, Integer> placeholders = targetParam.placeholderValues();
331-
if (placeholders.isEmpty() || !hasPlaceholder(replaced, placeholders.keySet())) {
332+
boolean hasNamedPlaceholders = !placeholders.isEmpty()
333+
&& hasPlaceholder(replaced, placeholders.keySet());
334+
boolean hasFormatPlaceholder = replaced.contains("%s");
335+
if (!hasNamedPlaceholders && !hasFormatPlaceholder) {
332336
super.visitLdcInsn(replaced);
333337
} else {
334-
injectDynamicMessage(replaced, placeholders);
338+
if (hasNamedPlaceholders) {
339+
injectDynamicMessage(replaced, placeholders);
340+
} else {
341+
super.visitLdcInsn(replaced);
342+
}
343+
if (hasFormatPlaceholder) {
344+
injectFormatMessage(mv, targetParam);
345+
}
335346
}
336347
} else {
337348
super.visitLdcInsn(value);
Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
jakarta.validation.constraints.AssertFalse.message = {@@@NAME@@@} must be false
22
jakarta.validation.constraints.AssertTrue.message = {@@@NAME@@@} must be true
3-
jakarta.validation.constraints.DecimalMax.message = {@@@NAME@@@} must be less than ${anno.inclusive() == true ? 'or equal to ' : ''}{anno.value()}
4-
jakarta.validation.constraints.DecimalMin.message = {@@@NAME@@@} must be greater than ${anno.inclusive() == true ? 'or equal to ' : ''}{anno.value()}
5-
jakarta.validation.constraints.Digits.message = {@@@NAME@@@} numeric value out of bounds (<{anno.integer()} digits>.<{anno.fraction()} digits> expected)
6-
jakarta.validation.constraints.Email.message = {@@@NAME@@@} must be a well-formed email address
7-
jakarta.validation.constraints.Future.message = {@@@NAME@@@} must be a future date
8-
jakarta.validation.constraints.FutureOrPresent.message = {@@@NAME@@@} must be a date in the present or in the future
9-
jakarta.validation.constraints.Max.message = {@@@NAME@@@} must be less than or equal to {anno.value()}
10-
jakarta.validation.constraints.Min.message = {@@@NAME@@@} must be greater than or equal to {anno.value()}
11-
jakarta.validation.constraints.Negative.message = {@@@NAME@@@} must be less than 0
12-
jakarta.validation.constraints.NegativeOrZero.message = {@@@NAME@@@} must be less than or equal to 0
3+
jakarta.validation.constraints.DecimalMax.message = {@@@NAME@@@} must be less than ${anno.inclusive() == true ? 'or equal to ' : ''}{anno.value()} but was %s
4+
jakarta.validation.constraints.DecimalMin.message = {@@@NAME@@@} must be greater than ${anno.inclusive() == true ? 'or equal to ' : ''}{anno.value()} but was %s
5+
jakarta.validation.constraints.Digits.message = {@@@NAME@@@} numeric value out of bounds (<{anno.integer()} digits>.<{anno.fraction()} digits> expected) but was %s
6+
jakarta.validation.constraints.Email.message = {@@@NAME@@@} must be a well-formed email address but was %s
7+
jakarta.validation.constraints.Future.message = {@@@NAME@@@} must be a future date but was %s
8+
jakarta.validation.constraints.FutureOrPresent.message = {@@@NAME@@@} must be a date in the present or in the future but was %s
9+
jakarta.validation.constraints.Max.message = {@@@NAME@@@} must be less than or equal to {anno.value()} but was %s
10+
jakarta.validation.constraints.Min.message = {@@@NAME@@@} must be greater than or equal to {anno.value()} but was %s
11+
jakarta.validation.constraints.Negative.message = {@@@NAME@@@} must be less than 0 but was %s
12+
jakarta.validation.constraints.NegativeOrZero.message = {@@@NAME@@@} must be less than or equal to 0 but was %s
1313
jakarta.validation.constraints.NotBlank.message = {@@@NAME@@@} must not be blank
1414
jakarta.validation.constraints.NotEmpty.message = {@@@NAME@@@} must not be empty
1515
jakarta.validation.constraints.NotNull.message = {@@@NAME@@@} must not be null
16-
jakarta.validation.constraints.Null.message = {@@@NAME@@@} must be null
17-
jakarta.validation.constraints.Past.message = {@@@NAME@@@} must be a past date
18-
jakarta.validation.constraints.PastOrPresent.message = {@@@NAME@@@} must be a date in the past or in the present
19-
jakarta.validation.constraints.Pattern.message = {@@@NAME@@@} must match "{anno.regexp()}"
20-
jakarta.validation.constraints.Positive.message = {@@@NAME@@@} must be greater than 0
21-
jakarta.validation.constraints.PositiveOrZero.message = {@@@NAME@@@} must be greater than or equal to 0
22-
jakarta.validation.constraints.Size.message = size of {@@@NAME@@@} must be between {anno.min()} and {anno.max()}
16+
jakarta.validation.constraints.Null.message = {@@@NAME@@@} must be null but was %s
17+
jakarta.validation.constraints.Past.message = {@@@NAME@@@} must be a past date but was %s
18+
jakarta.validation.constraints.PastOrPresent.message = {@@@NAME@@@} must be a date in the past or in the present but was %s
19+
jakarta.validation.constraints.Pattern.message = {@@@NAME@@@} must match "{anno.regexp()}" but was %s
20+
jakarta.validation.constraints.Positive.message = {@@@NAME@@@} must be greater than 0 but was %s
21+
jakarta.validation.constraints.PositiveOrZero.message = {@@@NAME@@@} must be greater than or equal to 0 but was %s
22+
jakarta.validation.constraints.Size.message = size of {@@@NAME@@@} must be between {anno.min()} and {anno.max()} but was %s
2323

vaadoo-bytebuddy/src/test/java/com/github/pfichtner/vaadoo/JMoleculesVaadooPluginTests.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,10 @@ void regex() throws Exception {
116116
var transformed = transformer.transform(ValueObjectWithRegexAttribute.class);
117117
var constructor = transformed.getDeclaredConstructor(String.class);
118118
constructor.newInstance("42");
119-
assertThatException().isThrownBy(() -> constructor.newInstance("4")).satisfies(e -> assertThat(e.getCause())
120-
.isInstanceOf(IllegalArgumentException.class).hasMessage(mustMatch("someTwoDigits", "\"\\d\\d\"")));
119+
String value = "4";
120+
assertThatException().isThrownBy(() -> constructor.newInstance(value))
121+
.satisfies(e -> assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class)
122+
.hasMessage(mustMatch("someTwoDigits", "\"\\d\\d\"", value)));
121123
}
122124

123125
@Test
@@ -182,7 +184,7 @@ void genericTypeAnnotations() throws Exception {
182184

183185
assertThatThrownBy(() -> listArgConstructor.newInstance(List.of(1, 2, 3, -1, 4, 5, 6, 7, 8, 9, 10)))
184186
.hasCauseInstanceOf(IllegalArgumentException.class)
185-
.hasRootCauseMessage("positiveIntsList[3] must be greater than 0");
187+
.hasRootCauseMessage("positiveIntsList[3] must be greater than 0 but was -1");
186188
}
187189

188190
static List<Arguments> customExampleSource() throws Exception {
@@ -200,7 +202,7 @@ static List<Arguments> customExampleSource() throws Exception {
200202
transformed2.getDeclaredConstructor(String.class, String.class), //
201203
List.of(validIban, ""), //
202204
List.of(invalidIban, ""), //
203-
"iban not a valid IBAN (this is a custum message from resourcebundle with [DE, AT, CH])"), //
205+
"iban not a valid IBAN (this is a custum message from resourcebundle with [DE, AT, CH]) but was DE02"), //
204206
arguments( //
205207
transformed2.getDeclaredConstructor(String.class, boolean.class), //
206208
List.of(validIban, true), //
@@ -217,8 +219,8 @@ static String notNull(String paramName) {
217219
return format("%s must not be null", paramName);
218220
}
219221

220-
static String mustMatch(String paramName, String regexp) {
221-
return format("%s must match %s", paramName, regexp);
222+
static String mustMatch(String paramName, String regexp, String actual) {
223+
return format("%s must match %s but was %s", paramName, regexp, actual);
222224
}
223225

224226
}

vaadoo-bytebuddy/src/test/java/com/github/pfichtner/vaadoo/Jsr380DynamicClassPBTest.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
import java.util.function.Function;
7474
import java.util.function.IntUnaryOperator;
7575
import java.util.function.Predicate;
76-
import java.util.stream.Collectors;
7776
import java.util.stream.Stream;
7877

7978
import org.approvaltests.ApprovalSettings;
@@ -282,6 +281,10 @@ static Predicate<Throwable> endsWith(Function<Throwable, String> mapper, String
282281
return t -> mapper.apply(t).endsWith(expectedMessage);
283282
}
284283

284+
static Predicate<Throwable> contains(Function<Throwable, String> mapper, String expectedMessage) {
285+
return t -> mapper.apply(t).contains(expectedMessage);
286+
}
287+
285288
static Object[] args(List<ParameterDefinition> params) {
286289
return params.stream() //
287290
.map(ParameterDefinition::typeDefinition) //
@@ -323,22 +326,22 @@ static Object getDefault(Class<?> clazz) {
323326
private static final String FIXED_SEED = "-1787866974758305853";
324327

325328
static final List<Predicate<Throwable>> oks = List.of( //
326-
isNPE().and(endsWith(Throwable::getMessage, "must not be null")), //
329+
isNPE().and(contains(Throwable::getMessage, "must not be null but was ")), //
327330
isIAE().and(endsWith(Throwable::getMessage, "must not be null")), //
328331
isNPE().and(endsWith(Throwable::getMessage, "must not be empty")), //
329332
isNPE().and(endsWith(Throwable::getMessage, "must not be blank")), //
330-
isIAE().and(endsWith(Throwable::getMessage, "must be null")), //
333+
isIAE().and(contains(Throwable::getMessage, "must be null but was ")), //
331334
isIAE().and(endsWith(Throwable::getMessage, "must not be empty")), //
332335
isIAE().and(endsWith(Throwable::getMessage, "must not be blank")), //
333-
isIAE().and(endsWith(Throwable::getMessage, "must be greater than 0")), //
334-
isIAE().and(endsWith(Throwable::getMessage, "must be less than 0")), //
336+
isIAE().and(contains(Throwable::getMessage, "must be greater than 0 but was ")), //
337+
isIAE().and(contains(Throwable::getMessage, "must be less than 0 but was ")), //
335338
isIAE().and(endsWith(Throwable::getMessage, "must be true")), //
336339
isIAE().and(endsWith(Throwable::getMessage, "must be false")), //
337-
isIAE().and(
338-
endsWith(Throwable::getMessage, "numeric value out of bounds (<0 digits>.<0 digits> expected)")), //
339-
isIAE().and(endsWith(Throwable::getMessage, "must be a future date")), //
340-
isIAE().and(endsWith(Throwable::getMessage, "must be a date in the present or in the future")), //
341-
isIAE().and(endsWith(Throwable::getMessage, "must be a past date")) //
340+
isIAE().and(contains(Throwable::getMessage,
341+
"numeric value out of bounds (<0 digits>.<0 digits> expected) but was ")), //
342+
isIAE().and(contains(Throwable::getMessage, "must be a future date but was ")), //
343+
isIAE().and(contains(Throwable::getMessage, "must be a date in the present or in the future but was ")), //
344+
isIAE().and(contains(Throwable::getMessage, "must be a past date but was ")) //
342345
);
343346

344347
static Predicate<Throwable> isIAE() {

vaadoo-bytebuddy/src/test/resources/com/github/pfichtner/vaadoo/GenericTypesTest.arrayWithPatternAnnotatedElements.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ implements ValueObject {
5858
for (int i = 0; i < n; ++i) {
5959
String string = stringArray[i];
6060
if (string == null || GenericGenerated.getCachedPattern("\\d*", 0).matcher(string).matches()) continue;
61-
throw new IllegalArgumentException("stringArray[" + i + "] must match \"\\d*\"");
61+
throw new IllegalArgumentException(String.format("stringArray[" + i + "] must match \"\\d*\" but was %s", string));
6262
}
6363
}
6464
}

vaadoo-bytebuddy/src/test/resources/com/github/pfichtner/vaadoo/GenericTypesTest.mapWithPatternAnnotatedKeysAndValues.approved.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ implements ValueObject {
5959
for (Map.Entry entry : map.entrySet()) {
6060
string = (String)entry.getKey();
6161
if (string == null || GenericGenerated.getCachedPattern("K\\d*", 0).matcher(string).matches()) continue;
62-
throw new IllegalArgumentException("map[key=" + entry.getKey() + "] must match \"K\\d*\"");
62+
throw new IllegalArgumentException(String.format("map[key=" + entry.getKey() + "] must match \"K\\d*\" but was %s", string));
6363
}
6464
}
6565
if (map != null) {
6666
for (Map.Entry entry : map.entrySet()) {
6767
string = (String)entry.getValue();
6868
if (string == null || GenericGenerated.getCachedPattern("V\\d*", 0).matcher(string).matches()) continue;
69-
throw new IllegalArgumentException("map[value for key=" + entry.getKey() + "] must match \"V\\d*\"");
69+
throw new IllegalArgumentException(String.format("map[value for key=" + entry.getKey() + "] must match \"V\\d*\" but was %s", string));
7070
}
7171
}
7272
}

vaadoo-bytebuddy/src/test/resources/com/github/pfichtner/vaadoo/GenericTypesTest.primitiveArrayWithAnnotatedElements.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ implements ValueObject {
5454
for (int i = 0; i < n; ++i) {
5555
int n2 = nArray[i];
5656
if ((long)n2 >= 1L) continue;
57-
throw new IllegalArgumentException("intArray[" + i + "] must be greater than or equal to 1");
57+
throw new IllegalArgumentException(String.format("intArray[" + i + "] must be greater than or equal to 1 but was %s", n2));
5858
}
5959
}
6060
}

vaadoo-bytebuddy/src/test/resources/com/github/pfichtner/vaadoo/Jsr380DynamicClassPBTest.implementsValueObject.140176688.approved.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,16 @@ implements ValueObject {
6868
throw new NullPointerException("linkedHashMap must not be null");
6969
}
7070
if (linkedHashMap != null) {
71-
throw new IllegalArgumentException("linkedHashMap must be null");
71+
throw new IllegalArgumentException(String.format("linkedHashMap must be null but was %s", linkedHashMap));
7272
}
7373
if (linkedHashMap != null && (linkedHashMap.size() < 0 || linkedHashMap.size() > Integer.MAX_VALUE)) {
74-
throw new IllegalArgumentException("size of linkedHashMap must be between 0 and 2147483647");
74+
throw new IllegalArgumentException(String.format("size of linkedHashMap must be between 0 and 2147483647 but was %s", linkedHashMap));
7575
}
7676
if (minguoDate != null && !minguoDate.isBefore(MinguoDate.now())) {
77-
throw new IllegalArgumentException("minguoDate must be a past date");
77+
throw new IllegalArgumentException(String.format("minguoDate must be a past date but was %s", minguoDate));
7878
}
7979
if (minguoDate != null) {
80-
throw new IllegalArgumentException("minguoDate must be null");
80+
throw new IllegalArgumentException(String.format("minguoDate must be null but was %s", minguoDate));
8181
}
8282
if (minguoDate == null) {
8383
throw new NullPointerException("minguoDate must not be null");

0 commit comments

Comments
 (0)