Add validation on route Article versions

This commit is contained in:
2021-04-06 00:05:27 +02:00
parent 2ef9f65f2c
commit 61a7091736
8 changed files with 113 additions and 29 deletions

View File

@@ -1,6 +1,10 @@
package fr.dcproject.application
import fr.dcproject.application.http.BadRequestException
import fr.dcproject.application.http.HttpErrorBadRequest
import fr.dcproject.application.http.HttpErrorBadRequest.InvalidParam
import io.ktor.features.DataConversion
import io.ktor.http.HttpStatusCode
import io.ktor.util.KtorExperimentalAPI
import org.koin.core.context.GlobalContext
import org.koin.core.parameter.ParametersDefinition
@@ -8,6 +12,7 @@ import org.koin.core.qualifier.Qualifier
import java.util.UUID
private typealias ConverterDeclaration = DataConversion.Configuration.() -> Unit
private inline fun <reified T> DataConversion.Configuration.get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
@@ -17,7 +22,21 @@ private inline fun <reified T> DataConversion.Configuration.get(
val converters: ConverterDeclaration = {
convert<UUID> {
decode { values, _ ->
values.singleOrNull()?.let { UUID.fromString(it) }
try {
values.singleOrNull()?.let { UUID.fromString(it) }
} catch (e: Throwable) {
throw BadRequestException(
HttpErrorBadRequest(
HttpStatusCode.BadRequest,
invalidParams = listOf(
InvalidParam(
"ID",
"ID must be UUID"
)
)
)
)
}
}
encode { value ->

View File

@@ -6,6 +6,7 @@ import fr.dcproject.component.auth.ForbiddenException
import fr.dcproject.component.auth.user
import io.ktor.application.call
import io.ktor.features.NotFoundException
import io.ktor.features.ParameterConversionException
import io.ktor.features.StatusPages
import io.ktor.http.HttpStatusCode
import io.ktor.response.respond
@@ -19,10 +20,6 @@ class HttpError(
val detail: String? = null,
) {
val statusCode: Int = statusCode.value
data class InvalidParam(
val name: String,
val reason: String
)
}
fun statusPagesInstallation(): StatusPages.Configuration.() -> Unit = {
@@ -80,4 +77,12 @@ fun statusPagesInstallation(): StatusPages.Configuration.() -> Unit = {
exception<BadRequestException> { e ->
call.respond(HttpStatusCode.BadRequest, e.httpError)
}
exception<ParameterConversionException> { e ->
val parent = e.cause
if (parent is BadRequestException) {
call.respond(HttpStatusCode.BadRequest, parent.httpError)
} else {
throw e
}
}
}

View File

@@ -1,42 +1,69 @@
package fr.dcproject.component.article.routes
import fr.dcproject.application.http.badRequestIfNotValid
import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.toUUID
import fr.dcproject.common.validation.isUuid
import fr.dcproject.component.article.ArticleAccessControl
import fr.dcproject.component.article.database.ArticleForListing
import fr.dcproject.component.article.database.ArticleRef
import fr.dcproject.component.article.database.ArticleRepository
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.routes.PaginatedRequest
import fr.dcproject.routes.PaginatedRequestI
import fr.postgresjson.repository.RepositoryI
import io.konform.validation.Validation
import io.konform.validation.jsonschema.enum
import io.konform.validation.jsonschema.maximum
import io.konform.validation.jsonschema.minimum
import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
import io.ktor.locations.get
import io.ktor.response.respond
import io.ktor.routing.Route
import java.util.UUID
@KtorExperimentalLocationsAPI
object FindArticleVersions {
@Location("/articles/{article}/versions")
class ArticleVersionsRequest(
article: UUID,
val article: String,
page: Int = 1,
limit: Int = 50,
val sort: String? = null,
val direction: RepositoryI.Direction? = null,
val search: String? = null
) {
val page: Int = if (page < 1) 1 else page
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
val article = ArticleRef(article)
) : PaginatedRequestI by PaginatedRequest(page, limit) {
fun validate() = Validation<ArticleVersionsRequest> {
ArticleVersionsRequest::page {
minimum(1)
maximum(100)
}
ArticleVersionsRequest::limit {
minimum(1)
maximum(50)
}
ArticleVersionsRequest::sort ifPresent {
enum(
"title",
"createdAt",
"vote",
"popularity",
)
}
ArticleVersionsRequest::article {
isUuid()
}
}.validate(this)
}
private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) =
findVersionsById(request.page, request.limit, request.article.id)
findVersionsById(request.page, request.limit, request.article.toUUID())
fun Route.findArticleVersions(repo: ArticleRepository, ac: ArticleAccessControl) {
get<ArticleVersionsRequest> {
it.validate().badRequestIfNotValid()
repo.findVersions(it)
.apply { ac.assert { canView(result, citizenOrNull) } }
.run {