run.qontract.core.Resolver.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of qontract-core Show documentation
Show all versions of qontract-core Show documentation
A Contract Testing Tool that leverages Gherkin to describe APIs in a human readable and machine enforceable manner
package run.qontract.core
import run.qontract.core.pattern.*
import run.qontract.core.value.StringValue
import run.qontract.core.value.True
import run.qontract.core.value.Value
sealed class KeyError
data class MissingKeyError(val name: String) : KeyError()
data class UnexpectedKeyError(val name: String) : KeyError()
data class Resolver(val factStore: FactStore = CheckFacts(), val mockMode: Boolean = false, val newPatterns: Map = emptyMap(), val findMissingKey: (pattern: Map, actual: Map, UnexpectedKeyCheck) -> KeyError? = ::checkOnlyPatternKeys ) {
constructor(facts: Map = emptyMap(), mockMode: Boolean = false, newPatterns: Map = emptyMap()) : this(CheckFacts(facts), mockMode, newPatterns)
constructor() : this(emptyMap(), false)
val patterns = builtInPatterns.plus(newPatterns)
fun matchesPattern(factKey: String?, pattern: Pattern, sampleValue: Value): Result {
if (mockMode
&& sampleValue is StringValue
&& isPatternToken(sampleValue.string)
&& pattern.encompasses(getPattern(sampleValue.string), this, this).isTrue())
return Result.Success()
when (val result = pattern.matches(sampleValue, this)) {
is Result.Failure -> {
return result
}
}
if (factKey != null && factStore.has(factKey)) {
when(val result = factStore.match(sampleValue, factKey)) {
is Result.Failure -> result.reason("Resolver was not able to match fact $factKey with value $sampleValue.")
}
}
return Result.Success()
}
fun getPattern(patternValue: String): Pattern =
when {
isPatternToken(patternValue) -> {
val resolvedPattern = patterns[patternValue] ?: parsedPattern(patternValue, null)
when {
resolvedPattern is DeferredPattern && resolvedPattern.pattern == patternValue -> throw ContractException("Type $patternValue does not exist")
else -> resolvedPattern
}
}
else -> throw ContractException("$patternValue is not a type")
}
fun generate(factKey: String, pattern: Pattern): Value {
if (!factStore.has(factKey))
return pattern.generate(this)
return when(val fact = factStore.get(factKey)) {
is StringValue ->
try {
pattern.parse(fact.string, this)
} catch (e: Throwable) {
throw ContractException("""Value $fact in fact $factKey is not a $pattern""")
}
True -> pattern.generate(this)
else -> {
when(val matchResult = pattern.matches(fact, this)) {
is Result.Failure -> throw ContractException(resultReport(matchResult))
else -> fact
}
}
}
}
}
typealias UnexpectedKeyCheck = (Map, Map) -> KeyError?
fun checkOnlyPatternKeys(pattern: Map, actual: Map, lookForUnexpected: UnexpectedKeyCheck = ignoreUnexpectedKeys): KeyError? {
return pattern.minus("...").keys.find { key ->
isMissingKey(actual, key)
}?.let {
MissingKeyError(it)
} ?: lookForUnexpected(pattern, actual)
}
fun validateUnexpectedKeys(pattern: Map, actual: Map): KeyError? {
val patternKeys = pattern.minus("...").keys.map { withoutOptionality(it) }
val actualKeys = actual.keys.map { withoutOptionality(it) }
return actualKeys.minus(patternKeys).firstOrNull()?.let { UnexpectedKeyError(it) }
}
internal val checkAllKeys = { pattern: Map, actual: Map, _: Any ->
pattern.minus("...").keys.find { key -> isMissingKey(actual, key) }?.let { MissingKeyError(it) } ?: validateUnexpectedKeys(pattern, actual)
}
fun missingKeyToResult(keyError: KeyError, keyLabel: String): Result.Failure {
val message = when(keyError) {
is MissingKeyError -> "Expected ${keyLabel.toLowerCase()} named \"${keyError.name}\" was missing"
is UnexpectedKeyError -> "${keyLabel.toLowerCase().capitalize()} named \"${keyError.name}\" was unexpected"
}
return Result.Failure(message)
}