run.qontract.core.pattern.JSONObjectPattern.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.pattern
import run.qontract.core.*
import run.qontract.core.utilities.mapZip
import run.qontract.core.utilities.stringToPatternMap
import run.qontract.core.utilities.withNullPattern
import run.qontract.core.value.JSONArrayValue
import run.qontract.core.value.JSONObjectValue
import run.qontract.core.value.Value
fun toJSONObjectPattern(jsonContent: String, typeAlias: String?): JSONObjectPattern = toJSONObjectPattern(stringToPatternMap(jsonContent)).copy(typeAlias = typeAlias)
fun toJSONObjectPattern(map: Map): JSONObjectPattern {
val missingKeyStrategy = when ("...") {
in map -> ignoreUnexpectedKeys
else -> ::validateUnexpectedKeys
}
return JSONObjectPattern(map.minus("..."), missingKeyStrategy)
}
val ignoreUnexpectedKeys = { _: Map, _: Map -> null }
data class JSONObjectPattern(override val pattern: Map = emptyMap(), private val unexpectedKeyCheck: UnexpectedKeyCheck = ::validateUnexpectedKeys, override val typeAlias: String? = null) : Pattern {
override fun equals(other: Any?): Boolean = when(other) {
is JSONObjectPattern -> this.pattern == other.pattern
else -> false
}
override fun encompasses(otherPattern: Pattern, thisResolver: Resolver, otherResolver: Resolver, typeStack: TypeStack): Result {
val thisResolverWithNullType = withNullPattern(thisResolver)
val otherResolverWithNullType = withNullPattern(otherResolver)
return when (otherPattern) {
is ExactValuePattern -> otherPattern.fitsWithin(listOf(this), otherResolverWithNullType, thisResolverWithNullType, typeStack)
!is JSONObjectPattern -> Result.Failure("Expected tabular json type, got ${otherPattern.typeName}")
else -> mapEncompassesMap(pattern, otherPattern.pattern, thisResolverWithNullType, otherResolverWithNullType)
}
}
override fun listOf(valueList: List, resolver: Resolver): Value {
return JSONArrayValue(valueList)
}
override fun matches(sampleData: Value?, resolver: Resolver): Result {
val resolverWithNullType = withNullPattern(resolver)
if(sampleData !is JSONObjectValue)
return mismatchResult("JSON object", sampleData)
val missingKey = resolverWithNullType.findMissingKey(pattern, sampleData.jsonObject, unexpectedKeyCheck)
if(missingKey != null)
return missingKeyToResult(missingKey, "key")
mapZip(pattern, sampleData.jsonObject).forEach { (key, patternValue, sampleValue) ->
when (val result = resolverWithNullType.matchesPattern(key, patternValue, sampleValue)) {
is Result.Failure -> return result.breadCrumb(key)
}
}
return Result.Success()
}
override fun generate(resolver: Resolver): JSONObjectValue {
val resolverWithNullType = withNullPattern(resolver)
return JSONObjectValue(generate(pattern, resolverWithNullType))
}
override fun newBasedOn(row: Row, resolver: Resolver): List {
val resolverWithNullType = withNullPattern(resolver)
return forEachKeyCombinationIn(pattern.minus("..."), row) { pattern ->
newBasedOn(pattern, row, resolverWithNullType)
}.map { toJSONObjectPattern(it) }
}
override fun parse(value: String, resolver: Resolver): Value = parsedJSON(value)
override fun hashCode(): Int = pattern.hashCode()
override val typeName: String = "json object"
}
fun generate(jsonPattern: Map, resolver: Resolver): Map {
val resolverWithNullType = withNullPattern(resolver)
return jsonPattern.mapKeys { entry -> withoutOptionality(entry.key) }.mapValues { (key, pattern) ->
attempt(breadCrumb = key) { resolverWithNullType.generate(key, pattern) }
}
}
internal fun mapEncompassesMap(pattern: Map, otherPattern: Map, thisResolverWithNullType: Resolver, otherResolverWithNullType: Resolver, typeStack: TypeStack = emptySet()): Result {
val myRequiredKeys = pattern.keys.filter { !isOptional(it) }
val otherRequiredKeys = otherPattern.keys.filter { !isOptional(it) }
val missingFixedKey = myRequiredKeys.find { it !in otherRequiredKeys }
if (missingFixedKey != null)
return missingKeyToResult(MissingKeyError(missingFixedKey), "key").breadCrumb(missingFixedKey)
return pattern.keys.asSequence().map { key ->
val bigger = pattern.getValue(key)
val smaller = otherPattern[key] ?: otherPattern[withoutOptionality(key)]
when {
smaller != null -> biggerEncompassesSmaller(bigger, smaller, thisResolverWithNullType, otherResolverWithNullType, typeStack).breadCrumb(key)
else -> Result.Success()
}
}.find { it is Result.Failure } ?: Result.Success()
}