From e16c045771e46e161c5b960e65c2f108f733e596 Mon Sep 17 00:00:00 2001 From: Okan Okumusoglu Date: Fri, 12 Jun 2026 15:09:14 +0300 Subject: [PATCH] fix pageable validations --- .../PaginationAutoConfiguration.kt | 20 +++++------ .../valensas/util/config/PageableConfig.kt | 9 +++++ .../com/valensas/util/exception/Exceptions.kt | 9 +++-- .../com/valensas/util/pagination/Pageable.kt | 34 +++++++++++------- .../kotlin/com/valensas/util/PageableTest.kt | 36 +++++++++++-------- 5 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 src/main/kotlin/com/valensas/util/config/PageableConfig.kt diff --git a/src/main/kotlin/com/valensas/util/autoconfigure/PaginationAutoConfiguration.kt b/src/main/kotlin/com/valensas/util/autoconfigure/PaginationAutoConfiguration.kt index 9008d6b..5d4341c 100644 --- a/src/main/kotlin/com/valensas/util/autoconfigure/PaginationAutoConfiguration.kt +++ b/src/main/kotlin/com/valensas/util/autoconfigure/PaginationAutoConfiguration.kt @@ -1,23 +1,23 @@ package com.valensas.util.autoconfigure +import com.valensas.util.config.PageableConfig import com.valensas.util.pagination.Pageable import org.springframework.aot.hint.annotation.RegisterReflectionForBinding -import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Configuration @Configuration @RegisterReflectionForBinding(classes = [Pageable::class]) +@EnableConfigurationProperties(PageableConfig::class) class PaginationAutoConfiguration( - @Value("\${spring.data.rest.max-page-size:50}") private val maxPageSize: Int, - @Value("\${spring.data.rest.default-page-size:20}") private val defaultPageSize: Int + pageableConfig: PageableConfig ) { init { - Companion.defaultPageSize = defaultPageSize - Companion.maxPageSize = maxPageSize - } - - companion object { - var defaultPageSize = 20 - var maxPageSize = 50 + pageableConfig.defaultPageSize?.let { + Pageable.defaultPageSize = it + } + pageableConfig.maxPageSize?.let { + Pageable.maxPageSize = it + } } } diff --git a/src/main/kotlin/com/valensas/util/config/PageableConfig.kt b/src/main/kotlin/com/valensas/util/config/PageableConfig.kt new file mode 100644 index 0000000..a8e6d08 --- /dev/null +++ b/src/main/kotlin/com/valensas/util/config/PageableConfig.kt @@ -0,0 +1,9 @@ +package com.valensas.util.config + +import org.springframework.boot.context.properties.ConfigurationProperties + +@ConfigurationProperties(prefix = "spring.data.rest") +data class PageableConfig( + val defaultPageSize: Int?, + val maxPageSize: Int? +) diff --git a/src/main/kotlin/com/valensas/util/exception/Exceptions.kt b/src/main/kotlin/com/valensas/util/exception/Exceptions.kt index 76bd597..230f6fc 100644 --- a/src/main/kotlin/com/valensas/util/exception/Exceptions.kt +++ b/src/main/kotlin/com/valensas/util/exception/Exceptions.kt @@ -2,11 +2,16 @@ package com.valensas.util.exception import com.valensas.exception.BadRequest -class InvalidPageSize : BadRequest( - "Page size must not be less than one", +class InvalidPageSize(max: Int) : BadRequest( + "Page size must be between 0 and $max", "INVALID_PAGE_SIZE" ) +class InvalidPageNumber : BadRequest( + "Page number must not be less than zero", + "INVALID_PAGE_NUMBER" +) + class InvalidSortDirection(direction: String) : BadRequest( "Invalid value:$direction, has to be either 'desc' or 'asc' (case insensitive)", "INVALID_SORT_DIRECTION" diff --git a/src/main/kotlin/com/valensas/util/pagination/Pageable.kt b/src/main/kotlin/com/valensas/util/pagination/Pageable.kt index 92a41b9..8708926 100644 --- a/src/main/kotlin/com/valensas/util/pagination/Pageable.kt +++ b/src/main/kotlin/com/valensas/util/pagination/Pageable.kt @@ -1,6 +1,6 @@ package com.valensas.util.pagination -import com.valensas.util.autoconfigure.PaginationAutoConfiguration +import com.valensas.util.exception.InvalidPageNumber import com.valensas.util.exception.InvalidPageSize import com.valensas.util.exception.InvalidSortDirection import org.springframework.data.domain.PageRequest @@ -9,27 +9,35 @@ import org.springframework.util.StringUtils import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.util.UriBuilder import java.io.Serializable -import kotlin.math.min class Pageable( - @RequestParam(required = false) val page: Int?, - @RequestParam(required = false) val size: Int?, + @RequestParam(required = false) var page: Int?, + @RequestParam(required = false) var size: Int?, @RequestParam(required = false) val sort: String? -) : Serializable +) : Serializable { + companion object { + var defaultPageSize = 20 + var maxPageSize = 1000 + } + + init { + size = size ?: defaultPageSize + size?.let { + if (it < 0) throw InvalidPageSize(maxPageSize) + if (it > maxPageSize) throw InvalidPageSize(maxPageSize) + } + page = page ?: 0 + + page?.let { if (it < 0) throw InvalidPageNumber() } + } +} fun Pageable.toJavaPageable(): PageRequest { val sortParam = parseParameterIntoSort(sort) val pageParam = this.page ?: 0 - val sizeParam = calculateSize(this.size) - return PageRequest.of(pageParam, sizeParam, sortParam) + return PageRequest.of(pageParam, this.size ?: Pageable.defaultPageSize, sortParam) } -private fun calculateSize(size: Int?) = - size - .run { this ?: PaginationAutoConfiguration.defaultPageSize } - .also { if (it < 1) throw InvalidPageSize() } - .let { min(PaginationAutoConfiguration.maxPageSize, it) } - private fun parseParameterIntoSort(sort: String?): Sort { return sort?.replace(" ", "") ?.split(";") diff --git a/src/test/kotlin/com/valensas/util/PageableTest.kt b/src/test/kotlin/com/valensas/util/PageableTest.kt index a59ce3c..6896ed6 100644 --- a/src/test/kotlin/com/valensas/util/PageableTest.kt +++ b/src/test/kotlin/com/valensas/util/PageableTest.kt @@ -1,11 +1,12 @@ package com.valensas.util -import com.valensas.util.autoconfigure.PaginationAutoConfiguration +import com.valensas.util.exception.InvalidPageNumber import com.valensas.util.exception.InvalidPageSize import com.valensas.util.exception.InvalidSortDirection import com.valensas.util.pagination.Pageable import com.valensas.util.pagination.toJavaPageable -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows @@ -19,10 +20,10 @@ class PageableTest { val pageable = Pageable(null, null, null) val page = pageable.toJavaPageable() - Assertions.assertNotNull(page) - Assertions.assertEquals(Sort.unsorted(), page.sort) - Assertions.assertEquals(0, page.pageNumber) - Assertions.assertEquals(PaginationAutoConfiguration.defaultPageSize, page.pageSize) + assertNotNull(page) + assertEquals(Sort.unsorted(), page.sort) + assertEquals(0, page.pageNumber) + assertEquals(Pageable.defaultPageSize, page.pageSize) } @Test @@ -30,20 +31,25 @@ class PageableTest { val pageable = Pageable(1, 5, "created_date,desc") val page = pageable.toJavaPageable() - Assertions.assertNotNull(page) - Assertions.assertNotNull(page.sort) - Assertions.assertNotNull(page.sort.getOrderFor("created_date")) - Assertions.assertEquals(Sort.Direction.DESC, page.sort.getOrderFor("created_date")!!.direction) - Assertions.assertEquals(1, page.pageNumber) - Assertions.assertEquals(min(pageable.size!!, PaginationAutoConfiguration.defaultPageSize), page.pageSize) + assertNotNull(page) + assertNotNull(page.sort) + assertNotNull(page.sort.getOrderFor("created_date")) + assertEquals(Sort.Direction.DESC, page.sort.getOrderFor("created_date")!!.direction) + assertEquals(1, page.pageNumber) + assertEquals(min(pageable.size!!, Pageable.defaultPageSize), page.pageSize) } @Test fun `try to initiate javaPageable with invalid size`() { - val pageable = - Pageable(null, 0, null) assertThrows { - pageable.toJavaPageable() + Pageable(null, -1, null) + } + } + + @Test + fun `try to initiate javaPageable with invalid page number`() { + assertThrows { + Pageable(-1, null, null) } }