Skip to content

Commit

Permalink
feature: add error message to interval < 1 minute by using bean valid…
Browse files Browse the repository at this point in the history
…ation annotation #46

ohsome-contributions-stats-service: 'Consider introducing 'Jakarta Bean validation' standard '
#46
  • Loading branch information
mmerdes committed Feb 6, 2024
1 parent 38da13b commit c4d2118
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ 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)

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


@ControllerAdvice
class GlobalExceptionHandler {
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Invalid ISO8601 string as interval!")
Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/org/heigit/ohsome/now/statsservice/Validations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import jakarta.validation.Constraint
import jakarta.validation.ConstraintValidator
import jakarta.validation.ConstraintValidatorContext
import jakarta.validation.Payload
import org.heigit.ohsome.now.statsservice.utils.isLessThanOneMinute
import org.heigit.ohsome.now.statsservice.utils.isParseableISO8601String
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.TYPE
Expand Down Expand Up @@ -44,3 +45,19 @@ class ISO8601StringIntervalValidator : ConstraintValidator<ParseableInterval, St

}


@Target(VALUE_PARAMETER, TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = [AtLeastOneMinuteIntervalValidator::class])
annotation class AtLeastOneMinuteInterval(
val message: String = "Interval must not be under 1 minute.",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)

class AtLeastOneMinuteIntervalValidator : ConstraintValidator<AtLeastOneMinuteInterval, String> {

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

}

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import jakarta.servlet.http.HttpServletRequest
import org.heigit.ohsome.now.statsservice.*
import org.heigit.ohsome.now.statsservice.utils.validateIntervalString
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME
Expand Down Expand Up @@ -110,14 +109,17 @@ class StatsController {
@Parameter(description = "the granularity defined as Intervals in ISO 8601 time format eg: P1M")
@RequestParam(name = "interval", defaultValue = "P1M", required = false)
@ParseableInterval
@AtLeastOneMinuteInterval
interval: String,

@Parameter(description = "A comma separated list of countries, can also only be one country")
@RequestParam("countries", required = false, defaultValue = "")
countries: List<String>?
): OhsomeFormat<StatsIntervalResult> {

validateIntervalString(interval)
println("########################## :" + interval)

// validateIntervalString(interval)

val result = measure {
statsService.getStatsForTimeSpanInterval(hashtag, startDate, endDate, interval, countries!!)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ fun String.replaceTime() = this
.replace("M", " MINUTE")



@Deprecated("is handled via jakarta bean validation annotations now")
fun validateIntervalString(interval: String) {
// checkIfStringIsParsable(interval)
checkIfStringIsParsable(interval)
checkIfDurationIsBiggerThanOneMinute(interval)
}

Expand All @@ -51,9 +53,12 @@ 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
@Deprecated("is handled via jakarta bean validation annotations now")
fun checkIfDurationIsBiggerThanOneMinute(interval: String) {
if (interval.startsWith("PT") && Duration.parseIsoString(interval) < Duration.parseIsoString("PT1M")) {
if (isLessThanOneMinute(interval)) {
throw ISO8601TooSmallException()
}
}
}

fun isLessThanOneMinute(interval: String) = interval.startsWith("PT") &&
Duration.parseIsoString(interval) < Duration.parseIsoString("PT1M")
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class StatsControllerMVCTests {
.andExpect(content().string(expectedErrorMessage))
}


//TODO: more controller actions? topic actions?
//TODO: check redundancy with other tests
@ParameterizedTest
@ValueSource(
strings = [
Expand All @@ -101,6 +104,26 @@ class StatsControllerMVCTests {
.andExpect(content().string(expectedErrorMessage))
}

//TODO: more controller actions? topic actions?
//TODO: check redundancy with other tests
@ParameterizedTest
@ValueSource(
strings = [
"/stats/missingmaps/interval?interval=PT1S"
]
)
fun `all requests with ISO interval less than 1 minute throw error`(url: String) {

val expectedErrorMessage = """[{"message":"Interval must not be under 1 minute.","invalidValue":"PT1S"}]"""

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 c4d2118

Please sign in to comment.