Skip to content

Commit

Permalink
Add guide (#37)
Browse files Browse the repository at this point in the history
Co-authored-by: Denis Stepanov <[email protected]>
  • Loading branch information
andriy-dmytruk and dstepanov authored Mar 2, 2023
1 parent 1c36f6b commit c68e4f6
Show file tree
Hide file tree
Showing 57 changed files with 1,663 additions and 10 deletions.
13 changes: 13 additions & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
plugins {
id 'groovy-gradle-plugin'
}

repositories {
gradlePluginPortal()
mavenCentral()
}

dependencies {
implementation libs.gradle.micronaut
implementation libs.gradle.kotlin
implementation libs.gradle.kotlin.allopen
implementation libs.gradle.kotlin.noarg
implementation libs.gradle.ksp
}
7 changes: 7 additions & 0 deletions buildSrc/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
libs {
from(files("../gradle/libs.versions.toml"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,3 @@ repositories {
maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/" }
}
}

configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ bomProperty=micronautValidationVersion

org.gradle.caching=true
org.gradle.jvmargs=-Xmx1g

37 changes: 34 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,52 @@
[versions]

micronaut = "4.0.0-SNAPSHOT"
micronaut-docs = "2.0.0"
micronaut-test = "3.7.0"
micronaut-test = "4.0.0-SNAPSHOT"
micronaut-reactor = "3.0.0-SNAPSHOT"
micronaut-rxjava2 = "2.0.0-SNAPSHOT"
micronaut-kotlin = "4.0.0-SNAPSHOT"

groovy = "4.0.7"
junit5 = "5.9.1"
kotlin = "1.7.20"
kotest = "5.5.4"
spotbugs = "4.7.1"

managed-validation = "2.0.1.Final"

# Gradle plugins

micronaut-gradle-plugin = "3.7.2"
kotlin-gradle-plugin = "1.8.10"
ksp-gradle-plugin = "1.8.10-1.0.9"

[libraries]

#BOMS
# BOMS
micronaut-reactor = { module = 'io.micronaut.reactor:micronaut-reactor-bom', version.ref = "micronaut-reactor" }
micronaut-rxjava2 = { module = 'io.micronaut.rxjava2:micronaut-rxjava2-bom', version.ref = "micronaut-rxjava2" }
micronaut-kotlin = { module = 'io.micronaut.kotlin:micronaut-kotlin-bom', version.ref = "micronaut-kotlin" }
micronaut-test-junit5 = { module = "io.micronaut.test:micronaut-test-junit5", version.ref = "micronaut-test" }
groovy-bom = { module = "org.apache.groovy:groovy-bom", version.ref = "groovy" }

groovy-json = { module = "org.apache.groovy:groovy-json" }

# TESTING

junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit5" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit5" }
kotlin-kotest-junit5 = { module = "io.kotest:kotest-runner-junit5-jvm", version.ref = "kotest" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
spotbugs = { module = "com.github.spotbugs:spotbugs-annotations", version.ref = "spotbugs" }

# MANAGED DEPENDENCIES

managed-validation = { module = "javax.validation:validation-api", version.ref = "managed-validation" }

# GRADLE PLUGINS

gradle-micronaut = { module = "io.micronaut.gradle:micronaut-gradle-plugin", version.ref = "micronaut-gradle-plugin" }
gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-gradle-plugin" }
gradle-kotlin-allopen = { module = "org.jetbrains.kotlin:kotlin-allopen", version.ref = "kotlin-gradle-plugin" }
gradle-kotlin-noarg = { module = "org.jetbrains.kotlin:kotlin-noarg", version.ref = "kotlin-gradle-plugin" }
gradle-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp-gradle-plugin" }
6 changes: 5 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pluginManagement {
}

plugins {
id 'io.micronaut.build.shared.settings' version '6.1.1'
id 'io.micronaut.build.shared.settings' version '6.2.2'
}

dependencyResolutionManagement {
Expand All @@ -21,8 +21,12 @@ rootProject.name = 'validation-parent'
include 'validation'
include 'validation-bom'
include 'validation-processor'
include 'test-suite'
include 'test-suite-groovy'
include 'test-suite-kotlin'

micronautBuild {
importMicronautCatalog()
importMicronautCatalog("micronaut-reactor")
}

37 changes: 37 additions & 0 deletions src/main/docs/guide/additionalConstraints.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

To define additional constraints, create a new annotation, for example:

.Example Constraint Annotation
snippet::io.micronaut.docs.validation.custom.DurationPattern[tags="imports,class", indent="0"]

<1> The annotation should be annotated with `javax.validation.Constraint`
<2> A `message` template can be provided in a hard-coded manner as above. If none is specified, Micronaut tries to find a message using `ClassName.message` using the api:context.MessageSource[] interface (optional)
<3> To support repeated annotations you can define an inner annotation (optional)

TIP: You can add messages and message bundles using the api:context.MessageSource[] and api:context.i18n.ResourceBundleMessageSource[] classes. See <<bundle, Resource Bundles>> documentation.

Once you have defined the annotation, implement a api:validation.validator.constraints.ConstraintValidator[] that validates the annotation. You can either create a bean class that implements the interface directly or define a factory that returns one or more validators.

The latter approach is recommended if you plan to define multiple validators:

.Example Constraint Validator
snippet::io.micronaut.docs.validation.custom.MyValidatorFactory[tags="imports,class", indent="0"]

<1> Override the default message template with an inline call for more control over the validation error message. (Since `2.5.0`)

The above example implements a validator that validates any field, parameter etc. that is annotated with `DurationPattern`, ensuring that the string can be parsed with `java.time.Duration.parse`.

NOTE: Generally `null` is regarded as valid and `@NotNull` is used to constrain a value as not being `null`. The example above regards `null` as a valid value.

For example:

.Example Custom Constraint Usage
snippet::io.micronaut.docs.validation.custom.HolidayService[tags="class", indent="0"]

To verify the above examples validates the `duration` parameter, define a test:

.Testing Example Custom Constraint Usage
snippet::io.micronaut.docs.validation.custom.DurationPatternValidatorSpec[tags="test", indent="0"]

<1> A validated method is invoked
<2> THe constraint violations are verified
24 changes: 24 additions & 0 deletions src/main/docs/guide/compileTimeValidation.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

You can use Micronaut's validator to validate annotation elements at compile time by including `micronaut-validation` in the annotation processor classpath:

dependency::io.micronaut.validation:micronaut-validation[scope="annotationProcessor"]

Then Micronaut will at compile time validate annotation values that are themselves annotated with `javax.validation`. For example consider the following annotation:

.Annotation Validation
snippet::io.micronaut.docs.validation.custom.TimeOff[tags="imports,class", indent="0"]

If you attempt to use `@TimeOff(duration="junk")` in your source, Micronaut will fail compilation due to the `duration` value violating the `DurationPattern` constraint.

NOTE: If `duration` is a property placeholder such as `@TimeOff(duration="${my.value}")`, validation is deferred until runtime.

Note that to use a custom `ConstraintValidator` at compile time you must instead define the validator as a class:

.Example Constraint Validator
snippet::io.micronaut.docs.validation.custom.DurationPatternValidator[tags="imports,class", indent="0"]

Additionally:

* Define a `META-INF/services/io.micronaut.validation.validator.constraints.ConstraintValidator` file that references the class.
* The class must be public and have a public no-argument constructor
* The class must be on the annotation processor classpath of the project to be validated.
4 changes: 4 additions & 0 deletions src/main/docs/guide/configurationValidation.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

You can also validate the properties of classes that are annotated with ann:context.annotation.ConfigurationProperties[] to ensure configuration is correct.

NOTE: It is recommended that you annotate ann:context.annotation.ConfigurationProperties[] that features validation with ann:context.annotation.Context[] to ensure that the validation occurs at startup.
46 changes: 46 additions & 0 deletions src/main/docs/guide/dataClassesValidation.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

To validate data classes, e.g. POJOs (typically used in JSON interchange), the class must be annotated with ann:core.annotation.Introspected[] (see link:https://docs.micronaut.io/latest/guide/#introspection[Micronaut Guide Introspection section]) or, if the class is external, be imported by the `@Introspected` annotation.

.POJO Validation Example
snippet::io.micronaut.docs.validation.Person[tags="class"]

TIP: The ann:core.annotation.Introspected[] annotation can be used as a meta-annotation; common annotations like `@javax.persistence.Entity` are treated as `@Introspected`

The above example defines a `Person` class that has two properties (`name` and `age`) that have constraints applied. Note that in Java the annotations can be on the field or the getter, and with Kotlin data classes, the annotation should target the field.

To validate the class manually, inject an instance of api:validation.validator.Validator[]:

.Manual Validation Example
snippet::io.micronaut.docs.validation.pojo.PersonServiceSpec[tags="validator", indent="0"]

<1> The validator validates the person
<2> The constraint violations are verified

Alternatively on Bean methods you can use `javax.validation.Valid` to trigger cascading validation:

.ConstraintViolationException Example
snippet::io.micronaut.docs.validation.pojo.PersonService[tags="class",indent="0"]

The `PersonService` now validates the `Person` class when invoked:

.Manual Validation Example
snippet::io.micronaut.docs.validation.pojo.PersonServiceSpec[tags="validate-service",indent="0"]

<1> A validated method is invoked
<2> The constraint violations are verified

.Iterables Validation Example

You can validate values of Java iterables, like `List`, `Set` and `Map` by defining validation annotations on generic parameters.

snippet::io.micronaut.docs.validation.iterable.BookInfoService[tags="validate-iterables",indent="0"]

<1> List items will be validated with given annotation
<2> Both keys and values of Map will be validated

snippet::io.micronaut.docs.validation.iterable.BookInfoSpec[tags="validate-iterables",indent="0"]

<1> The violation on the 1-st index in List
<2> The violation was found in Map's key

NOTE: This feature is not yet supported in Groovy and Kotlin
4 changes: 3 additions & 1 deletion src/main/docs/guide/introduction.adoc
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
TODO
Micronaut Validation is a customizable validation solution for your applications.

NOTE: Micronaut Validation was moved to a separate module for Micronaut version 4.0.0. For previous versions of Micronaut, refer to the link:https://docs.micronaut.io/latest/guide/[Micronaut User Guide].
16 changes: 16 additions & 0 deletions src/main/docs/guide/methodsValidation.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
You can validate methods of any class declared as a Micronaut bean by applying `javax.validation` annotations to arguments:

.Validating Methods
snippet::io.micronaut.docs.validation.PersonService[tags="imports,class",indent=0]

The above example declares that the `@NotBlank` annotation will be validated when invoking the `sayHello` method.

WARNING: If you use Kotlin, the class and method must be declared `open` so Micronaut can create a compile-time subclass. Alternatively you can annotate the class with ann:validation.Validated[] and configure the Kotlin `all-open` plugin to open classes annotated with this type. See the https://kotlinlang.org/docs/reference/compiler-plugins.html[Compiler plugins] section.

A `javax.validation.ConstraintViolationException` is thrown if a validation error occurs. For example:

.ConstraintViolationException Example
snippet::io.micronaut.docs.validation.PersonServiceSpec[tags="imports,test",indent=0]

<1> The method is called with a blank string
<2> An exception occurs
35 changes: 34 additions & 1 deletion src/main/docs/guide/quickStart.adoc
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
TODO
To use the Micronaut’s validation capabilities you must have the validation dependency on your classpath:

dependency:io.micronaut.vallidation:micronaut-validation-processor[scope='annotationProcessor']

dependency:io.micronaut.vallidation:micronaut-validation[]

You can validate types, fields and parameters by applying `javax.validation` annotations to arguments. Include the following dependency for annotations:

dependency:javax.validation:validation-api[]

== Supported Features

Note that Micronaut's implementation is not currently fully compliant with the https://beanvalidation.org/2.0/spec/[Bean Validator specification] as the specification heavily relies on reflection-based APIs.

The following features are unsupported at this time:

* Any interaction with the https://beanvalidation.org/2.0/spec/#constraintmetadata[constraint metadata API], since Micronaut uses compile-time generated metadata.
* XML-based configuration
* Instead of using `javax.validation.ConstraintValidator`, use api:validation.validator.constraints.ConstraintValidator[] (io.micronaut.validation.validator.constraints.ConstraintValidator) to define custom constraints, which supports validating annotations at compile time.

Micronaut's implementation includes the following benefits:

* Reflection and Runtime Proxy free validation, resulting in reduced memory consumption
* Smaller JAR size since Hibernate Validator adds another 1.4MB
* Faster startup since Hibernate Validator adds 200ms+ startup overhead
* Configurability via Annotation Metadata
* Support for Reactive Bean Validation
* Support for validating the source AST at compile time
* Automatic compatibility with GraalVM native without additional configuration

If you require full Bean Validator 2.0 compliance, add the `micronaut-hibernate-validator` module to your build, which replaces Micronaut's implementation.

dependency:micronaut-hibernate-validator[groupId="io.micronaut.beanvalidation"]

10 changes: 10 additions & 0 deletions src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,15 @@ introduction:
releaseHistory: Release History
quickStart:
title: Quick Start
methodsValidation:
title: Validating Bean Methods
dataClassesValidation:
title: Validating Data Classes
configurationValidation:
title: Validating Configuration Properties
additionalConstraints:
title: Defining Additional Constraints
compileTimeValidation:
title: Validating Annotations at Compile Time
repository: Repository

35 changes: 35 additions & 0 deletions test-suite-groovy/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
id 'io.micronaut.build.internal.validation-base'
id 'java'
id 'io.micronaut.application'
}

dependencies {
testImplementation mn.micronaut.inject
testImplementation mn.micronaut.core.reactive
testImplementation libs.managed.validation

compileOnly mn.micronaut.http.server

implementation mnReactor.micronaut.reactor

testImplementation project(":validation")
testAnnotationProcessor project(":validation-processor")
testAnnotationProcessor mn.micronaut.inject.java
testImplementation libs.spotbugs
testCompileOnly mn.micronaut.inject.groovy
testImplementation mn.micronaut.inject

testImplementation platform(libs.groovy.bom)

testImplementation mn.micronaut.http.client
testImplementation mn.micronaut.http.server.netty
testImplementation libs.groovy.json
testImplementation mn.micronaut.inject.java.test
testImplementation mn.micronaut.jackson.databind

testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("io.micronaut.test:micronaut-test-junit5:3.8.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:{junitVersion}")
testImplementation("org.junit.jupiter:junit-jupiter-engine:{junitVersion}")
}
1 change: 1 addition & 0 deletions test-suite-groovy/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
skipDocumentation=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.docs.validation

// tag::class[]
import io.micronaut.core.annotation.Introspected

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

@Introspected
class Person {

@NotBlank
String name

@Min(18L)
int age
}
// end::class[]
Loading

0 comments on commit c68e4f6

Please sign in to comment.