Skip to content

Commit

Permalink
feature: add error message to unparseable interval by using bean vali…
Browse files Browse the repository at this point in the history
…dation annotation #46

ohsome-contributions-stats-service: 'Consider introducing 'Jakarta Bean validation' standard '
#46
  • Loading branch information
mmerdes committed Feb 6, 2024
1 parent be92702 commit 38da13b
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus


@Deprecated("is handled via jakarta bean validation annotations now")
data class UnparsableISO8601StringException(override val message: String? = null) : Exception(message)

data class ISO8601TooSmallException(override val message: String? = null) : Exception(message)

@ControllerAdvice
Expand Down
25 changes: 23 additions & 2 deletions src/main/kotlin/org/heigit/ohsome/now/statsservice/Validations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import jakarta.validation.Constraint
import jakarta.validation.ConstraintValidator
import jakarta.validation.ConstraintValidatorContext
import jakarta.validation.Payload
import org.heigit.ohsome.now.statsservice.utils.isParseableISO8601String
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.TYPE
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
import kotlin.reflect.KClass


@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Target(VALUE_PARAMETER, TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = [HashtagValidator::class])
annotation class ValidHashtag(
val message: String = "Hashtag must not be '*'",
Expand All @@ -23,3 +27,20 @@ class HashtagValidator : ConstraintValidator<ValidHashtag, String> {

}



@Target(VALUE_PARAMETER, TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = [ISO8601StringIntervalValidator::class])
annotation class ParseableInterval(
val message: String = "Invalid ISO8601 string as interval.",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)

class ISO8601StringIntervalValidator : ConstraintValidator<ParseableInterval, String> {

override fun isValid(interval: String, context: ConstraintValidatorContext?) = isParseableISO8601String(interval)

}

Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class StatsController {

@Parameter(description = "the granularity defined as Intervals in ISO 8601 time format eg: P1M")
@RequestParam(name = "interval", defaultValue = "P1M", required = false)
@ParseableInterval
interval: String,

@Parameter(description = "A comma separated list of countries, can also only be one country")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,22 @@ fun String.replaceTime() = this


fun validateIntervalString(interval: String) {
checkIfStringIsParsable(interval)
// checkIfStringIsParsable(interval)
checkIfDurationIsBiggerThanOneMinute(interval)
}

//TODO: consider replacing with jakarta bean validation annotations instead of throwing exception
@Deprecated("is handled via jakarta bean validation annotations now")
fun checkIfStringIsParsable(interval: String) {
if (!interval.matches(Regex("^P(?!\$)(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+S)?)?\$"))) {
if (!isParseableISO8601String(interval)) {
throw UnparsableISO8601StringException()
}
}


fun isParseableISO8601String(interval: String) = interval
.matches(Regex("^P(?!\$)(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+S)?)?\$"))


//TODO: consider replacing with jakarta bean validation annotations instead of throwing exception
fun checkIfDurationIsBiggerThanOneMinute(interval: String) {
if (interval.startsWith("PT") && Duration.parseIsoString(interval) < Duration.parseIsoString("PT1M")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ class StatsControllerMVCTests {
.andExpect(content().string(expectedErrorMessage))
}

@ParameterizedTest
@ValueSource(
strings = [
"/stats/missingmaps/interval?interval=bad_interval"
]
)
fun `all requests with bad ISO interval throw error`(url: String) {

val expectedErrorMessage = """[{"message":"Invalid ISO8601 string as interval.","invalidValue":"bad_interval"}]"""

val GET = get(url)

this.mockMvc
.perform(GET)
.andExpect(status().isBadRequest)
.andExpect(content().string(expectedErrorMessage))
}


@Test
fun `stats can be served without explicit timespans`() {
Expand Down

0 comments on commit 38da13b

Please sign in to comment.