├── blackbox-test └── src │ ├── main │ ├── resources │ │ └── example │ │ │ └── avaje │ │ │ ├── CustomMessages_en.properties │ │ │ ├── CustomMessages_de.properties │ │ │ └── CustomMessages.properties │ └── java │ │ ├── example │ │ └── avaje │ │ │ ├── subtypes │ │ │ ├── SubtypeEntity.java │ │ │ ├── sealed │ │ │ │ ├── SealedEntity.java │ │ │ │ ├── ByQuerySelectorSealed.java │ │ │ │ ├── ByIdSelectorSealed.java │ │ │ │ └── SealedEntitySelector.java │ │ │ ├── ByQuerySelector.java │ │ │ ├── EntitySelector.java │ │ │ └── ByIdSelector.java │ │ │ ├── controller │ │ │ └── MyController.java │ │ │ ├── pkg_private │ │ │ └── PackagePrivate.java │ │ │ ├── cascade │ │ │ ├── ACrew.java │ │ │ ├── Recursive.java │ │ │ ├── CShip.java │ │ │ ├── BShip.java │ │ │ ├── AShip.java │ │ │ ├── CShip2.java │ │ │ ├── CShip3.java │ │ │ ├── MAddress.java │ │ │ ├── DShip.java │ │ │ ├── AShip2.java │ │ │ ├── AShip3.java │ │ │ ├── CascadeGroup.java │ │ │ └── MCustomer.java │ │ │ ├── composable │ │ │ ├── Sans.java │ │ │ ├── MyCustomPattern.java │ │ │ ├── MySerialExample.java │ │ │ ├── PositiveContraint.java │ │ │ ├── SansPositiveContraint.java │ │ │ ├── DigitsContraint.java │ │ │ ├── MyKey.java │ │ │ └── MySerial.java │ │ │ ├── typeuse │ │ │ ├── CrewMate.java │ │ │ └── Ship.java │ │ │ ├── custom │ │ │ ├── ACustomLong.java │ │ │ ├── MyCustomALong.java │ │ │ └── adapter │ │ │ │ └── MyCustomALongAdapter.java │ │ │ ├── mixin │ │ │ ├── Captain.java │ │ │ └── CaptainMixin.java │ │ │ ├── clock │ │ │ └── Clocky.java │ │ │ ├── otherannotation │ │ │ └── MyJson.java │ │ │ ├── crossfield │ │ │ ├── NotClassLevel.java │ │ │ ├── ATarnished.java │ │ │ ├── APassingSkill.java │ │ │ └── adapter │ │ │ │ └── APassingSkillAdapter.java │ │ │ ├── method │ │ │ └── MethodTest.java │ │ │ ├── optional │ │ │ └── CurseBearer.java │ │ │ └── repeat │ │ │ └── SignupRequest.java │ │ └── module-info.java │ └── test │ ├── java │ └── example │ │ ├── avaje │ │ ├── jspecify │ │ │ ├── package-info.java │ │ │ ├── JSpecifyNotNull.java │ │ │ ├── JSpecifyNullUnmarked.java │ │ │ └── JSpecifyTest.java │ │ ├── range │ │ │ ├── StrRange.java │ │ │ ├── AFloatRange.java │ │ │ ├── APrimitiveLongRange.java │ │ │ ├── ADoubleRange.java │ │ │ ├── ARange.java │ │ │ ├── ABigIntRange.java │ │ │ ├── ADecimalRange.java │ │ │ ├── APrimitiveMax.java │ │ │ ├── APrimitiveMin.java │ │ │ ├── AMaxCheck.java │ │ │ ├── AMinCheck.java │ │ │ ├── APrimitiveRange.java │ │ │ ├── AMaxTest.java │ │ │ └── AMinTest.java │ │ ├── uuid │ │ │ └── AUuid.java │ │ ├── notblank │ │ │ ├── AStringOnlyCheck.java │ │ │ ├── ANotBlank.java │ │ │ ├── MyNumericType.java │ │ │ ├── ATemporalOnlyCheck.java │ │ │ └── ANumberCheckTest.java │ │ ├── AMyEmail.java │ │ ├── positive │ │ │ ├── APositiveFloat.java │ │ │ ├── APositiveDouble.java │ │ │ ├── APrimitiveNegative.java │ │ │ ├── APrimitivePositive.java │ │ │ ├── APrimitiveNegativeOrZero.java │ │ │ └── APrimitivePositiveOrZero.java │ │ ├── uri │ │ │ └── AUri.java │ │ ├── AMyPattern.java │ │ ├── daterange │ │ │ ├── DateRangeLocal.java │ │ │ ├── DateRangeNow.java │ │ │ ├── DateRangeNowTolerance0.java │ │ │ └── DateRangeNowTolerance.java │ │ ├── length │ │ │ └── ALength.java │ │ ├── bool │ │ │ ├── ABoolTrue.java │ │ │ ├── ABoolFalse.java │ │ │ └── ABoolTest.java │ │ ├── decimal │ │ │ ├── ALongMax.java │ │ │ ├── AFloatMax.java │ │ │ ├── ADoubleMax.java │ │ │ ├── ADecimalStringMax.java │ │ │ └── ADecimalMax.java │ │ ├── past │ │ │ ├── APastFuture.java │ │ │ ├── APastFutureYear.java │ │ │ ├── APastFutureInstant.java │ │ │ ├── APastFutureLocalDate.java │ │ │ ├── APastFutureYearMonth.java │ │ │ ├── APastFutureLocalTime.java │ │ │ ├── APastFutureOffsetTime.java │ │ │ ├── APastFutureLocalDateTime.java │ │ │ ├── APastFutureZDT.java │ │ │ ├── APastFutureODT.java │ │ │ └── APastFutureDate.java │ │ ├── cascade │ │ │ ├── MCustomerTest.java │ │ │ ├── RecursiveTest.java │ │ │ └── CascadeGroupTest.java │ │ ├── nested │ │ │ ├── AContact.java │ │ │ ├── AAddress.java │ │ │ └── AContactWithNullable.java │ │ ├── ANums.java │ │ ├── controller │ │ │ └── MyControllerTest.java │ │ ├── subtypes │ │ │ ├── EntitySelectorTest.java │ │ │ └── sealed │ │ │ │ └── SealedEntitySelectorTest.java │ │ ├── custom │ │ │ └── ACustomLongTest.java │ │ ├── method │ │ │ └── MethodTestTest.java │ │ ├── mixin │ │ │ └── CaptainMixinTest.java │ │ ├── ACustomer.java │ │ ├── clock │ │ │ └── ClockTest.java │ │ ├── pkg_private │ │ │ └── PackagePrivateTest.java │ │ ├── AMyNumbers.java │ │ ├── AMyMinNumbers.java │ │ ├── composable │ │ │ └── SansComposableTest.java │ │ └── typeuse │ │ │ └── DefaultValidatorProviderTest.java │ │ ├── jakarta │ │ ├── JMyEmail.java │ │ ├── JMyPattern.java │ │ ├── AllSortsBean.java │ │ ├── JNums.java │ │ ├── JPastFuture.java │ │ ├── JCustomer.java │ │ ├── JMyNumbers.java │ │ └── JMyMinNumbers.java │ │ └── hibernate │ │ ├── HContact.java │ │ └── HCustomer.java │ └── resources │ └── skip-value-test-data.json ├── validator-http-plugin └── src │ └── main │ ├── resources │ └── META-INF │ │ └── services │ │ └── io.avaje.inject.spi.InjectExtension │ └── java │ └── module-info.java ├── validator-inject-plugin └── src │ ├── main │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── io.avaje.inject.spi.InjectExtension │ └── java │ │ └── module-info.java │ └── test │ └── java │ └── io │ └── avaje │ └── validation │ └── inject │ └── aspect │ ├── MethodTest.java │ └── TestMethodValidation.java ├── validator └── src │ ├── main │ ├── java │ │ ├── io │ │ │ └── avaje │ │ │ │ └── validation │ │ │ │ ├── core │ │ │ │ ├── package-info.java │ │ │ │ ├── DefaultBootstrap.java │ │ │ │ ├── BasicMessageInterpolator.java │ │ │ │ ├── adapters │ │ │ │ │ ├── UuidAdapter.java │ │ │ │ │ └── InfinityNumberComparatorHelper.java │ │ │ │ ├── DMessage.java │ │ │ │ ├── LocaleResolver.java │ │ │ │ ├── TemplateLookup.java │ │ │ │ └── ValidationType.java │ │ │ │ ├── spi │ │ │ │ ├── package-info.java │ │ │ │ ├── GeneratedComponent.java │ │ │ │ ├── Generated.java │ │ │ │ ├── MessageInterpolator.java │ │ │ │ ├── ValidatorCustomizer.java │ │ │ │ ├── AnnotationFactory.java │ │ │ │ ├── AdapterFactory.java │ │ │ │ ├── ValidationExtension.java │ │ │ │ └── MetaData.java │ │ │ │ ├── adapter │ │ │ │ ├── package-info.java │ │ │ │ ├── ArrayValidationAdapter.java │ │ │ │ ├── IterableValidationAdapter.java │ │ │ │ ├── OptionalAdapter.java │ │ │ │ ├── MapValidationAdapter.java │ │ │ │ ├── PrimitiveOptional.java │ │ │ │ ├── ValidationRequest.java │ │ │ │ └── AbstractConstraintAdapter.java │ │ │ │ ├── ConstraintViolation.java │ │ │ │ ├── groups │ │ │ │ └── Default.java │ │ │ │ ├── CrossParamConstraint.java │ │ │ │ ├── ValidMethod.java │ │ │ │ ├── ImportValidPojo.java │ │ │ │ ├── package-info.java │ │ │ │ ├── ConstraintViolationException.java │ │ │ │ ├── MixIn.java │ │ │ │ └── ValidSubTypes.java │ │ └── module-info.java │ └── resources │ │ ├── io │ │ └── avaje │ │ │ └── validation │ │ │ ├── Messages_en.properties │ │ │ └── Messages_tr.properties │ │ └── META-INF │ │ └── native-image │ │ └── io.avaje.validator.avaje-validator │ │ └── resource-config.json │ └── test │ └── java │ └── io │ └── avaje │ └── validation │ └── core │ ├── Address.java │ ├── TestBuilder.java │ ├── Contact.java │ ├── Customer.java │ ├── TemplateLookupTest.java │ ├── adapters │ └── EmailTest.java │ └── NotNullLocaleTest.java ├── validator-constraints ├── src │ └── main │ │ └── java │ │ ├── module-info.java │ │ └── io │ │ └── avaje │ │ └── validation │ │ └── constraints │ │ ├── package-info.java │ │ ├── UUID.java │ │ ├── Valid.java │ │ ├── Constraint.java │ │ ├── Null.java │ │ ├── NotNull.java │ │ ├── AssertFalse.java │ │ ├── AssertTrue.java │ │ ├── NotEmpty.java │ │ ├── Length.java │ │ ├── URI.java │ │ ├── NegativeOrZero.java │ │ ├── NotBlank.java │ │ ├── PositiveOrZero.java │ │ ├── Negative.java │ │ ├── Positive.java │ │ └── Email.java └── pom.xml ├── validator-spring-starter └── src │ ├── main │ ├── resources │ │ └── META-INF │ │ │ └── spring │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ └── java │ │ ├── module-info.java │ │ └── io │ │ └── avaje │ │ └── validation │ │ └── spring │ │ └── aspect │ │ └── MethodValidationAutoConfiguration.java │ └── test │ └── java │ └── io │ └── avaje │ └── validation │ └── spring │ └── aspect │ ├── ValidMethodClass.java │ └── MethodValidationTest.java ├── validator-generator └── src │ ├── test │ └── java │ │ └── io │ │ └── avaje │ │ └── validation │ │ └── generator │ │ └── models │ │ └── valid │ │ ├── Address.java │ │ ├── subtypes │ │ ├── ByQuerySelector.java │ │ ├── EntitySelector.java │ │ ├── sealed │ │ │ ├── ByQuerySelectorSealed.java │ │ │ ├── ByIdSelectorSealed.java │ │ │ └── SealedEntitySelector.java │ │ └── ByIdSelector.java │ │ ├── pkg_private │ │ ├── PackagePrivate.java │ │ └── PackagePrivateTestClass.java │ │ ├── typeconstraint │ │ ├── Tarnished.java │ │ ├── FraudWatch.java │ │ ├── PassingSkill.java │ │ ├── TypeConstrained.java │ │ ├── FraudAdapter.java │ │ └── PassingSkillAdapter.java │ │ ├── CrewMate.java │ │ ├── JSpecifyNullUnmarked.java │ │ ├── methods │ │ ├── Cross.java │ │ └── MethodTest.java │ │ ├── JSpecifyNotNull.java │ │ ├── nesting │ │ └── Repro.java │ │ ├── Recursive.java │ │ ├── CheckCase.java │ │ ├── PrimitiveTest.java │ │ ├── Captain.java │ │ ├── Contact.java │ │ ├── PrimitiveTestAdapter.java │ │ ├── Combining.java │ │ ├── Insect.java │ │ ├── Polus.java │ │ ├── Combining2.java │ │ ├── CaptainMixin.java │ │ ├── Ship.java │ │ ├── optional │ │ └── CurseBearer.java │ │ ├── CheckCaseAdapter.java │ │ ├── Customer.java │ │ └── AMyNumbers.java │ └── main │ └── java │ ├── io │ └── avaje │ │ └── validation │ │ └── generator │ │ ├── TopPackage.java │ │ ├── Constants.java │ │ ├── package-info.java │ │ ├── GenericTypeMap.java │ │ ├── BeanReader.java │ │ └── Append.java │ └── module-info.java ├── .editorconfig ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── jdk-ea-stable.yml │ ├── jdk-ea.yml │ ├── dependabot-merge.yml │ ├── build.yml │ ├── native-image.yml │ └── release-merge.yml └── test-native-image └── src ├── main └── java │ └── org │ └── example │ ├── Customer.java │ └── Main.java └── test └── java └── org └── example └── CustomerTest.java /blackbox-test/src/main/resources/example/avaje/CustomMessages_en.properties: -------------------------------------------------------------------------------- 1 | # intentionally blank 2 | -------------------------------------------------------------------------------- /blackbox-test/src/main/resources/example/avaje/CustomMessages_de.properties: -------------------------------------------------------------------------------- 1 | example.avaje.MyKey.message=darf nicht MyKey 2 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/jspecify/package-info.java: -------------------------------------------------------------------------------- 1 | @org.jspecify.annotations.NullMarked 2 | package example.avaje.jspecify; 3 | -------------------------------------------------------------------------------- /validator-http-plugin/src/main/resources/META-INF/services/io.avaje.inject.spi.InjectExtension: -------------------------------------------------------------------------------- 1 | io.avaje.validation.http.HttpValidatorProvider 2 | -------------------------------------------------------------------------------- /validator-inject-plugin/src/main/resources/META-INF/services/io.avaje.inject.spi.InjectExtension: -------------------------------------------------------------------------------- 1 | io.avaje.validation.inject.spi.DefaultValidatorProvider 2 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Core internal implementation. 3 | */ 4 | package io.avaje.validation.core; 5 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module io.avaje.validation.contraints { 2 | 3 | exports io.avaje.validation.constraints; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * SPI for the underlying validation adapters. 3 | */ 4 | package io.avaje.validation.spi; 5 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Constraint annotations for Avaje Validation. 3 | */ 4 | package io.avaje.validation.constraints; 5 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/adapter/package-info.java: -------------------------------------------------------------------------------- 1 | /** classes and interfaces used for building generated/custom constraint adapters */ 2 | package io.avaje.validation.adapter; 3 | -------------------------------------------------------------------------------- /validator/src/main/resources/io/avaje/validation/Messages_en.properties: -------------------------------------------------------------------------------- 1 | avaje.Length.max.message = maximum length {max} exceeded 2 | avaje.Size.max.message = maximum size {max} exceeded 3 | -------------------------------------------------------------------------------- /validator/src/main/resources/META-INF/native-image/io.avaje.validator.avaje-validator/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundles": [ 3 | { 4 | "name": "io.avaje.validation.Messages" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/subtypes/SubtypeEntity.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes; 2 | 3 | import jakarta.validation.Valid; 4 | 5 | @Valid 6 | public record SubtypeEntity(@Valid EntitySelector selector) {} 7 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/subtypes/sealed/SealedEntity.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes.sealed; 2 | 3 | import jakarta.validation.Valid; 4 | 5 | @Valid 6 | public record SealedEntity(@Valid SealedEntitySelector selector) {} 7 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/subtypes/ByQuerySelector.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | 5 | public final record ByQuerySelector(@NotBlank String query) implements EntitySelector {} 6 | -------------------------------------------------------------------------------- /validator/src/test/java/io/avaje/validation/core/Address.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | //@Valid 4 | public class Address { 5 | 6 | public String line1; 7 | public String line2; 8 | public long longValue; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/subtypes/EntitySelector.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes; 2 | 3 | import io.avaje.validation.ValidSubTypes; 4 | 5 | @ValidSubTypes({ByQuerySelector.class, ByIdSelector.class}) 6 | public interface EntitySelector {} 7 | -------------------------------------------------------------------------------- /validator-spring-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.avaje.validation.spring.validator.AvajeValidatorAutoConfiguration 2 | io.avaje.validation.spring.aspect.MethodValidationAutoConfiguration -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/controller/MyController.java: -------------------------------------------------------------------------------- 1 | package example.avaje.controller; 2 | 3 | import io.avaje.http.api.Controller; 4 | import io.avaje.http.api.Valid; 5 | 6 | @Valid 7 | @Controller 8 | public class MyController { 9 | } 10 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/pkg_private/PackagePrivate.java: -------------------------------------------------------------------------------- 1 | package example.avaje.pkg_private; 2 | 3 | import io.avaje.validation.constraints.Positive; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | record PackagePrivate(@Positive int id) {} 8 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/ACrew.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record ACrew(@NotBlank(max = 4) String name) { 8 | } 9 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/composable/Sans.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import jakarta.validation.Valid; 4 | 5 | @Valid 6 | public record Sans( 7 | @SansPositiveContraint(message = "must have positive double digit amount of puns") int puns) {} 8 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Address.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | public class Address { 4 | 5 | public String line1; 6 | public String line2; 7 | @PrimitiveTest public long longValue; 8 | } 9 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/typeuse/CrewMate.java: -------------------------------------------------------------------------------- 1 | package example.avaje.typeuse; 2 | 3 | import java.util.List; 4 | 5 | import io.avaje.validation.constraints.NotEmpty; 6 | 7 | public record CrewMate(@NotEmpty(message = "Must have valid task") String assignedTasks) {} 8 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/subtypes/sealed/ByQuerySelectorSealed.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes.sealed; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | 5 | public final record ByQuerySelectorSealed(@NotBlank String query) implements SealedEntitySelector {} 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | spaces_around_operators = true 13 | max_line_length = 135 14 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/custom/ACustomLong.java: -------------------------------------------------------------------------------- 1 | package example.avaje.custom; 2 | 3 | import io.avaje.validation.constraints.Valid; 4 | 5 | @Valid 6 | public record ACustomLong( 7 | @MyCustomALong long primitive, 8 | 9 | @MyCustomALong Long object 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/Recursive.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotNull; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record Recursive(@NotNull String name, @Valid @NotNull Recursive child) {} 8 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/mixin/Captain.java: -------------------------------------------------------------------------------- 1 | package example.avaje.mixin; 2 | 3 | import io.avaje.validation.constraints.NotNull; 4 | 5 | public record Captain(String name, @NotNull(message = "Not captain level") Bankai bankai) { 6 | 7 | public record Bankai(String name) {} 8 | } 9 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/StrRange.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Range; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public record StrRange( 8 | @Range(min = 1, max = 3) 9 | String strVal 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/jspecify/JSpecifyNotNull.java: -------------------------------------------------------------------------------- 1 | package example.avaje.jspecify; 2 | 3 | import org.jspecify.annotations.Nullable; 4 | 5 | import jakarta.validation.Valid; 6 | 7 | @Valid 8 | public record JSpecifyNotNull(String basic, String withMax, @Nullable String withCustom) {} 9 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/AFloatRange.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Range; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public record AFloatRange( 8 | @Range(min = 1, max = 3) 9 | float value 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | build/ 3 | .idea/ 4 | *.iml 5 | .gradle 6 | *.prefs 7 | *.classpath 8 | *.project 9 | *.class 10 | .DS_Store 11 | *.factorypath 12 | validator-generator/.factorypath 13 | validator-generator/io.avaje.validation.spi.ValidationExtension 14 | validator-generator/avaje-processors.txt 15 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/clock/Clocky.java: -------------------------------------------------------------------------------- 1 | package example.avaje.clock; 2 | 3 | import java.time.LocalDate; 4 | 5 | import io.avaje.validation.constraints.Future; 6 | import io.avaje.validation.constraints.Valid; 7 | 8 | @Valid 9 | public record Clocky(@Future LocalDate startDate) {} 10 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/otherannotation/MyJson.java: -------------------------------------------------------------------------------- 1 | package example.avaje.otherannotation; 2 | 3 | /** 4 | * An annotation we put on the class level that should be recognised as 5 | * NOT a constraint (no class level constraint for this annotation) 6 | */ 7 | public @interface MyJson { 8 | } 9 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/subtypes/ByIdSelector.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import io.avaje.validation.constraints.NotEmpty; 7 | 8 | public final record ByIdSelector(@NotEmpty List ids) implements EntitySelector {} 9 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/GeneratedComponent.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spi; 2 | 3 | /** Registers generated ValidationAdapters with the Validator.Builder */ 4 | @FunctionalInterface 5 | public non-sealed interface GeneratedComponent extends ValidatorCustomizer, ValidationExtension {} 6 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/uuid/AUuid.java: -------------------------------------------------------------------------------- 1 | package example.avaje.uuid; 2 | 3 | import io.avaje.validation.constraints.UUID; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public record AUuid( 8 | @UUID 9 | String str, 10 | @UUID 11 | CharSequence charSequence 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/APrimitiveLongRange.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Range; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public record APrimitiveLongRange( 8 | @Range(min = 1, max = 3) 9 | long value 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/subtypes/ByQuerySelector.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.subtypes; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | 5 | public final record ByQuerySelector(@NotBlank String query) implements EntitySelector {} 6 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/subtypes/EntitySelector.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.subtypes; 2 | 3 | import io.avaje.validation.ValidSubTypes; 4 | 5 | @ValidSubTypes({ByQuerySelector.class, ByIdSelector.class}) 6 | public interface EntitySelector {} 7 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/pkg_private/PackagePrivate.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.pkg_private; 2 | 3 | import io.avaje.validation.constraints.Positive; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | record PackagePrivate(@Positive Long id) {} 8 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/notblank/AStringOnlyCheck.java: -------------------------------------------------------------------------------- 1 | package example.avaje.notblank; 2 | 3 | import io.avaje.validation.constraints.Valid; 4 | 5 | @Valid 6 | public record AStringOnlyCheck( 7 | 8 | // @NotBlank 9 | int notString1, 10 | 11 | // @Email 12 | Boolean notString2 13 | 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/typeconstraint/Tarnished.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.typeconstraint; 2 | 3 | import io.avaje.validation.constraints.Valid; 4 | 5 | @Valid 6 | @PassingSkill 7 | public record Tarnished(int vigor, @TypeConstrained int endurance) {} 8 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/CShip.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record CShip( 8 | @NotBlank String name, 9 | @Valid ACrew[] crew // cascade validation 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/composable/MyCustomPattern.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record MyCustomPattern( 8 | @MyKey String key, 9 | @NotBlank String value) { 10 | } 11 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/composable/MySerialExample.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record MySerialExample( 8 | @MySerial String key, 9 | @NotBlank String value) { 10 | } 11 | -------------------------------------------------------------------------------- /validator-inject-plugin/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module io.avaje.validation.plugin { 2 | 3 | requires transitive io.avaje.validation; 4 | requires transitive io.avaje.inject; 5 | requires io.avaje.inject.aop; 6 | 7 | provides io.avaje.inject.spi.InjectExtension with io.avaje.validation.inject.spi.DefaultValidatorProvider; 8 | } 9 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/ADoubleRange.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Range; 4 | import jakarta.validation.Valid; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Valid 9 | public record ADoubleRange( 10 | @Range(min = 1, max = 3) 11 | double value 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/ARange.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Range; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public record ARange( 8 | @Range(min = 1, max = 3) 9 | int anIntVal, 10 | @Range(min = 1, max = 3) 11 | long aLongVal 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/ConstraintViolation.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation; 2 | 3 | /** 4 | * Describes a constraint violation. This object exposes the constraint violation context as well as 5 | * the message describing the violation. 6 | */ 7 | public record ConstraintViolation(String path, String field, String message) {} 8 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/ABigIntRange.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Range; 4 | import jakarta.validation.Valid; 5 | 6 | import java.math.BigInteger; 7 | 8 | @Valid 9 | public record ABigIntRange( 10 | @Range(min = 1, max = 3) 11 | BigInteger decimal 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/ADecimalRange.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Range; 4 | import jakarta.validation.Valid; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Valid 9 | public record ADecimalRange( 10 | @Range(min = 1, max = 3) 11 | BigDecimal decimal 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/jspecify/JSpecifyNullUnmarked.java: -------------------------------------------------------------------------------- 1 | package example.avaje.jspecify; 2 | 3 | import org.jspecify.annotations.NullUnmarked; 4 | 5 | import io.avaje.validation.constraints.Null; 6 | import jakarta.validation.Valid; 7 | 8 | @Valid 9 | @NullUnmarked 10 | public record JSpecifyNullUnmarked(@Null String basic) {} 11 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/CrewMate.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import java.util.List; 4 | 5 | public record CrewMate( 6 | @Combining( 7 | message = "combined", 8 | groups = {Character.class}) 9 | List assignedTasks) {} 10 | -------------------------------------------------------------------------------- /validator-http-plugin/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module io.avaje.validation.http { 2 | 3 | exports io.avaje.validation.http; 4 | 5 | requires transitive io.avaje.validation.plugin; 6 | requires transitive io.avaje.http.api; 7 | 8 | provides io.avaje.inject.spi.InjectExtension with io.avaje.validation.http.HttpValidatorProvider; 9 | } 10 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/jakarta/JMyEmail.java: -------------------------------------------------------------------------------- 1 | package example.jakarta; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.Email; 5 | 6 | @Valid 7 | public class JMyEmail { 8 | 9 | @Email 10 | public final String email; 11 | 12 | public JMyEmail(String email) { 13 | this.email = email; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/BShip.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | import java.util.Set; 7 | 8 | @Valid 9 | public record BShip( 10 | @NotBlank String name, 11 | @Valid Set crew // cascade validation 12 | ) { } 13 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/AShip.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | import java.util.List; 7 | 8 | @Valid 9 | public record AShip( 10 | @NotBlank String name, 11 | @Valid List crew // cascade validation 12 | ) { } 13 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/AMyEmail.java: -------------------------------------------------------------------------------- 1 | package example.avaje; 2 | 3 | import io.avaje.validation.constraints.Email; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public class AMyEmail { 8 | 9 | @Email 10 | public final String email; 11 | 12 | public AMyEmail(String email) { 13 | this.email = email; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/JSpecifyNullUnmarked.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import org.jspecify.annotations.NullUnmarked; 4 | 5 | import jakarta.validation.Valid; 6 | 7 | @Valid 8 | @NullUnmarked 9 | public record JSpecifyNullUnmarked(String basic, String withMax, String withCustom) {} 10 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/Cross.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.methods; 2 | 3 | import io.avaje.validation.CrossParamConstraint; 4 | 5 | @CrossParamConstraint 6 | public @interface Cross { 7 | 8 | String message() default "{io.avaje.validator.Cross}"; 9 | 10 | Class[] groups() default {}; 11 | } 12 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/CShip2.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.NotEmpty; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public record CShip2( 9 | @NotBlank String name, 10 | @Valid @NotEmpty ACrew[] crew // cascade validation 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/CShip3.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.NotEmpty; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public record CShip3( 9 | @NotBlank String name, 10 | @NotEmpty ACrew[] crew // NotEmpty with no cascade 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/crossfield/NotClassLevel.java: -------------------------------------------------------------------------------- 1 | package example.avaje.crossfield; 2 | 3 | import example.avaje.otherannotation.MyJson; 4 | import io.avaje.validation.constraints.NotBlank; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @MyJson // @MyJson is not a constraint 8 | @Valid 9 | public record NotClassLevel( 10 | @NotBlank String name 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/MAddress.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Size; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public class MAddress { 9 | 10 | @NotBlank @Size(max = 10) 11 | public String line1; 12 | public String line2; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/positive/APositiveFloat.java: -------------------------------------------------------------------------------- 1 | package example.avaje.positive; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | @Valid 6 | public record APositiveFloat( 7 | 8 | @Positive 9 | Float pos, 10 | 11 | @PositiveOrZero 12 | Float posOrZero, 13 | 14 | @Negative 15 | Float neg, 16 | 17 | @NegativeOrZero 18 | Float negOrZero 19 | ) { 20 | } 21 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/uri/AUri.java: -------------------------------------------------------------------------------- 1 | package example.avaje.uri; 2 | 3 | import io.avaje.validation.constraints.URI; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public record AUri( 8 | @URI(scheme="http", host = "localhost") 9 | String str, 10 | @URI(port=81) 11 | CharSequence charSequence, 12 | @URI(regexp="(https:.*)") 13 | String withReg 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/jakarta/JMyPattern.java: -------------------------------------------------------------------------------- 1 | package example.jakarta; 2 | 3 | import jakarta.validation.constraints.Pattern; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public class JMyPattern { 8 | 9 | @Pattern(regexp = "[0-3]+") 10 | public final String myPattern; 11 | 12 | public JMyPattern(String myPattern) { 13 | this.myPattern = myPattern; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/positive/APositiveDouble.java: -------------------------------------------------------------------------------- 1 | package example.avaje.positive; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | @Valid 6 | public record APositiveDouble( 7 | 8 | @Positive 9 | Double pos, 10 | 11 | @PositiveOrZero 12 | Double posOrZero, 13 | 14 | @Negative 15 | Double neg, 16 | 17 | @NegativeOrZero 18 | Double negOrZero 19 | ) { 20 | } 21 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/AMyPattern.java: -------------------------------------------------------------------------------- 1 | package example.avaje; 2 | 3 | import io.avaje.validation.constraints.Pattern; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public class AMyPattern { 8 | 9 | @Pattern(regexp = "[0-3]+") 10 | public final String myPattern; 11 | 12 | public AMyPattern(String myPattern) { 13 | this.myPattern = myPattern; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/JSpecifyNotNull.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | import org.jspecify.annotations.Nullable; 5 | 6 | import jakarta.validation.Valid; 7 | 8 | @Valid 9 | @NullMarked 10 | public record JSpecifyNotNull(String basic, String withMax, @Nullable String withCustom) {} 11 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/DShip.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.NotEmpty; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public record DShip( 9 | @NotBlank String name, 10 | @NotBlank String rating, 11 | @NotEmpty String[] crew // cascade validation 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/crossfield/ATarnished.java: -------------------------------------------------------------------------------- 1 | package example.avaje.crossfield; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Positive; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | @APassingSkill 9 | public record ATarnished( 10 | @NotBlank String name, 11 | @Positive int vigor, 12 | @Positive int endurance) { 13 | } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/notblank/ANotBlank.java: -------------------------------------------------------------------------------- 1 | package example.avaje.notblank; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public record ANotBlank( 8 | @NotBlank 9 | String basic, 10 | @NotBlank(max = 4) 11 | String withMax, 12 | 13 | @NotBlank(max = 4, message = "NotBlank n max 4") 14 | String withCustom 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/AShip2.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.NotEmpty; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.util.List; 8 | 9 | @Valid 10 | public record AShip2( 11 | @NotBlank String name, 12 | @Valid @NotEmpty List crew // not empty + cascade 13 | ) { } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/AShip3.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.NotEmpty; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.util.List; 8 | 9 | @Valid 10 | public record AShip3( 11 | @NotBlank String name, 12 | @NotEmpty List crew // not empty but no cascade validation 13 | ) { } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/APrimitiveMax.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Max; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record APrimitiveMax( 8 | 9 | @Max(3) byte abyte, 10 | @Max(3) short ashort, 11 | @Max(3) int aint, 12 | @Max(3) long along, 13 | @Max(3) double adouble, 14 | @Max(3) float afloat 15 | 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/APrimitiveMin.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Min; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record APrimitiveMin( 8 | 9 | @Min(3) byte abyte, 10 | @Min(3) short ashort, 11 | @Min(3) int aint, 12 | @Min(3) long along, 13 | @Min(3) double adouble, 14 | @Min(3) float afloat 15 | 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/nesting/Repro.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.nesting; 2 | 3 | import io.avaje.validation.constraints.NotNull; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | public class Repro { 7 | @Valid 8 | public static class Top { 9 | @NotNull public Nested nested; 10 | public static record Nested(String prop) {} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/AMaxCheck.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Max; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.BigInteger; 8 | 9 | @Valid 10 | public record AMaxCheck( 11 | @Max(4) Double aDouble, 12 | @Max(4) Float aFloat, 13 | @Max(4) BigDecimal decimal, 14 | @Max(4) BigInteger bigInt 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/AMinCheck.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Min; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.BigInteger; 8 | 9 | @Valid 10 | public record AMinCheck( 11 | @Min(4) Double aDouble, 12 | @Min(4) Float aFloat, 13 | @Min(4) BigDecimal decimal, 14 | @Min(4) BigInteger bigInt 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/CascadeGroup.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.NotNull; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public record CascadeGroup(@Valid(groups = {CascadeGroup.class}) @NotNull Cascaded name) { 9 | 10 | public record Cascaded(@NotBlank(groups = {CascadeGroup.class}) String val) {} 11 | } 12 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/positive/APrimitiveNegative.java: -------------------------------------------------------------------------------- 1 | package example.avaje.positive; 2 | 3 | import io.avaje.validation.constraints.Negative; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record APrimitiveNegative( 8 | 9 | @Negative byte abyte, 10 | @Negative short ashort, 11 | @Negative int aint, 12 | @Negative long along, 13 | @Negative double adouble, 14 | @Negative float afloat 15 | 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/positive/APrimitivePositive.java: -------------------------------------------------------------------------------- 1 | package example.avaje.positive; 2 | 3 | import io.avaje.validation.constraints.Positive; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record APrimitivePositive( 8 | 9 | @Positive byte abyte, 10 | @Positive short ashort, 11 | @Positive int aint, 12 | @Positive long along, 13 | @Positive double adouble, 14 | @Positive float afloat 15 | 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/daterange/DateRangeLocal.java: -------------------------------------------------------------------------------- 1 | package example.avaje.daterange; 2 | 3 | 4 | import io.avaje.validation.constraints.DateRange; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.time.Instant; 8 | import java.time.LocalDate; 9 | 10 | @Valid 11 | public record DateRangeLocal( 12 | @DateRange(min = "-P2D", max = "P2D") LocalDate value, 13 | @DateRange(min = "-P2D", max = "P2D") Instant instant 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/length/ALength.java: -------------------------------------------------------------------------------- 1 | package example.avaje.length; 2 | 3 | import io.avaje.validation.constraints.Length; 4 | import jakarta.validation.Valid; 5 | 6 | @Valid 7 | public record ALength( 8 | @Length(min = 1, max = 3) 9 | String both, 10 | @Length(max = 5) 11 | String onlyMax, 12 | @Length(min = 2) 13 | String onlyMin, 14 | @Length(min = 2, max = 10, message = "Custom length message") 15 | String withMsg 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Recursive.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import java.util.List; 4 | 5 | import io.avaje.validation.constraints.NotNull; 6 | import io.avaje.validation.constraints.Valid; 7 | 8 | @Valid 9 | public record Recursive( 10 | @NotNull String name, 11 | @Valid @NotNull Recursive child, 12 | @Valid Recursive[] array, 13 | @Valid List list) {} 14 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/daterange/DateRangeNow.java: -------------------------------------------------------------------------------- 1 | package example.avaje.daterange; 2 | 3 | 4 | import io.avaje.validation.constraints.DateRange; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.time.LocalDate; 8 | import java.time.YearMonth; 9 | 10 | @Valid 11 | public record DateRangeNow( 12 | @DateRange(min = "now", max = "P3M") LocalDate shipDate, 13 | @DateRange(min = "-P120Y", max = "-P3Y") YearMonth yearOfBirth 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /validator-spring-starter/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module io.avaje.validation.spring { 2 | 3 | exports io.avaje.validation.spring.aspect; 4 | exports io.avaje.validation.spring.validator; 5 | 6 | requires transitive io.avaje.validation; 7 | requires transitive jakarta.inject; 8 | requires transitive org.aspectj.weaver; 9 | requires transitive spring.beans; 10 | requires transitive spring.context; 11 | requires transitive spring.boot.autoconfigure; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/groups/Default.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.groups; 2 | 3 | /** 4 | * Default Validation group. 5 | * 6 | *

Unless a list of groups is explicitly defined: 7 | * 8 | *

    9 | *
  • constraints belong to the {@code Default} group 10 | *
  • validation applies to the {@code Default} group 11 | *
12 | * 13 | * Most structural constraints should belong to the default group. 14 | */ 15 | public interface Default {} 16 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/bool/ABoolTrue.java: -------------------------------------------------------------------------------- 1 | package example.avaje.bool; 2 | 3 | import io.avaje.validation.constraints.AssertTrue; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record ABoolTrue( 8 | @AssertTrue boolean primitive, 9 | @AssertTrue Boolean object 10 | ) { 11 | 12 | @AssertTrue public boolean primitiveMethod() { 13 | return primitive; 14 | } 15 | 16 | @AssertTrue public Boolean objectMethod() { 17 | return object; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/bool/ABoolFalse.java: -------------------------------------------------------------------------------- 1 | package example.avaje.bool; 2 | 3 | import io.avaje.validation.constraints.AssertFalse; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record ABoolFalse( 8 | 9 | @AssertFalse boolean primitive, 10 | @AssertFalse Boolean object 11 | ) { 12 | 13 | @AssertFalse public boolean primitiveMethod() { 14 | return primitive; 15 | } 16 | 17 | @AssertFalse public Boolean objectMethod() { 18 | return object; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/positive/APrimitiveNegativeOrZero.java: -------------------------------------------------------------------------------- 1 | package example.avaje.positive; 2 | 3 | import io.avaje.validation.constraints.NegativeOrZero; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record APrimitiveNegativeOrZero( 8 | 9 | @NegativeOrZero byte abyte, 10 | @NegativeOrZero short ashort, 11 | @NegativeOrZero int aint, 12 | @NegativeOrZero long along, 13 | @NegativeOrZero double adouble, 14 | @NegativeOrZero float afloat 15 | 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/positive/APrimitivePositiveOrZero.java: -------------------------------------------------------------------------------- 1 | package example.avaje.positive; 2 | 3 | import io.avaje.validation.constraints.PositiveOrZero; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record APrimitivePositiveOrZero( 8 | 9 | @PositiveOrZero byte abyte, 10 | @PositiveOrZero short ashort, 11 | @PositiveOrZero int aint, 12 | @PositiveOrZero long along, 13 | @PositiveOrZero double adouble, 14 | @PositiveOrZero float afloat 15 | 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/subtypes/sealed/ByQuerySelectorSealed.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.subtypes.sealed; 2 | 3 | import javax.validation.constraints.NotBlank; 4 | 5 | public final class ByQuerySelectorSealed implements SealedEntitySelector { 6 | 7 | @NotBlank private String query; 8 | 9 | public void setQuery(String query) { 10 | this.query = query; 11 | } 12 | 13 | public String getQuery() { 14 | return query; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/DefaultBootstrap.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import io.avaje.validation.Validator; 4 | 5 | /** Default bootstrap of Validator. */ 6 | public final class DefaultBootstrap { 7 | private DefaultBootstrap() {} 8 | 9 | /** Create the Validator.Builder. */ 10 | public static Validator.Builder builder() { 11 | return new DValidator.DBuilder(); 12 | } 13 | 14 | public static Validator instance() { 15 | return DValidator.DBuilder.DEFAULT; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/CheckCase.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import io.avaje.validation.constraints.Constraint; 4 | 5 | @Constraint 6 | public @interface CheckCase { 7 | 8 | String message() default "{io.avaje.validator.CheckCase}"; // default error message 9 | 10 | Class[] groups() default {}; // groups 11 | 12 | CaseMode value(); // specify case mode 13 | 14 | public enum CaseMode { 15 | UPPER, 16 | LOWER; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/APrimitiveRange.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.constraints.Range; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | @Valid 7 | public record APrimitiveRange( 8 | 9 | @Range(min = 1, max = 3) byte abyte, 10 | @Range(min = 1, max = 3) short ashort, 11 | @Range(min = 1, max = 3) int aint, 12 | @Range(min = 1, max = 3) long along, 13 | @Range(min = 1, max = 3) double adouble, 14 | @Range(min = 1, max = 3) float afloat 15 | 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/subtypes/sealed/ByIdSelectorSealed.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes.sealed; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import io.avaje.validation.constraints.NotEmpty; 7 | 8 | public final class ByIdSelectorSealed implements SealedEntitySelector { 9 | 10 | @NotEmpty private final List ids; 11 | 12 | public ByIdSelectorSealed(@NotEmpty List ids) { 13 | this.ids = ids; 14 | } 15 | 16 | public List getIds() { 17 | return ids; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/PrimitiveTest.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import io.avaje.validation.constraints.Constraint; 10 | 11 | @Retention(SOURCE) 12 | @Target(FIELD) 13 | @Constraint(unboxPrimitives = true) 14 | public @interface PrimitiveTest {} 15 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/CrossParamConstraint.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.RetentionPolicy.CLASS; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Marks a method annotation as a CrossParamConstraint used for validating multiple method 11 | * parameters 12 | */ 13 | @Retention(CLASS) 14 | @Target(ANNOTATION_TYPE) 15 | public @interface CrossParamConstraint {} 16 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/decimal/ALongMax.java: -------------------------------------------------------------------------------- 1 | package example.avaje.decimal; 2 | 3 | import io.avaje.validation.constraints.DecimalMax; 4 | import io.avaje.validation.constraints.DecimalMin; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public record ALongMax( 9 | 10 | @DecimalMax("4.5") 11 | long maxInc, 12 | @DecimalMax(value = "4.5", inclusive = false) 13 | long maxExc, 14 | @DecimalMin("4.5") 15 | long minInc, 16 | @DecimalMin(value = "4.5", inclusive = false) 17 | long minExc 18 | ) { 19 | } 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "maven" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | groups: 8 | dependencies: 9 | patterns: 10 | - "*" 11 | labels: 12 | - "dependencies" 13 | target-branch: "main" 14 | 15 | 16 | - package-ecosystem: "github-actions" 17 | directory: "/" 18 | schedule: 19 | interval: weekly 20 | commit-message: 21 | prefix: "[workflow]" 22 | labels: 23 | - "dependencies" 24 | target-branch: "main" 25 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/decimal/AFloatMax.java: -------------------------------------------------------------------------------- 1 | package example.avaje.decimal; 2 | 3 | import io.avaje.validation.constraints.DecimalMax; 4 | import io.avaje.validation.constraints.DecimalMin; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public record AFloatMax( 9 | 10 | @DecimalMax("4.5") 11 | float maxInc, 12 | @DecimalMax(value = "4.5", inclusive = false) 13 | float maxExc, 14 | @DecimalMin("4.5") 15 | float minInc, 16 | @DecimalMin(value = "4.5", inclusive = false) 17 | float minExc 18 | ) { 19 | } 20 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/subtypes/ByIdSelector.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.subtypes; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import io.avaje.validation.constraints.NotEmpty; 7 | 8 | public final class ByIdSelector implements EntitySelector { 9 | 10 | @NotEmpty private final List ids; 11 | 12 | public ByIdSelector(List ids) { 13 | this.ids = ids; 14 | } 15 | 16 | public List getIds() { 17 | return ids; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/daterange/DateRangeNowTolerance0.java: -------------------------------------------------------------------------------- 1 | package example.avaje.daterange; 2 | 3 | 4 | import io.avaje.validation.constraints.DateRange; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.time.*; 8 | 9 | /** 10 | * These types do not use the configured tolerance. 11 | */ 12 | @Valid 13 | public record DateRangeNowTolerance0( 14 | @DateRange(min = "now", max = "now") LocalDate ld, 15 | @DateRange(min = "now", max = "now") Year year, 16 | @DateRange(min = "now", max = "now") YearMonth ym 17 | ) { 18 | } 19 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/custom/MyCustomALong.java: -------------------------------------------------------------------------------- 1 | package example.avaje.custom; 2 | 3 | import io.avaje.validation.constraints.Constraint; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.Target; 7 | 8 | import static java.lang.annotation.ElementType.FIELD; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | @Retention(SOURCE) 12 | @Target(FIELD) 13 | @Constraint(unboxPrimitives = true) 14 | public @interface MyCustomALong { 15 | String message() default "{org.foo.MyCustomALong.message}"; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFuture.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.LocalDate; 6 | 7 | @Valid 8 | public class APastFuture { 9 | 10 | @Past 11 | public LocalDate past = LocalDate.now().minusDays(1); 12 | @PastOrPresent 13 | public LocalDate pastOrPresent = LocalDate.now().minusDays(1); 14 | @Future 15 | public LocalDate future = LocalDate.now().plusDays(1); 16 | @FutureOrPresent 17 | public LocalDate futureOrPresent = LocalDate.now().plusDays(1); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/adapter/ArrayValidationAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.adapter; 2 | 3 | final class ArrayValidationAdapter extends ContainerAdapter { 4 | 5 | ArrayValidationAdapter(ValidationAdapter adapters) { 6 | super(adapters); 7 | } 8 | 9 | @Override 10 | public boolean validate(T value, ValidationRequest req, String propertyName) { 11 | if (initalAdapter.validate(value, req, propertyName)) { 12 | 13 | validateArray((Object[]) value, req, propertyName); 14 | } 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test-native-image/src/main/java/org/example/Customer.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | 4 | import io.avaje.validation.constraints.NotBlank; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public class Customer { 9 | 10 | @NotBlank 11 | final String name; 12 | @NotBlank 13 | final String email; 14 | 15 | public Customer(String name, String email) { 16 | this.name = name; 17 | this.email = email; 18 | } 19 | 20 | public String name() { 21 | return name; 22 | } 23 | 24 | public String email() { 25 | return email; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/MethodTest.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.inject.aspect; 2 | 3 | import java.util.List; 4 | 5 | import io.avaje.validation.ValidMethod; 6 | import io.avaje.validation.constraints.NotEmpty; 7 | import io.avaje.validation.constraints.NotNull; 8 | import io.avaje.validation.constraints.Positive; 9 | import jakarta.inject.Singleton; 10 | 11 | @Singleton 12 | public class MethodTest { 13 | 14 | @ValidMethod 15 | public void test(@NotEmpty List<@NotNull String> str, @Positive int inty, String regular) {} 16 | } 17 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Captain.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import io.avaje.validation.constraints.NotNull; 4 | import jakarta.validation.Valid; 5 | 6 | public class Captain { 7 | 8 | private String name; 9 | 10 | @NotNull(message = "Not captain level") 11 | private Bankai bankai; 12 | 13 | public String name() { 14 | return name; 15 | } 16 | 17 | public Bankai bankai() { 18 | return bankai; 19 | } 20 | 21 | public record Bankai(String name, int forceMultiplier) {} 22 | } 23 | -------------------------------------------------------------------------------- /validator-spring-starter/src/test/java/io/avaje/validation/spring/aspect/ValidMethodClass.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spring.aspect; 2 | 3 | import java.util.List; 4 | 5 | import io.avaje.validation.ValidMethod; 6 | import io.avaje.validation.constraints.NotEmpty; 7 | import io.avaje.validation.constraints.NotNull; 8 | import io.avaje.validation.constraints.Positive; 9 | import jakarta.inject.Singleton; 10 | 11 | @Singleton 12 | public class ValidMethodClass { 13 | 14 | @ValidMethod 15 | public void test(@NotEmpty List<@NotNull String> str, @Positive int inty, String regular) {} 16 | } 17 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/decimal/ADoubleMax.java: -------------------------------------------------------------------------------- 1 | package example.avaje.decimal; 2 | 3 | import io.avaje.validation.constraints.DecimalMax; 4 | import io.avaje.validation.constraints.DecimalMin; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.math.BigDecimal; 8 | 9 | @Valid 10 | public record ADoubleMax( 11 | 12 | @DecimalMax("4.5") 13 | Double maxInc, 14 | @DecimalMax(value = "4.5", inclusive = false) 15 | Double maxExc, 16 | @DecimalMin("4.5") 17 | Double minInc, 18 | @DecimalMin(value = "4.5", inclusive = false) 19 | Double minExc 20 | ) { 21 | } 22 | -------------------------------------------------------------------------------- /validator-generator/src/main/java/io/avaje/validation/generator/TopPackage.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator; 2 | 3 | import java.util.Collection; 4 | 5 | final class TopPackage { 6 | 7 | private String topPackage; 8 | 9 | static String of(Collection values) { 10 | return new TopPackage(values).value(); 11 | } 12 | 13 | private String value() { 14 | return topPackage; 15 | } 16 | 17 | private TopPackage(Collection values) { 18 | for (final String pkg : values) { 19 | topPackage = ProcessorUtils.commonParent(topPackage, pkg); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | @io.avaje.inject.InjectModule(name="GeneratedModule") 2 | module blackbox.test { 3 | 4 | requires io.avaje.inject.aop; 5 | requires io.avaje.validation.http; 6 | requires io.avaje.validation.contraints; 7 | requires jakarta.validation; 8 | requires jakarta.inject; 9 | requires org.jspecify; 10 | 11 | provides io.avaje.validation.spi.ValidationExtension with example.avaje.pkg_private.PkgPrivateValidatorComponent, example.avaje.valid.GeneratedValidatorComponent; 12 | provides io.avaje.inject.spi.InjectExtension with example.avaje.GeneratedModule; 13 | } 14 | -------------------------------------------------------------------------------- /blackbox-test/src/main/resources/example/avaje/CustomMessages.properties: -------------------------------------------------------------------------------- 1 | example.avaje.MyKey.message=Invalid MyKey 2 | example.avaje.MySerial.message=Invalid my serial 3 | org.foo.MyCustomALong.message=Invalid special number 4 | 5 | signup.password.notblank1=Signup password must not be blank 6 | signup.password.size=Signup password size error 7 | signup.password.invalid=Signup password invalid 8 | signup.password.lowercase=Signup must have a lower case 9 | signup.password.uppercase=Signup must have at least 1 upper case 10 | signup.password.digit=Signup digit 11 | signup.password.special=Signup special character 12 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/subtypes/sealed/SealedEntitySelector.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes.sealed; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import example.avaje.subtypes.sealed.SealedEntitySelector.NestedSealed; 7 | import io.avaje.validation.ValidSubTypes; 8 | import io.avaje.validation.constraints.NotEmpty; 9 | 10 | @ValidSubTypes 11 | public sealed interface SealedEntitySelector 12 | permits ByQuerySelectorSealed, ByIdSelectorSealed, NestedSealed { 13 | 14 | public final record NestedSealed(@NotEmpty List ids) implements SealedEntitySelector {} 15 | } 16 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/cascade/MCustomerTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import io.avaje.validation.Validator; 7 | 8 | import java.time.LocalDate; 9 | 10 | class MCustomerTest { 11 | 12 | Validator validator = Validator.builder().build(); 13 | 14 | @Test 15 | void valid() { 16 | var customer = new MCustomer() 17 | .setName("Foo") 18 | .setActiveDate(LocalDate.now()) 19 | .setActive(true); 20 | 21 | validator.validate(customer); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/decimal/ADecimalStringMax.java: -------------------------------------------------------------------------------- 1 | package example.avaje.decimal; 2 | 3 | import io.avaje.validation.constraints.DecimalMax; 4 | import io.avaje.validation.constraints.DecimalMin; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.math.BigDecimal; 8 | 9 | @Valid 10 | public record ADecimalStringMax( 11 | 12 | @DecimalMax("4.5") 13 | String maxInc, 14 | @DecimalMax(value = "4.5", inclusive = false) 15 | String maxExc, 16 | @DecimalMin("4.5") 17 | String minInc, 18 | @DecimalMin(value = "4.5", inclusive = false) 19 | String minExc 20 | ) { 21 | } 22 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/decimal/ADecimalMax.java: -------------------------------------------------------------------------------- 1 | package example.avaje.decimal; 2 | 3 | import io.avaje.validation.constraints.DecimalMax; 4 | import io.avaje.validation.constraints.DecimalMin; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.math.BigDecimal; 8 | 9 | @Valid 10 | public record ADecimalMax( 11 | 12 | @DecimalMax("4.5") 13 | BigDecimal maxInc, 14 | @DecimalMax(value = "4.5", inclusive = false) 15 | BigDecimal maxExc, 16 | @DecimalMin("4.5") 17 | BigDecimal minInc, 18 | @DecimalMin(value = "4.5", inclusive = false) 19 | BigDecimal minExc 20 | ) { 21 | } 22 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/adapter/IterableValidationAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.adapter; 2 | 3 | final class IterableValidationAdapter extends ContainerAdapter { 4 | 5 | IterableValidationAdapter(ValidationAdapter adapters) { 6 | super(adapters); 7 | } 8 | 9 | @Override 10 | @SuppressWarnings("unchecked") 11 | public boolean validate(T value, ValidationRequest req, String propertyName) { 12 | if (initalAdapter.validate(value, req, propertyName)) { 13 | validateAll((Iterable) value, req, propertyName); 14 | } 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/Generated.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spi; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** Marks source code that has been generated. */ 10 | @Documented 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.CLASS) 13 | public @interface Generated { 14 | 15 | /** 16 | * The name of the generator used to generate this source. 17 | */ 18 | String value() default ""; 19 | } 20 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/crossfield/APassingSkill.java: -------------------------------------------------------------------------------- 1 | package example.avaje.crossfield; 2 | 3 | import io.avaje.validation.constraints.Constraint; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.Target; 7 | 8 | import static java.lang.annotation.ElementType.TYPE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | @Target(TYPE) 12 | @Retention(SOURCE) 13 | @Constraint 14 | public @interface APassingSkill { 15 | String message() default "put these foolish ambitions to rest"; // default error message 16 | 17 | Class[] groups() default {}; // groups 18 | } 19 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/hibernate/HContact.java: -------------------------------------------------------------------------------- 1 | package example.hibernate; 2 | 3 | 4 | import jakarta.validation.Valid; 5 | import jakarta.validation.constraints.NotEmpty; 6 | import jakarta.validation.constraints.Positive; 7 | 8 | @Valid 9 | public class HContact { 10 | 11 | @NotEmpty 12 | final String name; 13 | @Positive 14 | final long score; 15 | 16 | public HContact(String name, long score) { 17 | this.name = name; 18 | this.score = score; 19 | } 20 | 21 | public String name() { 22 | return name; 23 | } 24 | 25 | public long score() { 26 | return score; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /validator/src/test/java/io/avaje/validation/core/TestBuilder.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.avaje.validation.Validator; 8 | 9 | class TestBuilder { 10 | 11 | @Test 12 | void defaultBuilderReturnSameInstance() { 13 | 14 | assertThat(Validator.builder().build()).isSameAs(Validator.builder().build()); 15 | } 16 | 17 | @Test 18 | void changedBuilderNotSame() { 19 | 20 | assertThat(Validator.builder().failFast(true).build()).isNotSameAs(Validator.builder().build()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/ValidMethod.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import io.avaje.inject.aop.Aspect; 10 | 11 | /** Place on a method to execute validations on the parameters and return types */ 12 | @Aspect(ordering = 10) 13 | @Target(METHOD) 14 | @Retention(RUNTIME) 15 | public @interface ValidMethod { 16 | String locale() default ""; 17 | 18 | boolean throwOnParamFailure() default true; 19 | } 20 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/BasicMessageInterpolator.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import java.util.Map; 4 | 5 | import io.avaje.validation.spi.MessageInterpolator; 6 | 7 | final class BasicMessageInterpolator implements MessageInterpolator { 8 | 9 | @Override 10 | public String interpolate(String template, Map attributes) { 11 | String result = template; 12 | for (final Map.Entry entry : attributes.entrySet()) { 13 | result = result.replace('{' + entry.getKey() + '}', String.valueOf(entry.getValue())); 14 | } 15 | return result; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Contact.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import java.util.Optional; 4 | 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | @Valid 8 | public class Contact { 9 | public String firstName; 10 | public String lastName; 11 | 12 | @Valid 13 | public Optional
address; 14 | 15 | public Contact() { 16 | this.firstName = "fn"; 17 | this.lastName = "ln"; 18 | } 19 | 20 | public Contact(String firstName, String lastName) { 21 | this.firstName = firstName; 22 | this.lastName = lastName; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/cascade/RecursiveTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.avaje.validation.Validator; 8 | 9 | class RecursiveTest { 10 | 11 | Validator validator = Validator.builder().build(); 12 | 13 | @Test 14 | void valid() { 15 | var recurse = 16 | new Recursive("recursive1", new Recursive("recursive2", new Recursive("recursive3", null))); 17 | assertThat(validator.check(recurse).iterator().next()) 18 | .matches(c -> "child.child.child".equals(c.path())); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/nested/AContact.java: -------------------------------------------------------------------------------- 1 | package example.avaje.nested; 2 | 3 | import io.avaje.validation.constraints.NotNull; 4 | import jakarta.validation.Valid; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.Size; 7 | 8 | @Valid 9 | public class AContact { 10 | 11 | @NotBlank 12 | public String firstName; 13 | @Size(max = 5) 14 | public String lastName; 15 | 16 | @Valid 17 | @NotNull 18 | public AAddress address; 19 | 20 | public AContact(String firstName, String lastName) { 21 | this.firstName = firstName; 22 | this.lastName = lastName; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /validator-constraints/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.avaje 7 | avaje-validator-parent 8 | 2.16-RC1 9 | 10 | avaje-validator-constraints 11 | validator-constraints 12 | avaje validator constraint annotations 13 | 14 | 15 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/subtypes/sealed/ByIdSelectorSealed.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.subtypes.sealed; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import io.avaje.validation.constraints.NotEmpty; 7 | 8 | public final class ByIdSelectorSealed implements SealedEntitySelector { 9 | 10 | @NotEmpty 11 | private List<@io.avaje.validation.generator.models.valid.typeconstraint.TypeConstrained UUID> ids; 12 | 13 | public void setIds(List ids) { 14 | this.ids = ids; 15 | } 16 | 17 | public List getIds() { 18 | return ids; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/composable/PositiveContraint.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import io.avaje.validation.constraints.Constraint; 10 | import io.avaje.validation.constraints.Positive; 11 | 12 | @Positive 13 | @Constraint 14 | @Target(TYPE) 15 | @Retention(SOURCE) 16 | @DigitsContraint 17 | public @interface PositiveContraint { 18 | 19 | String message() default ""; 20 | 21 | Class[] groups() default {}; 22 | } 23 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/adapter/OptionalAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.adapter; 2 | 3 | import java.util.Optional; 4 | 5 | final class OptionalAdapter implements ValidationAdapter> { 6 | 7 | private final ValidationAdapter adapter; 8 | 9 | OptionalAdapter(ValidationAdapter adapter) { 10 | this.adapter = adapter; 11 | } 12 | 13 | @Override 14 | public boolean validate(Optional value, ValidationRequest req, String propertyName) { 15 | if (value == null) { 16 | return true; 17 | } 18 | 19 | value.ifPresent(v -> adapter.validate(v, req, propertyName)); 20 | return true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /validator-generator/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | import io.jstach.jstache.JStacheConfig; 2 | import io.jstach.jstache.JStacheType; 3 | 4 | @JStacheConfig(type = JStacheType.STACHE) 5 | module io.avaje.validation.generator { 6 | 7 | requires java.compiler; 8 | requires static io.avaje.validation; 9 | requires static io.avaje.prism; 10 | requires static io.avaje.http.api; 11 | requires static io.avaje.validation.contraints; 12 | requires static io.jstach.jstache; 13 | requires static java.validation; 14 | requires static jakarta.validation; 15 | 16 | provides javax.annotation.processing.Processor with io.avaje.validation.generator.ValidationProcessor; 17 | } 18 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/jakarta/AllSortsBean.java: -------------------------------------------------------------------------------- 1 | package example.jakarta; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.*; 5 | 6 | @Valid 7 | public class AllSortsBean { 8 | 9 | @NotNull 10 | public String myNotNull = "valid"; 11 | 12 | @NotBlank 13 | public String myNotBlank = "valid"; 14 | 15 | @NotEmpty 16 | public String myNotEmpty = "valid"; 17 | 18 | @Email 19 | public String myEmail = "valid@foo.com"; 20 | 21 | @AssertTrue 22 | public boolean myAssertTrue = true; 23 | 24 | @AssertFalse 25 | public boolean myAssertFalse = false; 26 | 27 | @Null 28 | public String myNull = null; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/PrimitiveTestAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import io.avaje.validation.adapter.ConstraintAdapter; 4 | import io.avaje.validation.adapter.PrimitiveAdapter; 5 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 6 | 7 | @ConstraintAdapter(PrimitiveTest.class) 8 | public final class PrimitiveTestAdapter extends PrimitiveAdapter { 9 | 10 | public PrimitiveTestAdapter(AdapterCreateRequest request) { 11 | super(request); 12 | } 13 | 14 | @Override 15 | public boolean isValid(Long object) { 16 | return false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/typeconstraint/FraudWatch.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.typeconstraint; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import io.avaje.validation.constraints.Constraint; 10 | 11 | @Constraint 12 | @Target(TYPE) 13 | @Retention(SOURCE) 14 | public @interface FraudWatch { 15 | String message() default "Frauds are not allowed"; // default error message 16 | 17 | Class[] groups() default {}; // groups 18 | } 19 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/nested/AAddress.java: -------------------------------------------------------------------------------- 1 | package example.avaje.nested; 2 | 3 | import io.avaje.validation.constraints.Valid; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Positive; 6 | import jakarta.validation.constraints.Size; 7 | 8 | @Valid 9 | public class AAddress { 10 | 11 | @NotBlank 12 | public final String line1; 13 | @Size(max = 4) 14 | public final String line2; 15 | @Positive 16 | public final long longValue; 17 | 18 | public AAddress(String line1, String line2, long longValue) { 19 | this.line1 = line1; 20 | this.line2 = line2; 21 | this.longValue = longValue; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /validator/src/test/java/io/avaje/validation/core/Contact.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import jakarta.validation.constraints.Size; 5 | 6 | //@Valid 7 | public class Contact { 8 | @NotBlank(groups = BasicTest.class) 9 | public String firstName; 10 | @Size(max = 5) 11 | public String lastName; 12 | 13 | public Address address; 14 | 15 | public Contact() { 16 | this.firstName = "fn"; 17 | this.lastName = "ln"; 18 | } 19 | public Contact(String firstName, String lastName) { 20 | this.firstName = firstName; 21 | this.lastName = lastName; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/composable/SansPositiveContraint.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.SOURCE; 6 | 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import jakarta.validation.Constraint; 11 | 12 | @Retention(SOURCE) 13 | @Target(FIELD) 14 | @Constraint(validatedBy = {}) 15 | @PositiveContraint(message = "ignored message") 16 | public @interface SansPositiveContraint { 17 | 18 | String message(); 19 | 20 | Class[] groups() default {}; 21 | } 22 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/nested/AContactWithNullable.java: -------------------------------------------------------------------------------- 1 | package example.avaje.nested; 2 | 3 | import org.jspecify.annotations.Nullable; 4 | 5 | import jakarta.validation.Valid; 6 | import jakarta.validation.constraints.NotBlank; 7 | import jakarta.validation.constraints.Size; 8 | 9 | @Valid 10 | public class AContactWithNullable { 11 | 12 | @NotBlank 13 | public String firstName; 14 | @Size(max = 5) 15 | public String lastName; 16 | 17 | @Valid 18 | @Nullable 19 | public AAddress address; 20 | 21 | public AContactWithNullable(String firstName, String lastName) { 22 | this.firstName = firstName; 23 | this.lastName = lastName; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /test-native-image/src/test/java/org/example/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import io.avaje.validation.ConstraintViolation; 4 | import io.avaje.validation.Validator; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Locale; 8 | import java.util.Set; 9 | 10 | class CustomerTest { 11 | 12 | @Test 13 | void test() { 14 | Validator validator = Validator.builder() 15 | .addLocales(Locale.GERMAN) 16 | .build(); 17 | 18 | var customer = new Customer("hello", ""); 19 | System.out.println("violations EN - " + validator.check(customer)); 20 | System.out.println("violations DE - " + validator.check(customer, Locale.GERMAN)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/typeconstraint/PassingSkill.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.typeconstraint; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import io.avaje.validation.constraints.Constraint; 10 | 11 | @Target(TYPE) 12 | @Retention(SOURCE) 13 | @Constraint 14 | public @interface PassingSkill { 15 | String message() default "put these foolish ambitions to rest"; // default error message 16 | 17 | Class[] groups() default {}; // groups 18 | } 19 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/MessageInterpolator.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spi; 2 | 3 | import java.util.Map; 4 | 5 | /** Reads an Annotation's attributes and the message template and interpolates the message */ 6 | public non-sealed interface MessageInterpolator extends ValidationExtension { 7 | 8 | /** 9 | * Interpolate the given message with the annotation attributes 10 | * 11 | * @param template The template loaded from annotation/resourceBundle 12 | * @param attributes The Constraint annotation's attributes 13 | * @return The interpolated validation error message 14 | */ 15 | String interpolate(String template, Map attributes); 16 | } 17 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/UUID.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import java.lang.annotation.*; 4 | 5 | import static java.lang.annotation.ElementType.*; 6 | 7 | /** 8 | * The annotated element must be a String validated to be a valid UUID. 9 | * 10 | *

Supported types are: 11 | * 12 | *

    13 | *
  • {@code String} 14 | *
  • {@code CharSequence} 15 | *
16 | */ 17 | @Constraint 18 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | public @interface UUID { 21 | String message() default "{avaje.UUID.message}"; 22 | 23 | Class[] groups() default {}; 24 | } 25 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/subtypes/sealed/SealedEntitySelector.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.subtypes.sealed; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import io.avaje.validation.ValidSubTypes; 7 | import io.avaje.validation.constraints.NotEmpty; 8 | import io.avaje.validation.generator.models.valid.subtypes.sealed.SealedEntitySelector.NestedSealed; 9 | 10 | @ValidSubTypes 11 | public sealed interface SealedEntitySelector 12 | permits ByQuerySelectorSealed, ByIdSelectorSealed, NestedSealed { 13 | 14 | public final record NestedSealed(@NotEmpty List ids) implements SealedEntitySelector {} 15 | } 16 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/method/MethodTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.method; 2 | 3 | import java.util.List; 4 | 5 | import io.avaje.http.api.Validator; 6 | import io.avaje.validation.ValidMethod; 7 | import jakarta.inject.Singleton; 8 | import jakarta.validation.constraints.NotEmpty; 9 | import jakarta.validation.constraints.NotNull; 10 | import jakarta.validation.constraints.Positive; 11 | 12 | @Singleton 13 | public class MethodTest { 14 | 15 | public MethodTest(Validator apiValidator) {} 16 | 17 | @NotNull 18 | @ValidMethod(throwOnParamFailure = false) 19 | String test(@NotEmpty List<@NotNull String> str, @Positive int inty, String regular) { 20 | return regular; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/methods/MethodTest.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.methods; 2 | 3 | import java.util.List; 4 | 5 | import io.avaje.inject.Component; 6 | import io.avaje.validation.ValidMethod; 7 | import io.avaje.validation.constraints.NotEmpty; 8 | import io.avaje.validation.constraints.Positive; 9 | import io.avaje.validation.constraints.Valid; 10 | import io.avaje.validation.generator.models.valid.CrewMate; 11 | 12 | @Component 13 | public class MethodTest { 14 | @NotEmpty 15 | @ValidMethod 16 | String test(@NotEmpty List<@Valid CrewMate> crew, @Positive int inty, String regular) { 17 | return regular; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/adapters/UuidAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core.adapters; 2 | 3 | import io.avaje.validation.adapter.AbstractConstraintAdapter; 4 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 5 | 6 | import java.util.UUID; 7 | 8 | final class UuidAdapter extends AbstractConstraintAdapter { 9 | 10 | UuidAdapter(AdapterCreateRequest request) { 11 | super(request); 12 | } 13 | 14 | @Override 15 | protected boolean isValid(Object value) { 16 | try { 17 | UUID.fromString(String.valueOf(value)); 18 | return true; 19 | } catch (IllegalArgumentException e) { 20 | return false; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/ANums.java: -------------------------------------------------------------------------------- 1 | package example.avaje; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.math.BigDecimal; 6 | 7 | @Valid 8 | public class ANums { 9 | 10 | @Digits(integer = 5, fraction = 3) 11 | public String digits = "1234.12"; 12 | 13 | @Digits(integer = 4, fraction = 2) 14 | public BigDecimal digitsDecimal; 15 | 16 | @Positive 17 | public int positive = 3; 18 | 19 | @PositiveOrZero 20 | public int positiveOrZero = 0; 21 | 22 | @Negative 23 | public int negative = -3; 24 | 25 | @NegativeOrZero 26 | public int negativeOrZero = 0; 27 | 28 | @Max(20) 29 | public int max = 0; 30 | 31 | @Min(5) 32 | public int min = 6; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/DMessage.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import io.avaje.validation.adapter.ValidationContext; 4 | 5 | import java.util.Map; 6 | 7 | record DMessage(String template, Map attributes, int dedupNumber) 8 | implements ValidationContext.Message { 9 | 10 | // templates can be the same across multiple adapters 11 | // these numbers ensure no cache collision 12 | private static int messageCounter = 0; 13 | 14 | DMessage(String template, Map attributes) { 15 | this(template, attributes, messageCounter++); 16 | } 17 | 18 | @Override 19 | public String lookupkey() { 20 | return template + dedupNumber; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/typeconstraint/TypeConstrained.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.typeconstraint; 2 | 3 | import static java.lang.annotation.RetentionPolicy.SOURCE; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import io.avaje.validation.constraints.Constraint; 10 | 11 | @Constraint //uncomment to fail compilation 12 | //(targets = String.class) 13 | @Target(ElementType.TYPE_USE) 14 | @Retention(SOURCE) 15 | public @interface TypeConstrained { 16 | String message() default ""; // default error message 17 | 18 | Class[] groups() default {}; // groups 19 | } 20 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/typeuse/Ship.java: -------------------------------------------------------------------------------- 1 | package example.avaje.typeuse; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import io.avaje.validation.constraints.NotBlank; 7 | import io.avaje.validation.constraints.NotNull; 8 | import io.avaje.validation.constraints.Valid; 9 | 10 | @Valid 11 | public record Ship( 12 | Map< 13 | @NotNull(message = "Names cannot be null") @NotBlank(message = "Names cannot be blank") 14 | String, 15 | @NotNull(message = "Values cannot be null") @Valid CrewMate> 16 | crew, 17 | List< 18 | @NotNull(message = "Tasks cannot be null") @NotBlank(message = "Tasks cannot be blank") 19 | String> 20 | tasks) {} 21 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/ValidatorCustomizer.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spi; 2 | 3 | import io.avaje.spi.ServiceProvider; 4 | import io.avaje.validation.Validator; 5 | 6 | /** 7 | * Callback interface that's used to customize a Validator.Builder. 8 | * 9 | *

These are service loaded when a Validator starts. The classes can be registered with {@link 10 | * ServiceProvider} or via a {@code provides} clause in module-info when using the java module 11 | * system. 12 | */ 13 | @FunctionalInterface 14 | public non-sealed interface ValidatorCustomizer extends ValidationExtension { 15 | 16 | /** Callback to customize a Validator.Builder instance. */ 17 | void customize(Validator.Builder builder); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Combining.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.SOURCE; 6 | 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import io.avaje.validation.constraints.Constraint; 11 | import io.avaje.validation.constraints.NotNull; 12 | 13 | @Target({FIELD}) 14 | @Retention(SOURCE) 15 | @Constraint 16 | @Combining2(message = "sus") 17 | @NotNull 18 | public @interface Combining { 19 | 20 | String message(); 21 | 22 | Class[] groups() default {}; 23 | } 24 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/composable/DigitsContraint.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import io.avaje.validation.constraints.Constraint; 10 | import io.avaje.validation.constraints.Digits; 11 | import io.avaje.validation.constraints.Negative; 12 | import io.avaje.validation.constraints.Positive; 13 | 14 | @Digits(integer = 2) 15 | @Constraint 16 | @Retention(SOURCE) 17 | @Target(TYPE) 18 | public @interface DigitsContraint { 19 | 20 | String message() default ""; 21 | 22 | Class[] groups() default {}; 23 | } 24 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/ImportValidPojo.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation; 2 | 3 | import static java.lang.annotation.ElementType.MODULE; 4 | import static java.lang.annotation.ElementType.PACKAGE; 5 | import static java.lang.annotation.ElementType.TYPE; 6 | import static java.lang.annotation.RetentionPolicy.SOURCE; 7 | 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * Specify external types for which to generate Valid Adapters. Use when you can't place a @Valid 13 | * annotation on an external type (such as a mvn/gradle dependency). 14 | */ 15 | @Retention(SOURCE) 16 | @Target({TYPE, PACKAGE, MODULE}) 17 | public @interface ImportValidPojo { 18 | 19 | Class[] value(); 20 | } 21 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/controller/MyControllerTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.controller; 2 | 3 | import io.avaje.validation.Validator; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | import static org.junit.jupiter.api.Assertions.assertThrows; 8 | 9 | class MyControllerTest { 10 | 11 | final Validator validator = Validator.builder().build(); 12 | 13 | @Test 14 | void expectNoValidator() { 15 | String message = assertThrows(IllegalArgumentException.class, () -> { 16 | validator.validate(new MyController()); 17 | }).getMessage(); 18 | 19 | assertThat(message).contains("No ValidationAdapter for class example.avaje.controller.MyController"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/jakarta/JNums.java: -------------------------------------------------------------------------------- 1 | package example.jakarta; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.*; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Valid 9 | public class JNums { 10 | 11 | @Digits(integer = 5, fraction = 3) 12 | public String digits = "1234.12"; 13 | 14 | @Digits(integer = 4, fraction = 2) 15 | public BigDecimal digitsDecimal; 16 | 17 | @Positive 18 | public int positive = 3; 19 | 20 | @PositiveOrZero 21 | public int positiveOrZero = 0; 22 | 23 | @Negative 24 | public int negative = -3; 25 | 26 | @NegativeOrZero 27 | public int negativeOrZero = 0; 28 | 29 | @Max(20) 30 | public int max = 0; 31 | 32 | @Min(5) 33 | public int min = 6; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/daterange/DateRangeNowTolerance.java: -------------------------------------------------------------------------------- 1 | package example.avaje.daterange; 2 | 3 | 4 | import io.avaje.validation.constraints.DateRange; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.time.Instant; 8 | import java.time.LocalDateTime; 9 | import java.time.OffsetDateTime; 10 | import java.time.ZonedDateTime; 11 | 12 | /** 13 | * These types use the configured tolerance when using 'now'. 14 | */ 15 | @Valid 16 | public record DateRangeNowTolerance( 17 | @DateRange(min = "now", max = "now") Instant instant, 18 | @DateRange(min = "now", max = "now") LocalDateTime ldt, 19 | @DateRange(min = "now", max = "now") OffsetDateTime odt, 20 | @DateRange(min = "now", max = "now") ZonedDateTime zdt 21 | ) { 22 | } 23 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/jakarta/JPastFuture.java: -------------------------------------------------------------------------------- 1 | package example.jakarta; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.Future; 5 | import jakarta.validation.constraints.FutureOrPresent; 6 | import jakarta.validation.constraints.Past; 7 | import jakarta.validation.constraints.PastOrPresent; 8 | 9 | import java.time.LocalDate; 10 | 11 | @Valid 12 | public class JPastFuture { 13 | 14 | @Past 15 | public LocalDate past = LocalDate.now().minusDays(1); 16 | @PastOrPresent 17 | public LocalDate pastOrPresent = LocalDate.now().minusDays(1); 18 | @Future 19 | public LocalDate future = LocalDate.now().plusDays(1); 20 | @FutureOrPresent 21 | public LocalDate futureOrPresent = LocalDate.now().plusDays(1); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/composable/MyKey.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import io.avaje.validation.constraints.Constraint; 4 | import io.avaje.validation.constraints.Pattern; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.*; 10 | import static java.lang.annotation.ElementType.TYPE_USE; 11 | import static java.lang.annotation.RetentionPolicy.SOURCE; 12 | 13 | @Pattern(regexp = "[A-Z0-9_]{2,8}") 14 | @Constraint 15 | @Retention(SOURCE) 16 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 17 | public @interface MyKey { 18 | 19 | String message() default "{example.avaje.MyKey.message}"; 20 | 21 | Class[] groups() default {}; 22 | } 23 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/mixin/CaptainMixin.java: -------------------------------------------------------------------------------- 1 | package example.avaje.mixin; 2 | 3 | import org.jspecify.annotations.Nullable; 4 | 5 | import example.avaje.mixin.Captain.Bankai; 6 | import io.avaje.validation.MixIn; 7 | import io.avaje.validation.constraints.NotBlank; 8 | import io.avaje.validation.constraints.Positive; 9 | import jakarta.validation.Valid; 10 | 11 | @MixIn(Captain.class) 12 | public abstract class CaptainMixin { 13 | 14 | @NotBlank private String name; 15 | 16 | // disables validation (kenpachi existed for a while) 17 | private Bankai bankai; 18 | 19 | @Valid 20 | @Nullable 21 | public abstract Bankai bankai(); 22 | 23 | @MixIn(Bankai.class) 24 | public record BankaiMixin(@NotBlank String name, @Positive int forceMultiplier) {} 25 | } 26 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Insect.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import java.util.Set; 4 | 5 | import org.jspecify.annotations.NullMarked; 6 | 7 | import io.avaje.validation.constraints.NotBlank; 8 | import io.avaje.validation.constraints.Size; 9 | import jakarta.validation.Valid; 10 | 11 | @Valid 12 | @NullMarked 13 | public record Insect(@NotBlank @Size(min = 1, max = 50) String name) { 14 | private static final Set FLYING = Set.of("Fly", "Butterfly"); 15 | private static final Set WALKING = Set.of("Ant", "Stick insect"); 16 | 17 | public Set associated() { 18 | if (FLYING.contains(name)) { 19 | return FLYING; 20 | } 21 | return WALKING; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/composable/MySerial.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import io.avaje.validation.constraints.Constraint; 4 | import io.avaje.validation.constraints.Length; 5 | import io.avaje.validation.constraints.Pattern; 6 | 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import static java.lang.annotation.ElementType.*; 11 | import static java.lang.annotation.RetentionPolicy.SOURCE; 12 | 13 | @Pattern(regexp = "[A-Z]+") 14 | @Length(max = 5) 15 | @Constraint 16 | @Retention(SOURCE) 17 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 18 | public @interface MySerial { 19 | 20 | String message() default "{example.avaje.MySerial.message}"; 21 | 22 | Class[] groups() default {}; 23 | } 24 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Polus.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import io.avaje.validation.constraints.NotBlank; 7 | import io.avaje.validation.constraints.NotNull; 8 | import io.avaje.validation.constraints.Valid; 9 | 10 | @Valid 11 | public record Polus( 12 | Map< 13 | @NotNull(message = "Names cannot be null") @NotBlank(message = "Names cannot be blank") 14 | String, 15 | @NotNull(message = "Values cannot be null") @Valid CrewMate> 16 | crew, 17 | List< 18 | @NotNull(message = "Tasks cannot be null") @NotBlank(message = "Tasks cannot be blank") 19 | String> 20 | tasks) {} 21 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureYear.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.Year; 6 | 7 | @Valid 8 | public class APastFutureYear { 9 | 10 | @Past 11 | public Year past = Year.now().minusYears(1); 12 | @PastOrPresent 13 | public Year pastOrPresent = Year.now().minusYears(1); 14 | @Future 15 | public Year future = Year.now().plusYears(1); 16 | @FutureOrPresent 17 | public Year futureOrPresent = Year.now().plusYears(1); 18 | 19 | APastFutureYear makeInvalid() { 20 | past = Year.now().plusYears(1); 21 | pastOrPresent = Year.now().plusYears(1); 22 | future = Year.now().minusYears(1); 23 | futureOrPresent = Year.now().minusYears(1); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Combining2.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.SOURCE; 6 | 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import io.avaje.validation.constraints.Constraint; 11 | import io.avaje.validation.constraints.NotEmpty; 12 | import io.avaje.validation.constraints.NotNull; 13 | 14 | @Target({TYPE, ANNOTATION_TYPE}) 15 | @Retention(SOURCE) 16 | @Constraint 17 | @NotEmpty 18 | public @interface Combining2 { 19 | 20 | String message(); 21 | 22 | Class[] groups() default {}; 23 | } 24 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/CaptainMixin.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import io.avaje.validation.MixIn; 4 | import io.avaje.validation.constraints.NotBlank; 5 | import io.avaje.validation.generator.models.valid.Captain.Bankai; 6 | import io.avaje.validation.generator.models.valid.typeconstraint.FraudWatch; 7 | import jakarta.validation.Valid; 8 | 9 | @FraudWatch 10 | @MixIn(Captain.class) 11 | public abstract class CaptainMixin { 12 | 13 | @NotBlank private String name; 14 | 15 | // disables validation (kenpachi existed for a while) 16 | private Bankai bankai; 17 | 18 | @Valid 19 | public abstract Bankai bankai(); 20 | 21 | @MixIn(Bankai.class) 22 | public record BankaiMixin(@NotBlank String name) {} 23 | } 24 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/notblank/MyNumericType.java: -------------------------------------------------------------------------------- 1 | package example.avaje.notblank; 2 | 3 | public final class MyNumericType extends Number { 4 | 5 | private final double value; 6 | 7 | private MyNumericType(double value) { 8 | this.value = value; 9 | } 10 | 11 | public static MyNumericType of(double value) { 12 | return new MyNumericType(value); 13 | } 14 | 15 | @Override 16 | public int intValue() { 17 | return Double.valueOf(value).intValue(); 18 | } 19 | 20 | @Override 21 | public long longValue() { 22 | return Double.valueOf(value).longValue(); 23 | } 24 | 25 | @Override 26 | public float floatValue() { 27 | return Double.valueOf(value).floatValue(); 28 | } 29 | 30 | @Override 31 | public double doubleValue() { 32 | return value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Ship.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import io.avaje.validation.constraints.NotBlank; 7 | import io.avaje.validation.constraints.NotEmpty; 8 | import io.avaje.validation.constraints.Valid; 9 | 10 | @Valid 11 | public record Ship( 12 | @NotEmpty(message = "sus ", groups = Ship.class) 13 | Map<@NotEmpty(groups = Ship.class) @NotBlank String, @Valid CrewMate> crew, 14 | @NotEmpty(message = "tasks,=(testing wierd chars&rparen; ") 15 | List< 16 | @NotEmpty(groups = List.class) 17 | @NotBlank(groups = Ship.class, message = "tasks,=(testing wierd chars&rparen; ") 18 | String> 19 | tasks) {} 20 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/optional/CurseBearer.java: -------------------------------------------------------------------------------- 1 | package example.avaje.optional; 2 | 3 | import java.util.Optional; 4 | import java.util.OptionalDouble; 5 | import java.util.OptionalInt; 6 | import java.util.OptionalLong; 7 | 8 | import io.avaje.validation.constraints.NotBlank; 9 | import io.avaje.validation.constraints.Positive; 10 | import io.avaje.validation.constraints.Valid; 11 | 12 | @Valid 13 | public record CurseBearer( 14 | @NotBlank(message = "it'll happen to you too") Optional name, 15 | @Positive OptionalInt estus, 16 | @Positive(message = "You Died") OptionalLong souls, 17 | @Positive(message = "you didn't pass the vigor check") OptionalDouble vigor, 18 | @Valid Optional ds) { 19 | 20 | public record DarkSign(@NotBlank(message = "not cursed") String brand) {} 21 | } 22 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/notblank/ATemporalOnlyCheck.java: -------------------------------------------------------------------------------- 1 | package example.avaje.notblank; 2 | 3 | import io.avaje.validation.constraints.Future; 4 | import io.avaje.validation.constraints.FutureOrPresent; 5 | import io.avaje.validation.constraints.Past; 6 | import io.avaje.validation.constraints.Valid; 7 | import jakarta.validation.constraints.PastOrPresent; 8 | 9 | import java.time.Instant; 10 | import java.time.LocalDateTime; 11 | import java.time.OffsetDateTime; 12 | import java.time.ZonedDateTime; 13 | 14 | @Valid 15 | public record ATemporalOnlyCheck 16 | ( 17 | @Past Instant prior, 18 | @Future OffsetDateTime after, 19 | @Future ZonedDateTime after2, 20 | @Future LocalDateTime after3, 21 | @Past java.util.Date utilDateOk, 22 | 23 | //@PastOrPresent 24 | String strVal 25 | ) { 26 | } 27 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/AnnotationFactory.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spi; 2 | 3 | import io.avaje.validation.adapter.ValidationAdapter; 4 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 5 | 6 | /** Factory for creating an Annotation Adapter for a given annotation. */ 7 | @FunctionalInterface 8 | public non-sealed interface AnnotationFactory extends ValidationExtension { 9 | 10 | /** 11 | * Create and return a ValidationAdapter given the type and annotations or return null. Returning 12 | * null means that the adapter could be created by another factory. 13 | * 14 | * @param request Holds the details used to create the adapter 15 | * @return The created validation adapter or null if not applicable 16 | */ 17 | ValidationAdapter create(AdapterCreateRequest request); 18 | } 19 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/typeconstraint/FraudAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.typeconstraint; 2 | 3 | import io.avaje.validation.adapter.AbstractConstraintAdapter; 4 | import io.avaje.validation.adapter.ConstraintAdapter; 5 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 6 | import io.avaje.validation.generator.models.valid.Captain; 7 | 8 | @ConstraintAdapter(FraudWatch.class) 9 | public final class FraudAdapter extends AbstractConstraintAdapter { 10 | 11 | public FraudAdapter(AdapterCreateRequest request) { 12 | super(request); 13 | } 14 | 15 | @Override 16 | public boolean isValid(Captain captain) { 17 | 18 | return !"ukitake".equals(captain.name()) || "Jakuhō Raikōben".equals(captain.bankai().name()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Avaje Validation API - see {@link io.avaje.validation.Validator}. 3 | * 4 | *

Example:

5 | * 6 | *
{@code
 7 |  * // Annotate classes with @Valid
 8 |  *
 9 |  * @Valid
10 |  * public class Address {
11 |  *
12 |  *   // annotate fields with constraints
13 |  *   @NotBlank
14 |  *   private String street;
15 |  *
16 |  *   @NotEmpty(message="must not be empty")
17 |  *   private List<@NotBlank String> owners; // message will be interpolated from bundle
18 |  *
19 |  *   //getters/setters
20 |  * }
21 |  *
22 |  * --------------------------------------------------
23 |  *
24 |  * final Validator validator = Validator.builder().build();
25 |  *
26 |  * Address address = ...;
27 |  * validator.validate(address);
28 |  *
29 |  * }
30 | */ 31 | package io.avaje.validation; 32 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/subtypes/EntitySelectorTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.List; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import io.avaje.validation.Validator; 10 | 11 | class EntitySelectorTest { 12 | 13 | Validator validator = Validator.builder().build(); 14 | 15 | @Test 16 | void validByIdSelector() { 17 | var entity = new SubtypeEntity(new ByIdSelector(List.of())); 18 | 19 | assertThat(validator.check(entity).iterator().next().message()).isEqualTo("must not be empty"); 20 | } 21 | 22 | @Test 23 | void validByIdQuery() { 24 | var entity = new SubtypeEntity(new ByQuerySelector("")); 25 | assertThat(validator.check(entity).iterator().next().message()).isEqualTo("must not be blank"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureInstant.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.Instant; 6 | 7 | @Valid 8 | public class APastFutureInstant { 9 | 10 | @Past 11 | public Instant past = Instant.now().minusSeconds(60); 12 | @PastOrPresent 13 | public Instant pastOrPresent = Instant.now().minusSeconds(60); 14 | @Future 15 | public Instant future = Instant.now().plusSeconds(60); 16 | @FutureOrPresent 17 | public Instant futureOrPresent = Instant.now().plusSeconds(60); 18 | 19 | APastFutureInstant makeInvalid() { 20 | past = Instant.now().plusSeconds(60); 21 | pastOrPresent = Instant.now().plusSeconds(60); 22 | future = Instant.now().minusSeconds(60); 23 | futureOrPresent = Instant.now().minusSeconds(60); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureLocalDate.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.LocalDate; 6 | 7 | @Valid 8 | public class APastFutureLocalDate { 9 | 10 | @Past 11 | public LocalDate past = LocalDate.now().minusDays(1); 12 | @PastOrPresent 13 | public LocalDate pastOrPresent = LocalDate.now().minusDays(1); 14 | @Future 15 | public LocalDate future = LocalDate.now().plusDays(1); 16 | @FutureOrPresent 17 | public LocalDate futureOrPresent = LocalDate.now().plusDays(1); 18 | 19 | APastFutureLocalDate makeInvalid() { 20 | past = LocalDate.now().plusDays(1); 21 | pastOrPresent = LocalDate.now().plusDays(1); 22 | future = LocalDate.now().minusDays(1); 23 | futureOrPresent = LocalDate.now().minusDays(1); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/typeconstraint/PassingSkillAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.typeconstraint; 2 | 3 | import io.avaje.validation.adapter.AbstractConstraintAdapter; 4 | import io.avaje.validation.adapter.ConstraintAdapter; 5 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 6 | 7 | @ConstraintAdapter(PassingSkill.class) 8 | public final class PassingSkillAdapter extends AbstractConstraintAdapter { 9 | 10 | public PassingSkillAdapter(AdapterCreateRequest request) { 11 | super(request); 12 | } 13 | 14 | @Override 15 | public boolean isValid(Tarnished lowlyTarnished) { 16 | if (lowlyTarnished == null) { 17 | return true; 18 | } 19 | return lowlyTarnished.vigor() >= 50 && lowlyTarnished.endurance() >= 50; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureYearMonth.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.YearMonth; 6 | 7 | @Valid 8 | public class APastFutureYearMonth { 9 | 10 | @Past 11 | public YearMonth past = YearMonth.now().minusMonths(1); 12 | @PastOrPresent 13 | public YearMonth pastOrPresent = YearMonth.now().minusMonths(1); 14 | @Future 15 | public YearMonth future = YearMonth.now().plusMonths(1); 16 | @FutureOrPresent 17 | public YearMonth futureOrPresent = YearMonth.now().plusMonths(1); 18 | 19 | APastFutureYearMonth makeInvalid() { 20 | past = YearMonth.now().plusMonths(1); 21 | pastOrPresent = YearMonth.now().plusMonths(1); 22 | future = YearMonth.now().minusMonths(1); 23 | futureOrPresent = YearMonth.now().minusMonths(1); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/adapter/MapValidationAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.adapter; 2 | 3 | import java.util.Map; 4 | 5 | final class MapValidationAdapter extends ContainerAdapter { 6 | 7 | private final boolean keys; 8 | 9 | MapValidationAdapter(ValidationAdapter adapters, boolean keys) { 10 | super(adapters); 11 | this.keys = keys; 12 | } 13 | 14 | @Override 15 | @SuppressWarnings("unchecked") 16 | public boolean validate(T value, ValidationRequest req, String propertyName) { 17 | final var map = (Map) value; 18 | 19 | if (initalAdapter.validate(value, req, propertyName)) { 20 | if (keys) { 21 | validateAll(map.keySet(), req, propertyName); 22 | } else { 23 | validateAll(map.values(), req, propertyName); 24 | } 25 | } 26 | 27 | return true; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureLocalTime.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.LocalTime; 6 | 7 | @Valid 8 | public class APastFutureLocalTime { 9 | 10 | @Past 11 | public LocalTime past = LocalTime.now().minusMinutes(1); 12 | @PastOrPresent 13 | public LocalTime pastOrPresent = LocalTime.now().minusMinutes(1); 14 | @Future 15 | public LocalTime future = LocalTime.now().plusMinutes(1); 16 | @FutureOrPresent 17 | public LocalTime futureOrPresent = LocalTime.now().plusMinutes(1); 18 | 19 | APastFutureLocalTime makeInvalid() { 20 | past = LocalTime.now().plusMinutes(1); 21 | pastOrPresent = LocalTime.now().plusMinutes(1); 22 | future = LocalTime.now().minusMinutes(1); 23 | futureOrPresent = LocalTime.now().minusMinutes(1); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/jdk-ea-stable.yml: -------------------------------------------------------------------------------- 1 | 2 | name: avaje-validator EA stable 3 | 4 | on: 5 | push: 6 | pull_request: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: '39 1 * * 1,3,5' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | 18 | steps: 19 | - uses: actions/checkout@v6 20 | - name: Set up Java 21 | uses: oracle-actions/setup-java@v1 22 | with: 23 | website: jdk.java.net 24 | release: ea 25 | version: stable 26 | - name: Maven cache 27 | uses: actions/cache@v5 28 | env: 29 | cache-name: maven-cache 30 | with: 31 | path: 32 | ~/.m2 33 | key: build-${{ env.cache-name }} 34 | - name: Maven version 35 | run: mvn --version 36 | - name: Build with Maven 37 | run: mvn package 38 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/subtypes/sealed/SealedEntitySelectorTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.subtypes.sealed; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.List; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import io.avaje.validation.Validator; 10 | 11 | class SealedEntitySelectorTest { 12 | 13 | Validator validator = Validator.builder().build(); 14 | 15 | @Test 16 | void validByIdSelector() { 17 | var entity = new SealedEntity(new ByIdSelectorSealed(List.of())); 18 | 19 | assertThat(validator.check(entity).iterator().next().message()).isEqualTo("must not be empty"); 20 | } 21 | 22 | @Test 23 | void validByIdQuery() { 24 | var entity = new SealedEntity(new ByQuerySelectorSealed("")); 25 | assertThat(validator.check(entity).iterator().next().message()).isEqualTo("must not be blank"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/optional/CurseBearer.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.optional; 2 | 3 | import java.util.Optional; 4 | import java.util.OptionalDouble; 5 | import java.util.OptionalInt; 6 | import java.util.OptionalLong; 7 | 8 | import io.avaje.validation.constraints.NotBlank; 9 | import io.avaje.validation.constraints.Positive; 10 | import io.avaje.validation.constraints.Valid; 11 | 12 | @Valid 13 | public record CurseBearer( 14 | @NotBlank(message = "it'll happen to you too") Optional name, 15 | @Positive OptionalInt estus, 16 | @Positive(message = "You Died") OptionalLong souls, 17 | @Positive(message = "you didn't pass the vigor check") OptionalDouble vigor, 18 | @Valid Optional ds) { 19 | 20 | public record DarkSign(@NotBlank(message = "not cursed") String brand) {} 21 | } 22 | -------------------------------------------------------------------------------- /validator-spring-starter/src/main/java/io/avaje/validation/spring/aspect/MethodValidationAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spring.aspect; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 9 | 10 | import io.avaje.validation.Validator; 11 | import io.avaje.validation.adapter.MethodAdapterProvider; 12 | 13 | @Configuration 14 | @EnableAspectJAutoProxy 15 | public class MethodValidationAutoConfiguration { 16 | 17 | @Bean 18 | public SpringAOPMethodValidator methodValidator(Validator validator, List providers) throws Exception { 19 | return new SpringAOPMethodValidator(validator, providers); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/AdapterFactory.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spi; 2 | 3 | import java.lang.reflect.Type; 4 | 5 | import io.avaje.validation.adapter.ValidationAdapter; 6 | import io.avaje.validation.adapter.ValidationContext; 7 | 8 | /** Factory for creating a ValidationAdapter for a given type. */ 9 | @FunctionalInterface 10 | public non-sealed interface AdapterFactory extends ValidationExtension { 11 | 12 | /** 13 | * Create and return a ValidationAdapter given the type and annotations or return null. Returning 14 | * null means that the adapter could be created by another factory. 15 | * 16 | * @param type The type for which the adapter is being created 17 | * @param ctx The validation context 18 | * @return The created validation adapter or null if not applicable 19 | */ 20 | ValidationAdapter create(Type type, ValidationContext ctx); 21 | } 22 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureOffsetTime.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.OffsetTime; 6 | 7 | @Valid 8 | public class APastFutureOffsetTime { 9 | 10 | @Past 11 | public OffsetTime past = OffsetTime.now().minusSeconds(60); 12 | @PastOrPresent 13 | public OffsetTime pastOrPresent = OffsetTime.now().minusSeconds(60); 14 | @Future 15 | public OffsetTime future = OffsetTime.now().plusSeconds(60); 16 | @FutureOrPresent 17 | public OffsetTime futureOrPresent = OffsetTime.now().plusSeconds(60); 18 | 19 | APastFutureOffsetTime makeInvalid() { 20 | past = OffsetTime.now().plusSeconds(60); 21 | pastOrPresent = OffsetTime.now().plusSeconds(60); 22 | future = OffsetTime.now().minusSeconds(60); 23 | futureOrPresent = OffsetTime.now().minusSeconds(60); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/crossfield/adapter/APassingSkillAdapter.java: -------------------------------------------------------------------------------- 1 | package example.avaje.crossfield.adapter; 2 | 3 | import example.avaje.crossfield.APassingSkill; 4 | import example.avaje.crossfield.ATarnished; 5 | import io.avaje.validation.adapter.AbstractConstraintAdapter; 6 | import io.avaje.validation.adapter.ConstraintAdapter; 7 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 8 | 9 | @ConstraintAdapter(APassingSkill.class) 10 | public final class APassingSkillAdapter extends AbstractConstraintAdapter { 11 | 12 | public APassingSkillAdapter(AdapterCreateRequest request) { 13 | super(request); 14 | } 15 | 16 | @Override 17 | public boolean isValid(ATarnished lowlyTarnished) { 18 | if (lowlyTarnished == null) { 19 | return true; 20 | } 21 | return lowlyTarnished.vigor() >= 50 && lowlyTarnished.endurance() >= 50; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/Valid.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.*; 7 | import static java.lang.annotation.RetentionPolicy.CLASS; 8 | 9 | /** 10 | * Marks a type for validation adapter generation 11 | * 12 | *

Additionally Marks a property, method parameter or method return type for validation 13 | * cascading. 14 | * 15 | *

Constraints defined on the object and its properties are validated when the property, method 16 | * parameter or method return type is validated. 17 | * 18 | *

This behavior is applied recursively. 19 | */ 20 | @Retention(CLASS) 21 | @Target({TYPE, TYPE_USE, FIELD}) 22 | public @interface Valid { 23 | 24 | /** Validation groups to use */ 25 | Class[] groups() default {}; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureLocalDateTime.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | @Valid 8 | public class APastFutureLocalDateTime { 9 | 10 | @Past 11 | public LocalDateTime past = LocalDateTime.now().minusDays(1); 12 | @PastOrPresent 13 | public LocalDateTime pastOrPresent = LocalDateTime.now().minusDays(1); 14 | @Future 15 | public LocalDateTime future = LocalDateTime.now().plusDays(1); 16 | @FutureOrPresent 17 | public LocalDateTime futureOrPresent = LocalDateTime.now().plusDays(1); 18 | 19 | APastFutureLocalDateTime makeInvalid() { 20 | past = LocalDateTime.now().plusDays(1); 21 | pastOrPresent = LocalDateTime.now().plusDays(1); 22 | future = LocalDateTime.now().minusDays(1); 23 | futureOrPresent = LocalDateTime.now().minusDays(1); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/jdk-ea.yml: -------------------------------------------------------------------------------- 1 | 2 | name: JDK EA 3 | 4 | on: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '48 0 * * 6' 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ${{ matrix.os }} 14 | permissions: 15 | contents: read 16 | packages: write 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | java_version: [GA,EA] 21 | os: [ubuntu-latest] 22 | 23 | steps: 24 | - uses: actions/checkout@v6 25 | - name: Set up Java 26 | uses: oracle-actions/setup-java@v1 27 | with: 28 | website: jdk.java.net 29 | release: ${{ matrix.java_version }} 30 | - name: Maven cache 31 | uses: actions/cache@v5 32 | env: 33 | cache-name: maven-cache 34 | with: 35 | path: 36 | ~/.m2 37 | key: build-${{ env.cache-name }} 38 | - name: Build with Maven 39 | run: mvn clean test 40 | 41 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureZDT.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.ZonedDateTime; 6 | 7 | @Valid 8 | public class APastFutureZDT { 9 | 10 | @Past 11 | public ZonedDateTime past = ZonedDateTime.now().minusSeconds(60); 12 | @PastOrPresent 13 | public ZonedDateTime pastOrPresent = ZonedDateTime.now().minusSeconds(60); 14 | @Future 15 | public ZonedDateTime future = ZonedDateTime.now().plusSeconds(60); 16 | @FutureOrPresent 17 | public ZonedDateTime futureOrPresent = ZonedDateTime.now().plusSeconds(60); 18 | 19 | APastFutureZDT makeInvalid() { 20 | past = ZonedDateTime.now().plusSeconds(60); 21 | pastOrPresent = ZonedDateTime.now().plusSeconds(60); 22 | future = ZonedDateTime.now().minusSeconds(60); 23 | futureOrPresent = ZonedDateTime.now().minusSeconds(60); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/custom/ACustomLongTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.custom; 2 | 3 | import io.avaje.validation.Validator; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.ArrayList; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | class ACustomLongTest { 11 | 12 | Validator validator = Validator.builder().addResourceBundles("example.avaje.CustomMessages").build(); 13 | 14 | @Test 15 | void valid() { 16 | validator.validate(new ACustomLong(4, 3L)); 17 | validator.validate(new ACustomLong(4, null)); 18 | } 19 | 20 | @Test 21 | void invalid() { 22 | var violations = new ArrayList<>(validator.check(new ACustomLong(3, 4L))); 23 | assertThat(violations).hasSize(2); 24 | assertThat(violations.get(0).message()).isEqualTo("Invalid special number"); 25 | assertThat(violations.get(1).message()).isEqualTo("Invalid special number"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureODT.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.OffsetDateTime; 6 | 7 | @Valid 8 | public class APastFutureODT { 9 | 10 | @Past 11 | public OffsetDateTime past = OffsetDateTime.now().minusSeconds(60); 12 | @PastOrPresent 13 | public OffsetDateTime pastOrPresent = OffsetDateTime.now().minusSeconds(60); 14 | @Future 15 | public OffsetDateTime future = OffsetDateTime.now().plusSeconds(60); 16 | @FutureOrPresent 17 | public OffsetDateTime futureOrPresent = OffsetDateTime.now().plusSeconds(60); 18 | 19 | APastFutureODT makeInvalid() { 20 | past = OffsetDateTime.now().plusSeconds(60); 21 | pastOrPresent = OffsetDateTime.now().plusSeconds(60); 22 | future = OffsetDateTime.now().minusSeconds(60); 23 | futureOrPresent = OffsetDateTime.now().minusSeconds(60); 24 | return this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/Constraint.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.RetentionPolicy.CLASS; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Marks an annotation as a constraint class. 11 | * Only annotations marked with {@code @Constraint} are composable. 12 | */ 13 | @Retention(CLASS) 14 | @Target({ANNOTATION_TYPE}) 15 | public @interface Constraint { 16 | 17 | /** Determines if the constraint can validate primitives without boxing */ 18 | boolean unboxPrimitives() default false; 19 | 20 | 21 | /** 22 | * The assignable types the constraint can be placed on. When the constraint 23 | * is placed on a type that cannot be assigned a compiler error will be thrown. 24 | */ 25 | Class[] targets() default {}; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v2 16 | with: 17 | github-token: "${{ secrets.GITHUB_TOKEN }}" 18 | - name: Approve a PR 19 | run: gh pr review --approve "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | # Enable for automerge 24 | - name: Enable auto-merge for Dependabot PRs 25 | run: gh pr merge --auto --merge "$PR_URL" 26 | env: 27 | PR_URL: ${{github.event.pull_request.html_url}} 28 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 29 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/past/APastFutureDate.java: -------------------------------------------------------------------------------- 1 | package example.avaje.past; 2 | 3 | import io.avaje.validation.constraints.*; 4 | 5 | import java.time.Instant; 6 | import java.util.Date; 7 | 8 | @Valid 9 | public class APastFutureDate { 10 | 11 | @Past 12 | public Date past = Date.from(Instant.now().minusSeconds(60)); 13 | @PastOrPresent 14 | public Date pastOrPresent = Date.from(Instant.now().minusSeconds(60)); 15 | @Future 16 | public Date future = Date.from(Instant.now().plusSeconds(60)); 17 | @FutureOrPresent 18 | public Date futureOrPresent = Date.from(Instant.now().plusSeconds(60)); 19 | 20 | APastFutureDate makeInvalid() { 21 | past = Date.from(Instant.now().plusSeconds(60)); 22 | pastOrPresent = Date.from(Instant.now().plusSeconds(60)); 23 | future = Date.from(Instant.now().minusSeconds(60)); 24 | futureOrPresent = Date.from(Instant.now().minusSeconds(60)); 25 | return this; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/custom/adapter/MyCustomALongAdapter.java: -------------------------------------------------------------------------------- 1 | package example.avaje.custom.adapter; 2 | 3 | import example.avaje.custom.MyCustomALong; 4 | import io.avaje.validation.adapter.ConstraintAdapter; 5 | import io.avaje.validation.adapter.PrimitiveAdapter; 6 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 7 | 8 | @ConstraintAdapter(MyCustomALong.class) 9 | public final class MyCustomALongAdapter extends PrimitiveAdapter { 10 | 11 | public MyCustomALongAdapter(AdapterCreateRequest request) { 12 | super(request); 13 | } 14 | 15 | @Override 16 | public boolean isValid(long value) { 17 | return value == 4; 18 | // assert on primitive long different to object Long to show 19 | // that we generally need both isValid() method implemented appropriately 20 | } 21 | 22 | @Override 23 | protected boolean isValid(Long value) { 24 | return value == null || value == 3; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /blackbox-test/src/test/resources/skip-value-test-data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "n0", 5 | "skip_string": "234" 6 | }, 7 | { 8 | "id": 2, 9 | "name": "n1", 10 | "skip_int": 139 11 | }, 12 | { 13 | "id": 3, 14 | "name": "n2", 15 | "skip_dec": 895.34 16 | }, 17 | { 18 | "id": 4, 19 | "node_id": "MDEwOlJlcG9zaXRvcnkxNTc2MDI1Mw==", 20 | "name": "n3", 21 | "skip_object": { 22 | "id": 208973, 23 | "node_id": "MDQ6VXNlcjIwODk3Mw==" 24 | } 25 | }, 26 | { 27 | "id": 5, 28 | "name": "n4", 29 | "skip_array": [1,2,3,4] 30 | }, 31 | { 32 | "id": 6, 33 | "name": "n5", 34 | "skip_null": null 35 | }, 36 | { 37 | "id": 7, 38 | "name": "n6", 39 | "skip_true": true 40 | }, 41 | { 42 | "id": 8, 43 | "name": "n7", 44 | "skip_false": false 45 | }, 46 | { 47 | "id": 9, 48 | "skip_string": "234", 49 | "name": "n8" 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/LocaleResolver.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | import java.util.Locale; 6 | import java.util.Set; 7 | 8 | import org.jspecify.annotations.Nullable; 9 | 10 | final class LocaleResolver { 11 | 12 | private final Locale defaultLocale; 13 | private final Set otherLocales = new HashSet<>(); 14 | 15 | LocaleResolver(Locale defaultLocale, Collection others) { 16 | this.defaultLocale = defaultLocale; 17 | otherLocales.addAll(others); 18 | } 19 | 20 | public Locale defaultLocale() { 21 | return defaultLocale; 22 | } 23 | 24 | public Set otherLocales() { 25 | return otherLocales; 26 | } 27 | 28 | public Locale resolve(@Nullable Locale requestLocale) { 29 | if (requestLocale == null || !otherLocales.contains(requestLocale)) { 30 | return defaultLocale; 31 | } 32 | return requestLocale; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [pull_request, workflow_dispatch] 4 | jobs: 5 | build: 6 | runs-on: ${{ matrix.os }} 7 | permissions: 8 | contents: read 9 | packages: write 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | java_version: [17,21] 14 | os: [ubuntu-latest] 15 | 16 | steps: 17 | - uses: actions/checkout@v6 18 | - name: Set up Java 19 | uses: actions/setup-java@v5 20 | with: 21 | java-version: ${{ matrix.java_version }} 22 | distribution: "zulu" 23 | - name: Maven cache 24 | uses: actions/cache@v5 25 | env: 26 | cache-name: maven-cache 27 | with: 28 | path: ~/.m2 29 | key: build-${{ env.cache-name }} 30 | - name: Maven version 31 | run: mvn --version 32 | - name: Build with Maven 33 | env: 34 | JAVA_VERSION: ${{ matrix.java_version }} 35 | run: mvn clean install 36 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/Null.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * The annotated element must be {@code null}. Accepts any type. 16 | * 17 | * @author Emmanuel Bernard 18 | */ 19 | @Constraint 20 | @Documented 21 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 22 | @Retention(RUNTIME) 23 | public @interface Null { 24 | 25 | String message() default "{avaje.Null.message}"; 26 | 27 | Class[] groups() default {}; 28 | } 29 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/cascade/CascadeGroupTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import example.avaje.cascade.CascadeGroup.Cascaded; 8 | import io.avaje.validation.Validator; 9 | 10 | class CascadeGroupTest { 11 | 12 | Validator validator = 13 | Validator.builder() 14 | .add(CascadeGroup.class, CascadeGroupValidationAdapter::new) 15 | .add(CascadeGroup.Cascaded.class, CascadeGroup$CascadedValidationAdapter::new) 16 | .build(); 17 | 18 | @Test 19 | void valid() { 20 | var value = new CascadeGroup(new Cascaded("")); 21 | assertThat(validator.check(value)).isEmpty(); 22 | } 23 | 24 | @Test 25 | void validGroup() { 26 | var value = new CascadeGroup(new Cascaded("")); 27 | assertThat(validator.check(value, CascadeGroup.class).iterator().next()) 28 | .matches(c -> "must not be blank".equals(c.message())); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/NotNull.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * The annotated element must not be {@code null}. Accepts any type. 16 | * 17 | * @author Emmanuel Bernard 18 | */ 19 | @Constraint 20 | @Documented 21 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 22 | @Retention(RUNTIME) 23 | public @interface NotNull { 24 | 25 | String message() default "{avaje.NotNull.message}"; 26 | 27 | Class[] groups() default {}; 28 | } 29 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/jakarta/JCustomer.java: -------------------------------------------------------------------------------- 1 | package example.jakarta; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import jakarta.validation.constraints.Size; 5 | import jakarta.validation.Valid; 6 | 7 | @Valid 8 | public class JCustomer { 9 | 10 | @NotBlank @Size(max = 5) 11 | final String name; 12 | 13 | @NotBlank @Size(max = 7, message = "My custom error message with max {max}") 14 | final String other; 15 | 16 | @Size(min = 2, max = 4) 17 | final String minMax; 18 | 19 | public JCustomer(String name, String other, String minMax) { 20 | this.name = name; 21 | this.other = other; 22 | this.minMax = minMax; 23 | } 24 | 25 | public JCustomer(String name, String other) { 26 | this(name, other, "val"); 27 | } 28 | 29 | public String getName() { 30 | return name; 31 | } 32 | 33 | public String getOther() { 34 | return other; 35 | } 36 | 37 | public String minMax() { 38 | return minMax; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/native-image.yml: -------------------------------------------------------------------------------- 1 | name: native image build 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '40 1 1 1 6' 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | permissions: 12 | contents: read 13 | packages: write 14 | strategy: 15 | fail-fast: true 16 | matrix: 17 | os: [ubuntu-latest] 18 | 19 | steps: 20 | - uses: actions/checkout@v6 21 | - uses: graalvm/setup-graalvm@v1 22 | with: 23 | java-version: '21' 24 | distribution: 'graalvm' 25 | cache: 'maven' 26 | github-token: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | - name: Versions 29 | run: | 30 | echo "GRAALVM_HOME: $GRAALVM_HOME" 31 | echo "JAVA_HOME: $JAVA_HOME" 32 | java --version 33 | native-image --version 34 | - name: Build with Maven 35 | run: | 36 | mvn clean install -DskipTests 37 | cd test-native-image 38 | mvn clean package -Pnative 39 | ./target/test-native-image 40 | -------------------------------------------------------------------------------- /validator-generator/src/main/java/io/avaje/validation/generator/Constants.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator; 2 | 3 | import java.util.Set; 4 | 5 | final class Constants { 6 | 7 | static final String META_INF_COMPONENT = 8 | "META-INF/services/io.avaje.validation.spi.ValidationExtension"; 9 | static final String META_INF_CUSTOMIZER = 10 | "META-INF/services/io.avaje.validation.spi.ValidatorCustomizer"; 11 | public static final String VALID_SPI = "io.avaje.validation.spi.*"; 12 | public static final String VALIDATOR = "io.avaje.validation.Validator"; 13 | public static final String COMPONENT = "io.avaje.inject.Component"; 14 | static final String SINGLETON_JAKARTA = "jakarta.inject.Singleton"; 15 | static final String SINGLETON_JAVAX = "javax.inject.Singleton"; 16 | 17 | public static final Set VALID_ANNOTATIONS = 18 | Set.of( 19 | AvajeValidPrism.PRISM_TYPE, 20 | HttpValidPrism.PRISM_TYPE, 21 | JavaxValidPrism.PRISM_TYPE, 22 | JakartaValidPrism.PRISM_TYPE); 23 | } 24 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/method/MethodTestTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.method; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatNoException; 5 | import static org.assertj.core.api.Assertions.fail; 6 | 7 | import java.util.List; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import io.avaje.inject.test.InjectTest; 12 | import io.avaje.validation.ConstraintViolationException; 13 | import jakarta.inject.Inject; 14 | 15 | @InjectTest 16 | class MethodTestTest { 17 | 18 | @Inject private MethodTest proxy; 19 | 20 | @Test 21 | void test() { 22 | assertThatNoException().isThrownBy(() -> proxy.test(List.of(""), 1, "result")); 23 | } 24 | 25 | @Test 26 | void invalid() { 27 | try { 28 | 29 | proxy.test(List.of(), 0, null); 30 | fail("how???"); 31 | } catch (final ConstraintViolationException e) { 32 | final var violations = e.violations(); 33 | 34 | assertThat(violations).hasSize(3); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/mixin/CaptainMixinTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.mixin; 2 | 3 | import static org.assertj.core.api.Assertions.assertThatNoException; 4 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 5 | 6 | import java.util.ArrayList; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import example.avaje.mixin.Captain.Bankai; 11 | import io.avaje.validation.Validator; 12 | 13 | class CaptainMixinTest { 14 | 15 | Validator validator = Validator.builder().build(); 16 | 17 | @Test 18 | void valid() { 19 | assertThatNoException().isThrownBy(() -> validator.validate(new Captain("kenpachi", null))); 20 | } 21 | 22 | @Test 23 | void invalidTrue() { 24 | var violations = new ArrayList<>(validator.check(new Captain("kenpachi", new Bankai("")))); 25 | violations.addAll(validator.check(new Captain(null, null))); 26 | assertThat(violations.get(0).message()).isEqualTo("must not be blank"); 27 | assertThat(violations.get(1).message()).isEqualTo("must not be blank"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/TemplateLookup.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import java.util.Locale; 4 | 5 | final class TemplateLookup { 6 | private final ResourceBundleManager bundleManager; 7 | 8 | TemplateLookup(ResourceBundleManager defaultBundle) { 9 | this.bundleManager = defaultBundle; 10 | } 11 | 12 | String lookup(String template, Locale resolvedLocale) { 13 | if (!isBundleKey(template)) { 14 | return template; 15 | } 16 | final String key = template.substring(1, template.length() - 1); 17 | final String msg = bundleManager.message(key, resolvedLocale); 18 | if (msg != null) { 19 | return lookup(msg, resolvedLocale); 20 | } 21 | return template; 22 | } 23 | 24 | private boolean isBundleKey(String template) { 25 | final int pos = template.indexOf('{'); 26 | if (pos != 0) { 27 | return false; 28 | } 29 | // is it a bundle lookup? 30 | return template.charAt(template.length() - 1) == '}' && template.indexOf('{', 1) == -1; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/adapter/PrimitiveOptional.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.adapter; 2 | 3 | import java.util.OptionalDouble; 4 | import java.util.OptionalInt; 5 | import java.util.OptionalLong; 6 | 7 | final class PrimitiveOptional extends ContainerAdapter { 8 | 9 | PrimitiveOptional(ValidationAdapter adapter) { 10 | super(adapter); 11 | } 12 | 13 | @Override 14 | @SuppressWarnings("unchecked") 15 | public boolean validate(T value, ValidationRequest req, String propertyName) { 16 | if (value == null) { 17 | return true; 18 | } 19 | if (value instanceof final OptionalInt i) { 20 | i.ifPresent(v -> initalAdapter.validate((T) (Integer) v, req, propertyName)); 21 | } else if (value instanceof final OptionalLong l) { 22 | l.ifPresent(v -> initalAdapter.validate((T) (Long) v, req, propertyName)); 23 | } else if (value instanceof final OptionalDouble d) { 24 | d.ifPresent(v -> initalAdapter.validate((T) (Double) v, req, propertyName)); 25 | } 26 | return true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/notblank/ANumberCheckTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.notblank; 2 | 3 | import io.avaje.validation.ConstraintViolation; 4 | import io.avaje.validation.ConstraintViolationException; 5 | import io.avaje.validation.Validator; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Locale; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.assertj.core.api.Assertions.fail; 14 | 15 | class ANumberCheckTest { 16 | 17 | final Validator validator = Validator.builder().addLocales(Locale.GERMAN).build(); 18 | 19 | @Test 20 | void valid() { 21 | var value = new ANumberCheck() 22 | .setCustomNumber(MyNumericType.of(3)) 23 | .setMyScore(1) 24 | .setNotNumericTypeHere("ok") 25 | .setSomeCollection(List.of()) 26 | .setNotNumericTypeHere("ok") 27 | .setActive(true) 28 | .setObjectActive(true) 29 | .setStringNotBoolean("blah"); 30 | 31 | validator.validate(value); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /validator/src/test/java/io/avaje/validation/core/Customer.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import jakarta.validation.constraints.Size; 8 | 9 | //@Valid 10 | public class Customer { 11 | boolean active; 12 | String name = ""; 13 | LocalDate activeDate; 14 | 15 | // Required | NotNull 16 | public Address billingAddress; 17 | // Optional | Nullable 18 | public Address shippingAddress; 19 | 20 | @Size(min = 0, max = 2) 21 | public List contacts = new ArrayList<>(); 22 | 23 | public Customer(boolean active, String name, LocalDate activeDate) { 24 | this(active, name, activeDate, "line1"); 25 | } 26 | 27 | public Customer(boolean active, String name, LocalDate activeDate, String line1) { 28 | this.active = active; 29 | this.name = name; 30 | this.activeDate = activeDate; 31 | if (line1 != null) { 32 | this.billingAddress = new Address(); 33 | this.billingAddress.line1 = line1; 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /validator-generator/src/main/java/io/avaje/validation/generator/package-info.java: -------------------------------------------------------------------------------- 1 | @GeneratePrism(io.avaje.http.api.Controller.class) 2 | @GeneratePrism(io.avaje.http.api.Path.class) 3 | @GeneratePrism(io.avaje.validation.adapter.ConstraintAdapter.class) 4 | @GeneratePrism(io.avaje.validation.ImportValidPojo.class) 5 | @GeneratePrism(io.avaje.validation.ValidSubTypes.class) 6 | @GeneratePrism(io.avaje.validation.spi.MetaData.class) 7 | @GeneratePrism(io.avaje.validation.spi.MetaData.ValidFactory.class) 8 | @GeneratePrism(io.avaje.validation.spi.MetaData.AnnotationFactory.class) 9 | @GeneratePrism(io.avaje.validation.MixIn.class) 10 | @GeneratePrism(org.jspecify.annotations.NullMarked.class) 11 | @GeneratePrism(org.jspecify.annotations.NullUnmarked.class) 12 | @GeneratePrism(org.jspecify.annotations.NonNull.class) 13 | @GeneratePrism(io.avaje.validation.ValidMethod.class) 14 | @JStacheConfig(type = JStacheType.STACHE) 15 | package io.avaje.validation.generator; 16 | 17 | import io.avaje.prism.GeneratePrism; 18 | import io.jstach.jstache.JStacheConfig; 19 | import io.jstach.jstache.JStacheType; 20 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/ConstraintViolationException.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation; 2 | 3 | import java.io.Serial; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | /** Exception holding a set of constraint violations. */ 8 | public final class ConstraintViolationException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = 1L; 12 | private final transient Set violations; 13 | private final transient List> groups; 14 | 15 | /** Create with the given constraint violations */ 16 | public ConstraintViolationException(String message, Set violations, List> groups) { 17 | super(message); 18 | this.violations = violations; 19 | this.groups = groups; 20 | } 21 | 22 | /** Return the constraint violations. */ 23 | public Set violations() { 24 | return violations; 25 | } 26 | 27 | /** Return the groups used for validations. */ 28 | public List> groups() { 29 | return groups; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/MixIn.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Marks this Class as a MixIn Type that can add/modify constraint annotations on the specified type. 11 | *

12 | * These types are typically in an external project / dependency or otherwise 13 | * types that we can't explicitly annotate or modify. 14 | *

15 | * In the example below, the VehicleMixin class augments the the generated Vehicle 16 | * adapter to add a @NotBlank annotation to the type property. 17 | * 18 | *

{@code
19 |  *
20 |  *   @MixIn(Vehicle.class)
21 |  *   public abstract class VehicleMixIn {
22 |  *
23 |  *   @NotBlank
24 |  *   private String type;
25 |  *    ...
26 |  *
27 |  * }
28 | */ 29 | @Target(TYPE) 30 | @Retention(SOURCE) 31 | public @interface MixIn { 32 | /** The concrete type to mix. */ 33 | Class value(); 34 | } 35 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/hibernate/HCustomer.java: -------------------------------------------------------------------------------- 1 | package example.hibernate; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Valid 11 | public class HCustomer { 12 | 13 | @NotBlank @Size(max = 5) 14 | final String name; 15 | 16 | @NotBlank @Size(max = 7, message = "My custom error message with max {max}") 17 | final String other; 18 | 19 | @Valid 20 | final List contacts = new ArrayList<>(); 21 | 22 | public HCustomer(String name, String other) { 23 | this.name = name; 24 | this.other = other; 25 | } 26 | public HCustomer(String name) { 27 | this.name = name; 28 | this.other = "valid"; 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public String getOther() { 36 | return other; 37 | } 38 | 39 | public List contacts() { 40 | return contacts; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/AssertFalse.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * The annotated element must be false. 15 | * Supported types are {@code boolean} and {@code Boolean}. 16 | *

17 | * {@code null} elements are considered valid. 18 | * 19 | * @author Emmanuel Bernard 20 | */ 21 | @Constraint(unboxPrimitives = true) 22 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 23 | @Retention(RetentionPolicy.RUNTIME) 24 | public @interface AssertFalse { 25 | String message() default "{avaje.AssertFalse.message}"; 26 | 27 | Class[] groups() default {}; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/AssertTrue.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * The annotated element must be true. 15 | * Supported types are {@code boolean} and {@code Boolean}. 16 | *

17 | * {@code null} elements are considered valid. 18 | * 19 | * @author Emmanuel Bernard 20 | */ 21 | @Constraint(unboxPrimitives = true) 22 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 23 | @Retention(RetentionPolicy.RUNTIME) 24 | public @interface AssertTrue { 25 | 26 | String message() default "{avaje.AssertTrue.message}"; 27 | 28 | Class[] groups() default {}; 29 | } 30 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/ACustomer.java: -------------------------------------------------------------------------------- 1 | package example.avaje; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.Size; 5 | import jakarta.validation.Valid; 6 | 7 | @Valid 8 | public class ACustomer { 9 | 10 | @NotBlank @Size(max = 5) 11 | final String name; 12 | 13 | @NotBlank @Size(max = 7, message = "My custom error message with max {max}") 14 | final String other; 15 | 16 | @Size(min = 2, max = 4) 17 | final String minMax; 18 | 19 | public ACustomer(String name, String other, String minMax) { 20 | this.name = name; 21 | this.other = other; 22 | this.minMax = minMax; 23 | } 24 | 25 | public ACustomer(String name, String other) { 26 | this(name, other, "val"); 27 | } 28 | 29 | public ACustomer(String name) { 30 | this(name, "valid"); 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public String getOther() { 38 | return other; 39 | } 40 | 41 | public String minMax() { 42 | return minMax; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test-native-image/src/main/java/org/example/Main.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import io.avaje.validation.ConstraintViolation; 4 | import io.avaje.validation.Validator; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Locale; 8 | import java.util.Set; 9 | 10 | public class Main { 11 | 12 | public static void main(String[] args) { 13 | Validator validator = Validator.builder() 14 | .addLocales(Locale.GERMAN) 15 | .build(); 16 | 17 | var customer = new Customer("hello", ""); 18 | System.out.println("violations EN - " + validator.check(customer)); 19 | System.out.println("violations DE - " + validator.check(customer, Locale.GERMAN)); 20 | 21 | if (!"must not be blank".equals(first(validator.check(customer)).message())) { 22 | System.exit(1); 23 | } 24 | if (!"darf nicht leer sein".equals(first(validator.check(customer, Locale.GERMAN)).message())) { 25 | System.exit(1); 26 | } 27 | } 28 | 29 | private static ConstraintViolation first(Set check) { 30 | return new ArrayList<>(check).get(0); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/CheckCaseAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import io.avaje.validation.adapter.AbstractConstraintAdapter; 4 | import io.avaje.validation.adapter.ConstraintAdapter; 5 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 6 | import io.avaje.validation.generator.models.valid.CheckCase.CaseMode; 7 | 8 | @ConstraintAdapter(CheckCase.class) 9 | public final class CheckCaseAdapter extends AbstractConstraintAdapter { 10 | 11 | private final CaseMode caseMode; 12 | 13 | public CheckCaseAdapter(AdapterCreateRequest request) { 14 | super(request); 15 | final var attributes = request.attributes(); 16 | caseMode = (CaseMode) attributes.get("caseMode"); 17 | } 18 | 19 | @Override 20 | public boolean isValid(String object) { 21 | if (object == null) { 22 | return true; 23 | } 24 | if (caseMode == CaseMode.UPPER) { 25 | return object.equals(object.toUpperCase()); 26 | } else { 27 | return object.equals(object.toLowerCase()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/ValidationExtension.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spi; 2 | 3 | import io.avaje.spi.Service; 4 | 5 | /** 6 | * Marker super interface for all validation Service Provider Interfaces (SPIs). 7 | * 8 | *

Implementations of this interface extend the validation system with custom adapters, 9 | * annotation handling, message interpolation, and other validation-related features. 10 | * 11 | *

Permitted subtypes include: 12 | * 13 | *

    14 | *
  • {@link AdapterFactory} - Provides adapters for validation logic. 15 | *
  • {@link AnnotationFactory} - Provides adapters for annotations. 16 | *
  • {@link GeneratedComponent} - Registry of generated validation adapters. 17 | *
  • {@link MessageInterpolator} - Interpolates validation messages. 18 | *
  • {@link ValidatorCustomizer} - Allows customization of the validator instance. 19 | *
20 | */ 21 | @Service 22 | public sealed interface ValidationExtension 23 | permits AdapterFactory, 24 | AnnotationFactory, 25 | GeneratedComponent, 26 | MessageInterpolator, 27 | ValidatorCustomizer {} 28 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/repeat/SignupRequest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.repeat; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Pattern; 6 | import jakarta.validation.constraints.Size; 7 | 8 | @Valid 9 | public class SignupRequest { 10 | 11 | @NotBlank(message = "{signup.password.notblank1}") 12 | @Size(min = 5, max = 32, message = "{signup.password.size}") 13 | @Pattern(regexp = "^[a-zA-Z0-9!@#$^&*]*$", message = "{signup.password.invalid}") 14 | @Pattern(regexp = ".*[a-z].*", message = "{signup.password.lowercase}") 15 | @Pattern(regexp = ".*[A-Z].*", message = "{signup.password.uppercase}") 16 | @Pattern(regexp = ".*[0-9].*", message = "{signup.password.digit}") 17 | @Pattern(regexp = ".*[!@#$^&*].*", message = "{signup.password.special}") 18 | private String password; 19 | 20 | public SignupRequest(String password) { 21 | this.password = password; 22 | } 23 | 24 | public String getPassword() { 25 | return password; 26 | } 27 | 28 | public void setPassword(String password) { 29 | this.password = password; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/spi/MetaData.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spi; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * For internal use, holds metadata on generated adapters for use by code generation (Java annotation processing). 10 | */ 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.CLASS) 13 | public @interface MetaData { 14 | 15 | /** 16 | * The generated ValidationAdapters. 17 | */ 18 | Class[] value(); 19 | 20 | /** 21 | * For internal use, holds metadata on generated adapters that also have factories. 22 | */ 23 | @interface ValidFactory { 24 | 25 | /** 26 | * The generated ValidationAdapters that have a factory. 27 | */ 28 | Class[] value(); 29 | } 30 | 31 | /** 32 | * For internal use, holds metadata on generated adapters that also have factories. 33 | */ 34 | @interface AnnotationFactory { 35 | 36 | /** 37 | * The custom Annotation ValidationAdapters. 38 | */ 39 | Class[] value(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/jspecify/JSpecifyTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.jspecify; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Locale; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import io.avaje.validation.ConstraintViolationException; 10 | import io.avaje.validation.Validator; 11 | 12 | public class JSpecifyTest { 13 | 14 | final Validator validator = 15 | Validator.builder() 16 | .add(JSpecifyNotNull.class, JSpecifyNotNullValidationAdapter::new) 17 | .add(JSpecifyNullUnmarked.class, JSpecifyNullUnmarkedValidationAdapter::new) 18 | .addLocales(Locale.GERMAN) 19 | .build(); 20 | 21 | @Test 22 | void valid() { 23 | var value = new JSpecifyNotNull("ok", "ok", "ok"); 24 | validator.validate(value); 25 | validator.validate(new JSpecifyNullUnmarked(null)); 26 | } 27 | 28 | @Test 29 | void inValidNull() { 30 | var value = new JSpecifyNotNull(null, null, null); 31 | try { 32 | validator.validate(value); 33 | } catch (ConstraintViolationException e) { 34 | assertThat(e.violations()).hasSize(2); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /validator-generator/src/main/java/io/avaje/validation/generator/GenericTypeMap.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | final class GenericTypeMap { 7 | 8 | private static final Map basic = new HashMap<>(); 9 | 10 | private GenericTypeMap() {} 11 | 12 | static { 13 | basic.put("char", "Character.TYPE"); 14 | basic.put("byte", "Byte.TYPE"); 15 | basic.put("boolean", "Boolean.TYPE"); 16 | basic.put("int", "Integer.TYPE"); 17 | basic.put("long", "Long.TYPE"); 18 | basic.put("short", "Short.TYPE"); 19 | basic.put("double", "Double.TYPE"); 20 | basic.put("float", "Float.TYPE"); 21 | 22 | basic.put("java.lang.Boolean", "Boolean.class"); 23 | basic.put("java.lang.Integer", "Integer.class"); 24 | basic.put("java.lang.Long", "Long.class"); 25 | basic.put("java.lang.Short", "Short.class"); 26 | basic.put("java.lang.Double", "Double.class"); 27 | basic.put("java.lang.Float", "Float.class"); 28 | basic.put("java.lang.String", "String.class"); 29 | } 30 | 31 | static String typeOfRaw(String rawType) { 32 | return basic.get(rawType); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/AMaxTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.ConstraintViolation; 4 | import io.avaje.validation.Validator; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.math.BigDecimal; 8 | import java.math.BigInteger; 9 | import java.util.ArrayList; 10 | import java.util.Set; 11 | 12 | import static java.math.BigDecimal.ONE; 13 | import static java.math.BigDecimal.TEN; 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | class AMaxTest { 17 | 18 | Validator validator = Validator.builder().build(); 19 | 20 | @Test 21 | void validNull() { 22 | validator.validate(new AMaxCheck(null, null, null, null)); 23 | } 24 | 25 | @Test 26 | void valid() { 27 | validator.validate(new AMaxCheck(1d, 1f, ONE, BigInteger.TWO)); 28 | } 29 | 30 | @Test 31 | void invalidMax() { 32 | var violations = new ArrayList<>(validator.check(new AMaxCheck(5d, 5f, TEN, BigInteger.TEN))); 33 | assertThat(violations).hasSize(4); 34 | for (ConstraintViolation violation : violations) { 35 | assertThat(violation.message()).isEqualTo("must be less than or equal to 4"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/clock/ClockTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.clock; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.fail; 5 | 6 | import java.time.LocalDate; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Locale; 10 | 11 | import org.junit.jupiter.api.Test; 12 | 13 | import io.avaje.validation.ConstraintViolation; 14 | import io.avaje.validation.ConstraintViolationException; 15 | import io.avaje.validation.Validator; 16 | 17 | class ClockTest { 18 | 19 | Validator validator = Validator.builder().build(); 20 | 21 | @Test 22 | void valid() { 23 | var ship = new Clocky(LocalDate.MIN); 24 | assertThat(violations(ship)).hasSize(1); 25 | } 26 | 27 | List violations(Object any) { 28 | return violations(any, Locale.ENGLISH); 29 | } 30 | 31 | List violations(Object any, Locale locale) { 32 | try { 33 | validator.validate(any, locale); 34 | fail("not expected"); 35 | return null; 36 | } catch (ConstraintViolationException e) { 37 | return new ArrayList<>(e.violations()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /validator-generator/src/main/java/io/avaje/validation/generator/BeanReader.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator; 2 | 3 | import java.util.Set; 4 | 5 | import javax.lang.model.element.Element; 6 | import javax.lang.model.element.TypeElement; 7 | 8 | interface BeanReader { 9 | 10 | void read(); 11 | 12 | void writeImports(Append writer, String adapterPackage); 13 | 14 | void writeFields(Append writer); 15 | 16 | void writeConstructor(Append writer); 17 | 18 | void writeValidatorMethod(Append writer); 19 | 20 | default boolean isPkgPrivate() { 21 | return false; 22 | } 23 | 24 | String shortName(); 25 | 26 | /** Return the short name of the element. */ 27 | default String shortName(Element element) { 28 | return element.getSimpleName().toString(); 29 | } 30 | 31 | default int genericTypeParamsCount() { 32 | return 0; 33 | } 34 | 35 | TypeElement beanType(); 36 | 37 | void cascadeTypes(Set extraTypes); 38 | 39 | default boolean nonAccessibleField() { 40 | return false; 41 | } 42 | 43 | default boolean hasValidationAnnotation() { 44 | return false; 45 | } 46 | 47 | default String contraintTarget() { 48 | return ""; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/range/AMinTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.range; 2 | 3 | import io.avaje.validation.ConstraintViolation; 4 | import io.avaje.validation.Validator; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.math.BigDecimal; 8 | import java.math.BigInteger; 9 | import java.util.ArrayList; 10 | 11 | import static java.math.BigDecimal.ONE; 12 | import static java.math.BigDecimal.TEN; 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | class AMinTest { 16 | 17 | Validator validator = Validator.builder().build(); 18 | 19 | @Test 20 | void validNull() { 21 | validator.validate(new AMinCheck(null, null, null, null)); 22 | } 23 | 24 | @Test 25 | void valid() { 26 | validator.validate(new AMinCheck(4d, 4f, new BigDecimal("4.0"), new BigInteger("4"))); 27 | } 28 | 29 | @Test 30 | void invalidMin() { 31 | var violations = new ArrayList<>(validator.check(new AMinCheck(3.9d, 3.9f, new BigDecimal("3.9"), new BigInteger("3")))); 32 | assertThat(violations).hasSize(4); 33 | for (ConstraintViolation violation : violations) { 34 | assertThat(violation.message()).isEqualTo("must be greater than or equal to 4"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /validator/src/test/java/io/avaje/validation/core/TemplateLookupTest.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.List; 6 | import java.util.Locale; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class TemplateLookupTest { 11 | 12 | private final TemplateLookup lookup; 13 | 14 | TemplateLookupTest() { 15 | final var localeResolver = new LocaleResolver(Locale.ENGLISH, List.of(Locale.GERMAN)); 16 | final var defaultResourceBundle = 17 | new ResourceBundleManager(List.of(), List.of(), localeResolver); 18 | this.lookup = new TemplateLookup(defaultResourceBundle); 19 | } 20 | 21 | @Test 22 | void lookupKnownKey() { 23 | final String key = "{avaje.AssertTrue.message}"; 24 | assertThat(lookup.lookup(key, Locale.ENGLISH)).isEqualTo("must be true"); 25 | assertThat(lookup.lookup(key, Locale.GERMAN)).isEqualTo("muss wahr sein"); 26 | } 27 | 28 | @Test 29 | void lookupUnknownKey() { 30 | final String key = "My literal msg"; 31 | assertThat(lookup.lookup(key, Locale.ENGLISH)).isEqualTo("My literal msg"); 32 | assertThat(lookup.lookup(key, Locale.GERMAN)).isEqualTo("My literal msg"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/release-merge.yml: -------------------------------------------------------------------------------- 1 | name: Release PR auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'github-actions[bot]'}} 12 | steps: 13 | - name: Check PR Title 14 | run: | 15 | PR_TITLE="${{ github.event.pull_request.title }}" 16 | REQUIRED_PREFIX="Release: Bump version to" 17 | 18 | if [[ ! "$PR_TITLE" == "$REQUIRED_PREFIX"* ]]; then 19 | echo "::error::PR title does not start with \"$REQUIRED_PREFIX\"" 20 | echo "Current PR title: \"$PR_TITLE\"" 21 | exit 1 22 | fi 23 | echo "PR title check passed." 24 | - name: Approve a PR 25 | run: gh pr review --approve "$PR_URL" 26 | env: 27 | PR_URL: ${{github.event.pull_request.html_url}} 28 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 29 | # Enable for automerge 30 | - name: Enable auto-merge for Dependabot PRs 31 | run: gh pr merge --auto --merge "$PR_URL" 32 | env: 33 | PR_URL: ${{github.event.pull_request.html_url}} 34 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 35 | -------------------------------------------------------------------------------- /validator-generator/src/main/java/io/avaje/validation/generator/Append.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | 6 | /** Helper that wraps a writer with some useful methods to append content. */ 7 | final class Append { 8 | 9 | private final Writer writer; 10 | 11 | Append(Writer writer) { 12 | this.writer = writer; 13 | } 14 | 15 | Append append(String content) { 16 | try { 17 | writer.append(content.replace("\"groups\",List.of(", "\"groups\",Set.of(")); 18 | return this; 19 | } catch (final IOException e) { 20 | throw new RuntimeException(e); 21 | } 22 | } 23 | 24 | void close() { 25 | try { 26 | writer.flush(); 27 | writer.close(); 28 | } catch (final IOException e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | Append eol() { 34 | try { 35 | writer.append("\n"); 36 | return this; 37 | } catch (final IOException e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | 42 | /** Append content with formatted arguments. */ 43 | Append append(String format, Object... args) { 44 | return append(String.format(format, args)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/adapter/ValidationRequest.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.adapter; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | import io.avaje.validation.ConstraintViolation; 7 | import io.avaje.validation.ConstraintViolationException; 8 | 9 | /** A validation request. */ 10 | public interface ValidationRequest { 11 | 12 | /** The groups tied to this ValidationRequest */ 13 | List> groups(); 14 | 15 | /** 16 | * Add a constraint violation for the given property. 17 | * 18 | * @param message The message 19 | * @param propertyName The property that failed the constraint 20 | */ 21 | void addViolation(ValidationContext.Message message, String propertyName); 22 | 23 | /** Push the nested property path. */ 24 | void pushPath(String path); 25 | 26 | /** Pop the nested property path. */ 27 | void popPath(); 28 | 29 | /** Throw ConstraintViolationException if there are violations in this request. */ 30 | void throwWithViolations() throws ConstraintViolationException; 31 | 32 | /** Return the violations */ 33 | Set violations(); 34 | 35 | /** return true if there are violations in this request. */ 36 | boolean hasViolations(); 37 | } 38 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/Customer.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.jspecify.annotations.Nullable; 8 | 9 | import io.avaje.validation.constraints.Valid; 10 | import jakarta.validation.constraints.NotNull; 11 | import jakarta.validation.constraints.Size; 12 | 13 | @Valid 14 | public class Customer { 15 | boolean active; 16 | String name = ""; 17 | LocalDate activeDate; 18 | 19 | @NotNull public Address billingAddress; 20 | 21 | // Optional | Nullable 22 | @Nullable public Address shippingAddress; 23 | 24 | @Size(min = 0, max = 2) 25 | public List contacts = new ArrayList<>(); 26 | 27 | public Customer(boolean active, String name, LocalDate activeDate) { 28 | this(active, name, activeDate, "line1"); 29 | } 30 | 31 | public Customer(boolean active, String name, LocalDate activeDate, String line1) { 32 | this.active = active; 33 | this.name = name; 34 | this.activeDate = activeDate; 35 | if (line1 != null) { 36 | this.billingAddress = new Address(); 37 | this.billingAddress.line1 = line1; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /validator/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Avaje Validation API - see {@link io.avaje.validation.Validator}. 3 | * 4 | *

Example:

5 | * 6 | *
{@code
 7 |  * // Annotate classes with @Valid
 8 |  *
 9 |  * @Valid
10 |  * public class Address {
11 |  *
12 |  *   // annotate fields with constraints
13 |  *   @NotBlank
14 |  *   private String street;
15 |  *
16 |  *   @NotEmpty(message="must not be empty")
17 |  *   private List<@NotBlank String> owners; // message will be interpolated from bundle
18 |  *
19 |  *   //getters/setters
20 |  * }
21 |  *
22 |  * --------------------------------------------------
23 |  *
24 |  * final Validator validator = Validator.builder().build();
25 |  *
26 |  * Address address = ...;
27 |  * validator.validate(address);
28 |  *
29 |  * }
30 | */ 31 | module io.avaje.validation { 32 | exports io.avaje.validation; 33 | exports io.avaje.validation.adapter; 34 | exports io.avaje.validation.groups; 35 | exports io.avaje.validation.spi; 36 | 37 | requires io.avaje.applog; 38 | requires static io.avaje.inject; 39 | requires static io.avaje.inject.aop; 40 | requires static io.avaje.spi; 41 | requires static transitive org.jspecify; 42 | 43 | uses io.avaje.validation.spi.ValidationExtension; 44 | } 45 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/bool/ABoolTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.bool; 2 | 3 | import io.avaje.validation.ConstraintViolation; 4 | import io.avaje.validation.Validator; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.ArrayList; 8 | 9 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 10 | 11 | class ABoolTest { 12 | 13 | Validator validator = Validator.builder().build(); 14 | 15 | @Test 16 | void valid() { 17 | validator.validate(new ABoolTrue(true, null)); 18 | validator.validate(new ABoolTrue(true, true)); 19 | validator.validate(new ABoolFalse(false, null)); 20 | validator.validate(new ABoolFalse(false, false)); 21 | } 22 | 23 | @Test 24 | void invalidTrue() { 25 | var violations = new ArrayList<>(validator.check(new ABoolTrue(false, false))); 26 | violations.stream() 27 | .map(ConstraintViolation::message) 28 | .forEach(msg -> assertThat(msg).isEqualTo("must be true")); 29 | } 30 | 31 | @Test 32 | void invalidFalse() { 33 | var violations = new ArrayList<>(validator.check(new ABoolFalse(true, true))); 34 | violations.stream() 35 | .map(ConstraintViolation::message) 36 | .forEach(msg -> assertThat(msg).isEqualTo("must be false")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/jakarta/JMyNumbers.java: -------------------------------------------------------------------------------- 1 | package example.jakarta; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.DecimalMax; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Valid 9 | public class JMyNumbers { 10 | 11 | @DecimalMax("10.50") 12 | final BigDecimal price; 13 | 14 | @DecimalMax(value = "9.30", inclusive = false) 15 | final BigDecimal priceInc; 16 | 17 | @DecimalMax("9.50") 18 | final double dprice; 19 | 20 | @DecimalMax(value = "8.30", inclusive = false) 21 | final double dpriceInc; 22 | 23 | public JMyNumbers(BigDecimal price, BigDecimal priceInc) { 24 | this.price = price; 25 | this.priceInc = priceInc; 26 | this.dprice = 1d; 27 | this.dpriceInc = 1d; 28 | } 29 | 30 | public JMyNumbers(double dprice, double dpriceInc) { 31 | this.price = BigDecimal.ONE; 32 | this.priceInc = BigDecimal.ONE; 33 | this.dprice = dprice; 34 | this.dpriceInc = dpriceInc; 35 | } 36 | 37 | public BigDecimal price() { 38 | return price; 39 | } 40 | 41 | public BigDecimal priceInc() { 42 | return priceInc; 43 | } 44 | 45 | public double dprice() { 46 | return dprice; 47 | } 48 | 49 | public double dpriceInc() { 50 | return dpriceInc; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/pkg_private/PackagePrivateTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.pkg_private; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.fail; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Locale; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | import io.avaje.validation.ConstraintViolation; 13 | import io.avaje.validation.ConstraintViolationException; 14 | import io.avaje.validation.Validator; 15 | 16 | class PackagePrivateTest { 17 | 18 | Validator validator = Validator.builder().build(); 19 | 20 | @Test 21 | void invalid() { 22 | var pkgPrivate = new PackagePrivate(-100); 23 | List violations = violations(pkgPrivate); 24 | 25 | assertThat(violations).hasSize(1); 26 | } 27 | 28 | List violations(Object any) { 29 | return violations(any, Locale.ENGLISH); 30 | } 31 | 32 | List violations(Object any, Locale locale) { 33 | try { 34 | validator.validate(any, locale); 35 | fail("not expected"); 36 | return null; 37 | } catch (ConstraintViolationException e) { 38 | return new ArrayList<>(e.violations()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/AMyNumbers.java: -------------------------------------------------------------------------------- 1 | package example.avaje; 2 | 3 | import io.avaje.validation.constraints.DecimalMax; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Valid 9 | public class AMyNumbers { 10 | 11 | @DecimalMax("10.50") 12 | final BigDecimal price; 13 | 14 | @DecimalMax(value = "9.30", inclusive = false) 15 | final BigDecimal priceInc; 16 | 17 | @DecimalMax("9.50") 18 | final double dprice; 19 | 20 | @DecimalMax(value = "8.30", inclusive = false) 21 | final double dpriceInc; 22 | 23 | public AMyNumbers(BigDecimal price, BigDecimal priceInc) { 24 | this.price = price; 25 | this.priceInc = priceInc; 26 | this.dprice = 1d; 27 | this.dpriceInc = 1d; 28 | } 29 | 30 | public AMyNumbers(double dprice, double dpriceInc) { 31 | this.price = BigDecimal.ONE; 32 | this.priceInc = BigDecimal.ONE; 33 | this.dprice = dprice; 34 | this.dpriceInc = dpriceInc; 35 | } 36 | 37 | public BigDecimal price() { 38 | return price; 39 | } 40 | 41 | public BigDecimal priceInc() { 42 | return priceInc; 43 | } 44 | 45 | public double dprice() { 46 | return dprice; 47 | } 48 | 49 | public double dpriceInc() { 50 | return dpriceInc; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/AMyMinNumbers.java: -------------------------------------------------------------------------------- 1 | package example.avaje; 2 | 3 | import jakarta.validation.Valid; 4 | import io.avaje.validation.constraints.DecimalMin; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Valid 9 | public class AMyMinNumbers { 10 | 11 | private static final BigDecimal VALID = new BigDecimal("20"); 12 | 13 | @DecimalMin("10.50") 14 | final BigDecimal price; 15 | 16 | @DecimalMin(value = "9.30", inclusive = false) 17 | final BigDecimal priceInc; 18 | 19 | @DecimalMin("9.50") 20 | final double dmin; 21 | 22 | @DecimalMin(value = "8.30", inclusive = false) 23 | final double dminInc; 24 | 25 | public AMyMinNumbers(BigDecimal price, BigDecimal priceInc) { 26 | this.price = price; 27 | this.priceInc = priceInc; 28 | this.dmin = 20; 29 | this.dminInc = 20; 30 | } 31 | 32 | public AMyMinNumbers(double dmin, double dminInc) { 33 | this.price = VALID; 34 | this.priceInc = VALID; 35 | this.dmin = dmin; 36 | this.dminInc = dminInc; 37 | } 38 | 39 | public BigDecimal price() { 40 | return price; 41 | } 42 | 43 | public BigDecimal priceInc() { 44 | return priceInc; 45 | } 46 | 47 | 48 | public double dmin() { 49 | return dmin; 50 | } 51 | 52 | public double dminInc() { 53 | return dminInc; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /blackbox-test/src/main/java/example/avaje/cascade/MCustomer.java: -------------------------------------------------------------------------------- 1 | package example.avaje.cascade; 2 | 3 | import io.avaje.validation.constraints.NotBlank; 4 | import io.avaje.validation.constraints.NotNull; 5 | import io.avaje.validation.constraints.Valid; 6 | 7 | import java.time.LocalDate; 8 | 9 | @Valid 10 | public class MCustomer { 11 | 12 | boolean active; 13 | 14 | @NotBlank(max = 20) 15 | String name; 16 | 17 | @NotNull 18 | LocalDate activeDate; 19 | 20 | @Valid 21 | MAddress billingAddress; 22 | 23 | public MCustomer setActive(boolean active) { 24 | this.active = active; 25 | return this; 26 | } 27 | 28 | public MCustomer setName(String name) { 29 | this.name = name; 30 | return this; 31 | } 32 | 33 | public MCustomer setActiveDate(LocalDate activeDate) { 34 | this.activeDate = activeDate; 35 | return this; 36 | } 37 | 38 | public MCustomer setBillingAddress(MAddress billingAddress) { 39 | this.billingAddress = billingAddress; 40 | return this; 41 | } 42 | 43 | public boolean active() { 44 | return active; 45 | } 46 | 47 | public String name() { 48 | return name; 49 | } 50 | 51 | public LocalDate activeDate() { 52 | return activeDate; 53 | } 54 | 55 | public MAddress billingAddress() { 56 | return billingAddress; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/adapters/InfinityNumberComparatorHelper.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core.adapters; 2 | 3 | import java.util.OptionalInt; 4 | 5 | /** @author Marko Bekhta */ 6 | final class InfinityNumberComparatorHelper { 7 | 8 | static final OptionalInt LESS_THAN = OptionalInt.of(-1); 9 | static final OptionalInt FINITE_VALUE = OptionalInt.empty(); 10 | static final OptionalInt GREATER_THAN = OptionalInt.of(1); 11 | 12 | private InfinityNumberComparatorHelper() {} 13 | 14 | static OptionalInt infinityCheck(Double number, OptionalInt treatNanAs) { 15 | OptionalInt result = FINITE_VALUE; 16 | if (number == Double.NEGATIVE_INFINITY) { 17 | result = LESS_THAN; 18 | } else if (number.isNaN()) { 19 | result = treatNanAs; 20 | } else if (number == Double.POSITIVE_INFINITY) { 21 | result = GREATER_THAN; 22 | } 23 | return result; 24 | } 25 | 26 | static OptionalInt infinityCheck(Float number, OptionalInt treatNanAs) { 27 | OptionalInt result = FINITE_VALUE; 28 | if (number == Float.NEGATIVE_INFINITY) { 29 | result = LESS_THAN; 30 | } else if (number.isNaN()) { 31 | result = treatNanAs; 32 | } else if (number == Float.POSITIVE_INFINITY) { 33 | result = GREATER_THAN; 34 | } 35 | return result; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/pkg_private/PackagePrivateTestClass.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid.pkg_private; 2 | 3 | import java.util.List; 4 | 5 | import javax.validation.constraints.Negative; 6 | import javax.validation.constraints.NotEmpty; 7 | 8 | import org.jspecify.annotations.Nullable; 9 | 10 | import io.avaje.validation.constraints.Pattern; 11 | import io.avaje.validation.constraints.RegexFlag; 12 | import jakarta.validation.Valid; 13 | import jakarta.validation.constraints.NotBlank; 14 | import jakarta.validation.constraints.NotNull; 15 | 16 | @Valid 17 | class PackagePrivateTestClass { 18 | 19 | @NotNull 20 | @NotBlank(message = "blankLmao") 21 | String alias; 22 | 23 | @Nullable String s; 24 | 25 | int i; 26 | @NotNull Integer integer; 27 | 28 | char ch; 29 | @NotNull Character chara; 30 | 31 | @NotEmpty List list; 32 | 33 | @Pattern( 34 | regexp = "ded", 35 | flags = {RegexFlag.CANON_EQ, RegexFlag.CASE_INSENSITIVE}) 36 | String getS() { 37 | return s; 38 | } 39 | 40 | @Negative(message = "message") 41 | int getI() { 42 | return i; 43 | } 44 | 45 | List getList() { 46 | return list; 47 | } 48 | 49 | void setList(List list) { 50 | this.list = list; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/NotEmpty.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * The annotated element must not be {@code null} nor empty. 16 | *

17 | * Supported types are: 18 | *

    19 | *
  • {@code CharSequence} (length of character sequence is evaluated)
  • 20 | *
  • {@code Collection} (collection size is evaluated)
  • 21 | *
  • {@code Map} (map size is evaluated)
  • 22 | *
  • Array (array length is evaluated)
  • 23 | *
24 | * 25 | * @author Hardy Ferentschik 26 | */ 27 | @Constraint 28 | @Documented 29 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 30 | @Retention(RUNTIME) 31 | public @interface NotEmpty { 32 | 33 | String message() default "{avaje.NotEmpty.message}"; 34 | 35 | Class[] groups() default {}; 36 | } 37 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/Length.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import java.lang.annotation.*; 4 | 5 | import static java.lang.annotation.ElementType.*; 6 | 7 | /** 8 | * The annotated string length must be between the specified boundaries (included). 9 | *

10 | * Supported types are: 11 | *

    12 | *
  • {@code CharSequence} (length of character sequence is evaluated) 13 | *
  • {@code String} (length of character sequence is evaluated) 14 | *
15 | */ 16 | @Constraint 17 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Repeatable(Length.Lengths.class) 20 | public @interface Length { 21 | 22 | String message() default "{avaje.Length.message}"; 23 | 24 | Class[] groups() default {}; 25 | 26 | /** Return size the string must be higher or equal to */ 27 | int min() default 0; 28 | 29 | /** Return size the string must be lower or equal to */ 30 | int max() default Integer.MAX_VALUE; 31 | 32 | /** 33 | * Defines several {@link Length} annotations on the same element. 34 | * 35 | * @see Length 36 | */ 37 | @Target({ElementType.METHOD, ElementType.FIELD}) 38 | @Retention(RetentionPolicy.RUNTIME) 39 | @Documented 40 | @interface Lengths { 41 | Length[] value(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /validator-generator/src/test/java/io/avaje/validation/generator/models/valid/AMyNumbers.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.generator.models.valid; 2 | 3 | import io.avaje.validation.constraints.DecimalMax; 4 | import io.avaje.validation.constraints.Valid; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Valid 9 | public class AMyNumbers { 10 | 11 | @DecimalMax("10.50") 12 | final BigDecimal price; 13 | 14 | @DecimalMax(value = "9.30", inclusive = false) 15 | final BigDecimal priceInc; 16 | 17 | @DecimalMax("9.50") 18 | final double dprice; 19 | 20 | @DecimalMax(value = "8.30", inclusive = false) 21 | final double dpriceInc; 22 | 23 | public AMyNumbers(BigDecimal price, BigDecimal priceInc) { 24 | this.price = price; 25 | this.priceInc = priceInc; 26 | this.dprice = 1d; 27 | this.dpriceInc = 1d; 28 | } 29 | 30 | public AMyNumbers(double dprice, double dpriceInc) { 31 | this.price = BigDecimal.ONE; 32 | this.priceInc = BigDecimal.ONE; 33 | this.dprice = dprice; 34 | this.dpriceInc = dpriceInc; 35 | } 36 | 37 | public BigDecimal price() { 38 | return price; 39 | } 40 | 41 | public BigDecimal priceInc() { 42 | return priceInc; 43 | } 44 | 45 | public double dprice() { 46 | return dprice; 47 | } 48 | 49 | public double dpriceInc() { 50 | return dpriceInc; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/ValidSubTypes.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Specify the subtypes that a given type can be represented as. 11 | *

12 | * This is used on an interface type, abstract type or type with inheritance 13 | * to indicate all the concrete subtypes that can represent the type. 14 | *

15 | * In the example below the abstract Vehicle type has 2 concrete subtypes 16 | * of Car and Truck that can represent the type. 17 | * 18 | *

{@code
19 |  *
20 |  *   @ValidSubTypes(Car.class, Truck.class)
21 |  *   public abstract class Vehicle {
22 |  *    ...
23 |  *
24 |  * }
25 | * 26 | * *

27 | * In the example below the abstract Vehicle type has 2 concrete subtypes 28 | * of Car and Truck that can represent the type. 29 | * 30 | *

{@code
31 |  *
32 |  *   @ValidSubTypes(Car.class, Truck.class)
33 |  *   public abstract class Vehicle {
34 |  *    ...
35 |  *
36 |  * }
37 | */ 38 | @Target(TYPE) 39 | @Retention(SOURCE) 40 | public @interface ValidSubTypes { 41 | 42 | /** Subclasses of the current type. Not needed for sealed classes */ 43 | Class[] value() default {}; 44 | } 45 | -------------------------------------------------------------------------------- /validator/src/test/java/io/avaje/validation/core/adapters/EmailTest.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core.adapters; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Map; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import io.avaje.validation.adapter.ValidationAdapter; 10 | import io.avaje.validation.adapter.ValidationContext; 11 | import io.avaje.validation.core.BasicTest; 12 | 13 | class EmailTest extends BasicTest { 14 | 15 | static final ValidationContext ctx = (ValidationContext) validator; 16 | 17 | @interface Email {} 18 | 19 | ValidationAdapter emailAdapter = ctx.adapter(Email.class, Map.of("message", "email")); 20 | 21 | @Test 22 | void continueOnInvalid_expect_false() { 23 | assertThat(emailAdapter.validate("notAnEmail", request, "foo")).isTrue(); 24 | } 25 | 26 | @Test 27 | void testNull() { 28 | assertThat(isValid(emailAdapter, null)).isTrue(); 29 | } 30 | 31 | @Test 32 | void testValid() { 33 | assertThat(isValid(emailAdapter, "someEmail@gmail.com")).isTrue(); 34 | } 35 | 36 | @Test 37 | void testBlank() { 38 | assertThat(isValid(emailAdapter, "")).isTrue(); 39 | assertThat(isValid(emailAdapter, " ")).isFalse(); 40 | } 41 | 42 | @Test 43 | void testInvalid() { 44 | assertThat(isValid(emailAdapter, "notAnEmail")).isFalse(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/URI.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import java.lang.annotation.*; 4 | 5 | import static java.lang.annotation.ElementType.*; 6 | 7 | /** 8 | * The annotated element must be a String validated to be a valid URI. 9 | * 10 | *

Supported types are: 11 | * 12 | *

    13 | *
  • {@code String} 14 | *
  • {@code CharSequence} 15 | *
16 | */ 17 | @Constraint 18 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | public @interface URI { 21 | 22 | /** Set a scheme to match. Per default any scheme is allowed. */ 23 | String scheme() default ""; 24 | 25 | /** Set the host to match, e.g. localhost. Per default any host is allowed. */ 26 | String host() default ""; 27 | 28 | /** Set the port to match, e.g. 80. Per default any port is allowed. */ 29 | int port() default -1; 30 | 31 | /** Set a regular expression to match against. Per default anything is allowed. */ 32 | String regexp() default ""; 33 | 34 | /** Used in combination with {@link #regexp()} in order to specify a regular expression option */ 35 | RegexFlag[] flags() default {}; 36 | 37 | /** Set the message to use. Per default uses the built-in message key. */ 38 | String message() default "{avaje.URI.message}"; 39 | 40 | Class[] groups() default {}; 41 | } 42 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/NegativeOrZero.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * The annotated element must be a negative number or 0. 16 | *

17 | * Supported types are: 18 | *

    19 | *
  • {@code BigDecimal}
  • 20 | *
  • {@code BigInteger}
  • 21 | *
  • {@code byte}, {@code short}, {@code int}, {@code long}, {@code float}, 22 | * {@code double} and their respective wrappers
  • 23 | *
24 | *

25 | * {@code null} elements are considered valid. 26 | * 27 | * @author Gunnar Morling 28 | */ 29 | @Constraint(unboxPrimitives = true) 30 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 31 | @Retention(RUNTIME) 32 | @Documented 33 | public @interface NegativeOrZero { 34 | 35 | String message() default "{avaje.NegativeOrZero.message}"; 36 | 37 | Class[] groups() default {}; 38 | } 39 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/composable/SansComposableTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.composable; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.fail; 5 | 6 | import java.util.Set; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import io.avaje.validation.ConstraintViolation; 11 | import io.avaje.validation.ConstraintViolationException; 12 | import io.avaje.validation.Validator; 13 | 14 | class SansComposableTest { 15 | 16 | final Validator validator = Validator.builder().build(); 17 | 18 | @Test 19 | void valid() { 20 | final var undertale = new Sans(10); 21 | validator.validate(undertale); 22 | } 23 | 24 | @Test 25 | void invalid() { 26 | var violations = violations(new Sans(-10)); 27 | 28 | violations.forEach( 29 | v -> assertThat(v.message()).isEqualTo("must have positive double digit amount of puns")); 30 | 31 | violations = violations(new Sans(-420)); 32 | 33 | violations.forEach( 34 | v -> assertThat(v.message()).isEqualTo("must have positive double digit amount of puns")); 35 | } 36 | 37 | Set violations(Object any) { 38 | try { 39 | validator.validate(any); 40 | fail("not expected"); 41 | return null; 42 | } catch (final ConstraintViolationException e) { 43 | return e.violations(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/NotBlank.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * The annotated element must not be {@code null} and must contain at least one 16 | * non-whitespace character. Accepts {@code CharSequence}. 17 | * 18 | * @author Hardy Ferentschik 19 | * @see Character#isWhitespace(char) 20 | */ 21 | @Constraint(targets = CharSequence.class) 22 | @Documented 23 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 24 | @Retention(RUNTIME) 25 | public @interface NotBlank { 26 | 27 | /** 28 | * Set the maximum length. By default, this is 0 (meaning unlimited). 29 | * This mimics the functionality of {@link Size} & {@link Length}, for a verbosity reduction. 30 | */ 31 | int max() default 0; 32 | 33 | String message() default "{avaje.NotBlank.message}"; 34 | 35 | Class[] groups() default {}; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/PositiveOrZero.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * The annotated element must be a positive number or 0. 16 | * 17 | *

Supported types are: 18 | * 19 | *

    20 | *
  • {@code BigDecimal} 21 | *
  • {@code BigInteger} 22 | *
  • {@code byte}, {@code short}, {@code int}, {@code long}, {@code float}, {@code double} and 23 | * their respective wrappers 24 | *
25 | * 26 | *

{@code null} elements are considered valid. 27 | * 28 | * @author Gunnar Morling 29 | * @since 2.0 30 | */ 31 | @Constraint(unboxPrimitives = true) 32 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 33 | @Retention(RUNTIME) 34 | @Documented 35 | public @interface PositiveOrZero { 36 | 37 | String message() default "{avaje.PositiveOrZero.message}"; 38 | 39 | Class[] groups() default {}; 40 | } 41 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/adapter/AbstractConstraintAdapter.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.adapter; 2 | 3 | import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; 4 | import io.avaje.validation.adapter.ValidationContext.Message; 5 | 6 | import java.util.Set; 7 | 8 | import org.jspecify.annotations.NonNull; 9 | 10 | /** Abstract Adapter that validates objects based on Constraint Annotations. */ 11 | public abstract class AbstractConstraintAdapter implements ValidationAdapter { 12 | 13 | protected final Message message; 14 | protected final Set> groups; 15 | 16 | /** Create given the create request */ 17 | protected AbstractConstraintAdapter(AdapterCreateRequest request) { 18 | this.message = request.message(); 19 | this.groups = request.groups(); 20 | } 21 | 22 | /** 23 | * Execute constraint validations for the given object. 24 | * 25 | * @param value the object to validate 26 | * @return false if a violation error should be added 27 | */ 28 | protected abstract boolean isValid(@NonNull T value); 29 | 30 | @Override 31 | public final boolean validate(T value, ValidationRequest req, String propertyName) { 32 | if (value == null || !checkGroups(groups, req)) { 33 | return true; 34 | } 35 | if (!isValid(value)) { 36 | req.addViolation(message, propertyName); 37 | } 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/jakarta/JMyMinNumbers.java: -------------------------------------------------------------------------------- 1 | package example.jakarta; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.DecimalMax; 5 | import jakarta.validation.constraints.DecimalMin; 6 | import jakarta.validation.constraints.Digits; 7 | 8 | import java.math.BigDecimal; 9 | 10 | @Valid 11 | public class JMyMinNumbers { 12 | 13 | private static final BigDecimal VALID = new BigDecimal("20"); 14 | 15 | @DecimalMin("10.50") 16 | final BigDecimal price; 17 | 18 | @DecimalMin(value = "9.30", inclusive = false) 19 | final BigDecimal priceInc; 20 | 21 | @DecimalMin("9.50") 22 | final double dmin; 23 | 24 | @DecimalMin(value = "8.30", inclusive = false) 25 | final double dminInc; 26 | 27 | public JMyMinNumbers(BigDecimal price, BigDecimal priceInc) { 28 | this.price = price; 29 | this.priceInc = priceInc; 30 | this.dmin = 20; 31 | this.dminInc = 20; 32 | } 33 | 34 | public JMyMinNumbers(double dmin, double dminInc) { 35 | this.price = VALID; 36 | this.priceInc = VALID; 37 | this.dmin = dmin; 38 | this.dminInc = dminInc; 39 | } 40 | 41 | public BigDecimal price() { 42 | return price; 43 | } 44 | 45 | public BigDecimal priceInc() { 46 | return priceInc; 47 | } 48 | 49 | 50 | public double dmin() { 51 | return dmin; 52 | } 53 | 54 | public double dminInc() { 55 | return dminInc; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/Negative.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * The annotated element must be a strictly negative number (i.e. 0 is considered as an 16 | * invalid value). 17 | *

18 | * Supported types are: 19 | *

    20 | *
  • {@code BigDecimal}
  • 21 | *
  • {@code BigInteger}
  • 22 | *
  • {@code byte}, {@code short}, {@code int}, {@code long}, {@code float}, 23 | * {@code double} and their respective wrappers
  • 24 | *
25 | *

26 | * {@code null} elements are considered valid. 27 | * 28 | * @author Gunnar Morling 29 | */ 30 | @Documented 31 | @Constraint(unboxPrimitives = true) 32 | @Retention(RUNTIME) 33 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 34 | public @interface Negative { 35 | 36 | String message() default "{avaje.Negative.message}"; 37 | 38 | Class[] groups() default {}; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /validator/src/main/java/io/avaje/validation/core/ValidationType.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import java.util.List; 4 | import java.util.Locale; 5 | import java.util.Set; 6 | 7 | import org.jspecify.annotations.Nullable; 8 | 9 | import io.avaje.validation.ConstraintViolation; 10 | import io.avaje.validation.ConstraintViolationException; 11 | import io.avaje.validation.adapter.ValidationAdapter; 12 | import io.avaje.validation.adapter.ValidationContext; 13 | import io.avaje.validation.adapter.ValidationRequest; 14 | 15 | final class ValidationType { 16 | 17 | private final ValidationContext ctx; 18 | private final ValidationAdapter adapter; 19 | 20 | ValidationType(ValidationContext validator, ValidationAdapter adapter) { 21 | this.ctx = validator; 22 | this.adapter = adapter; 23 | } 24 | 25 | void validate(T object, @Nullable Locale locale, List> groups) 26 | throws ConstraintViolationException { 27 | executeValidations(object, locale, groups).throwWithViolations(); 28 | } 29 | 30 | Set check(T object, @Nullable Locale locale, List> groups) { 31 | return executeValidations(object, locale, groups).violations(); 32 | } 33 | 34 | private ValidationRequest executeValidations(T object, Locale locale, List> groups) { 35 | final var req = ctx.request(locale, groups); 36 | adapter.validate(object, req); 37 | return req; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /blackbox-test/src/test/java/example/avaje/typeuse/DefaultValidatorProviderTest.java: -------------------------------------------------------------------------------- 1 | package example.avaje.typeuse; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | 6 | import org.junit.jupiter.api.BeforeAll; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import io.avaje.http.api.ValidationException; 10 | import io.avaje.http.api.Validator; 11 | import io.avaje.inject.spi.AvajeModule; 12 | import io.avaje.inject.spi.Builder; 13 | import io.avaje.inject.test.InjectTest; 14 | import jakarta.inject.Inject; 15 | 16 | @InjectTest 17 | class DefaultValidatorProviderTest { 18 | AvajeModule mod = 19 | new AvajeModule() { 20 | 21 | @Override 22 | public Class[] classes() { 23 | return new Class[] {}; 24 | } 25 | 26 | @Override 27 | public void build(Builder builder) {} 28 | }; 29 | 30 | @Inject private Validator validator; 31 | 32 | @BeforeAll 33 | static void setLocale() { 34 | System.setProperty("validation.locale.default", "en-us"); 35 | } 36 | 37 | @Test 38 | void test() { 39 | assertAll(() -> validator.validate(new CrewMate("hmm"), "en-GB,en;q=0.9,en-US;q=0.8,de;q=0.7")); 40 | } 41 | 42 | @Test 43 | void test2() { 44 | assertThrows( 45 | ValidationException.class, 46 | () -> validator.validate(new CrewMate(""), "en-GB,en;q=0.9,en-US;q=0.8,de;q=0.7")); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /validator-spring-starter/src/test/java/io/avaje/validation/spring/aspect/MethodValidationTest.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.spring.aspect; 2 | 3 | import static org.assertj.core.api.Assertions.assertThatNoException; 4 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 5 | 6 | import java.util.List; 7 | 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | import io.avaje.validation.ConstraintViolationException; 13 | import io.avaje.validation.Validator; 14 | import io.avaje.validation.spring.validator.AvajeValidatorAutoConfiguration; 15 | import jakarta.inject.Inject; 16 | 17 | @SpringBootTest( 18 | classes = { 19 | AvajeValidatorAutoConfiguration.class, 20 | MethodValidationAutoConfiguration.class, 21 | SpringAOPMethodValidator.class, 22 | TestParamProvider.class, 23 | ValidMethodClass.class, 24 | Validator.class 25 | }) 26 | class MethodValidationTest { 27 | 28 | @Inject private ValidMethodClass test; 29 | 30 | @BeforeAll 31 | static void setUpBeforeClass() throws Exception {} 32 | 33 | @Test 34 | void test() { 35 | assertThatNoException().isThrownBy(() -> test.test(List.of(""), 1, null)); 36 | } 37 | 38 | @Test 39 | void invalid() { 40 | assertThatThrownBy(() -> test.test(null, 0, null)) 41 | .isInstanceOf(ConstraintViolationException.class); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /validator/src/test/java/io/avaje/validation/core/NotNullLocaleTest.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.core; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 5 | 6 | import java.util.Locale; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import io.avaje.validation.ConstraintViolation; 11 | 12 | class NotNullLocaleTest extends BasicTest { 13 | 14 | @Test 15 | void testSize_DefaultLocale() { 16 | final var contact = new Contact("ok", null); 17 | final ConstraintViolation constraint = one(contact, Locale.ENGLISH); 18 | assertThat(constraint.message()).isEqualTo("must not be null"); 19 | } 20 | 21 | @Test 22 | void testSize_groups() { 23 | final var contact = new Contact("ok", null); 24 | assertThatThrownBy(()->one(contact, Locale.ENGLISH,BasicTest.class)).isExactlyInstanceOf(IllegalStateException.class); 25 | } 26 | 27 | @Test 28 | void testSize_DefaultLocale2() { 29 | final var contact = new Contact("ok", null); 30 | final ConstraintViolation constraint = one(contact, Locale.ENGLISH); 31 | assertThat(constraint.message()).isEqualTo("must not be null"); 32 | } 33 | 34 | @Test 35 | void testSize_DE() { 36 | final var contact = new Contact("ok", null); 37 | final ConstraintViolation constraint = one(contact, Locale.GERMAN); 38 | assertThat(constraint.message()).isEqualTo("darf nicht null sein"); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /validator-inject-plugin/src/test/java/io/avaje/validation/inject/aspect/TestMethodValidation.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.inject.aspect; 2 | 3 | import static java.util.stream.Collectors.toMap; 4 | import static org.assertj.core.api.Assertions.assertThatNoException; 5 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 6 | 7 | import java.util.List; 8 | 9 | import org.junit.jupiter.api.BeforeAll; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import io.avaje.validation.ConstraintViolationException; 13 | import io.avaje.validation.Validator; 14 | import io.avaje.validation.adapter.MethodAdapterProvider; 15 | import io.avaje.validation.adapter.ValidationContext; 16 | 17 | class TestMethodValidation { 18 | 19 | private static MethodTest proxy; 20 | 21 | @BeforeAll 22 | static void setUpBeforeClass() throws Exception { 23 | 24 | final var val = new AOPMethodValidator(); 25 | proxy = new MethodTest$Proxy(val); 26 | val.post( 27 | (ValidationContext) Validator.builder().build(), 28 | List.of(new TestParamProvider()).stream() 29 | .collect(toMap(MethodAdapterProvider::provide, p -> p))); 30 | } 31 | 32 | @Test 33 | void test() { 34 | assertThatNoException().isThrownBy(() -> proxy.test(List.of(""), 1, null)); 35 | } 36 | 37 | @Test 38 | void invalid() { 39 | assertThatThrownBy(() -> proxy.test(null, 0, null)) 40 | .isInstanceOf(ConstraintViolationException.class); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/Positive.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Repeatable; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * The annotated element must be a strictly positive number (i.e. 0 is considered as an invalid 17 | * value). 18 | * 19 | *

Supported types are: 20 | * 21 | *

    22 | *
  • {@code BigDecimal} 23 | *
  • {@code BigInteger} 24 | *
  • {@code byte}, {@code short}, {@code int}, {@code long}, {@code float}, {@code double} and 25 | * their respective wrappers 26 | *
27 | * 28 | *

{@code null} elements are considered valid. 29 | * 30 | * @author Gunnar Morling 31 | */ 32 | @Constraint(unboxPrimitives = true) 33 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 34 | @Retention(RUNTIME) 35 | @Documented 36 | public @interface Positive { 37 | 38 | String message() default "{avaje.Positive.message}"; 39 | 40 | Class[] groups() default {}; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /validator-constraints/src/main/java/io/avaje/validation/constraints/Email.java: -------------------------------------------------------------------------------- 1 | package io.avaje.validation.constraints; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE_USE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * The string has to be a well-formed email address. Exact semantics of what makes up a valid email 16 | * address are left to the provided Email Annotation ValidationAdapter providers. 17 | *

18 | * Accepts {@code CharSequence}. 19 | * {@code null} elements are considered valid. 20 | */ 21 | @Constraint 22 | @Documented 23 | @Retention(RUNTIME) 24 | @Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE}) 25 | public @interface Email { 26 | 27 | String message() default "{avaje.Email.message}"; 28 | 29 | Class[] groups() default {}; 30 | 31 | /** 32 | * An additional regular expression the annotated element must match. The default is any string 33 | * ('.*') 34 | */ 35 | String regexp() default ".*"; 36 | 37 | /** Used in combination with {@link #regexp()} in order to specify a regular expression option */ 38 | RegexFlag[] flags() default {}; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /validator/src/main/resources/io/avaje/validation/Messages_tr.properties: -------------------------------------------------------------------------------- 1 | avaje.AssertFalse.message = teyit ba\u015Far\u0131s\u0131z 2 | avaje.AssertTrue.message = teyit ba\u015Far\u0131s\u0131z 3 | avaje.DecimalMax.message = '{value}' de\u011Ferinden k\u00FC\u00E7\u00FCk yada e\u015Fit olmal\u0131 4 | avaje.DecimalMin.message = '{value}' de\u011Ferinden b\u00FCy\u00FCk yada e\u015Fit olmal\u0131 5 | avaje.Digits.message = s\u0131n\u0131rlar\u0131n d\u0131\u015F\u0131nda say\u0131sal de\u011Fer (beklenen <{integer} basamak>.<{fraction} basamak>) 6 | avaje.Email.message = d\u00FCzg\u00FCn bi\u00E7imli bir e-posta adresi de\u011Fil! 7 | avaje.Future.message = ileri bir tarih olmal\u0131 8 | avaje.Max.message = '{value}' de\u011Ferinden k\u00FC\u00E7\u00FCk yada e\u015Fit olmal\u0131 9 | avaje.Min.message = '{value}' de\u011Ferinden b\u00FCy\u00FCk yada e\u015Fit olmal\u0131 10 | avaje.NotBlank.message = bo\u015F de\u011Fer olamaz 11 | avaje.NotEmpty.message = bo\u015F de\u011Fer olamaz 12 | avaje.NotNull.message = bo\u015F de\u011Fer olamaz 13 | avaje.Null.message = bo\u015F de\u011Fer olmal\u0131 14 | avaje.Past.message = ge\u00E7mi\u015F bir tarih olmal\u0131 15 | avaje.Pattern.message = '{regexp}' ile e\u015Fle\u015Fmeli 16 | avaje.Size.message = boyut '{min}' ile '{max}' aras\u0131nda olmal\u0131 17 | 18 | avaje.Length.message = uzunluk '{min}' ile '{max}' aras\u0131nda olmal\u0131 19 | avaje.Range.message = {min} ve {max} aras\u0131nda olmal\u0131d\u0131r! 20 | --------------------------------------------------------------------------------