io.specmatic.core.Substitution.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
import io.specmatic.core.pattern.*
import io.specmatic.core.value.JSONArrayValue
import io.specmatic.core.value.JSONObjectValue
import io.specmatic.core.value.StringValue
import io.specmatic.core.value.Value
class Substitution(
val runningRequest: HttpRequest,
val originalRequest: HttpRequest,
val httpPathPattern: HttpPathPattern,
val headersPattern: HttpHeadersPattern,
val httpQueryParamPattern: HttpQueryParamPattern,
val body: Pattern,
val resolver: Resolver,
val data: JSONObjectValue,
val dictionary: Map
) {
val variableValues: Map
init {
val variableValuesFromHeaders = variablesFromMap(runningRequest.headers.filter { it.key in originalRequest.headers }, originalRequest.headers)
val variableValuesFromQueryParams = variablesFromMap(runningRequest.queryParams.asMap(), originalRequest.queryParams.asMap())
val runningPathPieces = runningRequest.path!!.split('/').filterNot { it.isBlank() }
val originalPathPieces = originalRequest.path!!.split('/').filterNot { it.isBlank() }
val variableValuesFromPath = runningPathPieces.zip(originalPathPieces).map { (runningPiece, originalPiece) ->
if (!isPatternToken(originalPiece))
null
else {
val pieces = withoutPatternDelimiters(originalPiece).split(':')
val name = pieces.getOrNull(0)
?: throw ContractException("Could not interpret substituion expression $originalPiece")
name to runningPiece
}
}.filterNotNull().toMap()
val variableValuesFromRequestBody: Map = getVariableValuesFromValue(runningRequest.body, originalRequest.body)
variableValues = variableValuesFromHeaders + variableValuesFromRequestBody + variableValuesFromQueryParams + variableValuesFromPath
}
private fun variableFromString(value: String, originalValue: String): Pair? {
if(!isPatternToken(originalValue))
return null
val pieces = withoutPatternDelimiters(originalValue).split(":")
val name = pieces.getOrNull(0) ?: return null
return Pair(name, value)
}
private fun variablesFromMap(map: Map, originalMap: Map) = map.entries.map { (key, value) ->
val originalValue = originalMap.get(key) ?: return@map null
variableFromString(value, originalValue)
}.filterNotNull().toMap()
private fun getVariableValuesFromValue(value: JSONObjectValue, originalValue: JSONObjectValue): Map {
return originalValue.jsonObject.entries.fold(emptyMap()) { acc, entry ->
val runningValue = value.jsonObject.getValue(entry.key)
acc + getVariableValuesFromValue(runningValue, entry.value)
}
}
private fun getVariableValuesFromValue(value: JSONArrayValue, originalValue: JSONArrayValue): Map {
return originalValue.list.foldRightIndexed(emptyMap()) { index: Int, item: Value, acc: Map ->
val runningItem = value.list.get(index)
acc + getVariableValuesFromValue(runningItem, item)
}
}
private fun getVariableValuesFromValue(value: Value, originalValue: Value): Map {
return when (originalValue) {
is StringValue -> {
if(isPatternToken(originalValue.string)) {
val pieces = withoutPatternDelimiters(originalValue.string).split(":")
val name = pieces.getOrNull(0) ?: return emptyMap()
mapOf(name to value.toStringLiteral())
} else emptyMap()
}
is JSONObjectValue -> getVariableValuesFromValue(value as JSONObjectValue, originalValue)
is JSONArrayValue -> getVariableValuesFromValue(value as JSONArrayValue, originalValue)
else -> emptyMap()
}
}
fun resolveSubstitutions(value: Value): Value {
return when(value) {
is JSONObjectValue -> resolveSubstitutions(value)
is JSONArrayValue -> resolveSubstitutions(value)
is StringValue -> {
if(value.string.startsWith("{{") && value.string.endsWith("}}"))
StringValue(substituteSimpleVariableLookup(value.string))
else
value
}
else -> value
}
}
fun substituteSimpleVariableLookup(string: String, key: String? = null): String {
val name = string.trim().removeSurrounding("$(", ")")
return variableValues[name]
?: key?.let { dictionary[key]?.toStringLiteral() }
?: throw ContractException("Could not resolve expression $string as no variable by the name $name was found")
}
private fun resolveSubstitutions(value: JSONObjectValue): Value {
return value.copy(
value.jsonObject.mapValues { entry ->
resolveSubstitutions(entry.value)
}
)
}
private fun resolveSubstitutions(value: JSONArrayValue): Value {
return value.copy(
value.list.map {
resolveSubstitutions(it)
}
)
}
fun resolveHeaderSubstitutions(headers: Map, patternMap: Map): ReturnValue