Skip to content

Commit 366e4f9

Browse files
authored
Merge f748f92 into 5c13605
2 parents 5c13605 + f748f92 commit 366e4f9

File tree

3 files changed

+154
-0
lines changed

3 files changed

+154
-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: 104 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,106 @@ 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(
608+
indexName,
609+
params.copy(hitsPerPage = params.hitsPerPage ?: 1000, cursor = previousResponse?.cursor),
610+
requestOptions
611+
)
612+
},
613+
validate = validate,
614+
aggregator = aggregator,
615+
)
616+
}
617+
618+
/**
619+
* Helper: Returns an iterator on top of the `browse` method.
620+
*
621+
* @param indexName The index in which to perform the request.
622+
* @param searchRulesParams The search rules request parameters
623+
* @param validate The function to validate the response. Default is to check if the cursor is not null.
624+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
625+
* the transporter requestOptions. (optional)
626+
*/
627+
public suspend fun SearchClient.browseRules(
628+
indexName: String,
629+
searchRulesParams: SearchRulesParams,
630+
validate: ((SearchRulesResponse) -> Boolean)? = null,
631+
aggregator: (SearchRulesResponse) -> Unit,
632+
requestOptions: RequestOptions? = null,
633+
): SearchRulesResponse {
634+
val hitsPerPage = searchRulesParams.hitsPerPage ?: 1000
635+
636+
return createIterable(
637+
execute = { previousResponse ->
638+
searchRules(
639+
indexName,
640+
searchRulesParams.copy(
641+
page = if (previousResponse != null) (previousResponse.page + 1) else 0,
642+
hitsPerPage = hitsPerPage
643+
),
644+
requestOptions
645+
)
646+
},
647+
validate = validate ?: { response -> response.hits.count() < hitsPerPage },
648+
aggregator = aggregator,
649+
)
650+
}
651+
652+
/**
653+
* Helper: Returns an iterator on top of the `browse` method.
654+
*
655+
* @param indexName The index in which to perform the request.
656+
* @param searchSynonymsParams The search synonyms request parameters
657+
* @param validate The function to validate the response. Default is to check if the cursor is not null.
658+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
659+
* the transporter requestOptions. (optional)
660+
*/
661+
public suspend fun SearchClient.browseSynonyms(
662+
indexName: String,
663+
searchSynonymsParams: SearchSynonymsParams,
664+
validate: ((SearchSynonymsResponse) -> Boolean)? = null,
665+
aggregator: (SearchSynonymsResponse) -> Unit,
666+
requestOptions: RequestOptions? = null,
667+
): SearchSynonymsResponse {
668+
val hitsPerPage = 1000
669+
var page = searchSynonymsParams.page ?: 0
670+
671+
return createIterable(
672+
execute = { _ ->
673+
try {
674+
searchSynonyms(
675+
indexName,
676+
searchSynonymsParams = searchSynonymsParams.copy(
677+
page = page,
678+
hitsPerPage = hitsPerPage
679+
),
680+
requestOptions
681+
)
682+
} finally {
683+
page += 1
684+
}
685+
},
686+
validate = validate ?: { response -> response.hits.count() < hitsPerPage },
687+
aggregator = aggregator,
688+
)
689+
}
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)