-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c167374
commit 0f34a1f
Showing
2 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
95 changes: 95 additions & 0 deletions
95
...-rivers/src/main/kotlin/com/github/navikt/tbd_libs/rapids_and_rivers/MessageValidation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
168 changes: 168 additions & 0 deletions
168
...ers/src/test/kotlin/com/github/navikt/tbd_libs/rapids_and_rivers/MessageValidationTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>" } | ||
} | ||
} |