Skip to content

Commit

Permalink
feat(kotlin): add browse helpers (#4059)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fluf22 authored Oct 31, 2024
1 parent 5c13605 commit b3804d7
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,12 @@ public class AlgoliaRetryException(
public class AlgoliaWaitException(
message: String? = null,
) : AlgoliaRuntimeException(message)

/**
* Exception thrown when an error occurs during an iterable helper execution.
*
* @param message the detail message
*/
public class AlgoliaIterableException(
message: String? = null,
) : AlgoliaRuntimeException(message)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.algolia.client.extensions

import com.algolia.client.api.SearchClient
import com.algolia.client.exception.AlgoliaApiException
import com.algolia.client.extensions.internal.*
import com.algolia.client.extensions.internal.DisjunctiveFaceting
import com.algolia.client.extensions.internal.buildRestrictionString
import com.algolia.client.extensions.internal.encodeKeySHA256
Expand Down Expand Up @@ -583,3 +584,106 @@ public suspend fun SearchClient.searchDisjunctiveFaceting(
val responses = searchForHits(queries, requestOptions = requestOptions)
return helper.mergeResponses(responses)
}

/**
* Helper: Returns an iterator on top of the `browse` method.
*
* @param indexName The index in which to perform the request.
* @param params The `browse` parameters.
* @param validate The function to validate the response. Default is to check if the cursor is not null.
* @param aggregator The function to aggregate the response.
* @param requestOptions The requestOptions to send along with the query, they will be merged with
* the transporter requestOptions. (optional)
*/
public suspend fun SearchClient.browseObjects(
indexName: String,
params: BrowseParamsObject,
validate: (BrowseResponse) -> Boolean = { response -> response.cursor == null },
aggregator: ((BrowseResponse) -> Unit),
requestOptions: RequestOptions? = null,
): BrowseResponse {
return createIterable(
execute = { previousResponse ->
browse(
indexName,
params.copy(hitsPerPage = params.hitsPerPage ?: 1000, cursor = previousResponse?.cursor),
requestOptions
)
},
validate = validate,
aggregator = aggregator,
)
}

/**
* Helper: Returns an iterator on top of the `browse` method.
*
* @param indexName The index in which to perform the request.
* @param searchRulesParams The search rules request parameters
* @param validate The function to validate the response. Default is to check if the cursor is not null.
* @param requestOptions The requestOptions to send along with the query, they will be merged with
* the transporter requestOptions. (optional)
*/
public suspend fun SearchClient.browseRules(
indexName: String,
searchRulesParams: SearchRulesParams,
validate: ((SearchRulesResponse) -> Boolean)? = null,
aggregator: (SearchRulesResponse) -> Unit,
requestOptions: RequestOptions? = null,
): SearchRulesResponse {
val hitsPerPage = searchRulesParams.hitsPerPage ?: 1000

return createIterable(
execute = { previousResponse ->
searchRules(
indexName,
searchRulesParams.copy(
page = if (previousResponse != null) (previousResponse.page + 1) else 0,
hitsPerPage = hitsPerPage
),
requestOptions
)
},
validate = validate ?: { response -> response.hits.count() < hitsPerPage },
aggregator = aggregator,
)
}

/**
* Helper: Returns an iterator on top of the `browse` method.
*
* @param indexName The index in which to perform the request.
* @param searchSynonymsParams The search synonyms request parameters
* @param validate The function to validate the response. Default is to check if the cursor is not null.
* @param requestOptions The requestOptions to send along with the query, they will be merged with
* the transporter requestOptions. (optional)
*/
public suspend fun SearchClient.browseSynonyms(
indexName: String,
searchSynonymsParams: SearchSynonymsParams,
validate: ((SearchSynonymsResponse) -> Boolean)? = null,
aggregator: (SearchSynonymsResponse) -> Unit,
requestOptions: RequestOptions? = null,
): SearchSynonymsResponse {
val hitsPerPage = 1000
var page = searchSynonymsParams.page ?: 0

return createIterable(
execute = { _ ->
try {
searchSynonyms(
indexName,
searchSynonymsParams = searchSynonymsParams.copy(
page = page,
hitsPerPage = hitsPerPage
),
requestOptions
)
} finally {
page += 1
}
},
validate = validate ?: { response -> response.hits.count() < hitsPerPage },
aggregator = aggregator,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.algolia.client.extensions.internal

import com.algolia.client.exception.AlgoliaIterableException
import kotlinx.coroutines.delay
import kotlin.time.Duration

public data class IterableError<T>(
public val validate: (T) -> Boolean,
public val message: ((T) -> String)? = null
)

public suspend fun <T> createIterable(
execute: suspend (T?) -> T,
validate: (T) -> Boolean,
aggregator: ((T) -> Unit)? = null,
timeout: () -> Duration = { Duration.ZERO },
error: IterableError<T>? = null
): T {
suspend fun executor(previousResponse: T? = null): T {
val response = execute(previousResponse)

if (aggregator != null) {
aggregator(response)
}

if (validate(response)) {
return response
}

if (error != null && error.validate(response)) {
val message = error.message?.invoke(response) ?: "An error occurred"
throw AlgoliaIterableException(message)
}

delay(timeout().inWholeMilliseconds)

return executor(response)
}

return executor()
}

0 comments on commit b3804d7

Please sign in to comment.