From 2a9a583f5c45ecf9524f46140b9f257c985387aa Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 7 Jul 2023 21:45:33 -0400 Subject: [PATCH 1/8] start --- .../main/java/example/avaje/typeuse/Ship.java | 2 +- .../src/test/java/example/avaje/AMyEmail.java | 2 +- .../test/java/example/avaje/AMyNumbers.java | 2 +- .../test/java/example/avaje/AMyPattern.java | 2 +- .../src/test/java/example/avaje/ANums.java | 1 - .../test/java/example/avaje/APastFuture.java | 1 - .../java/example/avaje/nested/AAddress.java | 2 +- .../avaje/validation/constraints}/Valid.java | 10 +- .../validation/generator/AdapterHelper.java | 104 ++++++++++++++++++ .../validation/generator/FieldReader.java | 89 +-------------- .../generator/ValidMethodReader.java | 98 +---------------- .../generator/ValidationProcessor.java | 13 ++- .../validation/generator/package-info.java | 4 +- .../generator/models/valid/AMyNumbers.java | 2 +- .../generator/models/valid/Contact.java | 2 +- .../generator/models/valid/Customer.java | 2 +- .../generator/models/valid/Polus.java | 2 +- .../generator/models/valid/Ship.java | 2 +- .../models/valid/methods/MethodTest.java | 2 +- .../io/avaje/validation/ImportValidPojo.java | 10 ++ ...ter.java => AbstractContainerAdapter.java} | 6 +- .../adapter/ArrayValidationAdapter.java | 2 +- .../adapter/CollectionValidationAdapter.java | 2 +- .../adapter/MapValidationAdapter.java | 2 +- .../adapter/OptionalValidationAdapter.java | 28 +++++ .../validation/adapter/ValidationAdapter.java | 12 +- .../core/BasicMessageInterpolator.java | 2 + .../validation/core/DValidationType.java | 26 ----- .../io/avaje/validation/core/DValidator.java | 5 +- .../avaje/validation/core/ValidationType.java | 26 ++++- .../{core => spi}/MessageInterpolator.java | 2 +- validator/src/main/java/module-info.java | 3 +- .../io/avaje/validation/core/Address.java | 4 +- .../io/avaje/validation/core/Contact.java | 4 +- .../io/avaje/validation/core/Customer.java | 7 +- 35 files changed, 213 insertions(+), 270 deletions(-) rename {validator/src/main/java/io/avaje/validation => validator-constraints/src/main/java/io/avaje/validation/constraints}/Valid.java (61%) create mode 100644 validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java create mode 100644 validator/src/main/java/io/avaje/validation/ImportValidPojo.java rename validator/src/main/java/io/avaje/validation/adapter/{AbstractMultiAdapter.java => AbstractContainerAdapter.java} (84%) create mode 100644 validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java delete mode 100644 validator/src/main/java/io/avaje/validation/core/DValidationType.java rename validator/src/main/java/io/avaje/validation/{core => spi}/MessageInterpolator.java (80%) diff --git a/blackbox-test/src/main/java/example/avaje/typeuse/Ship.java b/blackbox-test/src/main/java/example/avaje/typeuse/Ship.java index 05a343ea..c37a4e9e 100644 --- a/blackbox-test/src/main/java/example/avaje/typeuse/Ship.java +++ b/blackbox-test/src/main/java/example/avaje/typeuse/Ship.java @@ -3,9 +3,9 @@ import java.util.List; import java.util.Map; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.NotBlank; import io.avaje.validation.constraints.NotNull; +import io.avaje.validation.constraints.Valid; @Valid public record Ship( diff --git a/blackbox-test/src/test/java/example/avaje/AMyEmail.java b/blackbox-test/src/test/java/example/avaje/AMyEmail.java index 62f9f0b0..6e25f373 100644 --- a/blackbox-test/src/test/java/example/avaje/AMyEmail.java +++ b/blackbox-test/src/test/java/example/avaje/AMyEmail.java @@ -1,7 +1,7 @@ package example.avaje; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.Email; +import io.avaje.validation.constraints.Valid; @Valid public class AMyEmail { diff --git a/blackbox-test/src/test/java/example/avaje/AMyNumbers.java b/blackbox-test/src/test/java/example/avaje/AMyNumbers.java index 16e1c203..19c7271e 100644 --- a/blackbox-test/src/test/java/example/avaje/AMyNumbers.java +++ b/blackbox-test/src/test/java/example/avaje/AMyNumbers.java @@ -1,7 +1,7 @@ package example.avaje; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.DecimalMax; +import io.avaje.validation.constraints.Valid; import java.math.BigDecimal; diff --git a/blackbox-test/src/test/java/example/avaje/AMyPattern.java b/blackbox-test/src/test/java/example/avaje/AMyPattern.java index e8c56e54..cafb0bf8 100644 --- a/blackbox-test/src/test/java/example/avaje/AMyPattern.java +++ b/blackbox-test/src/test/java/example/avaje/AMyPattern.java @@ -1,7 +1,7 @@ package example.avaje; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.Pattern; +import io.avaje.validation.constraints.Valid; @Valid public class AMyPattern { diff --git a/blackbox-test/src/test/java/example/avaje/ANums.java b/blackbox-test/src/test/java/example/avaje/ANums.java index b816c51f..1ce7f983 100644 --- a/blackbox-test/src/test/java/example/avaje/ANums.java +++ b/blackbox-test/src/test/java/example/avaje/ANums.java @@ -1,6 +1,5 @@ package example.avaje; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.*; import java.math.BigDecimal; diff --git a/blackbox-test/src/test/java/example/avaje/APastFuture.java b/blackbox-test/src/test/java/example/avaje/APastFuture.java index 639988e8..eff18d52 100644 --- a/blackbox-test/src/test/java/example/avaje/APastFuture.java +++ b/blackbox-test/src/test/java/example/avaje/APastFuture.java @@ -1,6 +1,5 @@ package example.avaje; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.*; import java.time.LocalDate; diff --git a/blackbox-test/src/test/java/example/avaje/nested/AAddress.java b/blackbox-test/src/test/java/example/avaje/nested/AAddress.java index a2001046..aa539da9 100644 --- a/blackbox-test/src/test/java/example/avaje/nested/AAddress.java +++ b/blackbox-test/src/test/java/example/avaje/nested/AAddress.java @@ -1,6 +1,6 @@ package example.avaje.nested; -import io.avaje.validation.Valid; +import io.avaje.validation.constraints.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; diff --git a/validator/src/main/java/io/avaje/validation/Valid.java b/validator-constraints/src/main/java/io/avaje/validation/constraints/Valid.java similarity index 61% rename from validator/src/main/java/io/avaje/validation/Valid.java rename to validator-constraints/src/main/java/io/avaje/validation/constraints/Valid.java index 5a911b4e..33def535 100644 --- a/validator/src/main/java/io/avaje/validation/Valid.java +++ b/validator-constraints/src/main/java/io/avaje/validation/constraints/Valid.java @@ -1,4 +1,4 @@ -package io.avaje.validation; +package io.avaje.validation.constraints; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -15,13 +15,5 @@ /** */ - @Retention(CLASS) - @Target({TYPE, PACKAGE}) - @interface Import { - /** - * Specify types to generate Valid Adapters for. - */ - Class[] value(); - } } diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java b/validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java new file mode 100644 index 00000000..d4b5975a --- /dev/null +++ b/validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java @@ -0,0 +1,104 @@ +package io.avaje.validation.generator; + +import java.util.Map; + +public class AdapterHelper { + + static void writeAdapterWithValues( + Append writer, ElementAnnotationContainer elementAnnotations, String indent, String type) { + boolean first = true; + final var annotations = elementAnnotations.annotations(); + final var genericType = elementAnnotations.genericType(); + final var typeUse1 = elementAnnotations.typeUse1(); + final var typeUse2 = elementAnnotations.typeUse2(); + final var hasValid = elementAnnotations.hasValid(); + for (final var a : annotations.entrySet()) { + if (first) { + writer.append( + "%sctx.<%s>adapter(%s.class, %s)", indent, type, a.getKey().shortName(), a.getValue()); + first = false; + continue; + } + writer + .eol() + .append( + "%s .andThen(ctx.adapter(%s.class,%s))", + indent, a.getKey().shortName(), a.getValue()); + } + + if (annotations.isEmpty()) { + writer.append("%sctx.<%s>noop()", indent, type); + } + + if (!typeUse1.isEmpty() + && ("java.util.List".equals(genericType.topType()) + || "java.util.Set".equals(genericType.topType()))) { + writer.eol().append("%s .list()", indent); + final var t = genericType.firstParamType(); + writeTypeUse(writer, indent, t, typeUse1, genericType); + + } else if ((!typeUse1.isEmpty() || !typeUse2.isEmpty()) + && "java.util.Map".equals(genericType.topType())) { + + writer.eol().append("%s .mapKeys()", indent); + writeTypeUse(writer, indent, genericType.firstParamType(), typeUse1, genericType); + + writer.eol().append("%s .mapValues()", indent); + writeTypeUse(writer, indent, genericType.secondParamType(), typeUse2, false, genericType); + + } else if (genericType.topType().contains("[]") && hasValid) { + + writer.eol().append("%s .array()", indent); + writeTypeUse(writer, indent, genericType.firstParamType(), typeUse1, genericType); + } else if (hasValid) { + writer + .eol() + .append( + "%s .andThen(ctx.adapter(%s.class))", + indent, Util.shortName(genericType.topType())); + } else if (genericType.topType().contains("java.util.Optional")) { + writer.eol().append("%s .optional()", indent); + } + } + + private static void writeTypeUse( + Append writer, + String indent, + String firstParamType, + Map typeUse12, + GenericType genericType) { + writeTypeUse(writer, indent, firstParamType, typeUse12, true, genericType); + } + + private static void writeTypeUse( + Append writer, + String indent, + String paramType, + Map typeUseMap, + boolean keys, + GenericType genericType) { + + for (final var a : typeUseMap.entrySet()) { + + if (Constants.VALID_ANNOTATIONS.contains(a.getKey().topType())) { + continue; + } + final var k = a.getKey().shortName(); + final var v = a.getValue(); + writer.eol().append("%s .andThenMulti(ctx.adapter(%s.class,%s))", indent, k, v); + } + + if (!Util.isBasicType(paramType) + && typeUseMap.keySet().stream() + .map(GenericType::topType) + .anyMatch(Constants.VALID_ANNOTATIONS::contains)) { + + writer + .eol() + .append( + "%s .andThenMulti(ctx.adapter(%s.class))", + indent, + Util.shortName(keys ? genericType.firstParamType() : genericType.secondParamType())); + } + } +} diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/FieldReader.java b/validator-generator/src/main/java/io/avaje/validation/generator/FieldReader.java index 7eb47976..d10938f9 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/FieldReader.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/FieldReader.java @@ -1,7 +1,6 @@ package io.avaje.validation.generator; import java.util.List; -import java.util.Map; import java.util.Set; import javax.lang.model.element.Element; @@ -158,92 +157,8 @@ public GenericType type() { public void writeConstructor(Append writer) { writer.append(" this.%s = ", adapterFieldName).eol(); - boolean first = true; - final var annotations = elementAnnotations.annotations(); - final var typeUse1 = elementAnnotations.typeUse1(); - final var typeUse2 = elementAnnotations.typeUse2(); - final var hasValid = elementAnnotations.hasValid(); - for (final var a : annotations.entrySet()) { - if (first) { - writer.append( - " ctx.<%s>adapter(%s.class, %s)", - PrimitiveUtil.wrap(genericType.shortType()), a.getKey().shortName(), a.getValue()); - first = false; - continue; - } - writer - .eol() - .append( - " .andThen(ctx.adapter(%s.class,%s))", - a.getKey().shortName(), a.getValue()); - } - final var topType = genericType.topType(); - if (Util.isBasicType(topType)) { - writer.append(";").eol(); - return; - } - - if (annotations.isEmpty()) { - writer.append(" ctx.<%s>noop()", PrimitiveUtil.wrap(genericType.shortType())); - } - - if (!typeUse1.isEmpty() - && ("java.util.List".equals(genericType.topType()) - || "java.util.Set".equals(genericType.topType()))) { - writer.eol().append(" .list()"); - final var t = genericType.firstParamType(); - writeTypeUse(writer, t, typeUse1); - - } else if ((!typeUse1.isEmpty() || !typeUse2.isEmpty()) - && "java.util.Map".equals(genericType.topType())) { - - writer.eol().append(" .mapKeys()"); - writeTypeUse(writer, genericType.firstParamType(), typeUse1); - - writer.eol().append(" .mapValues()"); - writeTypeUse(writer, genericType.secondParamType(), typeUse2, false); - - } else if (genericType.topType().contains("[]") && hasValid) { - - writer.eol().append(" .array()"); - writeTypeUse(writer, genericType.firstParamType(), typeUse1); - } else if (hasValid) { - writer - .eol() - .append( - " .andThen(ctx.adapter(%s.class))", Util.shortName(genericType.topType())); - } + AdapterHelper.writeAdapterWithValues( + writer, elementAnnotations, " ", PrimitiveUtil.wrap(genericType.shortType())); writer.append(";").eol().eol(); } - - private void writeTypeUse( - Append writer, String firstParamType, Map typeUse12) { - writeTypeUse(writer, firstParamType, typeUse12, true); - } - - private void writeTypeUse( - Append writer, String t, Map typeUseMap, boolean keys) { - - for (final var a : typeUseMap.entrySet()) { - - if (Constants.VALID_ANNOTATIONS.contains(a.getKey().topType())) { - continue; - } - final var k = a.getKey().shortName(); - final var v = a.getValue(); - writer.eol().append(" .andThenMulti(ctx.adapter(%s.class,%s))", k, v); - } - - if (!Util.isBasicType(t) - && typeUseMap.keySet().stream() - .map(GenericType::topType) - .anyMatch(Constants.VALID_ANNOTATIONS::contains)) { - - writer - .eol() - .append( - " .andThenMulti(ctx.adapter(%s.class))", - Util.shortName(keys ? genericType.firstParamType() : genericType.secondParamType())); - } - } } diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java b/validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java index 3db25ea8..12332158 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java @@ -93,7 +93,7 @@ public List> paramAdapters(ValidationContext ctx) { writer.append(" return List.of("); final var size = paramAnnotations.size(); for (int i = 0; i < paramAnnotations.size(); i++) { - writeAdapters(writer, paramAnnotations.get(i)); + AdapterHelper.writeAdapterWithValues(writer, paramAnnotations.get(i), "\n ", "Object"); if (i + 1 != size) { writer.append(","); } @@ -103,102 +103,6 @@ public List> paramAdapters(ValidationContext ctx) { writer.append(" }").eol(); } - private void writeAdapters(Append writer, final ElementAnnotationContainer params) { - final var genericType = params.genericType(); - final var paramAnnotations = params.annotations(); - final var typeUse1 = params.typeUse1(); - final var typeUse2 = params.typeUse2(); - final boolean hasValid = params.hasValid(); - - boolean first = true; - for (final var a : paramAnnotations.entrySet()) { - if (first) { - writer - .eol() - .append( - " ctx.adapter(%s.class, %s)", a.getKey().shortName(), a.getValue()); - first = false; - continue; - } - writer - .eol() - .append( - " .andThen(ctx.adapter(%s.class,%s))", - a.getKey().shortName(), a.getValue()); - } - - if (paramAnnotations.isEmpty()) { - writer - .eol() - .append(" ctx.noop()", PrimitiveUtil.wrap(genericType.shortType())); - } - - if (!typeUse1.isEmpty() - && ("java.util.List".equals(genericType.topType()) - || "java.util.Set".equals(genericType.topType()))) { - writer.eol().append(" .list()"); - final var t = genericType.firstParamType(); - writeTypeUse(writer, t, genericType, typeUse1); - - } else if ((!typeUse1.isEmpty() || !typeUse2.isEmpty()) - && "java.util.Map".equals(genericType.topType())) { - - writer.eol().append(" .mapKeys()"); - writeTypeUse(writer, genericType.firstParamType(), genericType, typeUse1); - - writer.eol().append(" .mapValues()"); - writeTypeUse(writer, genericType.secondParamType(), genericType, typeUse2, false); - - } else if (genericType.topType().contains("[]") && hasValid) { - - writer.eol().append(" .array()"); - writeTypeUse(writer, genericType.firstParamType(), genericType, typeUse1); - } else if (hasValid) { - writer - .eol() - .append( - " .andThen(ctx.adapter(%s.class))", Util.shortName(genericType.topType())); - } - } - - private void writeTypeUse( - Append writer, - String firstParamType, - GenericType genericType, - Map typeUse12) { - writeTypeUse(writer, firstParamType, genericType, typeUse12, true); - } - - private void writeTypeUse( - Append writer, - String firstParamType, - GenericType genericType, - Map typeUseMap, - boolean keys) { - - for (final var a : typeUseMap.entrySet()) { - - if (Constants.VALID_ANNOTATIONS.contains(a.getKey().topType())) { - continue; - } - final var k = a.getKey().shortName(); - final var v = a.getValue(); - writer.eol().append(" .andThenMulti(ctx.adapter(%s.class,%s))", k, v); - } - - if (!Util.isBasicType(firstParamType) - && typeUseMap.keySet().stream() - .map(GenericType::topType) - .anyMatch(Constants.VALID_ANNOTATIONS::contains)) { - - writer - .eol() - .append( - " .andThenMulti(ctx.adapter(%s.class))", - Util.shortName(keys ? genericType.firstParamType() : genericType.secondParamType())); - } - } - public ExecutableElement getBeanType() { return methodElement; } diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java b/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java index 86d9f505..fe082778 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java @@ -26,7 +26,7 @@ @SupportedAnnotationTypes({ AvajeValidPrism.PRISM_TYPE, - ImportPrism.PRISM_TYPE, + ImportValidPojoPrism.PRISM_TYPE, HttpValidPrism.PRISM_TYPE, JavaxValidPrism.PRISM_TYPE, JakartaValidPrism.PRISM_TYPE, @@ -86,8 +86,9 @@ public boolean process(Set annotations, RoundEnvironment registerCustomAdapters( round.getElementsAnnotatedWith(element(AnnotationValidatorPrism.PRISM_TYPE))); - writeAdapters(round.getElementsAnnotatedWith(element(AvajeValidPrism.PRISM_TYPE))); - + Optional.ofNullable(element(AvajeValidPrism.PRISM_TYPE)) + .map(round::getElementsAnnotatedWith) + .ifPresent(this::writeAdapters); Optional.ofNullable(element(HttpValidPrism.PRISM_TYPE)) .map(round::getElementsAnnotatedWith) .ifPresent(this::writeAdapters); @@ -103,7 +104,8 @@ public boolean process(Set annotations, RoundEnvironment .map(ElementFilter::methodsIn) .ifPresent(this::writeParamProviderForMethod); - writeAdaptersForImported(round.getElementsAnnotatedWith(element(ImportPrism.PRISM_TYPE))); + writeAdaptersForImported( + round.getElementsAnnotatedWith(element(ImportValidPojoPrism.PRISM_TYPE))); initialiseComponent(); cascadeTypes(); initialiseComponent(); @@ -159,7 +161,8 @@ private boolean ignoreType(String type) { /** Elements that have a {@code @Valid.Import} annotation. */ private void writeAdaptersForImported(Set importedElements) { for (final var importedElement : ElementFilter.typesIn(importedElements)) { - for (final TypeMirror importType : ImportPrism.getInstanceOn(importedElement).value()) { + for (final TypeMirror importType : + ImportValidPojoPrism.getInstanceOn(importedElement).value()) { // if imported by mixin annotation skip if (mixInImports.contains(importType.toString())) { continue; diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/package-info.java b/validator-generator/src/main/java/io/avaje/validation/generator/package-info.java index df9534d3..4409019c 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/package-info.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/package-info.java @@ -1,8 +1,8 @@ @GeneratePrism( - value = io.avaje.validation.Valid.class, + value = io.avaje.validation.constraints.Valid.class, name = "AvajeValidPrism", superInterfaces = ValidPrism.class) -@GeneratePrism(io.avaje.validation.Valid.Import.class) +@GeneratePrism(io.avaje.validation.ImportValidPojo.class) @GeneratePrism(io.avaje.validation.adapter.AnnotationValidator.class) @GeneratePrism( value = javax.validation.Valid.class, diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/AMyNumbers.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/AMyNumbers.java index c8d5fb05..2bc74329 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/AMyNumbers.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/AMyNumbers.java @@ -1,7 +1,7 @@ package io.avaje.validation.generator.models.valid; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.DecimalMax; +import io.avaje.validation.constraints.Valid; import java.math.BigDecimal; diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java index f7bff1df..3c902e0f 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java @@ -1,6 +1,6 @@ package io.avaje.validation.generator.models.valid; -import io.avaje.validation.Valid; +import io.avaje.validation.constraints.Valid; @Valid public class Contact { diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Customer.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Customer.java index 74ba9e7f..9a4ada3c 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Customer.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Customer.java @@ -5,7 +5,7 @@ import java.util.List; import io.avaje.lang.Nullable; -import io.avaje.validation.Valid; +import io.avaje.validation.constraints.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Polus.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Polus.java index 177785b3..c302ac83 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Polus.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Polus.java @@ -3,9 +3,9 @@ import java.util.List; import java.util.Map; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.NotBlank; import io.avaje.validation.constraints.NotNull; +import io.avaje.validation.constraints.Valid; @Valid public record Polus( diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Ship.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Ship.java index bc1f3091..4aacd055 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Ship.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Ship.java @@ -3,9 +3,9 @@ import java.util.List; import java.util.Map; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.NotBlank; import io.avaje.validation.constraints.NotEmpty; +import io.avaje.validation.constraints.Valid; @Valid public record Ship( diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java index 87131e8f..ef8457fc 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java @@ -3,9 +3,9 @@ import java.util.List; import io.avaje.inject.Component; -import io.avaje.validation.Valid; import io.avaje.validation.constraints.NotEmpty; import io.avaje.validation.constraints.Positive; +import io.avaje.validation.constraints.Valid; import io.avaje.validation.generator.models.valid.CrewMate; import io.avaje.validation.inject.aspect.ValidateParams; diff --git a/validator/src/main/java/io/avaje/validation/ImportValidPojo.java b/validator/src/main/java/io/avaje/validation/ImportValidPojo.java new file mode 100644 index 00000000..c999a3d0 --- /dev/null +++ b/validator/src/main/java/io/avaje/validation/ImportValidPojo.java @@ -0,0 +1,10 @@ +package io.avaje.validation; + +/** + * Specify types to generate Valid Adapters for. Use if you can't place a @Valid annotation on an + * external type. + */ +public @interface ImportValidPojo { + + Class[] value(); +} diff --git a/validator/src/main/java/io/avaje/validation/adapter/AbstractMultiAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java similarity index 84% rename from validator/src/main/java/io/avaje/validation/adapter/AbstractMultiAdapter.java rename to validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java index 0a98bdd1..96dcde2e 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/AbstractMultiAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java @@ -2,16 +2,16 @@ import java.util.Collection; -public abstract class AbstractMultiAdapter implements ValidationAdapter { +public abstract class AbstractContainerAdapter implements ValidationAdapter { protected final ValidationAdapter starterAdapter; private ValidationAdapter adapters; - protected AbstractMultiAdapter(ValidationAdapter starterAdapter) { + protected AbstractContainerAdapter(ValidationAdapter starterAdapter) { this.starterAdapter = starterAdapter; } - public AbstractMultiAdapter andThenMulti(ValidationAdapter adapter) { + public AbstractContainerAdapter andThenMulti(ValidationAdapter adapter) { this.adapters = this.adapters != null ? adapters.andThen((ValidationAdapter) adapter) diff --git a/validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java index a439bcf9..73823697 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java @@ -1,6 +1,6 @@ package io.avaje.validation.adapter; -class ArrayValidationAdapter extends AbstractMultiAdapter { +class ArrayValidationAdapter extends AbstractContainerAdapter { ArrayValidationAdapter(ValidationAdapter adapters) { super(adapters); diff --git a/validator/src/main/java/io/avaje/validation/adapter/CollectionValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/CollectionValidationAdapter.java index 1866eeee..c335f749 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/CollectionValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/CollectionValidationAdapter.java @@ -2,7 +2,7 @@ import java.util.Collection; -class CollectionValidationAdapter extends AbstractMultiAdapter { +class CollectionValidationAdapter extends AbstractContainerAdapter { CollectionValidationAdapter(ValidationAdapter adapters) { super(adapters); diff --git a/validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java index 0343b545..14619154 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java @@ -2,7 +2,7 @@ import java.util.Map; -class MapValidationAdapter extends AbstractMultiAdapter { +class MapValidationAdapter extends AbstractContainerAdapter { private final boolean keys; diff --git a/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java new file mode 100644 index 00000000..71039cb1 --- /dev/null +++ b/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java @@ -0,0 +1,28 @@ +package io.avaje.validation.adapter; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +class OptionalValidationAdapter extends AbstractContainerAdapter { + + OptionalValidationAdapter(ValidationAdapter adapters) { + super(adapters); + } + + @Override + @SuppressWarnings("unchecked") + public boolean validate(T value, ValidationRequest req, String propertyName) { + if (value instanceof final Optional o) { + o.ifPresent(v -> starterAdapter.validate((T) v, req, propertyName)); + } else if (value instanceof final OptionalInt i) { + i.ifPresent(v -> starterAdapter.validate(((T) (Object) v), req, propertyName)); + } else if (value instanceof final OptionalLong l) { + l.ifPresent(v -> starterAdapter.validate(((T) (Object) v), req, propertyName)); + } else if (value instanceof final OptionalDouble d) { + d.ifPresent(v -> starterAdapter.validate(((T) (Object) v), req, propertyName)); + } + return true; + } +} diff --git a/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java index 2da70688..f147eccb 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java @@ -13,23 +13,27 @@ default boolean validate(T value, ValidationRequest req) { return validate(value, req, null); } - default AbstractMultiAdapter list() { + default AbstractContainerAdapter list() { return new CollectionValidationAdapter<>(this); } - default AbstractMultiAdapter mapKeys() { + default AbstractContainerAdapter mapKeys() { return new MapValidationAdapter<>(this, true); } - default AbstractMultiAdapter mapValues() { + default AbstractContainerAdapter mapValues() { return new MapValidationAdapter<>(this, false); } - default AbstractMultiAdapter array() { + default AbstractContainerAdapter array() { return new ArrayValidationAdapter<>(this); } + default AbstractContainerAdapter optional() { + return new OptionalValidationAdapter<>(this); + } + default ValidationAdapter andThen(ValidationAdapter after) { Objects.requireNonNull(after); return (value, req, propertyName) -> { diff --git a/validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java b/validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java index 223e09cb..f2c78930 100644 --- a/validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java +++ b/validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java @@ -2,6 +2,8 @@ import java.util.Map; +import io.avaje.validation.spi.MessageInterpolator; + final class BasicMessageInterpolator implements MessageInterpolator { @Override diff --git a/validator/src/main/java/io/avaje/validation/core/DValidationType.java b/validator/src/main/java/io/avaje/validation/core/DValidationType.java deleted file mode 100644 index d77b2088..00000000 --- a/validator/src/main/java/io/avaje/validation/core/DValidationType.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.avaje.validation.core; - -import java.util.List; -import java.util.Locale; - -import io.avaje.validation.adapter.ValidationAdapter; -import io.avaje.validation.adapter.ValidationContext; - -final class DValidationType implements ValidationType { - - private final ValidationContext ctx; - private final ValidationAdapter adapter; - - DValidationType(ValidationContext validator, ValidationAdapter adapter) { - this.ctx = validator; - this.adapter = adapter; - } - - @Override - public void validate(T object, Locale locale, List> groups) { - final var req = ctx.request(locale, groups); - adapter.validate(object, req); - req.throwWithViolations(); - } - -} diff --git a/validator/src/main/java/io/avaje/validation/core/DValidator.java b/validator/src/main/java/io/avaje/validation/core/DValidator.java index 53061aa9..e15d7c30 100644 --- a/validator/src/main/java/io/avaje/validation/core/DValidator.java +++ b/validator/src/main/java/io/avaje/validation/core/DValidator.java @@ -28,12 +28,13 @@ import io.avaje.validation.adapter.ValidationContext; import io.avaje.validation.adapter.ValidationRequest; import io.avaje.validation.adapter.ValidatorComponent; +import io.avaje.validation.spi.MessageInterpolator; /** Default implementation of Validator. */ final class DValidator implements Validator, ValidationContext { private final CoreAdapterBuilder builder; - private final Map> typeCache = new ConcurrentHashMap<>(); + private final Map> typeCache = new ConcurrentHashMap<>(); private final MessageInterpolator interpolator; private final LocaleResolver localeResolver; private final DTemplateLookup templateLookup; @@ -83,7 +84,7 @@ private ValidationType type(Class cls) { @SuppressWarnings("unchecked") private ValidationType typeWithCache(Type type) { - return (ValidationType) typeCache.computeIfAbsent(type, _type -> new DValidationType<>(this, adapter(_type))); + return (ValidationType) typeCache.computeIfAbsent(type, _type -> new ValidationType<>(this, adapter(_type))); } @Override diff --git a/validator/src/main/java/io/avaje/validation/core/ValidationType.java b/validator/src/main/java/io/avaje/validation/core/ValidationType.java index 477b1c0a..a937cd6f 100644 --- a/validator/src/main/java/io/avaje/validation/core/ValidationType.java +++ b/validator/src/main/java/io/avaje/validation/core/ValidationType.java @@ -1,13 +1,27 @@ package io.avaje.validation.core; -import io.avaje.validation.ConstraintViolationException; -import io.avaje.lang.Nullable; - import java.util.List; import java.util.Locale; -public interface ValidationType { +import io.avaje.lang.Nullable; +import io.avaje.validation.ConstraintViolationException; +import io.avaje.validation.adapter.ValidationAdapter; +import io.avaje.validation.adapter.ValidationContext; + +final class ValidationType { + + private final ValidationContext ctx; + private final ValidationAdapter adapter; + + public ValidationType(ValidationContext validator, ValidationAdapter adapter) { + this.ctx = validator; + this.adapter = adapter; + } - void validate(T object, @Nullable Locale locale, List> groups) - throws ConstraintViolationException; + public void validate(T object, @Nullable Locale locale, List> groups) + throws ConstraintViolationException { + final var req = ctx.request(locale, groups); + adapter.validate(object, req); + req.throwWithViolations(); + } } diff --git a/validator/src/main/java/io/avaje/validation/core/MessageInterpolator.java b/validator/src/main/java/io/avaje/validation/spi/MessageInterpolator.java similarity index 80% rename from validator/src/main/java/io/avaje/validation/core/MessageInterpolator.java rename to validator/src/main/java/io/avaje/validation/spi/MessageInterpolator.java index 6a514f59..93061e01 100644 --- a/validator/src/main/java/io/avaje/validation/core/MessageInterpolator.java +++ b/validator/src/main/java/io/avaje/validation/spi/MessageInterpolator.java @@ -1,4 +1,4 @@ -package io.avaje.validation.core; +package io.avaje.validation.spi; import java.util.Map; diff --git a/validator/src/main/java/module-info.java b/validator/src/main/java/module-info.java index 1e99fe7d..a37130a8 100644 --- a/validator/src/main/java/module-info.java +++ b/validator/src/main/java/module-info.java @@ -2,13 +2,12 @@ exports io.avaje.validation; exports io.avaje.validation.adapter; exports io.avaje.validation.spi; - exports io.avaje.validation.core; requires io.avaje.lang; uses io.avaje.validation.spi.Bootstrap; uses io.avaje.validation.Validator.GeneratedComponent; - uses io.avaje.validation.core.MessageInterpolator; + uses io.avaje.validation.spi.MessageInterpolator; uses io.avaje.validation.adapter.ValidatorComponent; uses io.avaje.validation.adapter.ValidationContext.AdapterFactory; uses io.avaje.validation.adapter.ValidationContext.AnnotationFactory; diff --git a/validator/src/test/java/io/avaje/validation/core/Address.java b/validator/src/test/java/io/avaje/validation/core/Address.java index 76a3f280..7eb33fd1 100644 --- a/validator/src/test/java/io/avaje/validation/core/Address.java +++ b/validator/src/test/java/io/avaje/validation/core/Address.java @@ -1,8 +1,6 @@ package io.avaje.validation.core; -import io.avaje.validation.Valid; - -@Valid +//@Valid public class Address { public String line1; diff --git a/validator/src/test/java/io/avaje/validation/core/Contact.java b/validator/src/test/java/io/avaje/validation/core/Contact.java index 4633883d..e78ae624 100644 --- a/validator/src/test/java/io/avaje/validation/core/Contact.java +++ b/validator/src/test/java/io/avaje/validation/core/Contact.java @@ -1,11 +1,9 @@ package io.avaje.validation.core; -import io.avaje.validation.Valid; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -@Valid +//@Valid public class Contact { @NotBlank(groups = BasicTest.class) public String firstName; diff --git a/validator/src/test/java/io/avaje/validation/core/Customer.java b/validator/src/test/java/io/avaje/validation/core/Customer.java index e499199d..1de90129 100644 --- a/validator/src/test/java/io/avaje/validation/core/Customer.java +++ b/validator/src/test/java/io/avaje/validation/core/Customer.java @@ -1,13 +1,12 @@ package io.avaje.validation.core; -import io.avaje.validation.Valid; -import jakarta.validation.constraints.Size; - import java.time.LocalDate; import java.util.ArrayList; import java.util.List; -@Valid +import jakarta.validation.constraints.Size; + +//@Valid public class Customer { boolean active; String name = ""; From ada63199c180d1e7af3321abc2cf716493fb77b0 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 7 Jul 2023 21:50:31 -0400 Subject: [PATCH 2/8] Update OptionalValidationAdapter.java --- .../avaje/validation/adapter/OptionalValidationAdapter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java index 71039cb1..cda8e7b0 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java @@ -14,7 +14,9 @@ class OptionalValidationAdapter extends AbstractContainerAdapter { @Override @SuppressWarnings("unchecked") public boolean validate(T value, ValidationRequest req, String propertyName) { - if (value instanceof final Optional o) { + if (value == null) { + return true; + } else if (value instanceof final Optional o) { o.ifPresent(v -> starterAdapter.validate((T) v, req, propertyName)); } else if (value instanceof final OptionalInt i) { i.ifPresent(v -> starterAdapter.validate(((T) (Object) v), req, propertyName)); From ceb5eb36d6fc0fe7239bfde822fdb351cd21eaad Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 7 Jul 2023 22:19:12 -0400 Subject: [PATCH 3/8] method return type validation --- .../java/example/avaje/method/MethodTest.java | 2 +- .../validation/generator/ValidMethodReader.java | 16 +++++++++++++++- .../generator/ValidationProcessor.java | 6 +++--- .../avaje/validation/generator/package-info.java | 2 +- .../generator/models/valid/Contact.java | 4 +++- .../models/valid/methods/MethodTest.java | 10 ++++++---- .../inject/aspect/AOPMethodValidator.java | 4 ++-- .../{ValidateParams.java => ValidateMethod.java} | 3 ++- .../inject/aspect/MethodTest$Proxy.java | 4 ++-- .../validation/inject/aspect/MethodTest.java | 2 +- 10 files changed, 36 insertions(+), 17 deletions(-) rename validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/{ValidateParams.java => ValidateMethod.java} (74%) diff --git a/blackbox-test/src/main/java/example/avaje/method/MethodTest.java b/blackbox-test/src/main/java/example/avaje/method/MethodTest.java index ddcb22d9..2ce7ff27 100644 --- a/blackbox-test/src/main/java/example/avaje/method/MethodTest.java +++ b/blackbox-test/src/main/java/example/avaje/method/MethodTest.java @@ -11,6 +11,6 @@ @Singleton public class MethodTest { - @ValidateParams + @ValidateMethod void test(@NotEmpty List<@NotNull String> str, @Positive int inty, String regular) {} } diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java b/validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java index 12332158..61f6c062 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/ValidMethodReader.java @@ -17,6 +17,7 @@ final class ValidMethodReader { private final Set importTypes = new TreeSet<>(); private final List params; private final List paramAnnotations; + private final ElementAnnotationContainer returnElementAnnotation; ValidMethodReader(ExecutableElement element) { this.methodElement = element; @@ -33,6 +34,7 @@ final class ValidMethodReader { importTypes.add("io.avaje.validation.spi.Generated"); importTypes.add("java.lang.reflect.Method"); paramAnnotations = params.stream().map(ElementAnnotationContainer::create).toList(); + returnElementAnnotation = ElementAnnotationContainer.create(element); } public String shortName() { @@ -45,6 +47,7 @@ private Set importTypes() { } paramAnnotations.forEach(a -> a.addImports(importTypes)); + returnElementAnnotation.addImports(importTypes); return importTypes; } @@ -99,7 +102,18 @@ public List> paramAdapters(ValidationContext ctx) { } } - writer.append(");").eol(); + writer.append( + """ + ); + } + + @Override + public ValidationAdapter returnAdapter(ValidationContext ctx) { + """); + writer.append(" return "); + AdapterHelper.writeAdapterWithValues(writer, returnElementAnnotation, "", "Object"); + + writer.append(";").eol(); writer.append(" }").eol(); } diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java b/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java index fe082778..510dc424 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java @@ -34,7 +34,7 @@ AvajeConstraintPrism.PRISM_TYPE, JakartaConstraintPrism.PRISM_TYPE, JavaxConstraintPrism.PRISM_TYPE, - ValidateParamsPrism.PRISM_TYPE + ValidateMethodPrism.PRISM_TYPE }) public final class ValidationProcessor extends AbstractProcessor { @@ -248,14 +248,14 @@ private void writeAdapter(TypeElement typeElement, BeanReader beanReader) { private void writeParamProviderForMethod(Set elements) { if (element(ComponentPrism.PRISM_TYPE) == null) { - throw new IllegalStateException("ValidateParams can only be used with Avaje Inject Beans"); + throw new IllegalStateException("ValidateMethod can only be used with Avaje Inject Beans"); } for (final ExecutableElement executableElement : elements) { if (executableElement.getEnclosingElement().getAnnotationMirrors().stream() .map(m -> m.getAnnotationType().toString()) .noneMatch(s -> s.contains("Singleton") || s.contains("Component"))) { - throw new IllegalStateException("ValidateParams can only be used with Avaje Inject Beans"); + throw new IllegalStateException("ValidateMethod can only be used with Avaje Inject Beans"); } writeParamProvider(executableElement); } diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/package-info.java b/validator-generator/src/main/java/io/avaje/validation/generator/package-info.java index 4409019c..04a2d231 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/package-info.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/package-info.java @@ -19,7 +19,7 @@ @GeneratePrism(io.avaje.validation.spi.MetaData.class) @GeneratePrism(io.avaje.validation.spi.MetaData.Factory.class) @GeneratePrism(io.avaje.validation.spi.MetaData.AnnotationFactory.class) -@GeneratePrism(io.avaje.validation.inject.aspect.ValidateParams.class) +@GeneratePrism(io.avaje.validation.inject.aspect.ValidateMethod.class) @GeneratePrism(io.avaje.inject.Component.class) package io.avaje.validation.generator; diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java index 3c902e0f..f7e30186 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java @@ -1,5 +1,7 @@ package io.avaje.validation.generator.models.valid; +import java.util.Optional; + import io.avaje.validation.constraints.Valid; @Valid @@ -7,7 +9,7 @@ public class Contact { public String firstName; public String lastName; - public Address address; + public Optional
address; public Contact() { this.firstName = "fn"; diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java index ef8457fc..1753f6d7 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java @@ -7,11 +7,13 @@ import io.avaje.validation.constraints.Positive; import io.avaje.validation.constraints.Valid; import io.avaje.validation.generator.models.valid.CrewMate; -import io.avaje.validation.inject.aspect.ValidateParams; +import io.avaje.validation.inject.aspect.ValidateMethod; @Component public class MethodTest { - - @ValidateParams - void test(@NotEmpty List<@Valid CrewMate> crew, @Positive int inty, String regular) {} + @NotEmpty + @ValidateMethod + String test(@NotEmpty List<@Valid CrewMate> crew, @Positive int inty, String regular) { + return regular; + } } diff --git a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java index ec0cd011..9b397749 100644 --- a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java +++ b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java @@ -14,7 +14,7 @@ import io.avaje.validation.adapter.ValidationContext; // Imported into scope -public class AOPMethodValidator implements AspectProvider { +public class AOPMethodValidator implements AspectProvider { final ValidationContext ctx; private final Map paramAdapters; @@ -26,7 +26,7 @@ public AOPMethodValidator(Validator validator, List adapt } @Override - public MethodInterceptor interceptor(Method method, ValidateParams aspectAnnotation) { + public MethodInterceptor interceptor(Method method, ValidateMethod aspectAnnotation) { final var localeStr = aspectAnnotation.locale(); final Locale locale; diff --git a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateParams.java b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateMethod.java similarity index 74% rename from validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateParams.java rename to validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateMethod.java index e2c29c42..d0271fea 100644 --- a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateParams.java +++ b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateMethod.java @@ -11,6 +11,7 @@ @Aspect @Target(METHOD) @Retention(RUNTIME) -public @interface ValidateParams { +/** Place on a method to execute validations on the parameters and return types */ +public @interface ValidateMethod { String locale() default ""; } diff --git a/validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest$Proxy.java b/validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest$Proxy.java index 15fb2b31..2ae5b7bf 100644 --- a/validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest$Proxy.java +++ b/validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest$Proxy.java @@ -13,11 +13,11 @@ public final class MethodTest$Proxy extends MethodTest { private final Method test0; private final MethodInterceptor test0ValidateParams; - public MethodTest$Proxy(AspectProvider validateParams) { + public MethodTest$Proxy(AspectProvider validateParams) { try { test0 = MethodTest.class.getDeclaredMethod("test", List.class, int.class, String.class); test0ValidateParams = - validateParams.interceptor(test0, test0.getAnnotation(ValidateParams.class)); + validateParams.interceptor(test0, test0.getAnnotation(ValidateMethod.class)); } catch (final Exception e) { throw new IllegalStateException(e); diff --git a/validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest.java b/validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest.java index 65991dea..be7afcda 100644 --- a/validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest.java +++ b/validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest.java @@ -10,6 +10,6 @@ @Singleton public class MethodTest { - @ValidateParams + @ValidateMethod public void test(@NotEmpty List<@NotNull String> str, @Positive int inty, String regular) {} } From 9416c5de0d4cb4cd097cab1b350f1d52e7bdec1c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 7 Jul 2023 23:07:20 -0400 Subject: [PATCH 4/8] tests --- .../java/example/avaje/method/MethodTest.java | 8 +- .../example/avaje/optional/CurseBearer.java | 17 +++++ .../example/avaje/method/MethodTestTest.java | 16 +++- .../example/avaje/optional/OptionalTest.java | 73 +++++++++++++++++++ .../generator/ValidationProcessor.java | 2 +- .../generator/models/valid/Contact.java | 22 +++--- .../inject/aspect/AOPMethodValidator.java | 1 - 7 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 blackbox-test/src/main/java/example/avaje/optional/CurseBearer.java create mode 100644 blackbox-test/src/test/java/example/avaje/optional/OptionalTest.java diff --git a/blackbox-test/src/main/java/example/avaje/method/MethodTest.java b/blackbox-test/src/main/java/example/avaje/method/MethodTest.java index 2ce7ff27..eb067691 100644 --- a/blackbox-test/src/main/java/example/avaje/method/MethodTest.java +++ b/blackbox-test/src/main/java/example/avaje/method/MethodTest.java @@ -2,7 +2,7 @@ import java.util.List; -import io.avaje.validation.inject.aspect.ValidateParams; +import io.avaje.validation.inject.aspect.ValidateMethod; import jakarta.inject.Singleton; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -10,7 +10,9 @@ @Singleton public class MethodTest { - + @NotNull @ValidateMethod - void test(@NotEmpty List<@NotNull String> str, @Positive int inty, String regular) {} + String test(@NotEmpty List<@NotNull String> str, @Positive int inty, String regular) { + return regular; + } } diff --git a/blackbox-test/src/main/java/example/avaje/optional/CurseBearer.java b/blackbox-test/src/main/java/example/avaje/optional/CurseBearer.java new file mode 100644 index 00000000..93201bf9 --- /dev/null +++ b/blackbox-test/src/main/java/example/avaje/optional/CurseBearer.java @@ -0,0 +1,17 @@ +package example.avaje.optional; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import io.avaje.validation.constraints.NotBlank; +import io.avaje.validation.constraints.Positive; +import io.avaje.validation.constraints.Valid; + +@Valid +public record CurseBearer( + @NotBlank(message = "it'll happen to you too") Optional name, + @Positive OptionalInt estus, + @Positive(message = "You Died") OptionalLong souls, + @Positive(message = "you didn't pass the vigor check") OptionalDouble vigor) {} diff --git a/blackbox-test/src/test/java/example/avaje/method/MethodTestTest.java b/blackbox-test/src/test/java/example/avaje/method/MethodTestTest.java index 1bd96966..189728bb 100644 --- a/blackbox-test/src/test/java/example/avaje/method/MethodTestTest.java +++ b/blackbox-test/src/test/java/example/avaje/method/MethodTestTest.java @@ -1,7 +1,8 @@ package example.avaje.method; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; import java.util.List; @@ -18,12 +19,19 @@ class MethodTestTest { @Test void test() { - assertThatNoException().isThrownBy(() -> proxy.test(List.of(""), 1, null)); + assertThatNoException().isThrownBy(() -> proxy.test(List.of(""), 1, "result")); } @Test void invalid() { - assertThatThrownBy(() -> proxy.test(null, 0, null)) - .isInstanceOf(ConstraintViolationException.class); + try { + + proxy.test(List.of(), 0, null); + fail("how???"); + } catch (final ConstraintViolationException e) { + final var violations = e.violations(); + + assertThat(violations).hasSize(3); + } } } diff --git a/blackbox-test/src/test/java/example/avaje/optional/OptionalTest.java b/blackbox-test/src/test/java/example/avaje/optional/OptionalTest.java new file mode 100644 index 00000000..df363bc3 --- /dev/null +++ b/blackbox-test/src/test/java/example/avaje/optional/OptionalTest.java @@ -0,0 +1,73 @@ +package example.avaje.optional; + +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import example.avaje.composable.Sans; +import io.avaje.validation.ConstraintViolation; +import io.avaje.validation.ConstraintViolationException; +import io.avaje.validation.Validator; + +class OptionalTest { + + final Validator validator = Validator.builder().build(); + + @Test + void valid() { + final var monarch = + new CurseBearer( + Optional.of("Belmont"), + OptionalInt.of(5), + OptionalLong.of(10000), + OptionalDouble.of(42.0)); + validator.validate(monarch); + } + + @Test + void validEmpty() { + final var hollow = + new CurseBearer( + Optional.empty(), OptionalInt.empty(), OptionalLong.empty(), OptionalDouble.empty()); + validator.validate(hollow); + } + + @Test + void validNull() { + final var hollow = new CurseBearer(null, null, null, null); + validator.validate(hollow); + } + + @Test + void invalid() { + final var violations = + violations( + new CurseBearer( + Optional.of(""), OptionalInt.of(0), OptionalLong.of(0), OptionalDouble.of(0))); + assertThat(violations) + .contains( + "it'll happen to you too", + "must be greater than 0", + "You Died", + "you didn't pass the vigor check"); + } + + Set violations(Object any) { + try { + validator.validate(any); + fail("not expected"); + return null; + } catch (final ConstraintViolationException e) { + return e.violations().stream().map(ConstraintViolation::message).collect(toSet()); + } + } +} diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java b/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java index 510dc424..f9c15a49 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/ValidationProcessor.java @@ -99,7 +99,7 @@ public boolean process(Set annotations, RoundEnvironment .map(round::getElementsAnnotatedWith) .ifPresent(this::writeAdapters); - Optional.ofNullable(element(ValidateParamsPrism.PRISM_TYPE)) + Optional.ofNullable(element(ValidateMethodPrism.PRISM_TYPE)) .map(round::getElementsAnnotatedWith) .map(ElementFilter::methodsIn) .ifPresent(this::writeParamProviderForMethod); diff --git a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java index f7e30186..066b7938 100644 --- a/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java +++ b/validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java @@ -6,18 +6,18 @@ @Valid public class Contact { - public String firstName; - public String lastName; + public String firstName; + public String lastName; - public Optional
address; + public Optional
address; - public Contact() { - this.firstName = "fn"; - this.lastName = "ln"; - } - public Contact(String firstName, String lastName) { - this.firstName = firstName; - this.lastName = lastName; - } + public Contact() { + this.firstName = "fn"; + this.lastName = "ln"; + } + public Contact(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } } diff --git a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java index 9b397749..0ddbf55f 100644 --- a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java +++ b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java @@ -10,7 +10,6 @@ import io.avaje.inject.aop.AspectProvider; import io.avaje.inject.aop.MethodInterceptor; import io.avaje.validation.Validator; -import io.avaje.validation.adapter.ValidationAdapter; import io.avaje.validation.adapter.ValidationContext; // Imported into scope From c0e8d6e9a11a268a7ebda762f6c36e7fee2a65ea Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 7 Jul 2023 23:16:20 -0400 Subject: [PATCH 5/8] throw on param failure --- .../validation/inject/aspect/AOPMethodValidator.java | 3 ++- .../validation/inject/aspect/ParamInterceptor.java | 12 +++++++++++- .../validation/inject/aspect/ValidateMethod.java | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java index 0ddbf55f..97a1c6ee 100644 --- a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java +++ b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java @@ -34,6 +34,7 @@ public MethodInterceptor interceptor(Method method, ValidateMethod aspectAnnotat } else { locale = Locale.forLanguageTag(localeStr); } - return new ParamInterceptor(locale, ctx, paramAdapters.get(method)); + return new ParamInterceptor( + locale, ctx, paramAdapters.get(method), aspectAnnotation.throwOnParamFailure()); } } diff --git a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ParamInterceptor.java b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ParamInterceptor.java index a9134d7d..b70443a2 100644 --- a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ParamInterceptor.java +++ b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ParamInterceptor.java @@ -14,14 +14,19 @@ public class ParamInterceptor implements MethodInterceptor { private final ValidationAdapter returnValidationAdapter; private final ValidationContext ctx; private final Locale locale; + private final boolean throwOnParamFailure; public ParamInterceptor( - Locale locale, ValidationContext ctx, MethodAdapterProvider methodAdapterProvider) { + Locale locale, + ValidationContext ctx, + MethodAdapterProvider methodAdapterProvider, + boolean throwOnParamFailure) { this.locale = locale; this.ctx = ctx; this.paramValidationAdapter = methodAdapterProvider.paramAdapters(ctx); this.returnValidationAdapter = methodAdapterProvider.returnAdapter(ctx); + this.throwOnParamFailure = throwOnParamFailure; } @Override @@ -36,7 +41,12 @@ public void invoke(Invocation invocation) throws Throwable { ++i; } + if (throwOnParamFailure) { + req.throwWithViolations(); + } + returnValidationAdapter.validate(invocation.invoke(), req); + req.throwWithViolations(); } } diff --git a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateMethod.java b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateMethod.java index d0271fea..61daf92c 100644 --- a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateMethod.java +++ b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/ValidateMethod.java @@ -14,4 +14,6 @@ /** Place on a method to execute validations on the parameters and return types */ public @interface ValidateMethod { String locale() default ""; + + boolean throwOnParamFailure() default false; } From f3435bc74dff47f1376cbe54e2d7921ca8a6561a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 7 Jul 2023 23:52:34 -0400 Subject: [PATCH 6/8] can validate any subtype of iterable --- .../validation/generator/AdapterHelper.java | 4 ++-- .../validation/generator/ProcessingContext.java | 16 ++++++++++++++++ .../adapter/AbstractContainerAdapter.java | 4 +--- ...apter.java => IterableValidationAdapter.java} | 8 +++----- .../validation/adapter/ValidationAdapter.java | 2 +- 5 files changed, 23 insertions(+), 11 deletions(-) rename validator/src/main/java/io/avaje/validation/adapter/{CollectionValidationAdapter.java => IterableValidationAdapter.java} (53%) diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java b/validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java index d4b5975a..615d815e 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/AdapterHelper.java @@ -1,6 +1,7 @@ package io.avaje.validation.generator; import java.util.Map; +import static io.avaje.validation.generator.ProcessingContext.isAssignable2Interface; public class AdapterHelper { @@ -31,8 +32,7 @@ static void writeAdapterWithValues( } if (!typeUse1.isEmpty() - && ("java.util.List".equals(genericType.topType()) - || "java.util.Set".equals(genericType.topType()))) { + && (isAssignable2Interface(genericType.topType(), "java.lang.Iterable"))) { writer.eol().append("%s .list()", indent); final var t = genericType.firstParamType(); writeTypeUse(writer, indent, t, typeUse1, genericType); diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/ProcessingContext.java b/validator-generator/src/main/java/io/avaje/validation/generator/ProcessingContext.java index c2327da0..fa433465 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/ProcessingContext.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/ProcessingContext.java @@ -1,6 +1,8 @@ package io.avaje.validation.generator; import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; @@ -51,6 +53,20 @@ static int jdkVersion() { return CTX.get().jdkVersion; } + static boolean isAssignable2Interface(String type, String superType) { + return type.equals(superType) + || superTypes(element(type)).stream().anyMatch(t -> t.toString().equals(superType)); + } + + public static List superTypes(Element element) { + final Types types = CTX.get().types; + return types.directSupertypes(element.asType()).stream() + .filter(type -> !type.toString().contains("java.lang.Object")) + .map(superType -> (TypeElement) types.asElement(superType)) + .flatMap(e -> Stream.concat(superTypes(e).stream(), Stream.of(e))) + .toList(); + } + /** Log an error message. */ static void logError(Element e, String msg, Object... args) { CTX.get().messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e); diff --git a/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java index 96dcde2e..908eca04 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java @@ -1,7 +1,5 @@ package io.avaje.validation.adapter; -import java.util.Collection; - public abstract class AbstractContainerAdapter implements ValidationAdapter { protected final ValidationAdapter starterAdapter; @@ -20,7 +18,7 @@ public AbstractContainerAdapter andThenMulti(ValidationAdapter adapter) { } protected boolean validateAll( - Collection value, ValidationRequest req, String propertyName) { + Iterable value, ValidationRequest req, String propertyName) { if (value == null) { return true; } diff --git a/validator/src/main/java/io/avaje/validation/adapter/CollectionValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/IterableValidationAdapter.java similarity index 53% rename from validator/src/main/java/io/avaje/validation/adapter/CollectionValidationAdapter.java rename to validator/src/main/java/io/avaje/validation/adapter/IterableValidationAdapter.java index c335f749..e8ec8190 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/CollectionValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/IterableValidationAdapter.java @@ -1,10 +1,8 @@ package io.avaje.validation.adapter; -import java.util.Collection; +class IterableValidationAdapter extends AbstractContainerAdapter { -class CollectionValidationAdapter extends AbstractContainerAdapter { - - CollectionValidationAdapter(ValidationAdapter adapters) { + IterableValidationAdapter(ValidationAdapter adapters) { super(adapters); } @@ -12,7 +10,7 @@ class CollectionValidationAdapter extends AbstractContainerAdapter { @SuppressWarnings("unchecked") public boolean validate(T value, ValidationRequest req, String propertyName) { if (starterAdapter.validate(value, req, propertyName)) { - return validateAll((Collection) value, req, propertyName); + return validateAll((Iterable) value, req, propertyName); } return true; diff --git a/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java index f147eccb..257b7eeb 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java @@ -15,7 +15,7 @@ default boolean validate(T value, ValidationRequest req) { default AbstractContainerAdapter list() { - return new CollectionValidationAdapter<>(this); + return new IterableValidationAdapter<>(this); } default AbstractContainerAdapter mapKeys() { From 247014935bdfef3d58b5b4450740fb9fde3e85f6 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 8 Jul 2023 16:21:17 -0400 Subject: [PATCH 7/8] Update ProcessingContext.java --- .../validation/generator/ProcessingContext.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/validator-generator/src/main/java/io/avaje/validation/generator/ProcessingContext.java b/validator-generator/src/main/java/io/avaje/validation/generator/ProcessingContext.java index fa433465..d666b79f 100644 --- a/validator-generator/src/main/java/io/avaje/validation/generator/ProcessingContext.java +++ b/validator-generator/src/main/java/io/avaje/validation/generator/ProcessingContext.java @@ -1,7 +1,7 @@ package io.avaje.validation.generator; import java.io.IOException; -import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import javax.annotation.processing.Filer; @@ -55,16 +55,18 @@ static int jdkVersion() { static boolean isAssignable2Interface(String type, String superType) { return type.equals(superType) - || superTypes(element(type)).stream().anyMatch(t -> t.toString().equals(superType)); + || Optional.ofNullable(element(type)).stream() + .flatMap(ProcessingContext::superTypes) + .anyMatch(superType::equals); } - public static List superTypes(Element element) { + public static Stream superTypes(Element element) { final Types types = CTX.get().types; return types.directSupertypes(element.asType()).stream() .filter(type -> !type.toString().contains("java.lang.Object")) .map(superType -> (TypeElement) types.asElement(superType)) - .flatMap(e -> Stream.concat(superTypes(e).stream(), Stream.of(e))) - .toList(); + .flatMap(e -> Stream.concat(superTypes(e), Stream.of(e))) + .map(Object::toString); } /** Log an error message. */ From 6a9f2570e3ae4e7040e0b8f5b4ea1d00ed597e0f Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 9 Jul 2023 15:25:21 -0400 Subject: [PATCH 8/8] javadocs --- .../inject/aspect/AOPMethodValidator.java | 2 +- .../io/avaje/validation/ImportValidPojo.java | 4 +- .../java/io/avaje/validation/Validator.java | 21 ++-- .../adapter/AbstractContainerAdapter.java | 32 +++++-- .../adapter/ArrayValidationAdapter.java | 2 +- .../adapter/IterableValidationAdapter.java | 2 +- .../adapter/MapValidationAdapter.java | 2 +- .../adapter/OptionalValidationAdapter.java | 8 +- .../validation/adapter/ValidationAdapter.java | 69 +++++++++++-- .../validation/adapter/ValidationContext.java | 96 +++++++++++++++---- .../validation/adapter/ValidationRequest.java | 21 ++-- .../core/BasicMessageInterpolator.java | 2 - .../validation/core/CoreAdapterBuilder.java | 2 - .../io/avaje/validation/core/DValidator.java | 11 ++- .../avaje/validation/core/LocaleResolver.java | 2 +- ...anager.java => ResourceBundleManager.java} | 4 +- ...emplateLookup.java => TemplateLookup.java} | 6 +- .../io/avaje/validation/spi/Bootstrap.java | 1 - .../validation/spi/MessageInterpolator.java | 8 ++ ...ookupTest.java => TemplateLookupTest.java} | 10 +- 20 files changed, 217 insertions(+), 88 deletions(-) rename validator/src/main/java/io/avaje/validation/core/{DResourceBundleManager.java => ResourceBundleManager.java} (90%) rename validator/src/main/java/io/avaje/validation/core/{DTemplateLookup.java => TemplateLookup.java} (83%) rename validator/src/test/java/io/avaje/validation/core/{DTemplateLookupTest.java => TemplateLookupTest.java} (77%) diff --git a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java index 97a1c6ee..e6d3d2f0 100644 --- a/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java +++ b/validator-inject-plugin/src/main/java/io/avaje/validation/inject/aspect/AOPMethodValidator.java @@ -19,7 +19,7 @@ public class AOPMethodValidator implements AspectProvider { private final Map paramAdapters; public AOPMethodValidator(Validator validator, List adapterProviders) { - this.ctx = (ValidationContext) validator; + this.ctx = validator.getContext(); this.paramAdapters = adapterProviders.stream().collect(toMap(MethodAdapterProvider::provide, p -> p)); } diff --git a/validator/src/main/java/io/avaje/validation/ImportValidPojo.java b/validator/src/main/java/io/avaje/validation/ImportValidPojo.java index c999a3d0..bed721cf 100644 --- a/validator/src/main/java/io/avaje/validation/ImportValidPojo.java +++ b/validator/src/main/java/io/avaje/validation/ImportValidPojo.java @@ -1,8 +1,8 @@ package io.avaje.validation; /** - * Specify types to generate Valid Adapters for. Use if you can't place a @Valid annotation on an - * external type. + * Specify external types for which to generate Valid Adapters. Use when you can't place a @Valid + * annotation on an external type (such as a mvn/gradle dependency). */ public @interface ImportValidPojo { diff --git a/validator/src/main/java/io/avaje/validation/Validator.java b/validator/src/main/java/io/avaje/validation/Validator.java index a01f1c4f..7970b845 100644 --- a/validator/src/main/java/io/avaje/validation/Validator.java +++ b/validator/src/main/java/io/avaje/validation/Validator.java @@ -31,6 +31,9 @@ public interface Validator { void validate(Object any, @Nullable Locale locale, @Nullable Class... groups) throws ConstraintViolationException; + /** Get the validation context used to create adapters */ + ValidationContext getContext(); + static Builder builder() { final var bootstrapService = ServiceLoader.load(Bootstrap.class).iterator(); if (bootstrapService.hasNext()) { @@ -48,17 +51,13 @@ interface Builder { /** Add a AnnotationValidationAdapter to use for the given type. */ Builder add(Class type, ValidationAdapter adapter); - /** - * Lookup ResourceBundles with the given name and for error message interpolation - * - * @param bundleName the name of the bundleFiles - */ + /** Lookup ResourceBundles with the given names for error message interpolation */ Builder addResourceBundles(String... bundleName); - /** Add ResourceBundle for error message interpolation */ + /** Add ResourceBundles for error message interpolation */ Builder addResourceBundles(ResourceBundle... bundle); - /** Set Default Locale for this validator, if not set, will use Locale.getDefault() */ + /** Set Default Locale for this validator. If not set, will use Locale.getDefault() */ Builder setDefaultLocale(Locale defaultLocale); /** Adds additional Locales for this validator */ @@ -74,7 +73,7 @@ interface Builder { Builder temporalTolerance(Duration temporalTolerance); /** - * En- or disables the fail fast mode. When fail fast is enabled the validation will stop on the + * Enable/Disable fail fast mode. When fail fast is enabled the validation will stop on the * first constraint violation detected. */ Builder failFast(boolean failFast); @@ -82,7 +81,9 @@ interface Builder { /** Add a AdapterBuilder which provides a ValidationAdapter to use for the given type. */ Builder add(Type type, AdapterBuilder builder); - /** Add a AdapterBuilder which provides a ValidationAdapter to use for the given type. */ + /** + * Add a AdapterBuilder which provides a Annotation ValidationAdapter to use for the given type. + */ Builder add(Class type, AnnotationAdapterBuilder builder); /** Add a Component which can provide multiple ValidationAdapters and or configuration. */ @@ -100,7 +101,7 @@ interface Builder { Validator build(); } - /** Function to build a ValidationAdapter that needs Validator. */ + /** Function to build a ValidationAdapter from a Validation Context */ @FunctionalInterface interface AdapterBuilder { diff --git a/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java index 908eca04..30d4ba51 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/AbstractContainerAdapter.java @@ -1,22 +1,33 @@ package io.avaje.validation.adapter; - +/** + * Adapter that validates container types. + * + * @param the type we are validating + */ public abstract class AbstractContainerAdapter implements ValidationAdapter { - protected final ValidationAdapter starterAdapter; - private ValidationAdapter adapters; + protected final ValidationAdapter initalAdapter; + + private ValidationAdapter multiAdapter; - protected AbstractContainerAdapter(ValidationAdapter starterAdapter) { - this.starterAdapter = starterAdapter; + /** @param initialAdapter initial adapter that can be used to validate the container itself */ + protected AbstractContainerAdapter(ValidationAdapter initalAdapter) { + this.initalAdapter = initalAdapter; } + /** + * Compose the given adapter with the multiAdapter of this AbstractContainerAdapter for validating + * multiple items. + */ public AbstractContainerAdapter andThenMulti(ValidationAdapter adapter) { - this.adapters = - this.adapters != null - ? adapters.andThen((ValidationAdapter) adapter) + this.multiAdapter = + this.multiAdapter != null + ? multiAdapter.andThen((ValidationAdapter) adapter) : (ValidationAdapter) adapter; return this; } + /** Execute validations for all items in the given iterable */ protected boolean validateAll( Iterable value, ValidationRequest req, String propertyName) { if (value == null) { @@ -28,7 +39,7 @@ protected boolean validateAll( int index = -1; for (final var element : value) { index++; - adapters.validate(element, req, String.valueOf(index)); + multiAdapter.validate(element, req, String.valueOf(index)); } if (propertyName != null) { req.popPath(); @@ -36,6 +47,7 @@ protected boolean validateAll( return true; } + /** Execute validations for all items in the given array */ protected boolean validateArray(Object[] value, ValidationRequest req, String propertyName) { if (value == null) { return true; @@ -46,7 +58,7 @@ protected boolean validateArray(Object[] value, ValidationRequest req, String pr int index = -1; for (final Object element : value) { index++; - adapters.validate(element, req, String.valueOf(index)); + multiAdapter.validate(element, req, String.valueOf(index)); } if (propertyName != null) { req.popPath(); diff --git a/validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java index 73823697..ee5bfb5b 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java @@ -8,7 +8,7 @@ class ArrayValidationAdapter extends AbstractContainerAdapter { @Override public boolean validate(T value, ValidationRequest req, String propertyName) { - if (starterAdapter.validate(value, req, propertyName)) { + if (initalAdapter.validate(value, req, propertyName)) { return validateArray((Object[]) value, req, propertyName); } diff --git a/validator/src/main/java/io/avaje/validation/adapter/IterableValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/IterableValidationAdapter.java index e8ec8190..17464a8c 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/IterableValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/IterableValidationAdapter.java @@ -9,7 +9,7 @@ class IterableValidationAdapter extends AbstractContainerAdapter { @Override @SuppressWarnings("unchecked") public boolean validate(T value, ValidationRequest req, String propertyName) { - if (starterAdapter.validate(value, req, propertyName)) { + if (initalAdapter.validate(value, req, propertyName)) { return validateAll((Iterable) value, req, propertyName); } diff --git a/validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java index 14619154..0bef5a43 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java @@ -16,7 +16,7 @@ class MapValidationAdapter extends AbstractContainerAdapter { public boolean validate(T value, ValidationRequest req, String propertyName) { final var map = (Map) value; - if (starterAdapter.validate(value, req, propertyName)) { + if (initalAdapter.validate(value, req, propertyName)) { if (keys) { return validateAll(map.keySet(), req, propertyName); } diff --git a/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java index cda8e7b0..3327d9ae 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/OptionalValidationAdapter.java @@ -17,13 +17,13 @@ public boolean validate(T value, ValidationRequest req, String propertyName) { if (value == null) { return true; } else if (value instanceof final Optional o) { - o.ifPresent(v -> starterAdapter.validate((T) v, req, propertyName)); + o.ifPresent(v -> initalAdapter.validate((T) v, req, propertyName)); } else if (value instanceof final OptionalInt i) { - i.ifPresent(v -> starterAdapter.validate(((T) (Object) v), req, propertyName)); + i.ifPresent(v -> initalAdapter.validate(((T) (Object) v), req, propertyName)); } else if (value instanceof final OptionalLong l) { - l.ifPresent(v -> starterAdapter.validate(((T) (Object) v), req, propertyName)); + l.ifPresent(v -> initalAdapter.validate(((T) (Object) v), req, propertyName)); } else if (value instanceof final OptionalDouble d) { - d.ifPresent(v -> starterAdapter.validate(((T) (Object) v), req, propertyName)); + d.ifPresent(v -> initalAdapter.validate(((T) (Object) v), req, propertyName)); } return true; } diff --git a/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java b/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java index 257b7eeb..11613770 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java +++ b/validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java @@ -2,40 +2,90 @@ import java.util.Objects; import java.util.Set; - -@FunctionalInterface +/** + * This interface defines a set of validation methods for validating a value based on specific + * rules. The methods in this interface allow for executing validations, composing validation + * adapters, and checking validation groups. + * + * @param The type of value to be validated + */ public interface ValidationAdapter { - /** Return true if validation should recurse */ + /** + * Execute validations for the given value. + * + * @param value The value to be validated + * @param req The validation request containing group/locale/violation information + * @param propertyName The name of the property being validated + * @return {@code true} if validation should continue, {@code false} otherwise + */ boolean validate(T value, ValidationRequest req, String propertyName); + /** + * Execute validations for the given value + * + * @param value The value to be validated + * @param req The validation request containing group/locale/violation information + * @return {@code true} if validation should continue, {@code false} otherwise + */ default boolean validate(T value, ValidationRequest req) { return validate(value, req, null); } + /** + * Create an adapter for validating a list of values. + * + * @return The adapter for list validation + */ default AbstractContainerAdapter list() { - return new IterableValidationAdapter<>(this); } + /** + * Create an adapter for validating map keys. + * + * @return The adapter for map key validation + */ default AbstractContainerAdapter mapKeys() { return new MapValidationAdapter<>(this, true); } + /** + * Create an adapter for validating map values. + * + * @return The adapter for map value validation + */ default AbstractContainerAdapter mapValues() { return new MapValidationAdapter<>(this, false); } + /** + * Create an adapter for validating an array. + * + * @return The adapter for array validation + */ default AbstractContainerAdapter array() { return new ArrayValidationAdapter<>(this); } + /** + * Create an adapter for validating an optional value. + * + * @return The adapter for optional value validation + */ default AbstractContainerAdapter optional() { return new OptionalValidationAdapter<>(this); } + /** + * Compose this validation adapter with another adapter by applying the validations in sequence. + * + * @param after The validation adapter to be applied after this adapter + * @return The composed validation adapter + * @throws NullPointerException if {@code after} is null + */ default ValidationAdapter andThen(ValidationAdapter after) { - Objects.requireNonNull(after); + Objects.requireNonNull(after, "after cannot be null"); return (value, req, propertyName) -> { if (validate(value, req, propertyName)) { return after.validate(value, req, propertyName); @@ -45,8 +95,13 @@ default ValidationAdapter andThen(ValidationAdapter after) { } /** - * Returns true if the validation request groups is empty or matches any of the adapter's - * configured groups + * Check if the validation request groups are empty or match any of the adapter's configured + * groups. + * + * @param adapterGroups The groups configured for this adapter + * @param request The validation request containing the groups to be checked + * @return {@code true} if the groups match or if the validation request groups are empty, {@code + * false} otherwise */ default boolean checkGroups(Set> adapterGroups, ValidationRequest request) { final var requestGroups = request.groups(); diff --git a/validator/src/main/java/io/avaje/validation/adapter/ValidationContext.java b/validator/src/main/java/io/avaje/validation/adapter/ValidationContext.java index 6c5ec717..49d4b250 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/ValidationContext.java +++ b/validator/src/main/java/io/avaje/validation/adapter/ValidationContext.java @@ -9,29 +9,47 @@ import io.avaje.lang.Nullable; -/** - * Context available when validation adapters are being created. - */ +/** Context used to lookup validation adapters and create validation requests. */ public interface ValidationContext { /** * Return the adapter for the given type. + * + * @param cls The class for which the adapter is requested + * @param The type this adapter validates + * @return The validation adapter for the given type */ ValidationAdapter adapter(Class cls); /** * Return the adapter for the given type. + * + * @param type The type for which the adapter is requested + * @param The type this adapter validates + * @return The validation adapter for the given type */ ValidationAdapter adapter(Type type); /** - * Return the adapter for the given annotation with attributes. + * Return the annotation adapter for the given annotation with attributes. + * + * @param cls The annotation class + * @param attributes The attributes associated with the annotation + * @param The type this adapter validates + * @return The validation adapter for the given annotation with attributes */ ValidationAdapter adapter(Class cls, Map attributes); /** * Return the adapter for the given annotation with attributes. Used for adapters that combine - * multiple annotation adapters + * multiple annotation adapters. + * + * @param cls The class representing the annotation type + * @param groups The validation groups associated with the annotation + * @param message The error message associated with the annotation + * @param attributes The attributes associated with the annotation + * @param The type this adapter validates + * @return The validation adapter for the given annotation with attributes */ ValidationAdapter adapter( Class cls, @@ -40,56 +58,98 @@ ValidationAdapter adapter( Map attributes); /** - * Return A no-op adapter + * Return a no-op adapter. + * + * @param The type this adapter validates + * @return The no-op validation adapter */ ValidationAdapter noop(); /** - * Create a message object using the annotation attribute "message"; + * Create a message object using the annotation attribute "message". + * + * @param attributes The attributes associated with the annotation + * @return The message object */ Message message(Map attributes); - /** Create a message object using the given string and annotation attributes */ + /** + * Create a message object using the given string. + * + * @param message The error message + * @param attributes The attributes associated with the annotation + * @return The message object created using the given string and annotation attributes + */ Message message(String message, Map attributes); + /** + * Create a validation request with the specified locale and groups. + * + * @param locale The locale for the validation request + * @param groups The validation groups for the request + * @return The validation request with the specified locale and groups + */ ValidationRequest request(@Nullable Locale locale, List> groups); + /** Represents a message object used in error message interpolation. */ interface Message { + /** + * Get the template for the message. A lookup will be performed on the configured resource + * bundles to interpolate the message + * + * @return The template for the message + */ String template(); + /** + * Get the annotation attributes associated with the message. + * + * @return The annotation attributes associated with the message + */ Map attributes(); + /** + * Get the lookup key for the message. + * + * @return The template for the message + a unique number for deduplication purposes. + */ String lookupkey(); } - /** - * Factory for creating a ValidationAdapter for a given type. - */ + /** Factory for creating a ValidationAdapter for a given type. */ @FunctionalInterface interface AdapterFactory { /** * Create and return a ValidationAdapter given the type and annotations or return null. + * Returning null means that the adapter could be created by another factory. * - *

Returning null means that the adapter could be created by another factory. + * @param type The type for which the adapter is being created + * @param ctx The validation context + * @return The created validation adapter or null if not applicable */ ValidationAdapter create(Type type, ValidationContext ctx); } - /** - * Factory for creating a ValidationAdapter for a given annotation. - */ + /** Factory for creating an Annotation Adapter for a given annotation. */ @FunctionalInterface interface AnnotationFactory { /** * Create and return a ValidationAdapter given the type and annotations or return null. + * Returning null means that the adapter could be created by another factory. * - *

Returning null means that the adapter could be created by another factory. + * @param annotationType The annotation type for which the adapter is being created + * @param ctx The validation context + * @param groups The validation groups associated with the annotation + * @param attributes The attributes associated with the annotation + * @return The created validation adapter or null if not applicable */ ValidationAdapter create( - Class annotationType, ValidationContext ctx, Set> groups, Map attributes); + Class annotationType, + ValidationContext ctx, + Set> groups, + Map attributes); } - } diff --git a/validator/src/main/java/io/avaje/validation/adapter/ValidationRequest.java b/validator/src/main/java/io/avaje/validation/adapter/ValidationRequest.java index e3e12aad..20768f86 100644 --- a/validator/src/main/java/io/avaje/validation/adapter/ValidationRequest.java +++ b/validator/src/main/java/io/avaje/validation/adapter/ValidationRequest.java @@ -2,9 +2,9 @@ import java.util.List; -/** - * A validation request. - */ +import io.avaje.validation.ConstraintViolationException; + +/** A validation request. */ public interface ValidationRequest { /** The groups tied to this ValidationRequest */ @@ -18,19 +18,12 @@ public interface ValidationRequest { */ void addViolation(ValidationContext.Message message, String propertyName); - /** - * Push the nested path. - */ + /** Push the nested property path. */ void pushPath(String path); - /** - * Pop the nested path. - */ + /** Pop the nested property path. */ void popPath(); - /** - * Throw ConstraintViolationException if there are violations for this request. - */ - void throwWithViolations(); - + /** Throw ConstraintViolationException if there are violations in this request. */ + void throwWithViolations() throws ConstraintViolationException; } diff --git a/validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java b/validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java index f2c78930..ec165214 100644 --- a/validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java +++ b/validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java @@ -10,8 +10,6 @@ final class BasicMessageInterpolator implements MessageInterpolator { public String interpolate(String template, Map attributes) { String result = template; for (final Map.Entry entry : attributes.entrySet()) { - // needs work here to improve functionality, support local specific value formatting eg - // Duration Max result = result.replace('{' + entry.getKey() + '}', String.valueOf(entry.getValue())); } // return the message diff --git a/validator/src/main/java/io/avaje/validation/core/CoreAdapterBuilder.java b/validator/src/main/java/io/avaje/validation/core/CoreAdapterBuilder.java index 482c213a..ad169777 100644 --- a/validator/src/main/java/io/avaje/validation/core/CoreAdapterBuilder.java +++ b/validator/src/main/java/io/avaje/validation/core/CoreAdapterBuilder.java @@ -1,7 +1,5 @@ package io.avaje.validation.core; -import static java.util.stream.Collectors.toUnmodifiableSet; - import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.time.Clock; diff --git a/validator/src/main/java/io/avaje/validation/core/DValidator.java b/validator/src/main/java/io/avaje/validation/core/DValidator.java index e15d7c30..c35c054f 100644 --- a/validator/src/main/java/io/avaje/validation/core/DValidator.java +++ b/validator/src/main/java/io/avaje/validation/core/DValidator.java @@ -37,7 +37,7 @@ final class DValidator implements Validator, ValidationContext { private final Map> typeCache = new ConcurrentHashMap<>(); private final MessageInterpolator interpolator; private final LocaleResolver localeResolver; - private final DTemplateLookup templateLookup; + private final TemplateLookup templateLookup; private final Map messageCache = new ConcurrentHashMap<>(); private final boolean failfast; @@ -53,8 +53,8 @@ final class DValidator implements Validator, ValidationContext { boolean failfast) { this.localeResolver = localeResolver; final var defaultResourceBundle = - new DResourceBundleManager(bundleNames, bundles, localeResolver); - this.templateLookup = new DTemplateLookup(defaultResourceBundle); + new ResourceBundleManager(bundleNames, bundles, localeResolver); + this.templateLookup = new TemplateLookup(defaultResourceBundle); this.interpolator = interpolator; this.builder = new CoreAdapterBuilder( @@ -78,6 +78,11 @@ public void validate(Object any, @Nullable Locale locale, @Nullable Class... type.validate(any, locale, List.of(groups)); } + @Override + public ValidationContext getContext() { + return this; + } + private ValidationType type(Class cls) { return typeWithCache(cls); } diff --git a/validator/src/main/java/io/avaje/validation/core/LocaleResolver.java b/validator/src/main/java/io/avaje/validation/core/LocaleResolver.java index f58fa5bd..e71abca4 100644 --- a/validator/src/main/java/io/avaje/validation/core/LocaleResolver.java +++ b/validator/src/main/java/io/avaje/validation/core/LocaleResolver.java @@ -5,7 +5,7 @@ import java.util.Locale; import java.util.Set; -public interface LocaleResolver { +interface LocaleResolver { Locale resolve(@Nullable Locale requestLocale); Locale defaultLocale(); diff --git a/validator/src/main/java/io/avaje/validation/core/DResourceBundleManager.java b/validator/src/main/java/io/avaje/validation/core/ResourceBundleManager.java similarity index 90% rename from validator/src/main/java/io/avaje/validation/core/DResourceBundleManager.java rename to validator/src/main/java/io/avaje/validation/core/ResourceBundleManager.java index 6371a187..7180fc1b 100644 --- a/validator/src/main/java/io/avaje/validation/core/DResourceBundleManager.java +++ b/validator/src/main/java/io/avaje/validation/core/ResourceBundleManager.java @@ -11,13 +11,13 @@ import io.avaje.lang.Nullable; -final class DResourceBundleManager { +final class ResourceBundleManager { private final Map> map = new HashMap<>(); private static final List EMPTY = List.of(); private static final String DEFAULT_BUNDLE = "io.avaje.validation.Messages"; - DResourceBundleManager(List names, List providedBundles, LocaleResolver localeResolver) { + ResourceBundleManager(List names, List providedBundles, LocaleResolver localeResolver) { for (final var name : names) { addBundle(name, localeResolver.defaultLocale()); for (final Locale locale : localeResolver.otherLocales()) { diff --git a/validator/src/main/java/io/avaje/validation/core/DTemplateLookup.java b/validator/src/main/java/io/avaje/validation/core/TemplateLookup.java similarity index 83% rename from validator/src/main/java/io/avaje/validation/core/DTemplateLookup.java rename to validator/src/main/java/io/avaje/validation/core/TemplateLookup.java index 5cf3e92c..6a4d2c7e 100644 --- a/validator/src/main/java/io/avaje/validation/core/DTemplateLookup.java +++ b/validator/src/main/java/io/avaje/validation/core/TemplateLookup.java @@ -2,10 +2,10 @@ import java.util.Locale; -final class DTemplateLookup { - private final DResourceBundleManager bundleManager; +final class TemplateLookup { + private final ResourceBundleManager bundleManager; - DTemplateLookup(DResourceBundleManager defaultBundle) { + TemplateLookup(ResourceBundleManager defaultBundle) { this.bundleManager = defaultBundle; } diff --git a/validator/src/main/java/io/avaje/validation/spi/Bootstrap.java b/validator/src/main/java/io/avaje/validation/spi/Bootstrap.java index bf09b18e..16dbbf81 100644 --- a/validator/src/main/java/io/avaje/validation/spi/Bootstrap.java +++ b/validator/src/main/java/io/avaje/validation/spi/Bootstrap.java @@ -10,7 +10,6 @@ public interface Bootstrap { /** * Create and return a Builder (with an underling SPI implementation). *

- * The default implementation uses Jackson Core. */ Validator.Builder builder(); } diff --git a/validator/src/main/java/io/avaje/validation/spi/MessageInterpolator.java b/validator/src/main/java/io/avaje/validation/spi/MessageInterpolator.java index 93061e01..42b66785 100644 --- a/validator/src/main/java/io/avaje/validation/spi/MessageInterpolator.java +++ b/validator/src/main/java/io/avaje/validation/spi/MessageInterpolator.java @@ -2,7 +2,15 @@ import java.util.Map; +/** Reads an Annotation's attributes and the message template and interpolates the message */ public interface MessageInterpolator { + /** + * Interpolate the given message with the annotation attributes + * + * @param template template + * @param attributes + * @return the interpolated validation error message + */ String interpolate(String template, Map attributes); } diff --git a/validator/src/test/java/io/avaje/validation/core/DTemplateLookupTest.java b/validator/src/test/java/io/avaje/validation/core/TemplateLookupTest.java similarity index 77% rename from validator/src/test/java/io/avaje/validation/core/DTemplateLookupTest.java rename to validator/src/test/java/io/avaje/validation/core/TemplateLookupTest.java index a87bb19e..916b8652 100644 --- a/validator/src/test/java/io/avaje/validation/core/DTemplateLookupTest.java +++ b/validator/src/test/java/io/avaje/validation/core/TemplateLookupTest.java @@ -7,15 +7,15 @@ import org.junit.jupiter.api.Test; -class DTemplateLookupTest { +class TemplateLookupTest { - private final DTemplateLookup lookup; + private final TemplateLookup lookup; - DTemplateLookupTest() { + TemplateLookupTest() { final var localeResolver = new DLocaleResolver(Locale.ENGLISH, List.of(Locale.GERMAN)); final var defaultResourceBundle = - new DResourceBundleManager(List.of(), List.of(), localeResolver); - this.lookup = new DTemplateLookup(defaultResourceBundle); + new ResourceBundleManager(List.of(), List.of(), localeResolver); + this.lookup = new TemplateLookup(defaultResourceBundle); } @Test