Skip to content

Commit

Permalink
ny dsl for validering av meldinger
Browse files Browse the repository at this point in the history
  • Loading branch information
davidsteinsland committed Aug 28, 2024
1 parent c167374 commit 0f34a1f
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.github.navikt.tbd_libs.rapids_and_rivers

import com.fasterxml.jackson.databind.JsonNode
import com.github.navikt.tbd_libs.rapids_and_rivers.ValidationResult.Invalid
import com.github.navikt.tbd_libs.rapids_and_rivers.ValidationResult.Valid
import com.github.navikt.tbd_libs.rapids_and_rivers.ValidationSpec.Companion.allAreOK
import com.github.navikt.tbd_libs.rapids_and_rivers.ValueValidation.Companion.optional
import com.github.navikt.tbd_libs.rapids_and_rivers_api.MessageProblems

val exist = ValidationResult.create("Feltet finnes ikke") { node ->
!node.isMissingOrNull()
}
fun be(expectedValue: String) = ValidationResult.create("Feltet har ikke forventet verdi $expectedValue") { node ->
node.asText() == expectedValue
}


fun validate(validationSpec: MessageValidation.() -> Unit): MessageValidation {
val spec = MessageValidation()
spec.validationSpec()
return spec
}

/**
* validation = MessageValidation {
* "key" should exist // samme som requireKey("key")
* "key" must exist" // samme som demandKey("key")
* "key" must be("value")
* }
*/
fun interface ValueValidation {
fun validate(node: JsonNode): ValidationResult

companion object {
fun ValueValidation.optional() = ValueValidation { node ->
if (node.isMissingOrNull()) Valid else validate(node)
}
}
}

sealed class ValidationResult {
data object Valid : ValidationResult()
data class Invalid(val message: String) : ValidationResult()
companion object {
fun create(message: String, validation: (JsonNode) -> Boolean) = ValueValidation { node ->
when (validation(node)) {
true -> Valid
false -> Invalid(message)
}

}
}
}

class MessageValidation {
private val fields = mutableMapOf<String, MutableList<ValidationSpec>>()

fun validatedKeys(node: JsonNode, problems: MessageProblems): Set<String> {
return fields
.filter { (key, validations) ->
val valueToBeEvaluated = node.path(key)
validations.allAreOK(key, valueToBeEvaluated, problems)
}
.keys
}

infix fun String.should(what: ValueValidation) =
addValidation(this, ValidationSpec(what, MessageProblems::error))

infix fun String.must(what: ValueValidation) =
addValidation(this, ValidationSpec(what, MessageProblems::severe))

infix fun String.can(what: ValueValidation) =
should(what.optional())

private fun addValidation(key: String, validation: ValidationSpec) = validation.also {
fields.getOrPut(key) { mutableListOf() }.add(it)
}
}

class ValidationSpec(private val validation: ValueValidation, private val errorStrategy: MessageProblems.(String) -> Unit) {
companion object {
fun List<ValidationSpec>.allAreOK(key: String, valueToBeEvaluated: JsonNode, problems: MessageProblems): Boolean {
return all { spec -> spec.validate(key, valueToBeEvaluated, problems) is Valid }
}
}
fun validate(key: String, node: JsonNode, problems: MessageProblems): ValidationResult {
val result = validation.validate(node)
when (result) {
is Valid -> { /* 😌 */ }
is Invalid -> errorStrategy(problems, "$key: ${result.message}")
}
return result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.github.navikt.tbd_libs.rapids_and_rivers

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.github.navikt.tbd_libs.rapids_and_rivers_api.MessageProblems
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test

class MessageValidationTest {
@Test
fun `key can exist`() {
val validation = validate {
"@event_name" can exist
}

validation.test("""{ "event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}

validation.test("""{ "@event_name": null }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}

validation.test("""{ "@event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}
}

@Test
fun `key should exist`() {
val validation = validate {
"@event_name" should exist
}

validation.test("""{ "event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet finnes ikke", problems.toString())
}

validation.test("""{ "@event_name": null }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet finnes ikke", problems.toString())
}

validation.test("""{ "@event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}
}


@Test
fun `key must exist`() {
val validation = validate {
"@event_name" must exist
}

validation.test("""{ "event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet finnes ikke", problems.toString())
}

validation.test("""{ "@event_name": null }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet finnes ikke", problems.toString())
}

validation.test("""{ "@event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}
}

@Test
fun `key should be`() {
val validation = validate {
"@event_name" should be("mitt_eventnavn")
}

validation.test("""{ "event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet har ikke forventet verdi mitt_eventnavn", problems.toString())
}

validation.test("""{ "@event_name": "uventet_verdi" }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet har ikke forventet verdi mitt_eventnavn", problems.toString())
}

validation.test("""{ "@event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}
}

@Test
fun `key must be`() {
val validation = validate {
"@event_name" must be("mitt_eventnavn")
}

validation.test("""{ "event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet har ikke forventet verdi mitt_eventnavn", problems.toString())
}

validation.test("""{ "@event_name": "uventet_verdi" }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet har ikke forventet verdi mitt_eventnavn", problems.toString())
}

validation.test("""{ "@event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}
}



@Test
fun `key can be`() {
val validation = validate {
"@event_name" can be("mitt_eventnavn")
}

validation.test("""{ "event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}

validation.test("""{ "@event_name": "uventet_verdi" }""") { result, problems ->
assertEquals(emptySet<String>(), result)
assertTrue(problems.hasErrors())
assertContains("@event_name: Feltet har ikke forventet verdi mitt_eventnavn", problems.toString())
}

validation.test("""{ "@event_name": "mitt_eventnavn" }""") { result, problems ->
assertEquals(setOf("@event_name"), result)
assertFalse(problems.hasErrors())
}
}

private fun MessageValidation.test(@Language("JSON") testMessage: String, assertBlock: (Set<String>, MessageProblems) -> Unit) {
val problems = MessageProblems(testMessage)
val node = jacksonObjectMapper().readTree(testMessage)
val result = try {
validatedKeys(node, problems)
} catch (err: MessageProblems.MessageException) {
emptySet()
}
assertBlock(result, problems)
}

private fun assertContains(expected: String, actual: String) {
assertTrue(expected in actual) { "<$actual> does not contain <$expected>" }
}
}

0 comments on commit 0f34a1f

Please sign in to comment.