in.specmatic.core.HttpRequestPattern.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.
Deprecation Notice for group ID "in.specmatic"
******************************************************************************************************
Updates for "specmatic-core" will no longer be available under the deprecated group ID "in.specmatic".
Please update your dependencies to use the new group ID "io.specmatic".
******************************************************************************************************
package `in`.specmatic.core
import `in`.specmatic.conversions.NoSecurityScheme
import `in`.specmatic.conversions.OpenAPISecurityScheme
import `in`.specmatic.core.Result.Failure
import `in`.specmatic.core.Result.Success
import `in`.specmatic.core.pattern.*
import `in`.specmatic.core.value.StringValue
import io.ktor.util.*
private const val MULTIPART_FORMDATA_BREADCRUMB = "MULTIPART-FORMDATA"
private const val FORM_FIELDS_BREADCRUMB = "FORM-FIELDS"
const val CONTENT_TYPE = "Content-Type"
private val invalidRequestStatuses = listOf(400, 422)
data class HeaderMatchParams(val request: HttpRequest, val headersResolver: Resolver?, val defaultResolver: Resolver, val failures: List)
data class HttpRequestPattern(
val headersPattern: HttpHeadersPattern = HttpHeadersPattern(),
val httpPathPattern: HttpPathPattern? = null,
val httpQueryParamPattern: HttpQueryParamPattern = HttpQueryParamPattern(emptyMap()),
val method: String? = null,
val body: Pattern = EmptyStringPattern,
val formFieldsPattern: Map = emptyMap(),
val multiPartFormDataPattern: List = emptyList(),
val securitySchemes: List = listOf(NoSecurityScheme())
) {
fun matches(incomingHttpRequest: HttpRequest, resolver: Resolver, headersResolver: Resolver? = null, requestBodyReqex: Regex? = null): Result {
val result = incomingHttpRequest to resolver to
::matchPath then
::matchMethod then
::matchSecurityScheme then
::matchQuery then
{ (request, defaultResolver, failures) ->
matchHeaders(HeaderMatchParams(request, headersResolver, defaultResolver, failures))
} then
::matchFormFields then
::matchMultiPartFormData then
::matchBody then
{
(request, resolver, failures) ->
if (requestBodyReqex?.matches(request.bodyString) == false)
MatchSuccess(Triple(request, resolver, failures.plus(Failure("Request did not match regex $requestBodyReqex"))))
else
MatchSuccess(Triple(request, resolver, failures))
} then
::summarize otherwise
::handleError toResult
::returnResult
return when (result) {
is Failure -> result.breadCrumb("REQUEST")
else -> result
}
}
private fun matchSecurityScheme(parameters: Triple>): MatchingResult>> {
val (httpRequest, resolver, failures) = parameters
val matchFailures = mutableListOf()
val matchingSecurityScheme: OpenAPISecurityScheme = securitySchemes.firstOrNull {
when (val result = it.matches(httpRequest)) {
is Failure -> false.also { matchFailures.add(result) }
is Success -> true
}
} ?: return MatchSuccess(Triple(httpRequest, resolver, failures.plus(matchFailures)))
return MatchSuccess(Triple(matchingSecurityScheme.removeParam(httpRequest), resolver, failures))
}
fun matchesSignature(other: HttpRequestPattern): Boolean =
httpPathPattern!!.path == other.httpPathPattern!!.path && method.equals(method)
private fun matchMultiPartFormData(parameters: Triple>): MatchingResult>> {
val (httpRequest, resolver, failures) = parameters
if (multiPartFormDataPattern.isEmpty() && httpRequest.multiPartFormData.isEmpty()) {
return MatchSuccess(parameters)
}
val results: List = multiPartFormDataPattern.map { type ->
val results = httpRequest.multiPartFormData.map { value ->
type.matches(value, resolver)
}
val result = results.find { it is Success } ?: results.find { it is Failure && it.failureReason != FailureReason.PartNameMisMatch }?.breadCrumb(type.name)?.breadCrumb(MULTIPART_FORMDATA_BREADCRUMB)
result ?: when {
isOptional(type.name) -> Success()
else -> null
}
}
val payloadFailures: List = results.filterIsInstance()
val typeKeys = multiPartFormDataPattern.map { withoutOptionality(it.name) }.sorted()
val valueKeys = httpRequest.multiPartFormData.map { it.name }.sorted()
val missingInType: List = valueKeys.filter { it !in typeKeys }.map {
Failure(resolver.mismatchMessages.unexpectedKey("part", it)).breadCrumb(it).breadCrumb(MULTIPART_FORMDATA_BREADCRUMB)
}
val originalTypeKeys = multiPartFormDataPattern.map { it.name }.sorted()
val missingInValue = originalTypeKeys.filter { !isOptional(it) }.filter { withoutOptionality(it) !in valueKeys }.map { partName ->
Failure(resolver.mismatchMessages.expectedKeyWasMissing("part", withoutOptionality(partName))).breadCrumb(withoutOptionality(partName)).breadCrumb(MULTIPART_FORMDATA_BREADCRUMB)
}
val allFailures: List = missingInValue.plus(missingInType).plus(payloadFailures)
return if(allFailures.isEmpty())
MatchSuccess(parameters)
else
MatchSuccess(Triple(httpRequest, resolver, failures.plus(allFailures)))
}
fun matchFormFields(parameters: Triple>): MatchingResult>> {
val (httpRequest, resolver, _: List) = parameters
val keyErrorResults: List = resolver.findKeyErrorList(formFieldsPattern, httpRequest.formFields).map {
it.missingKeyToResult("form field", resolver.mismatchMessages).breadCrumb(it.name).breadCrumb(FORM_FIELDS_BREADCRUMB)
}
val payloadResults: List = formFieldsPattern
.filterKeys { key -> withoutOptionality(key) in httpRequest.formFields }
.map { (key, pattern) -> Triple(withoutOptionality(key), pattern, httpRequest.formFields.getValue(key)) }
.map { (key, pattern, value) ->
try {
when (val result = resolver.matchesPattern(
key, pattern, try {
pattern.parse(value, resolver)
} catch (e: Throwable) {
StringValue(value)
}
)) {
is Failure -> result.breadCrumb(key).breadCrumb(FORM_FIELDS_BREADCRUMB)
else -> result
}
} catch (e: ContractException) {
e.failure().breadCrumb(key).breadCrumb(FORM_FIELDS_BREADCRUMB)
} catch (e: Throwable) {
mismatchResult(pattern, value).breadCrumb(key).breadCrumb(FORM_FIELDS_BREADCRUMB)
}
}
val allFailures = keyErrorResults.plus(payloadResults.filterIsInstance())
return if(allFailures.isEmpty())
MatchSuccess(parameters)
else
MatchSuccess(Triple(httpRequest, resolver, allFailures))
}
private fun matchHeaders(parameters: HeaderMatchParams): MatchingResult>> {
val (httpRequest, headersResolver, defaultResolver, failures) = parameters
val headers = httpRequest.headers
return when (val result = this.headersPattern.matches(headers, headersResolver ?: defaultResolver)) {
is Failure -> MatchSuccess(Triple(httpRequest, defaultResolver, failures.plus(result)))
else -> MatchSuccess(Triple(httpRequest, defaultResolver, failures))
}
}
private fun matchBody(parameters: Triple>): MatchingResult>> {
val (httpRequest, resolver, failures) = parameters
val result = try {
val bodyValue =
if (isPatternToken(httpRequest.bodyString))
StringValue(httpRequest.bodyString)
else
body.parse(httpRequest.bodyString, resolver)
resolver.matchesPattern(null, body, bodyValue).breadCrumb("BODY")
} catch (e: ContractException) {
e.failure().breadCrumb("BODY")
}
return when (result) {
is Failure -> MatchSuccess(Triple(httpRequest, resolver, failures.plus(result)))
else -> MatchSuccess(parameters)
}
}
private fun matchMethod(parameters: Pair): MatchingResult>> {
val (httpRequest, resolver) = parameters
method.let {
return if (it != httpRequest.method)
MatchFailure(mismatchResult(method ?: "", httpRequest.method ?: "").copy(failureReason = FailureReason.MethodMismatch).breadCrumb("METHOD"))
else
MatchSuccess(Triple(httpRequest, resolver, emptyList()))
}
}
private fun matchPath(parameters: Pair): MatchingResult> {
val (httpRequest, resolver) = parameters
val result = matchesPath(httpRequest.path!!, resolver)
return if (result is Failure)
MatchFailure(result)
else
MatchSuccess(parameters)
}
fun matchesPath(
path: String,
resolver: Resolver
) = httpPathPattern!!.matches(path, resolver)
private fun matchQuery(parameters: Triple>): MatchingResult>> {
val (httpRequest, resolver, failures) = parameters
val result = httpQueryParamPattern.matches(httpRequest, resolver)
return if (result is Failure)
MatchSuccess(Triple(httpRequest, resolver, failures.plus(result)))
else
MatchSuccess(parameters)
}
fun generate(request: HttpRequest, resolver: Resolver): HttpRequestPattern {
var requestPattern = HttpRequestPattern()
return attempt(breadCrumb = "REQUEST") {
if (method == null) {
throw missingParam("HTTP method")
}
if (httpPathPattern == null) {
throw missingParam("URL path")
}
requestPattern = requestPattern.copy(method = request.method)
requestPattern = attempt(breadCrumb = "URL") {
val path = request.path ?: ""
val pathTypes = pathToPattern(path)
val queryParamTypes = toTypeMapForQueryParameters(request.queryParams, httpQueryParamPattern, resolver)
requestPattern.copy(httpPathPattern = HttpPathPattern(pathTypes, path), httpQueryParamPattern = HttpQueryParamPattern(queryParamTypes))
}
requestPattern = attempt(breadCrumb = "HEADERS") {
val headersFromRequest = toTypeMap(
toLowerCaseKeys(request.headers),
toLowerCaseKeys(headersPattern.pattern),
resolver
)
requestPattern.copy(headersPattern = HttpHeadersPattern(headersFromRequest))
}
requestPattern = attempt(breadCrumb = "BODY") {
requestPattern.copy(
body = when (request.body) {
is StringValue -> encompassedType(request.bodyString, null, body, resolver)
else -> request.body.exactMatchElseType()
}
)
}
requestPattern = attempt(breadCrumb = "FORM FIELDS") {
requestPattern.copy(formFieldsPattern = toTypeMap(request.formFields, formFieldsPattern, resolver))
}
val multiPartFormDataRequestMap =
request.multiPartFormData.fold(emptyMap()) { acc, part ->
acc.plus(part.name to part)
}
attempt(breadCrumb = "MULTIPART DATA") {
requestPattern.copy(multiPartFormDataPattern = multiPartFormDataPattern.filter {
withoutOptionality(it.name) in multiPartFormDataRequestMap
}.map {
val key = withoutOptionality(it.name)
multiPartFormDataRequestMap.getValue(key).inferType()
})
}
}
}
private fun toLowerCaseKeys(map: Map) =
map.map { (key, value) -> key.toLowerCasePreservingASCIIRules() to value }.toMap()
private fun toTypeMap(
values: Map,
types: Map,
resolver: Resolver
): Map {
return types.filterKeys { withoutOptionality(it) in values }.mapValues {
val key = withoutOptionality(it.key)
val type = it.value
attempt(breadCrumb = key) {
val valueString = values.getValue(key)
encompassedType(valueString, key, type, resolver)
}
}
}
private fun toTypeMapForQueryParameters(
queryParams: QueryParameters,
httpQueryParamPattern: HttpQueryParamPattern,
resolver: Resolver
): Map {
val patterns: Map = httpQueryParamPattern.queryPatterns
val paramsWithinPattern = patterns.filterKeys { withoutOptionality(it) in queryParams.paramPairs.map { it.first } }.map {
val key = withoutOptionality(it.key)
val pattern = it.value
attempt(breadCrumb = key) {
val values: List = queryParams.getValues(key)
when (pattern) {
is QueryParameterArrayPattern -> {
val queryParameterValuePatterns = values.map { value ->
encompassedType(value, key, pattern.pattern.first(), resolver)
}
key to QueryParameterArrayPattern(queryParameterValuePatterns, key)
}
is QueryParameterScalarPattern -> {
key to QueryParameterScalarPattern(
encompassedType(
values.single(),
key,
pattern.pattern,
resolver
)
)
}
else -> {
throw ContractException("Non query type: $pattern found")
}
}
}
}.toMap()
val paramsUnaccountedFor = queryParams.paramPairs.filter { (name, _) ->
name !in paramsWithinPattern
}.groupBy { (name, _) ->
name
}
val paramsOutsidePattern = if(httpQueryParamPattern.additionalProperties != null) {
val results = paramsUnaccountedFor.map { (name, values) ->
values.map { (_, rawValue) ->
val value = httpQueryParamPattern.additionalProperties.parse(rawValue, resolver)
httpQueryParamPattern.additionalProperties.matches(value, resolver)
}
}.flatten()
val matchResult = Result.fromResults(results)
if(matchResult is Failure)
throw ContractException(matchResult.toFailureReport())
paramsUnaccountedFor.map { (name, values) ->
val pattern = if (values.size > 1) {
QueryParameterArrayPattern(values.map { ExactValuePattern(StringValue(it.second)) }, name)
} else {
QueryParameterScalarPattern(ExactValuePattern(StringValue(values.single().second)))
}
name to pattern
}.toMap()
} else {
emptyMap()
}
return paramsWithinPattern + paramsOutsidePattern
}
private fun encompassedType(valueString: String, key: String?, type: Pattern, resolver: Resolver): Pattern {
return when {
isPatternToken(valueString) -> resolvedHop(parsedPattern(valueString, key), resolver).let { parsedType ->
when (val result = type.encompasses(parsedType, resolver, resolver)) {
is Success -> parsedType
is Failure -> throw ContractException(result.toFailureReport())
}
}
else -> type.parseToType(valueString, resolver)
}
}
fun generate(resolver: Resolver): HttpRequest {
var newRequest = HttpRequest()
return attempt(breadCrumb = "REQUEST") {
if (method == null) {
throw missingParam("HTTP method")
}
if (httpPathPattern == null) {
throw missingParam("URL path")
}
newRequest = newRequest.updateMethod(method)
attempt(breadCrumb = "URL") {
newRequest = newRequest.updatePath(httpPathPattern.generate(resolver))
val queryParams = httpQueryParamPattern.generate(resolver)
for (queryParam in queryParams) {
newRequest = newRequest.updateQueryParam(queryParam.first, queryParam.second)
}
}
val headers = headersPattern.generate(resolver)
val body = body
attempt(breadCrumb = "BODY") {
resolver.withCyclePrevention(body) {cyclePreventedResolver ->
body.generate(cyclePreventedResolver).let { value ->
newRequest = newRequest.updateBody(value)
}
}
}
newRequest = newRequest.copy(headers = headers)
val formFieldsValue = attempt(breadCrumb = "FORM FIELDS") {
formFieldsPattern.mapValues { (key, pattern) ->
attempt(breadCrumb = key) {
resolver.withCyclePrevention(pattern) { cyclePreventedResolver ->
cyclePreventedResolver.generate(key, pattern)
}.toString()
}
}
}
newRequest = when (formFieldsValue.size) {
0 -> newRequest
else -> newRequest.copy(
formFields = formFieldsValue,
headers = newRequest.headers.plus(CONTENT_TYPE to "application/x-www-form-urlencoded")
)
}
newRequest = securitySchemes.fold(newRequest) { request, securityScheme ->
securityScheme.addTo(request)
}
val multipartData = attempt(breadCrumb = "MULTIPART DATA") {
multiPartFormDataPattern.mapIndexed { index, multiPartFormDataPattern ->
attempt(breadCrumb = "[$index]") { multiPartFormDataPattern.generate(resolver) }
}
}
when (multipartData.size) {
0 -> newRequest
else -> newRequest.copy(
multiPartFormData = multipartData,
headers = newRequest.headers.plus(CONTENT_TYPE to "multipart/form-data")
)
}
}
}
fun newBasedOn(row: Row, initialResolver: Resolver, status: Int = 0): Sequence> {
val resolver = when (status) {
in invalidRequestStatuses -> initialResolver.invalidRequestResolver()
else -> initialResolver
}
return returnValue(breadCrumb = "REQUEST") {
val newHttpPathPatterns: Sequence> = httpPathPattern?.let { httpPathPattern ->
val newURLPathSegmentPatternsList = httpPathPattern.newBasedOn(row, resolver)
newURLPathSegmentPatternsList.map { HttpPathPattern(it, httpPathPattern.path) }.map { HasValue(it) }
} ?: sequenceOf(HasValue(null))
val newQueryParamsPatterns: Sequence> =
if(status.toString().startsWith("2")) {
val new = httpQueryParamPattern.newBasedOn(row, resolver)
httpQueryParamPattern.addComplimentaryPatterns(new, row, resolver)
} else {
httpQueryParamPattern.readFrom(row, resolver)
}.map {
HasValue(HttpQueryParamPattern(it))
}
val newHeadersPattern: Sequence> = if(status.toString().startsWith("2")) {
val new = headersPattern.newBasedOn(row, resolver)
headersPattern.addComplimentaryPatterns(new, row, resolver)
} else {
headersPattern.readFrom(row, resolver)
}.map { HasValue(it) }
val newBodies: Sequence> = attempt(breadCrumb = "BODY") {
body.let {
if (it is DeferredPattern && row.containsField(it.pattern)) {
val example = row.getField(it.pattern)
sequenceOf(HasValue(ExactValuePattern(it.parse(example, resolver))))
} else if (it.typeAlias?.let { p -> isPatternToken(p) } == true && row.containsField(it.typeAlias!!)) {
val example = row.getField(it.typeAlias!!)
sequenceOf(HasValue(ExactValuePattern(it.parse(example, resolver))))
} else if (it is XMLPattern && it.referredType?.let { referredType -> row.containsField("($referredType)") } == true) {
val referredType = "(${it.referredType})"
val example = row.getField(referredType)
sequenceOf(HasValue(ExactValuePattern(it.parse(example, resolver))))
} else if (row.containsField("(REQUEST-BODY)")) {
val example = row.getField("(REQUEST-BODY)")
val value = it.parse(example, resolver)
val requestBodyAsIs = if (!isInvalidRequestResponse(status)) {
val result = body.matches(value, resolver)
if (result is Failure)
throw ContractException(result.toFailureReport())
else
ExactValuePattern(value)
} else
ExactValuePattern(value)
if(status.toString().startsWith("2"))
resolver.generateHttpRequestbodies(body, row, requestBodyAsIs, value)
else
sequenceOf(HasValue(requestBodyAsIs))
} else {
resolver.generateHttpRequestbodies(body, row)
}
}
}
val newFormFieldsPatterns: Sequence>> = newBasedOn(formFieldsPattern, row, resolver).map { HasValue(it) }
val newFormDataPartLists: Sequence>> = newMultiPartBasedOn(multiPartFormDataPattern, row, resolver).map { HasValue(it) }
newHttpPathPatterns.flatMap("PATH") { newPathParamPattern ->
newQueryParamsPatterns.flatMap("QUERY") { newQueryParamPattern ->
newBodies.flatMap("BODY") { newBody ->
newHeadersPattern.flatMap("HEADERS") { newHeadersPattern ->
newFormFieldsPatterns.flatMap("FORM-FIELDS") { newFormFieldsPattern ->
newFormDataPartLists.flatMap("FORM-DATA") { newFormDataPartList ->
val newRequestPattern = HttpRequestPattern(
headersPattern = newHeadersPattern.value,
httpPathPattern = newPathParamPattern.value,
httpQueryParamPattern = newQueryParamPattern.value,
method = method,
body = newBody.value,
formFieldsPattern = newFormFieldsPattern.value,
multiPartFormDataPattern = newFormDataPartList.value
)
val schemeInRow = securitySchemes.find { it.isInRow(row) }
if (schemeInRow != null) {
listOf(schemeInRow.addTo(newRequestPattern, row)).asSequence()
} else {
securitySchemes.asSequence().map {
newRequestPattern.copy(securitySchemes = listOf(it))
}
}.map { HasValue(it) }
}
}
}
}
}
}
}
}
fun Sequence.flatMap(breadCrumb: String = "", fn: (T) -> Sequence>): Sequence> {
val iterator = this.iterator()
return sequence {
try {
while(iterator.hasNext()) {
val next = iterator.next()
val results: Sequence> = fn(next)
yieldAll(results)
}
} catch(t: Throwable) {
yield(HasException(t, breadCrumb = breadCrumb))
}
}
}
private fun isInvalidRequestResponse(status: Int): Boolean {
return status in invalidRequestStatuses
}
fun newBasedOn(resolver: Resolver): Sequence {
return attempt(breadCrumb = "REQUEST") {
val newHttpPathPatterns = httpPathPattern?.let { httpPathPattern ->
val newURLPathSegmentPatternsList = httpPathPattern.newBasedOn(resolver)
newURLPathSegmentPatternsList.map { HttpPathPattern(it, httpPathPattern.path) }
} ?: sequenceOf(null)
val newQueryParamsPatterns = httpQueryParamPattern.newBasedOn(resolver).map { HttpQueryParamPattern(it) }
val newBodies = attempt(breadCrumb = "BODY") {
resolver.withCyclePrevention(body) { cyclePreventedResolver ->
body.newBasedOn(cyclePreventedResolver)
}
}
val newHeadersPattern = headersPattern.newBasedOn(resolver)
val newFormFieldsPatterns = newBasedOn(formFieldsPattern, resolver)
//TODO: Backward Compatibility
val newFormDataPartLists = newMultiPartBasedOn(multiPartFormDataPattern, Row(), resolver)
newHttpPathPatterns.flatMap { newPathParamPattern ->
newQueryParamsPatterns.flatMap { newQueryParamPattern ->
newBodies.flatMap { newBody ->
newHeadersPattern.flatMap { newHeadersPattern ->
newFormFieldsPatterns.flatMap { newFormFieldsPattern ->
newFormDataPartLists.flatMap { newFormDataPartList ->
val newRequestPattern = HttpRequestPattern(
headersPattern = newHeadersPattern,
httpPathPattern = newPathParamPattern,
httpQueryParamPattern = newQueryParamPattern,
method = method,
body = newBody,
formFieldsPattern = newFormFieldsPattern,
multiPartFormDataPattern = newFormDataPartList
)
securitySchemes.map {
newRequestPattern.copy(securitySchemes = listOf(it))
}.asSequence().map { HasValue(it) }
}
}
}
}
}
}.map { it.value }
}
}
fun testDescription(): String {
return "$method ${httpPathPattern.toString()}"
}
fun negativeBasedOn(row: Row, resolver: Resolver): Sequence> {
return returnValue(breadCrumb = "REQUEST") {
val newHttpPathPatterns: Sequence?> =
httpPathPattern?.let { httpPathPattern ->
httpPathPattern.negativeBasedOn(row, resolver)
.map { it.ifValue { HttpPathPattern(it, httpPathPattern.path) } }
} ?: sequenceOf(null)
val newQueryParamsPatterns = httpQueryParamPattern.negativeBasedOn(row, resolver).map { it.ifValue { HttpQueryParamPattern(it) } }
val newBodies: Sequence> = returnValue(breadCrumb = "BODY") {
body.let {
if (it is DeferredPattern && row.containsField(it.pattern)) {
val example = row.getField(it.pattern)
sequenceOf(HasValue(ExactValuePattern(it.parse(example, resolver))))
} else if (it.typeAlias?.let { p -> isPatternToken(p) } == true && row.containsField(it.typeAlias!!)) {
val example = row.getField(it.typeAlias!!)
sequenceOf(HasValue(ExactValuePattern(it.parse(example, resolver))))
} else if (it is XMLPattern && it.referredType?.let { referredType -> row.containsField("($referredType)") } == true) {
val referredType = "(${it.referredType})"
val example = row.getField(referredType)
sequenceOf(HasValue(ExactValuePattern(it.parse(example, resolver))))
} else if (row.containsField("(REQUEST-BODY)")) {
val example = row.getField("(REQUEST-BODY)")
val value = it.parse(example, resolver)
val result = body.matches(value, resolver)
if (result is Failure)
throw ContractException(result.toFailureReport())
body.negativeBasedOn(row.noteRequestBody(), resolver)
} else {
body.negativeBasedOn(row, resolver)
}
}
}
val newHeadersPattern = headersPattern.negativeBasedOn(row, resolver)
val newFormFieldsPatterns = newBasedOn(formFieldsPattern, row, resolver)
val newFormDataPartLists = newMultiPartBasedOn(multiPartFormDataPattern, row, resolver)
sequence {
try {
// If security schemes are present, for now we'll just take the first scheme and assign it to each negative request pattern.
// Ideally we should generate negative patterns from the security schemes and use them.
val positivePattern: HttpRequestPattern =
newBasedOn(row, resolver, 400).first().value.copy(securitySchemes = listOf(securitySchemes.first()))
newHttpPathPatterns.forEach { pathParamPatternR ->
if(pathParamPatternR != null) {
yield(pathParamPatternR.ifValue { pathParamPattern ->
positivePattern.copy(httpPathPattern = pathParamPattern)
})
}
}
newQueryParamsPatterns.forEach { queryParamPatternR ->
yield(
queryParamPatternR.ifValue { queryParamPattern ->
positivePattern.copy(httpQueryParamPattern = queryParamPattern)
}
)
}
newBodies.forEach { newBodyPatternR ->
yield(
newBodyPatternR.ifValue { newBodyPattern -> positivePattern.copy(body = newBodyPattern) }
)
}
newHeadersPattern.forEach { newHeaderPatternR ->
yield(
newHeaderPatternR.ifValue { newHeaderPattern ->
positivePattern.copy(headersPattern = newHeaderPattern)
}
)
}
newFormFieldsPatterns.forEach { newFormFieldPattern ->
yield(
HasValue(positivePattern.copy(formFieldsPattern = newFormFieldPattern))
)
}
newFormDataPartLists.forEach { newFormDataPartListPattern ->
yield(
HasValue(positivePattern.copy(multiPartFormDataPattern = newFormDataPartListPattern))
)
}
} catch(t: Throwable) {
yield(HasException(t))
}
}
}
}
fun addPathParamsToRows(requestPath: String, row: Row, resolver: Resolver): Row {
return httpPathPattern?.let { httpPathPattern ->
val pathParams = httpPathPattern.extractPathParams(requestPath, resolver)
return row.addFields(pathParams)
} ?: row
}
}
fun missingParam(missingValue: String): ContractException {
return ContractException("$missingValue is missing. Can't generate the contract test.")
}
fun newMultiPartBasedOn(
partList: List,
row: Row,
resolver: Resolver
): Sequence> {
val values = partList.map { part ->
attempt(breadCrumb = part.name) {
part.newBasedOn(row, resolver)
}
}
return multiPartListCombinations(values)
}
fun multiPartListCombinations(values: List>): Sequence> {
if (values.isEmpty())
return sequenceOf(emptyList())
val value: Sequence = values.last()
val subLists = multiPartListCombinations(values.dropLast(1))
return subLists.flatMap { list ->
value.map { type ->
when (type) {
null -> list
else -> list.plus(type)
}
}
}
}