Skip to content

Commit

Permalink
Strongly Typed vs. JavaX Validation: optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
slu-it committed Oct 5, 2019
1 parent 51f3741 commit b242fda
Show file tree
Hide file tree
Showing 30 changed files with 700 additions and 196 deletions.
2 changes: 2 additions & 0 deletions strongly-typed-vs-javax-validation/build.gradle.kts
Expand Up @@ -20,7 +20,9 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("io.mockk:mockk:1.9.3")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntime("org.junit.jupiter:junit-jupiter-engine")
}

tasks.withType<Test> {
Expand Down
@@ -1,7 +1,6 @@
package example.javaxvalidation

import example.javaxvalidation.model.Person
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
Expand All @@ -13,19 +12,9 @@ import javax.validation.Valid
@RequestMapping("/javaxvalidation/persons")
class JavaxValidationPersonController {

private val database: MutableMap<UUID, Person> = mutableMapOf()

@PostMapping
fun addPerson(@Valid @RequestBody person: Person): Person {
val id = UUID.randomUUID()
val persistedPerson = person.copy(id = id)
database.put(id, persistedPerson)
return persistedPerson
}

@GetMapping
fun getAllPersons(): List<Person> {
return database.values.toList()
return person.copy(id = UUID.randomUUID())
}

}
@@ -1,33 +1,16 @@
package example.javaxvalidation.validation

import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size
import kotlin.reflect.KClass

@NotBlank // constraint #1
@Size(max = 50) // constraint #2
@Constraint(validatedBy = [City.Validator::class])
@DoesNotContainAnyPossibleInjectionCharacters // constraint #3
@Constraint(validatedBy = [])
annotation class City(
val message: String = "Invalid City",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Any>> = []
) {

class Validator : ConstraintValidator<City, CharSequence?> {
private val illegalCharacters = possibleInjectionCharacters
private val illegalCharactersMessage =
"contains one of these illegal characters: ${illegalCharacters.joinToString(separator = " ")}"

override fun isValid(value: CharSequence?, context: ConstraintValidatorContext): Boolean {
if (value containsAny illegalCharacters) { // constraint #3
context.disableDefaultConstraintViolation()
context.buildConstraintViolationWithTemplate(illegalCharactersMessage).addConstraintViolation()
return false
}
return true
}
}
}
)
@@ -0,0 +1,43 @@
package example.javaxvalidation.validation

import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import kotlin.reflect.KClass

internal val possibleInjectionCharacters = setOf('$', '<', '>', '"', ';', '\'') // not complete ...

@Constraint(validatedBy = [DoesNotContainAnyPossibleInjectionCharacters.Validator::class])
annotation class DoesNotContainAnyPossibleInjectionCharacters(
val whitelist: CharArray = [],
val message: String = "Invalid First Name",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Any>> = []
) {

class Validator : ConstraintValidator<DoesNotContainAnyPossibleInjectionCharacters, CharSequence?> {

private lateinit var illegalCharacters: Set<Char>

override fun initialize(constraintAnnotation: DoesNotContainAnyPossibleInjectionCharacters) {
illegalCharacters = possibleInjectionCharacters - constraintAnnotation.whitelist.toSet()
}

override fun isValid(value: CharSequence?, context: ConstraintValidatorContext): Boolean {
if (value containsAny illegalCharacters) {
context.disableDefaultConstraintViolation()
context.buildConstraintViolationWithTemplate(message()).addConstraintViolation()
return false
}
return true
}

private infix fun CharSequence?.containsAny(characters: Iterable<Char>): Boolean =
this?.any { char -> characters.contains(char) } ?: false

private fun message() =
"contains one of these illegal characters: ${illegalCharacters.joinToString(separator = " ")}"

}

}
@@ -1,33 +1,16 @@
package example.javaxvalidation.validation

import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size
import kotlin.reflect.KClass

@NotBlank // constraint #1
@Size(max = 50) // constraint #2
@Constraint(validatedBy = [FirstName.Validator::class])
@DoesNotContainAnyPossibleInjectionCharacters(whitelist = ['\'']) // constraint #3
@Constraint(validatedBy = [])
annotation class FirstName(
val message: String = "Invalid First Name",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Any>> = []
) {

class Validator : ConstraintValidator<FirstName, CharSequence?> {
private val illegalCharacters = possibleInjectionCharacters - '\''
private val illegalCharactersMessage =
"contains one of these illegal characters: ${illegalCharacters.joinToString(separator = " ")}"

override fun isValid(value: CharSequence?, context: ConstraintValidatorContext): Boolean {
if (value containsAny illegalCharacters) { // constraint #3
context.disableDefaultConstraintViolation()
context.buildConstraintViolationWithTemplate(illegalCharactersMessage).addConstraintViolation()
return false
}
return true
}
}
}
)
@@ -1,33 +1,16 @@
package example.javaxvalidation.validation

import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size
import kotlin.reflect.KClass

@NotBlank // constraint #1
@Size(max = 50) // constraint #2
@Constraint(validatedBy = [LastName.Validator::class])
@DoesNotContainAnyPossibleInjectionCharacters(whitelist = ['\'']) // constraint #3
@Constraint(validatedBy = [])
annotation class LastName(
val message: String = "Invalid Last Name",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Any>> = []
) {

class Validator : ConstraintValidator<LastName, CharSequence?> {
private val illegalCharacters = possibleInjectionCharacters - '\''
private val illegalCharactersMessage =
"contains one of these illegal characters: ${illegalCharacters.joinToString(separator = " ")}"

override fun isValid(value: CharSequence?, context: ConstraintValidatorContext): Boolean {
if (value containsAny illegalCharacters) { // constraint #3
context.disableDefaultConstraintViolation()
context.buildConstraintViolationWithTemplate(illegalCharactersMessage).addConstraintViolation()
return false
}
return true
}
}
}
)
@@ -1,33 +1,16 @@
package example.javaxvalidation.validation

import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size
import kotlin.reflect.KClass

@NotBlank // constraint #1
@Size(max = 100) // constraint #2
@Constraint(validatedBy = [Street.Validator::class])
@DoesNotContainAnyPossibleInjectionCharacters // constraint #3
@Constraint(validatedBy = [])
annotation class Street(
val message: String = "Invalid Street",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Any>> = []
) {

class Validator : ConstraintValidator<Street, CharSequence?> {
private val illegalCharacters = possibleInjectionCharacters
private val illegalCharactersMessage =
"contains one of these illegal characters: ${illegalCharacters.joinToString(separator = " ")}"

override fun isValid(value: CharSequence?, context: ConstraintValidatorContext): Boolean {
if (value containsAny illegalCharacters) { // constraint #3
context.disableDefaultConstraintViolation()
context.buildConstraintViolationWithTemplate(illegalCharactersMessage).addConstraintViolation()
return false
}
return true
}
}
}
)
@@ -1,33 +1,16 @@
package example.javaxvalidation.validation

import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size
import kotlin.reflect.KClass

@NotBlank // constraint #1
@Size(max = 15) // constraint #2
@Constraint(validatedBy = [ZipCode.Validator::class])
@DoesNotContainAnyPossibleInjectionCharacters // constraint #3
@Constraint(validatedBy = [])
annotation class ZipCode(
val message: String = "Invalid Zip Code",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Any>> = []
) {

class Validator : ConstraintValidator<ZipCode, CharSequence?> {
private val illegalCharacters = possibleInjectionCharacters
private val illegalCharactersMessage =
"contains one of these illegal characters: ${illegalCharacters.joinToString(separator = " ")}"

override fun isValid(value: CharSequence?, context: ConstraintValidatorContext): Boolean {
if (value containsAny illegalCharacters) { // constraint #3
context.disableDefaultConstraintViolation()
context.buildConstraintViolationWithTemplate(illegalCharactersMessage).addConstraintViolation()
return false
}
return true
}
}
}
)

This file was deleted.

This file was deleted.

Expand Up @@ -4,13 +4,10 @@ import com.fasterxml.jackson.databind.exc.InvalidDefinitionException
import example.ErrorResponse
import example.errorResponse
import example.stronglytyped.model.Person
import example.stronglytyped.validation.ValidationException
import org.springframework.http.HttpStatus.BAD_REQUEST
import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.status
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
Expand All @@ -26,19 +23,9 @@ class StronglyTypedPersonController(
private val clock: Clock
) {

private val database: MutableMap<UUID, Person> = mutableMapOf()

@PostMapping
fun addPerson(@RequestBody person: Person): Person {
val id = UUID.randomUUID()
val persistedPerson = person.copy(id = id)
database.put(id, persistedPerson)
return persistedPerson
}

@GetMapping
fun getAllPersons(): List<Person> {
return database.values.toList()
return person.copy(id = UUID.randomUUID())
}

@ExceptionHandler(InvalidDefinitionException::class)
Expand All @@ -47,22 +34,15 @@ class StronglyTypedPersonController(
request: HttpServletRequest
): ResponseEntity<ErrorResponse> =
when (val cause = e.cause) {
is ValidationException -> status(BAD_REQUEST).body(
is IllegalArgumentException -> status(BAD_REQUEST).body(
errorResponse(
clock = clock,
status = BAD_REQUEST,
request = request,
message = cause.message
)
)
else -> status(INTERNAL_SERVER_ERROR).body(
errorResponse(
clock = clock,
status = INTERNAL_SERVER_ERROR,
request = request,
message = e.message
)
)
else -> throw IllegalStateException("Should not happen ..", e)
}

}
Expand Up @@ -2,17 +2,16 @@ package example.stronglytyped.model

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
import example.stronglytyped.validation.validate

data class Age @JsonCreator constructor(
@JsonValue private val value: Int
) {

init {
validate(value >= 0) {
require(value >= 0) {
"Value of Age must be greater than or equal to 0, but was $value!"
}
validate(value <= 150) {
require(value <= 150) {
"Value of Age must be less than or equal to 150, but was $value!"
}
}
Expand Down
Expand Up @@ -4,20 +4,19 @@ import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
import example.stronglytyped.validation.doesNotContainsAny
import example.stronglytyped.validation.possibleInjectionCharacters
import example.stronglytyped.validation.validate

data class City @JsonCreator constructor(
@JsonValue private val value: String
) {

init {
validate(value.isNotBlank()) {
require(value.isNotBlank()) {
"Value of City must not be blank!"
}
validate(value.length <= 50) {
require(value.length <= 50) {
"Value of City must not exceed 50 characters. [$value] is ${value.length} characters long!"
}
validate(value doesNotContainsAny possibleInjectionCharacters) {
require(value doesNotContainsAny possibleInjectionCharacters) {
"Value of City must not contain any illegal characters. [$value] contains at least one of these: $possibleInjectionCharacters"
}
}
Expand Down

0 comments on commit b242fda

Please sign in to comment.