All Downloads are FREE. Search and download functionalities are using the official Maven repository.

run.qontract.core.Resolver.kt Maven / Gradle / Ivy

Go to download

A Contract Testing Tool that leverages Gherkin to describe APIs in a human readable and machine enforceable manner

There is a newer version: 0.23.1
Show newest version
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)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy