All Downloads are FREE. Search and download functionalities are using the official Maven repository.

in.specmatic.core.pattern.StringPattern.kt Maven / Gradle / Ivy

Go to download

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". ******************************************************************************************************

There is a newer version: 1.3.39
Show newest version
package `in`.specmatic.core.pattern

import com.mifmif.common.regex.Generex
import `in`.specmatic.core.Resolver
import `in`.specmatic.core.Result
import `in`.specmatic.core.mismatchResult
import `in`.specmatic.core.value.JSONArrayValue
import `in`.specmatic.core.value.StringValue
import `in`.specmatic.core.value.Value
import java.nio.charset.StandardCharsets
import java.util.*

data class StringPattern (
    override val typeAlias: String? = null,
    val minLength: Int? = null,
    val maxLength: Int? = null,
    override val example: String? = null,
    val regex: String? = null
) : Pattern, ScalarType, HasDefaultExample {
    init {
        if (minLength != null && maxLength != null && minLength > maxLength) {
            throw IllegalArgumentException("maxLength cannot be less than minLength")
        }
    }

    override fun matches(sampleData: Value?, resolver: Resolver): Result {
        return when (sampleData) {
            is StringValue -> {
                if (minLength != null && sampleData.toStringLiteral().length < minLength) return mismatchResult(
                    "string with minLength $minLength",
                    sampleData, resolver.mismatchMessages
                )

                if (maxLength != null && sampleData.toStringLiteral().length > maxLength) return mismatchResult(
                    "string with maxLength $maxLength",
                    sampleData, resolver.mismatchMessages
                )

                if(regex != null && !Regex(regex).matches(sampleData.toStringLiteral())) {
                    return mismatchResult(
                        """string that matches regex /$regex/""",
                        sampleData,
                        resolver.mismatchMessages
                    )
                }

                return Result.Success()
            }
            else -> mismatchResult("string", sampleData, resolver.mismatchMessages)
        }
    }

    override fun encompasses(
        otherPattern: Pattern,
        thisResolver: Resolver,
        otherResolver: Resolver,
        typeStack: TypeStack
    ): Result {
        return encompasses(this, otherPattern, thisResolver, otherResolver, typeStack)
    }

    override fun listOf(valueList: List, resolver: Resolver): Value {
        return JSONArrayValue(valueList)
    }

    private val randomStringLength: Int =
        when {
            minLength != null && 5 < minLength -> minLength
            maxLength != null && 5 > maxLength -> maxLength
            else -> 5
        }
    
    override fun generate(resolver: Resolver): Value {
        val defaultExample: Value? = resolver.resolveExample(example, this)

        if (regex != null) {
            if(defaultExample == null)
                return StringValue(Generex(regex.removePrefix("^").removeSuffix("$")).random(randomStringLength))

            val defaultExampleMatchResult = matches(defaultExample, resolver)

            if(defaultExampleMatchResult.isSuccess())
                return defaultExample

            throw ContractException("Schema example ${defaultExample.toStringLiteral()} does not match pattern $regex")
        }

        if(defaultExample != null) {
            if(defaultExample !is StringValue)
                throw ContractException("Schema example ${defaultExample.toStringLiteral()} is not a string")

            return defaultExample
        }

        return StringValue(randomString(randomStringLength))
    }
    override fun newBasedOn(row: Row, resolver: Resolver): Sequence {
        val minLengthExample = minLength?.let {
            ExactValuePattern(StringValue(randomString(it)))
        }

        val withinRangeExample = this

        val maxLengthExample = maxLength?.let {
            ExactValuePattern(StringValue(randomString(it)))
        }

        return sequenceOf(minLengthExample, withinRangeExample, maxLengthExample).filterNotNull()
    }

    override fun newBasedOn(resolver: Resolver): Sequence = sequenceOf(this)

    override fun negativeBasedOn(row: Row, resolver: Resolver): Sequence> {
        val current = this

        return sequence {
            yieldAll(scalarAnnotation(current, sequenceOf(NullPattern, NumberPattern(), BooleanPattern())))

            if(minLength != null)
                yield(HasValue(ExactValuePattern(StringValue(randomString(minLength - 1)))))

            if(maxLength != null)
                yield(HasValue(ExactValuePattern(StringValue(randomString(maxLength + 1)))))
        }
    }

    override fun parse(value: String, resolver: Resolver): Value = StringValue(value)
    override val typeName: String = "string"

    override val pattern: Any = "(string)"
    override fun toString(): String = pattern.toString()
}

fun randomString(length: Int = 5): String {
    val array = ByteArray(length)
    val random = Random()
    for (index in array.indices) {
        array[index] = (random.nextInt(25) + 65).toByte()
    }
    return String(array, StandardCharsets.UTF_8)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy