diff --git a/src/main/kotlin/com/ctrlhub/core/datacapture/FormSchemasRouter.kt b/src/main/kotlin/com/ctrlhub/core/datacapture/FormSchemasRouter.kt index 0e23dee..85892ae 100644 --- a/src/main/kotlin/com/ctrlhub/core/datacapture/FormSchemasRouter.kt +++ b/src/main/kotlin/com/ctrlhub/core/datacapture/FormSchemasRouter.kt @@ -27,12 +27,12 @@ enum class FormSchemaIncludes(val value: String) : JsonApiIncludes { class FormSchemaRequestParameters( offset: Int = 0, limit: Int = 100, - filterOptions: List = emptyList(), + filters: List = emptyList(), includes: List = emptyList() ) : RequestParametersWithIncludes( offset = offset, limit = limit, - filterOptions = filterOptions, + filters = filters, includes = includes, ) diff --git a/src/main/kotlin/com/ctrlhub/core/datacapture/FormSubmissionVersionsRouter.kt b/src/main/kotlin/com/ctrlhub/core/datacapture/FormSubmissionVersionsRouter.kt index 37e9797..a882ec2 100644 --- a/src/main/kotlin/com/ctrlhub/core/datacapture/FormSubmissionVersionsRouter.kt +++ b/src/main/kotlin/com/ctrlhub/core/datacapture/FormSubmissionVersionsRouter.kt @@ -34,12 +34,12 @@ enum class FormSubmissionVersionIncludes(val key: String) : JsonApiIncludes { class FormSubmissionVersionRequestParameters( offset: Int = 0, limit: Int = 100, - filterOptions: List = emptyList(), + filters: List = emptyList(), includes: List = emptyList() ) : RequestParametersWithIncludes( offset = offset, limit = limit, - filterOptions = filterOptions, + filters = filters, includes = includes, ) diff --git a/src/main/kotlin/com/ctrlhub/core/datacapture/FormSubmissionsRouter.kt b/src/main/kotlin/com/ctrlhub/core/datacapture/FormSubmissionsRouter.kt index aba79d8..e0d967e 100644 --- a/src/main/kotlin/com/ctrlhub/core/datacapture/FormSubmissionsRouter.kt +++ b/src/main/kotlin/com/ctrlhub/core/datacapture/FormSubmissionsRouter.kt @@ -18,7 +18,7 @@ class FormSubmissionsRouter(httpClient: HttpClient) : Router(httpClient) { suspend fun all(organisationId: String, formId: String): PaginatedList { return fetchPaginatedJsonApiResources( "/v3/orgs/$organisationId/data-capture/forms/$formId/submissions", - queryParameters = emptyMap(), + queryParameters = emptyMap(), FormSubmission::class.java, User::class.java, Form::class.java, diff --git a/src/main/kotlin/com/ctrlhub/core/router/request/FilterExpression.kt b/src/main/kotlin/com/ctrlhub/core/router/request/FilterExpression.kt new file mode 100644 index 0000000..7269d51 --- /dev/null +++ b/src/main/kotlin/com/ctrlhub/core/router/request/FilterExpression.kt @@ -0,0 +1,24 @@ +package com.ctrlhub.core.router.request + +sealed interface FilterOption { + fun format(): String +} + +class FieldFilterExpression(val field: String, val values: List) : FilterOption { + override fun format(): String { + val quoted = values.joinToString(",") { "'${it}'" } + return "${field}($quoted)" + } +} + +class ValueFilterExpression(val value: String) : FilterOption { + override fun format(): String = value +} + +class AndExpression(val parts: List) : FilterOption { + override fun format(): String = "and(${parts.joinToString(",") { it.format() }})" +} + +class OrExpression(val parts: List) : FilterOption { + override fun format(): String = "or(${parts.joinToString(",") { it.format() }})" +} \ No newline at end of file diff --git a/src/main/kotlin/com/ctrlhub/core/router/request/RequestParameters.kt b/src/main/kotlin/com/ctrlhub/core/router/request/RequestParameters.kt index 6235114..a42f170 100644 --- a/src/main/kotlin/com/ctrlhub/core/router/request/RequestParameters.kt +++ b/src/main/kotlin/com/ctrlhub/core/router/request/RequestParameters.kt @@ -1,21 +1,24 @@ package com.ctrlhub.core.router.request -data class FilterOption( - val field: String, - val value: String -) - abstract class AbstractRequestParameters( val offset: Int? = null, val limit: Int? = null, - val filterOptions: List + val filters: List = emptyList() ) { open fun toMap(): Map { val queryParams = mutableMapOf() offset?.let { queryParams["offset"] = it.toString() } limit?.let { queryParams["limit"] = it.toString() } - filterOptions.forEach { queryParams["filter"] = "${it.field}('${it.value}')" } + val parts = mutableListOf() + + for (expr in filters) { + parts += expr.format() + } + + if (parts.isNotEmpty()) { + queryParams["filter"] = parts.joinToString(",") + } return queryParams } @@ -30,10 +33,11 @@ class RequestParameters( open class RequestParametersWithIncludes( offset: Int? = null, limit: Int? = null, - filterOptions: List = emptyList(), + filters: List = emptyList(), val includes: List = emptyList() -) : AbstractRequestParameters(offset, limit, filterOptions) where TIncludes : JsonApiIncludes { +) : AbstractRequestParameters(offset, limit, filters) where TIncludes : JsonApiIncludes { + @Suppress("unused") fun withIncludes(vararg includes: TIncludes): RequestParametersWithIncludes { return copy(includes = this.includes + includes) } @@ -53,7 +57,7 @@ open class RequestParametersWithIncludes( } private fun copy( - filterOptions: List = this.filterOptions, - includes: List = this.includes - ) = RequestParametersWithIncludes(offset, limit, filterOptions, includes) + includes: List = this.includes, + filters: List = this.filters + ) = RequestParametersWithIncludes(offset, limit, filters, includes) } diff --git a/src/test/kotlin/com/ctrlhub/core/router/request/RequestParametersFiltersTest.kt b/src/test/kotlin/com/ctrlhub/core/router/request/RequestParametersFiltersTest.kt new file mode 100644 index 0000000..c283971 --- /dev/null +++ b/src/test/kotlin/com/ctrlhub/core/router/request/RequestParametersFiltersTest.kt @@ -0,0 +1,53 @@ +package com.ctrlhub.core.router.request + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class RequestParametersFiltersTest { + + @Test + fun `and of two field expressions formats correctly`() { + val expr = AndExpression( + listOf( + FieldFilterExpression("status", listOf("open")), + FieldFilterExpression("category", listOf("news")) + ) + ) + + val params = RequestParameters(filterOptions = listOf(expr)) + val map = params.toMap() + + assertEquals("and(status('open'),category('news'))", map["filter"]) + } + + @Test + fun `and of functions formats correctly`() { + val expr = AndExpression( + listOf( + ValueFilterExpression("is_latest()"), + ValueFilterExpression("no_start()"), + ) + ) + + val params = RequestParameters(filterOptions = listOf(expr)) + val map = params.toMap() + + assertEquals("and(is_latest(),no_start())", map["filter"]) + } + + @Test + fun `mixed and expression with field and function`() { + val expr = AndExpression( + listOf( + FieldFilterExpression("status", listOf("active")), + ValueFilterExpression("is_latest()") + ) + ) + + val params = RequestParameters(filterOptions = listOf(expr)) + val map = params.toMap() + + assertEquals("and(status('active'),is_latest())", map["filter"]) + } +} +