Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(kotlin): add browse helpers #4059

Merged
merged 5 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Fluf22 marked this conversation as resolved.
Show resolved Hide resolved
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
Fluf22 marked this conversation as resolved.
Show resolved Hide resolved
),
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
Fluf22 marked this conversation as resolved.
Show resolved Hide resolved

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()
}
Loading