mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +03:00
move schema validation to separate package
This commit is contained in:
4
edge-contracts/build.gradle.kts
Normal file
4
edge-contracts/build.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
implementation(rootProject.libs.spring.cloud.stream)
|
||||
implementation(rootProject.libs.json.schema.validator)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.github.dannecron.demo.edgecontracts.validation
|
||||
|
||||
import com.github.dannecron.demo.edgecontracts.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.edgecontracts.validation.exceptions.SchemaNotFoundException
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
interface SchemaValidator {
|
||||
|
||||
@Throws(ElementNotValidException::class, SchemaNotFoundException::class)
|
||||
fun validate(schemaName: String, value: JsonElement)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.github.dannecron.demo.edgecontracts.validation
|
||||
|
||||
import com.github.dannecron.demo.edgecontracts.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.edgecontracts.validation.exceptions.SchemaNotFoundException
|
||||
import io.github.optimumcode.json.schema.JsonSchema
|
||||
import io.github.optimumcode.json.schema.ValidationError
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
class SchemaValidatorImp(
|
||||
private val schemaMap: Map<String, String>,
|
||||
): SchemaValidator {
|
||||
|
||||
@Throws(
|
||||
SchemaNotFoundException::class,
|
||||
ElementNotValidException::class,
|
||||
)
|
||||
override fun validate(schemaName: String, value: JsonElement) {
|
||||
JsonSchema.fromDefinition(
|
||||
getSchema(schemaName),
|
||||
).also {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
if (!it.validate(value, errors::add)) {
|
||||
throw ElementNotValidException(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SchemaNotFoundException::class)
|
||||
private fun getSchema(schemaName: String) = schemaMap[schemaName]
|
||||
?: throw SchemaNotFoundException()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.dannecron.demo.edgecontracts.validation.config
|
||||
|
||||
import com.github.dannecron.demo.edgecontracts.validation.SchemaValidator
|
||||
import com.github.dannecron.demo.edgecontracts.validation.SchemaValidatorImp
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.util.ResourceUtils
|
||||
|
||||
@Configuration
|
||||
class SchemaValidationConfig(
|
||||
private val validationProperties: ValidationProperties,
|
||||
) {
|
||||
|
||||
@Bean
|
||||
fun schemaValidator(): SchemaValidator = SchemaValidatorImp(
|
||||
schemaMap = validationProperties.schema.mapValues {
|
||||
schema -> ResourceUtils.getFile("classpath:json-schemas/${schema.value}")
|
||||
.readText(Charsets.UTF_8)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.github.dannecron.demo.edgecontracts.validation.config
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
|
||||
@ConfigurationProperties("validation")
|
||||
data class ValidationProperties(
|
||||
val schema: Map<String, String>
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.github.dannecron.demo.edgecontracts.validation.exceptions
|
||||
|
||||
import io.github.optimumcode.json.schema.ValidationError
|
||||
|
||||
class ElementNotValidException(
|
||||
val validationErrors: List<ValidationError>,
|
||||
): RuntimeException()
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.github.dannecron.demo.edgecontracts.validation.exceptions
|
||||
|
||||
class SchemaNotFoundException: RuntimeException()
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "event sync product",
|
||||
"type": "object",
|
||||
"required": ["id", "guid", "name", "price", "createdAt"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"guid": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
"price": {
|
||||
"type": "number"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
"deletedAt": {
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.github.dannecron.demo.edgecontracts.validation
|
||||
|
||||
import com.github.dannecron.demo.edgecontracts.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.edgecontracts.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 kotlin.reflect.KClass
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class SchemaValidatorImpTest {
|
||||
private val schemaValidatorImp = SchemaValidatorImp(
|
||||
schemaMap = mapOf(
|
||||
KAFKA_PRODUCT_SYNC_SCHEMA to getJsonSchema("json-schemas/kafka/product/sync.json")),
|
||||
)
|
||||
|
||||
@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)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
assertFailsWith(expectedException) {
|
||||
schemaValidatorImp.validate(schemaName = schemaName, value = element)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KAFKA_PRODUCT_SYNC_SCHEMA = "kafkaProductSync"
|
||||
|
||||
@JvmStatic
|
||||
fun validateDataProvider() = listOf(
|
||||
Arguments.of(
|
||||
KAFKA_PRODUCT_SYNC_SCHEMA,
|
||||
"""
|
||||
{
|
||||
"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
|
||||
KAFKA_PRODUCT_SYNC_SCHEMA,
|
||||
"""
|
||||
{
|
||||
"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
|
||||
KAFKA_PRODUCT_SYNC_SCHEMA,
|
||||
"""
|
||||
{
|
||||
"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,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun getJsonSchema(resourcePath: String) = javaClass.classLoader
|
||||
.getResourceAsStream(resourcePath)!!
|
||||
.readAllBytes()
|
||||
.toString(Charsets.UTF_8)
|
||||
}
|
||||
Reference in New Issue
Block a user