io.specmatic.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 specmatic-core Show documentation
Show all versions of specmatic-core Show documentation
Turn your contracts into executable specifications. Contract Driven Development - Collaboratively Design & Independently Deploy MicroServices & MicroFrontends.
package io.specmatic.core.pattern
import io.specmatic.core.*
import io.specmatic.core.pattern.config.NegativePatternConfiguration
import io.specmatic.core.utilities.mapZip
import io.specmatic.core.utilities.stringToPatternMap
import io.specmatic.core.utilities.withNullPattern
import io.specmatic.core.value.JSONArrayValue
import io.specmatic.core.value.JSONObjectValue
import io.specmatic.core.value.Value
import java.util.Optional
fun toJSONObjectPattern(jsonContent: String, typeAlias: String?): JSONObjectPattern =
toJSONObjectPattern(stringToPatternMap(jsonContent), typeAlias)
fun toJSONObjectPattern(map: Map, typeAlias: String? = null): JSONObjectPattern {
val missingKeyStrategy: UnexpectedKeyCheck = when ("...") {
in map -> IgnoreUnexpectedKeys
else -> ValidateUnexpectedKeys
}
return JSONObjectPattern(map.minus("..."), missingKeyStrategy, typeAlias)
}
data class JSONObjectPattern(
override val pattern: Map = emptyMap(),
private val unexpectedKeyCheck: UnexpectedKeyCheck = ValidateUnexpectedKeys,
override val typeAlias: String? = null,
val minProperties: Int? = null,
val maxProperties: Int? = 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 TabularPattern -> {
mapEncompassesMap(
pattern,
otherPattern.pattern,
thisResolverWithNullType,
otherResolverWithNullType,
typeStack
)
}
is JSONObjectPattern -> {
val propertyLimitResults: List = olderPropertyLimitsEncompassNewer(this, otherPattern)
mapEncompassesMap(
pattern,
otherPattern.pattern,
thisResolverWithNullType,
otherResolverWithNullType,
typeStack,
propertyLimitResults
)
}
else -> Result.Failure("Expected json type, got ${otherPattern.typeName}")
}
}
private fun olderPropertyLimitsEncompassNewer(
newer: JSONObjectPattern,
older: JSONObjectPattern
): List {
val minPropertiesResult =
if (older.minProperties != null && newer.minProperties != null && older.minProperties > newer.minProperties)
Result.Failure("Expected at least ${older.minProperties} properties, got ${newer.minProperties}")
else
Result.Success()
val maxPropertiesResult =
if (older.maxProperties != null && newer.maxProperties != null && older.maxProperties < newer.maxProperties)
Result.Failure("Expected at most ${older.maxProperties} properties, got ${newer.maxProperties}")
else
Result.Success()
return listOf(minPropertiesResult, maxPropertiesResult).filterIsInstance()
}
override fun generateWithAll(resolver: Resolver): Value {
return attempt(breadCrumb = "HEADERS") {
JSONObjectValue(pattern.filterNot { it.key == "..." }.mapKeys {
attempt(breadCrumb = it.key) {
withoutOptionality(it.key)
}
}.mapValues {
it.value.generateWithAll(resolver)
})
}
}
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, resolver.mismatchMessages)
val minCountErrors: List = if (sampleData.jsonObject.keys.size < (minProperties ?: 0))
listOf(Result.Failure("Expected at least $minProperties properties, got ${sampleData.jsonObject.keys.size}"))
else
emptyList()
val maxCountErrors: List =
if (sampleData.jsonObject.keys.size > (maxProperties ?: Int.MAX_VALUE))
listOf(Result.Failure("Expected at most $maxProperties properties, got ${sampleData.jsonObject.keys.size}"))
else
emptyList()
val keyErrors: List =
resolverWithNullType.findKeyErrorList(pattern, sampleData.jsonObject).map {
it.missingKeyToResult("key", resolver.mismatchMessages).breadCrumb(it.name)
}
val results: List =
mapZip(pattern, sampleData.jsonObject).map { (key, patternValue, sampleValue) ->
resolverWithNullType.matchesPattern(key, patternValue, sampleValue).breadCrumb(key)
}.filterIsInstance()
val failures: List = minCountErrors + maxCountErrors + keyErrors + results
return if (failures.isEmpty())
Result.Success()
else
Result.Failure.fromFailures(failures)
}
override fun generate(resolver: Resolver): JSONObjectValue {
return JSONObjectValue(
generate(
selectPropertiesWithinMaxAndMin(pattern, minProperties, maxProperties),
withNullPattern(resolver)
)
)
}
override fun newBasedOn(row: Row, resolver: Resolver): Sequence> =
allOrNothingCombinationIn(
pattern.minus("..."),
resolver.resolveRow(row),
minProperties,
maxProperties
) { pattern ->
newMapBasedOn(pattern, row, withNullPattern(resolver))
}.map { it: ReturnValue