Skip to content

Commit 349390a

Browse files
committed
feat(kotlin): add browse helpers
1 parent f29dfa7 commit 349390a

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed

clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/exception/AlgoliaRuntimeException.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,12 @@ public class AlgoliaRetryException(
5454
public class AlgoliaWaitException(
5555
message: String? = null,
5656
) : AlgoliaRuntimeException(message)
57+
58+
/**
59+
* Exception thrown when an error occurs during an iterable helper execution.
60+
*
61+
* @param message the detail message
62+
*/
63+
public class AlgoliaIterableException(
64+
message: String? = null,
65+
) : AlgoliaRuntimeException(message)

clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.algolia.client.extensions
22

33
import com.algolia.client.api.SearchClient
44
import com.algolia.client.exception.AlgoliaApiException
5+
import com.algolia.client.extensions.internal.*
56
import com.algolia.client.extensions.internal.DisjunctiveFaceting
67
import com.algolia.client.extensions.internal.buildRestrictionString
78
import com.algolia.client.extensions.internal.encodeKeySHA256
@@ -583,3 +584,102 @@ public suspend fun SearchClient.searchDisjunctiveFaceting(
583584
val responses = searchForHits(queries, requestOptions = requestOptions)
584585
return helper.mergeResponses(responses)
585586
}
587+
588+
/**
589+
* Helper: Returns an iterator on top of the `browse` method.
590+
*
591+
* @param indexName The index in which to perform the request.
592+
* @param params The `browse` parameters.
593+
* @param validate The function to validate the response. Default is to check if the cursor is not null.
594+
* @param aggregator The function to aggregate the response.
595+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
596+
* the transporter requestOptions. (optional)
597+
*/
598+
public suspend fun SearchClient.browseObjects(
599+
indexName: String,
600+
params: BrowseParamsObject,
601+
validate: (BrowseResponse) -> Boolean = { response -> response.cursor == null },
602+
aggregator: ((BrowseResponse) -> Unit),
603+
requestOptions: RequestOptions? = null,
604+
): BrowseResponse {
605+
return createIterable(
606+
execute = { previousResponse ->
607+
browse(indexName, params.copy(cursor = previousResponse?.cursor), requestOptions)
608+
},
609+
validate = validate,
610+
aggregator = aggregator,
611+
)
612+
}
613+
614+
/**
615+
* Helper: Returns an iterator on top of the `browse` method.
616+
*
617+
* @param indexName The index in which to perform the request.
618+
* @param searchRulesParams The search rules request parameters
619+
* @param validate The function to validate the response. Default is to check if the cursor is not null.
620+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
621+
* the transporter requestOptions. (optional)
622+
*/
623+
public suspend fun SearchClient.browseRules(
624+
indexName: String,
625+
searchRulesParams: SearchRulesParams,
626+
validate: ((SearchRulesResponse) -> Boolean)? = null,
627+
aggregator: (SearchRulesResponse) -> Unit,
628+
requestOptions: RequestOptions? = null,
629+
): SearchRulesResponse {
630+
val hitsPerPage = searchRulesParams.hitsPerPage ?: 1000
631+
632+
return createIterable(
633+
execute = { previousResponse ->
634+
searchRules(
635+
indexName,
636+
searchRulesParams.copy(
637+
page = if (previousResponse != null) (previousResponse.page + 1) else 0,
638+
hitsPerPage = hitsPerPage
639+
),
640+
requestOptions
641+
)
642+
},
643+
validate = validate ?: { response -> response.hits.count() < hitsPerPage },
644+
aggregator = aggregator,
645+
)
646+
}
647+
648+
/**
649+
* Helper: Returns an iterator on top of the `browse` method.
650+
*
651+
* @param indexName The index in which to perform the request.
652+
* @param searchSynonymsParams The search synonyms request parameters
653+
* @param validate The function to validate the response. Default is to check if the cursor is not null.
654+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
655+
* the transporter requestOptions. (optional)
656+
*/
657+
public suspend fun SearchClient.browseSynonyms(
658+
indexName: String,
659+
searchSynonymsParams: SearchSynonymsParams,
660+
validate: ((SearchSynonymsResponse) -> Boolean)? = null,
661+
aggregator: (SearchSynonymsResponse) -> Unit,
662+
requestOptions: RequestOptions? = null,
663+
): SearchSynonymsResponse {
664+
val hitsPerPage = 1000
665+
var page = searchSynonymsParams.page ?: 0
666+
667+
return createIterable(
668+
execute = { _ ->
669+
try {
670+
searchSynonyms(
671+
indexName,
672+
searchSynonymsParams = searchSynonymsParams.copy(
673+
page = page,
674+
hitsPerPage = hitsPerPage
675+
),
676+
requestOptions
677+
)
678+
} finally {
679+
page += 1
680+
}
681+
},
682+
validate = validate ?: { response -> response.hits.count() < hitsPerPage },
683+
aggregator = aggregator,
684+
)
685+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.algolia.client.extensions.internal
2+
3+
import com.algolia.client.exception.AlgoliaIterableException
4+
import kotlinx.coroutines.delay
5+
import kotlin.time.Duration
6+
7+
public data class IterableError<T>(
8+
public val validate: (T) -> Boolean,
9+
public val message: ((T) -> String)? = null
10+
)
11+
12+
public suspend fun <T> createIterable(
13+
execute: suspend (T?) -> T,
14+
validate: (T) -> Boolean,
15+
aggregator: ((T) -> Unit)? = null,
16+
timeout: () -> Duration = { Duration.ZERO },
17+
error: IterableError<T>? = null
18+
): T {
19+
suspend fun executor(previousResponse: T? = null): T {
20+
val response = execute(previousResponse)
21+
22+
if (aggregator != null) {
23+
aggregator(response)
24+
}
25+
26+
if (validate(response)) {
27+
return response
28+
}
29+
30+
if (error != null && error.validate(response)) {
31+
val message = error.message?.invoke(response) ?: "An error occurred"
32+
throw AlgoliaIterableException(message)
33+
}
34+
35+
delay(timeout().inWholeMilliseconds)
36+
37+
return executor(response)
38+
}
39+
40+
return executor()
41+
}

0 commit comments

Comments
 (0)