io.specmatic.core.pattern.TabularPattern.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.cucumber.messages.types.TableRow
import io.specmatic.core.Resolver
import io.specmatic.core.Result
import io.specmatic.core.UnexpectedKeyCheck
import io.specmatic.core.ValidateUnexpectedKeys
import io.specmatic.core.mismatchResult
import io.specmatic.core.pattern.config.NegativePatternConfiguration
import io.specmatic.core.utilities.Flags.Companion.MAX_TEST_REQUEST_COMBINATIONS
import io.specmatic.core.utilities.Flags.Companion.getStringValue
import io.specmatic.core.utilities.mapZip
import io.specmatic.core.utilities.stringToPatternMap
import io.specmatic.core.utilities.withNullPattern
import io.specmatic.core.value.BooleanValue
import io.specmatic.core.value.JSONArrayValue
import io.specmatic.core.value.JSONObjectValue
import io.specmatic.core.value.NullValue
import io.specmatic.core.value.NumberValue
import io.specmatic.core.value.StringValue
import io.specmatic.core.value.Value
fun toTabularPattern(jsonContent: String, typeAlias: String? = null): TabularPattern =
toTabularPattern(stringToPatternMap(jsonContent), typeAlias)
fun toTabularPattern(map: Map, typeAlias: String? = null): TabularPattern {
val missingKeyStrategy: UnexpectedKeyCheck = when ("...") {
in map -> IgnoreUnexpectedKeys
else -> ValidateUnexpectedKeys
}
return TabularPattern(map.minus("..."), missingKeyStrategy, typeAlias)
}
data class TabularPattern(
override val pattern: Map,
private val unexpectedKeyCheck: UnexpectedKeyCheck = ValidateUnexpectedKeys,
override val typeAlias: String? = null
) : Pattern {
override fun matches(sampleData: Value?, resolver: Resolver): Result {
if (sampleData !is JSONObjectValue)
return mismatchResult("JSON object", sampleData, resolver.mismatchMessages)
val resolverWithNullType = withNullPattern(resolver)
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 = keyErrors.plus(results)
return if (failures.isEmpty())
Result.Success()
else
Result.Failure.fromFailures(failures)
}
override fun listOf(valueList: List, resolver: Resolver): Value = JSONArrayValue(valueList)
override fun generate(resolver: Resolver): JSONObjectValue {
val resolverWithNullType = withNullPattern(resolver)
return JSONObjectValue(pattern.mapKeys { entry -> withoutOptionality(entry.key) }.mapValues { (key, pattern) ->
attempt(breadCrumb = key) { resolverWithNullType.withCyclePrevention(pattern) {it.generate(key, pattern)} }
})
}
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 newBasedOn(row: Row, resolver: Resolver): Sequence> {
val resolverWithNullType = withNullPattern(resolver)
return allOrNothingCombinationIn(
pattern,
resolver.resolveRow(row),
null,
null, returnValues { pattern: Map ->
newMapBasedOn(pattern, row, resolverWithNullType).map { it.value }
}).map { it.value }.map {
toTabularPattern(it.mapKeys { (key, _) ->
withoutOptionality(key)
})
}.map { HasValue(it) }
}
override fun newBasedOn(resolver: Resolver): Sequence {
val resolverWithNullType = withNullPattern(resolver)
val allOrNothingCombinationIn =
allOrNothingCombinationIn(
pattern,
Row(),
null,
null, returnValues { pattern: Map ->
newBasedOn(pattern, resolverWithNullType)
}).map { it.value }
return allOrNothingCombinationIn.map { toTabularPattern(it) }
}
override fun negativeBasedOn(row: Row, resolver: Resolver, config: NegativePatternConfiguration): Sequence> {
return this.newBasedOn(row, resolver).map { it.value }.map { HasValue(it) }
}
override fun parse(value: String, resolver: Resolver): Value = parsedJSONObject(value, resolver.mismatchMessages)
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 -> mapEncompassesMap(
pattern,
otherPattern.pattern,
thisResolverWithNullType,
otherResolverWithNullType,
typeStack
)
else -> Result.Failure("Expected json type, got ${otherPattern.typeName}")
}
}
override val typeName: String = "json object"
}
fun newMapBasedOn(patternMap: Map, row: Row, resolver: Resolver): Sequence>> {
val patternCollection: Map>> = patternMap.mapValues { (key, pattern) ->
attempt(breadCrumb = withoutOptionality(key)) {
newPatternsBasedOn(row, key, pattern, resolver).map {
it.addDetails(breadCrumb = withoutOptionality(key), message = "")
}
}
}
return patternList(patternCollection)
}
fun newBasedOn(patternMap: Map, resolver: Resolver): Sequence