mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
refactor validator, add tests
This commit is contained in:
@@ -10,6 +10,7 @@ import com.example.demo.services.database.product.ProductService
|
|||||||
import com.example.demo.services.database.product.ProductServiceImpl
|
import com.example.demo.services.database.product.ProductServiceImpl
|
||||||
import com.example.demo.services.kafka.Producer
|
import com.example.demo.services.kafka.Producer
|
||||||
import com.example.demo.services.validation.SchemaValidator
|
import com.example.demo.services.validation.SchemaValidator
|
||||||
|
import com.example.demo.services.validation.SchemaValidatorImp
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
@@ -53,7 +54,7 @@ class AppConfig(
|
|||||||
fun cityService(@Autowired cityRepository: CityRepository): CityService = CityServiceImpl(cityRepository)
|
fun cityService(@Autowired cityRepository: CityRepository): CityService = CityServiceImpl(cityRepository)
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun schemaValidator(): SchemaValidator = SchemaValidator(kafkaProperties.validation.schema)
|
fun schemaValidator(): SchemaValidator = SchemaValidatorImp(kafkaProperties.validation.schema)
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun otlpHttpSpanExporter(@Value("\${tracing.url}") url: String): OtlpHttpSpanExporter {
|
fun otlpHttpSpanExporter(@Value("\${tracing.url}") url: String): OtlpHttpSpanExporter {
|
||||||
|
|||||||
@@ -2,43 +2,9 @@ package com.example.demo.services.validation
|
|||||||
|
|
||||||
import com.example.demo.services.validation.exceptions.ElementNotValidException
|
import com.example.demo.services.validation.exceptions.ElementNotValidException
|
||||||
import com.example.demo.services.validation.exceptions.SchemaNotFoundException
|
import com.example.demo.services.validation.exceptions.SchemaNotFoundException
|
||||||
import io.github.optimumcode.json.schema.JsonSchema
|
|
||||||
import io.github.optimumcode.json.schema.ValidationError
|
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import org.springframework.util.ResourceUtils
|
|
||||||
|
|
||||||
class SchemaValidator(
|
interface SchemaValidator {
|
||||||
private val schemaMap: Map<String, String>,
|
@Throws(ElementNotValidException::class, SchemaNotFoundException::class)
|
||||||
) {
|
fun validate(schemaName: String, value: JsonElement)
|
||||||
private val loadedSchema: MutableMap<String, String> = mutableMapOf()
|
|
||||||
|
|
||||||
fun validate(schemaName: String, value: JsonElement) {
|
|
||||||
|
|
||||||
val schema = JsonSchema.fromDefinition(
|
|
||||||
getSchema(schemaName),
|
|
||||||
)
|
|
||||||
|
|
||||||
val errors = mutableListOf<ValidationError>()
|
|
||||||
|
|
||||||
val valid = schema.validate(value, errors::add)
|
|
||||||
if (!valid) {
|
|
||||||
throw ElementNotValidException(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSchema(schemaName: String): String {
|
|
||||||
val loaded = loadedSchema[schemaName]
|
|
||||||
if (loaded != null) {
|
|
||||||
return loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
val schemaFile = schemaMap[schemaName]
|
|
||||||
?: throw SchemaNotFoundException()
|
|
||||||
|
|
||||||
val schema = ResourceUtils.getFile("classpath:json-schemas/$schemaFile")
|
|
||||||
.readText(Charsets.UTF_8)
|
|
||||||
loadedSchema[schemaName] = schema
|
|
||||||
|
|
||||||
return schema
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.example.demo.services.validation
|
||||||
|
|
||||||
|
import com.example.demo.services.validation.exceptions.ElementNotValidException
|
||||||
|
import com.example.demo.services.validation.exceptions.SchemaNotFoundException
|
||||||
|
import io.github.optimumcode.json.schema.JsonSchema
|
||||||
|
import io.github.optimumcode.json.schema.ValidationError
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import org.springframework.util.ResourceUtils
|
||||||
|
|
||||||
|
class SchemaValidatorImp(
|
||||||
|
private val schemaMap: Map<String, String>,
|
||||||
|
): SchemaValidator {
|
||||||
|
private val loadedSchema: MutableMap<String, String> = mutableMapOf()
|
||||||
|
|
||||||
|
override fun validate(schemaName: String, value: JsonElement) {
|
||||||
|
|
||||||
|
val schema = JsonSchema.fromDefinition(
|
||||||
|
getSchema(schemaName),
|
||||||
|
)
|
||||||
|
|
||||||
|
val errors = mutableListOf<ValidationError>()
|
||||||
|
|
||||||
|
val valid = schema.validate(value, errors::add)
|
||||||
|
if (!valid) {
|
||||||
|
throw ElementNotValidException(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSchema(schemaName: String): String {
|
||||||
|
val loaded = loadedSchema[schemaName]
|
||||||
|
if (loaded != null) {
|
||||||
|
return loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
val schemaFile = schemaMap[schemaName]
|
||||||
|
?: throw SchemaNotFoundException()
|
||||||
|
|
||||||
|
val schema = ResourceUtils.getFile("classpath:json-schemas/$schemaFile")
|
||||||
|
.readText(Charsets.UTF_8)
|
||||||
|
loadedSchema[schemaName] = schema
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"schema": "http://json-schema.org/draft-07/schema#",
|
"schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "event sync product",
|
"title": "event sync product",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["id", "guid"],
|
"required": ["id", "guid", "name", "price", "createdAt"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
{ "type": "null" }
|
{ "type": "null" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"number": {
|
"price": {
|
||||||
"type": "string"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ open class BaseUnitTest {
|
|||||||
autoOffsetReset = "none",
|
autoOffsetReset = "none",
|
||||||
),
|
),
|
||||||
validation = KafkaProperties.Validation(
|
validation = KafkaProperties.Validation(
|
||||||
schema = mapOf("product-sync" to "foo"),
|
schema = mapOf("product-sync" to "product/sync.json"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.example.demo.services.validation
|
||||||
|
|
||||||
|
import com.example.demo.BaseUnitTest
|
||||||
|
import com.example.demo.services.validation.exceptions.ElementNotValidException
|
||||||
|
import com.example.demo.services.validation.exceptions.SchemaNotFoundException
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.Arguments
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
@RunWith(SpringRunner::class)
|
||||||
|
@SpringBootTest
|
||||||
|
class SchemaValidatorImpTest(
|
||||||
|
@Autowired val schemaValidatorImp: SchemaValidatorImp
|
||||||
|
): BaseUnitTest() {
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("validateDataProvider")
|
||||||
|
fun validate(schemaName: String, inputRawJson: String, expectedException: KClass<out Throwable>?) {
|
||||||
|
val element = Json.parseToJsonElement(inputRawJson.trimIndent())
|
||||||
|
|
||||||
|
if (expectedException == null) {
|
||||||
|
schemaValidatorImp.validate(schemaName = schemaName, value = element)
|
||||||
|
// second time should use cache
|
||||||
|
schemaValidatorImp.validate(schemaName = schemaName, value = element)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFailsWith(expectedException) {
|
||||||
|
schemaValidatorImp.validate(schemaName = schemaName, value = element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun validateDataProvider() = listOf(
|
||||||
|
Arguments.of(
|
||||||
|
"product-sync",
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"guid": "3a27e322-b5b6-427f-b761-a02284c1cfa4",
|
||||||
|
"name": "some-name",
|
||||||
|
"description": null,
|
||||||
|
"price": 12.22,
|
||||||
|
"createdAt": "2024-01-01T12:11:10+04:00",
|
||||||
|
"updatedAt": null,
|
||||||
|
"deletedAt": null
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
Arguments.of( // no id
|
||||||
|
"product-sync",
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"guid": "3a27e322-b5b6-427f-b761-a02284c1cfa4",
|
||||||
|
"name": "some-name",
|
||||||
|
"description": null,
|
||||||
|
"price": 12.22,
|
||||||
|
"createdAt": "2024-01-01T12:11:10+04:00",
|
||||||
|
"updatedAt": null,
|
||||||
|
"deletedAt": null
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
ElementNotValidException::class,
|
||||||
|
),
|
||||||
|
Arguments.of( // wrong guid
|
||||||
|
"product-sync",
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"id": 213,
|
||||||
|
"guid": 77373,
|
||||||
|
"name": "some-name",
|
||||||
|
"description": null,
|
||||||
|
"price": 12.22,
|
||||||
|
"createdAt": "2024-01-01T12:11:10+04:00",
|
||||||
|
"updatedAt": null,
|
||||||
|
"deletedAt": null
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
ElementNotValidException::class,
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
"some-unknown-schema",
|
||||||
|
"{}",
|
||||||
|
SchemaNotFoundException::class,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user