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

com.github.erosb.jsonsKema.Format.kt Maven / Gradle / Ivy

package com.github.erosb.jsonsKema

import org.apache.commons.validator.routines.EmailValidator
import org.apache.commons.validator.routines.InetAddressValidator
import java.lang.IllegalArgumentException
import java.net.URI
import java.net.URISyntaxException
import java.time.Duration
import java.time.LocalTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeFormatterBuilder
import java.time.format.DateTimeParseException
import java.time.format.ResolverStyle
import java.time.temporal.ChronoField
import java.util.UUID
import java.util.regex.Pattern

typealias FormatValidator = (instance: IJsonValue, schema: FormatSchema) -> ValidationFailure?

internal val dateFormatValidator: FormatValidator = { inst, schema -> inst.maybeString { str ->
    try {
        DateTimeFormatter.ISO_LOCAL_DATE.parse(str.value)
        null
    } catch (e: DateTimeParseException) {
        FormatValidationFailure(schema, str)
    }
}}

private val DATE_TIME_FORMATTER: DateTimeFormatter = run {
    val secondsFractionFormatter = DateTimeFormatterBuilder()
        .appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, true)
        .toFormatter()
    DateTimeFormatterBuilder()
        .appendPattern("yyyy-MM-dd'T'HH:mm:ss")
        .appendOptional(secondsFractionFormatter)
        .appendPattern("XXX")
        .toFormatter()
        .withResolverStyle(ResolverStyle.STRICT)
}

private fun validateDateTime(str: IJsonString, schema: FormatSchema): FormatValidationFailure? {
    try {
        DATE_TIME_FORMATTER.parse(str.value.uppercase())
        ZonedDateTime.parse(str.value)
    } catch (e: DateTimeParseException) {
        if ((e.message?.indexOf("Invalid value for SecondOfMinute") ?: -1) > -1) {
            // handle leap second
            if (str.value.indexOf("23:59:60") > -1) {
                val sanitized = JsonString(str.value.replace("23:59:60", "23:59:59"), str.location)
                return validateDateTime(sanitized, schema)
            }
        }
        return FormatValidationFailure(schema, str)
    }
    return null
}

internal val dateTimeFormatValidator: FormatValidator = {inst, schema -> inst.maybeString { str ->
    validateDateTime(str, schema)
}}

internal val uriFormatValidator: FormatValidator = {inst, schema -> inst.maybeString { str ->
    try {
        if (URI(str.value).scheme == null) {
            FormatValidationFailure(schema, str)
        } else {
            null
        }
    } catch (e: URISyntaxException) {
        FormatValidationFailure(schema, str)
    }
}}

internal val emailFormatValidator: FormatValidator = {inst, schema -> inst.maybeString { str ->
    if (EmailValidator.getInstance(false, true).isValid(str.value)) {
        null
    } else {
        FormatValidationFailure(schema, str)
    }
}}

internal val ipv4FormatValidator: FormatValidator = {inst, schema -> inst.maybeString { str ->
    if (InetAddressValidator.getInstance().isValidInet4Address(str.value)) {
        null
    } else {
        FormatValidationFailure(schema, str)
    }
}}

private val allowedIpv6Chars = setOf('.', ':') + ('0'..'9') + ('a'..'f') + ('A'..'F')

internal val ipv6FormatValidator: FormatValidator = {inst, schema -> inst.maybeString { str ->
    if (InetAddressValidator.getInstance().isValidInet6Address(str.value)
        && str.value.toCharArray().all { it in allowedIpv6Chars }) {
        null
    } else {
        FormatValidationFailure(schema, str)
    }
}}

internal val timeFormatValidator: FormatValidator = {inst, schema -> inst.maybeString { str ->
    try {
        DateTimeFormatter.ISO_OFFSET_TIME.parse(str.value)
        null
    } catch (e: DateTimeParseException) {
        FormatValidationFailure(schema, str)
    }
}}

internal val uuidFormatValidator: FormatValidator = {inst, schema -> inst.maybeString { str ->
    if (str.value.length == 36)
    try {
        UUID.fromString(str.value)
        null
    } catch (e: IllegalArgumentException) {
        FormatValidationFailure(schema, str)
    } else {
        FormatValidationFailure(schema, str)
    }
}}

internal val durationFormatValidator: FormatValidator = {inst, schema -> inst.maybeString { str ->
    val regex = Pattern.compile("^P(?=\\d|T\\d)(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)([DW]))?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+(?:\\.\\d+)?)S)?)?$")
    if (!regex.matcher(str.value).matches()) {
        return@maybeString FormatValidationFailure(schema, str)
    }
    return@maybeString null
}}

data class FormatSchema(
    val format: String,
    override val location: SourceLocation
) : Schema(location) {
    override fun 

accept(visitor: SchemaVisitor

): P? = visitor.visitFormatSchema(this) } internal val formatLoader: KeywordLoader = { ctx -> FormatSchema(ctx.keywordValue.requireString().value, ctx.location) } data class FormatValidationFailure( override val schema: FormatSchema, override val instance: IJsonValue ) : ValidationFailure( message = "instance does not match format '${schema.format}'", keyword = Keyword.FORMAT, causes = setOf(), schema = schema, instance = instance )





© 2015 - 2025 Weber Informatics LLC | Privacy Policy