Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Add the following dependency to your `build.gradle.kts`:

```kotlin
dependencies {
implementation("com.icerockdev.boko:spring-boot-starter-konform:0.1.2")
implementation("com.icerockdev.boko:spring-boot-starter-konform:0.1.3")
}
```

Expand Down Expand Up @@ -46,6 +46,7 @@ data class AddressRequest(
### 2. Create a Validator

```kotlin
@Component
class CreateUserRequestValidator : RequestValidator<CreateUserRequest> {
override val validator = Validation.Companion<CreateUserRequest> {
CreateUserRequest::username {
Expand Down Expand Up @@ -133,6 +134,39 @@ class GlobalExceptionHandler {
}
```

### Using localization

Create your own implementation of `ConformValidationMessageFormatter`. For example:

```kotlin
@Component
class KonformValidationMessageFormatterWithLocalization(
private val messageSource: MessageSource,
) : ConformValidationMessageFormatter {
override fun getMessage(code: String, userContext: Any?): String {
return messageSource.getMessage(code, emptyArray(), LocaleContextHolder.getLocale()).let {
if (userContext != null) {
if (userContext is Collection<Any?>) {
it.format(*userContext.toTypedArray())
} else it.format(userContext)
} else it
}
}
}
```

Update validators:
```kotlin
@Component
class CreateUserRequestValidator : RequestValidator<CreateUserRequest> {
override val validator = Validation.Companion<CreateUserRequest> {
CreateUserRequest::username {
length(min = 3, max = 50) hint LK.usernameLength
}
}
}
```

## Contributing

All development (both new features and bug fixes) is performed in the `develop` branch. This way `master` always contains the sources of the most recently released version. Please send PRs with bug fixes to the `develop` branch. Documentation fixes in the markdown files are an exception to this rule. They are updated directly in `master`.
Expand Down
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ plugins {
}

group = "com.icerockdev.boko"
version = "0.1.2"
version = "0.1.3"

repositories {
mavenCentral()
Expand Down Expand Up @@ -94,7 +94,7 @@ publishing {
}

signing {
setRequired({ !properties.containsKey("libraryPublishToMavenLocal") })
setRequired({ !properties.containsKey("publishToMavenLocal") })
val signingKeyId: String? = System.getenv("SIGNING_KEY_ID")
val signingPassword: String? = System.getenv("SIGNING_PASSWORD")
val signingKey: String? = System.getenv("SIGNING_KEY")?.let { base64Key ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.aspectj.lang.reflect.MethodSignature
import java.lang.reflect.Method
import java.util.concurrent.ConcurrentHashMap

@Aspect
class KonformValidationAspect(
private val registry: ValidatorRegistry
private val registry: ValidatorRegistry,
private val konformValidationMessageFormatter: KonformValidationMessageFormatter,
) {

@Before("execution(* *(.., @com.icerockdev.boko.konformstarter.KonformValidated (*), ..))")
Expand All @@ -20,18 +23,48 @@ class KonformValidationAspect(
val args = joinPoint.args
val params = method.parameters

params.forEachIndexed { i, param ->
if (param.isAnnotationPresent(KonformValidated::class.java)) {
params.forEachIndexed { i, _ ->
val hasAnnotation = hasKonformValidatedAnnotationWithCache(method, i)

if (hasAnnotation) {
val arg = args[i]
val validator = registry.findValidatorFor(arg) ?: return@forEachIndexed
val result = validator.validate(arg)
if (result is Invalid) {
val errors = result.errors.map {
FieldError(it.dataPath, it.message)
FieldError(
it.dataPath,
konformValidationMessageFormatter.getMessage(it.message, it.userContext)
)
}
throw KonformValidationException(errors)
}
}
}
}

private val methodsCache = ConcurrentHashMap<Pair<Method, Int>, Boolean>()

private fun hasKonformValidatedAnnotationWithCache(method: Method, index: Int): Boolean {
return methodsCache.computeIfAbsent(method to index) {
hasKonformValidatedAnnotation(method, index)
}
}

private fun hasKonformValidatedAnnotation(method: Method, index: Int): Boolean {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check out spring annotation utils

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

org.springframework.core.annotation.AnnotationUtils
org.springframework.core.annotation.AnnotatedElementUtils

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these util classes does not provide functionality to search annotations for parameters in hierarchy. As I understand, they have it for methods and classes only, not for method parameters. So I would keep current implementation

if (method.parameters[index].isAnnotationPresent(KonformValidated::class.java)) return true

for (iface in method.declaringClass.interfaces) {
val ifaceMethod = iface.methods.firstOrNull {
it.name == method.name &&
it.parameterCount == method.parameterCount
} ?: continue

if (ifaceMethod.parameters[index].isAnnotationPresent(KonformValidated::class.java)) {
return true
}
}

return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
*/
package com.icerockdev.boko.konformstarter

import com.icerockdev.boko.konformstarter.configuration.MessageFormatterConfiguration
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Import

@AutoConfiguration
@Import(KonformValidationAspect::class)
@Import(
KonformValidationAspect::class,
MessageFormatterConfiguration::class,
)
open class KonformValidationAutoConfiguration {

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.icerockdev.boko.konformstarter

interface KonformValidationMessageFormatter {
fun getMessage(code: String, userContext: Any?): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.icerockdev.boko.konformstarter.configuration

import com.icerockdev.boko.konformstarter.KonformValidationMessageFormatter
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
open class MessageFormatterConfiguration {
@Bean
@ConditionalOnMissingBean
open fun konformValidationMessageFormatter(): KonformValidationMessageFormatter {
return object : KonformValidationMessageFormatter {
override fun getMessage(code: String, userContext: Any?): String {
return code
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package com.icerockdev.boko.konformstarter

import com.fasterxml.jackson.databind.ObjectMapper
import com.icerockdev.boko.konformstarter.common.TestApplication
import com.icerockdev.boko.konformstarter.configuration.MessageFormatterConfiguration
import com.icerockdev.boko.konformstarter.presentation.TestController
import com.icerockdev.boko.konformstarter.presentation.TestRequest
import com.icerockdev.boko.konformstarter.presentation.TestValidator
Expand All @@ -27,6 +28,7 @@ import org.springframework.test.web.servlet.request
TestApplication::class,
TestController::class,
TestValidator::class,
MessageFormatterConfiguration::class,
]
)
@AutoConfigureMockMvc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package com.icerockdev.boko.konformstarter

import com.fasterxml.jackson.databind.ObjectMapper
import com.icerockdev.boko.konformstarter.common.TestApplication
import com.icerockdev.boko.konformstarter.configuration.MessageFormatterConfiguration
import com.icerockdev.boko.konformstarter.presentation.AddressRequest
import com.icerockdev.boko.konformstarter.presentation.CreateUserRequest
import com.icerockdev.boko.konformstarter.presentation.CreateUserRequestValidator
Expand All @@ -29,6 +30,7 @@ import org.springframework.test.web.servlet.request
TestApplication::class,
UserController::class,
CreateUserRequestValidator::class,
MessageFormatterConfiguration::class,
]
)
@AutoConfigureMockMvc
Expand Down
Loading