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

run.qontract.core.HttpHeadersPattern.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

data class HttpHeadersPattern(val pattern: Map = emptyMap(), val ancestorHeaders: Map? = null) {
    fun matches(headers: Map, resolver: Resolver): Result {
        val result = headers to resolver to
                ::matchEach otherwise
                ::handleError toResult
                ::returnResult

        return when (result) {
            is Result.Failure -> result.breadCrumb("HEADERS")
            else -> result
        }
    }

    private fun matchEach(parameters: Pair, Resolver>): MatchingResult, Resolver>> {
        val (headers, resolver) = parameters

        val headersWithRelevantKeys = when {
            ancestorHeaders != null -> withoutIgnorableHeaders(headers, ancestorHeaders)
            else -> withoutContentTypeGeneratedByQontract(headers, pattern)
        }

        val missingKey = resolver.findMissingKey(pattern, headersWithRelevantKeys.mapValues { StringValue(it.value) }, ignoreUnexpectedKeys)
        if(missingKey != null) {
            return MatchFailure(missingKeyToResult(missingKey, "header"))
        }

        this.pattern.forEach { (key, pattern) ->
            val keyWithoutOptionality = withoutOptionality(key)
            val sampleValue = headersWithRelevantKeys[keyWithoutOptionality]

            when {
                sampleValue != null -> try {
                    val result = resolver.matchesPattern(keyWithoutOptionality, pattern, attempt(breadCrumb = keyWithoutOptionality) { parseOrString(pattern, sampleValue, resolver) } )
                    if (result is Result.Failure) {
                        return MatchFailure(result.breadCrumb(keyWithoutOptionality))
                    }
                } catch(e: ContractException) {
                    return MatchFailure(e.failure())
                } catch(e: Throwable) {
                    return MatchFailure(Result.Failure(e.localizedMessage, breadCrumb = keyWithoutOptionality))
                }
                !key.endsWith("?") ->
                    return MatchFailure(missingKeyToResult(MissingKeyError(key), "header").breadCrumb(key))
            }
        }

        return MatchSuccess(parameters)
    }

    private fun withoutIgnorableHeaders(headers: Map, ancestorHeaders: Map): Map {
        return headers.filterKeys { key ->
            val keyWithoutOptionality = withoutOptionality(key)
            ancestorHeaders.containsKey(keyWithoutOptionality) || ancestorHeaders.containsKey("$keyWithoutOptionality?")
        }
    }

    private fun withoutContentTypeGeneratedByQontract(headers: Map, pattern: Map): Map {
        val contentTypeHeader = "Content-Type"
        return when {
            contentTypeHeader in headers && contentTypeHeader !in pattern && "$contentTypeHeader?" !in pattern -> headers.minus(contentTypeHeader)
            else -> headers
        }
    }

    fun generate(resolver: Resolver): Map {
        return attempt(breadCrumb = "HEADERS") {
            pattern.mapValues { (key, pattern) ->
                attempt(breadCrumb = key) {
                    resolver.generate(key, pattern).toStringValue()
                }
            }
        }
    }

    fun newBasedOn(row: Row, resolver: Resolver): List =
            forEachKeyCombinationIn(pattern, row) { pattern ->
            newBasedOn(pattern, row, resolver)
        }.map { HttpHeadersPattern(it.mapKeys { withoutOptionality(it.key) }) }

    fun encompasses(other: HttpHeadersPattern, thisResolver: Resolver, otherResolver: Resolver): Result {
        val myRequiredKeys = pattern.keys.filter { !isOptional(it) }
        val otherRequiredKeys = other.pattern.keys.filter { !isOptional(it) }

        return checkMissingHeaders(myRequiredKeys, otherRequiredKeys).ifSuccess {
            val otherWithoutOptionality = other.pattern.mapKeys { withoutOptionality(it.key) }
            val thisWithoutOptionality = pattern.filterKeys { withoutOptionality(it) in otherWithoutOptionality }.mapKeys { withoutOptionality(it.key) }

            val valueResults =
                    thisWithoutOptionality.keys.asSequence().map { key ->
                        Pair(key, thisWithoutOptionality.getValue(key).encompasses(resolvedHop(otherWithoutOptionality.getValue(key), otherResolver), thisResolver, otherResolver))
                    }

            valueResults.find { it.second is Result.Failure }.let { result ->
                result?.second?.breadCrumb(result.first) ?: Result.Success()
            }
        }.breadCrumb("HEADER")
    }

    private fun checkMissingHeaders(myRequiredKeys: List, otherRequiredKeys: List): Result =
            when(val missingFixedKey = myRequiredKeys.find { it !in otherRequiredKeys }) {
                null -> Result.Success()
                else -> missingKeyToResult(MissingKeyError(missingFixedKey), "header").breadCrumb(missingFixedKey)
            }
}

private fun parseOrString(pattern: Pattern, sampleValue: String, resolver: Resolver) =
        try {
            pattern.parse(sampleValue, resolver)
        } catch (e: Throwable) {
            StringValue(sampleValue)
        }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy