Skip to content

Commit c2a7540

Browse files
authored
Merge pull request #116 from algolia/develop
1.1.3
2 parents b345040 + e413f51 commit c2a7540

40 files changed

+564
-293
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# 1.1.3
2+
3+
- Removed `Index.searchDisjunctiveFacets` method
4+
- New `Index.advancedSearch` for both disjunctive and hierarchical search
5+
- New `FilterGroup.And.Hierarchical` class
6+
- New `getSecuredApiKeyRemainingValidity` method on `APIKey`
7+
- New `indexLanguages` field in `Settings`
8+
- New `alternative` field in `Rule.condition`
9+
- New `ResponseSearch.Hit.getObjectIDPosition` method
10+
- New `findFirstObject` method
11+
112
# 1.1.2
213

314
- Ktor version 1.2.3

buildSrc/src/main/kotlin/Library.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ object Library: Dependency {
22

33
override val group = "com.algolia"
44
override val artifact = "algoliasearch-client-kotlin"
5-
override val version = "1.1.2"
5+
override val version = "1.1.3"
66
}

src/commonMain/kotlin/com/algolia/search/client/ClientSearch.kt

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ package com.algolia.search.client
33
import com.algolia.search.configuration.*
44
import com.algolia.search.dsl.requestOptionsBuilder
55
import com.algolia.search.endpoint.*
6+
import com.algolia.search.helper.decodeBase64
67
import com.algolia.search.helper.encodeBase64
78
import com.algolia.search.helper.sha256
89
import com.algolia.search.helper.toAPIKey
9-
import com.algolia.search.model.APIKey
10-
import com.algolia.search.model.ApplicationID
11-
import com.algolia.search.model.IndexName
12-
import com.algolia.search.model.LogType
10+
import com.algolia.search.model.*
1311
import com.algolia.search.model.apikey.SecuredAPIKeyRestriction
1412
import com.algolia.search.model.response.ResponseAPIKey
1513
import com.algolia.search.model.response.ResponseBatches
@@ -173,5 +171,26 @@ public class ClientSearch private constructor(
173171

174172
return "$hash$restrictionString".encodeBase64().toAPIKey()
175173
}
174+
175+
/**
176+
* Gets how many milliseconds are left before the secured API key expires.
177+
*
178+
* @param apiKey The secured API Key to check.
179+
* @return Milliseconds left before the secured API key expires.
180+
* @throws IllegalArgumentException if [apiKey] doesn't have a [SecuredAPIKeyRestriction.validUntil].
181+
*/
182+
public fun getSecuredApiKeyRemainingValidity(apiKey: APIKey): Long {
183+
val decoded = apiKey.raw.decodeBase64()
184+
val pattern = Regex("validUntil=(\\d+)")
185+
val match = pattern.find(decoded)
186+
187+
return if (match != null) {
188+
val timestamp = match.groupValues[1].toLong()
189+
190+
timestamp - Time.getCurrentTimeMillis()
191+
} else {
192+
throw IllegalArgumentException("The Secured API Key doesn't have a validUntil parameter.");
193+
}
194+
}
176195
}
177196
}

src/commonMain/kotlin/com/algolia/search/client/Index.kt

Lines changed: 1 addition & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.algolia.search.endpoint.*
55
import com.algolia.search.model.Attribute
66
import com.algolia.search.model.IndexName
77
import com.algolia.search.model.filter.Filter
8+
import com.algolia.search.model.filter.FilterGroup
89
import com.algolia.search.model.multipleindex.IndexQuery
910
import com.algolia.search.model.response.ResponseSearch
1011
import com.algolia.search.model.response.ResponseSearchRules
@@ -108,113 +109,4 @@ public data class Index internal constructor(
108109
}
109110
return responses
110111
}
111-
112-
suspend fun searchHierarchical(
113-
query: Query = Query(),
114-
disjunctiveFacets: List<Attribute> = listOf(),
115-
filters: Set<Filter> = setOf(),
116-
hierarchicalAttributes: List<Attribute> = listOf(),
117-
hierarchicalFilters: List<Filter.Facet> = listOf(),
118-
requestOptions: RequestOptions? = null
119-
): ResponseSearch {
120-
val (filtersOr, filtersAnd) = filters.partition { disjunctiveFacets.contains(it.attribute) }
121-
val filtersOrFacet = filtersOr.filterIsInstance<Filter.Facet>()
122-
val filtersOrTag = filtersOr.filterIsInstance<Filter.Tag>()
123-
val filtersOrNumeric = filtersOr.filterIsInstance<Filter.Numeric>()
124-
val queryForResults = query
125-
.toIndexQuery()
126-
.filters(filtersAnd, filtersOrFacet, filtersOrTag, filtersOrNumeric)
127-
val queriesForDisjunctiveFacets = disjunctiveFacets.map { attribute ->
128-
query
129-
.toIndexQuery()
130-
.filters(
131-
filtersAnd,
132-
filtersOrFacet.filter { it.attribute != attribute },
133-
filtersOrTag,
134-
filtersOrNumeric
135-
)
136-
.setFacets(attribute)
137-
.optimize()
138-
}
139-
val queriesForHierarchicalFacets = hierarchicalAttributes
140-
.take(hierarchicalFilters.size + 1)
141-
.mapIndexed { index, attribute ->
142-
query
143-
.toIndexQuery()
144-
.filters(filtersAnd.combine(hierarchicalFilters.getOrNull(index - 1)).minus(hierarchicalFilters.last()), filtersOrFacet, filtersOrTag, filtersOrNumeric)
145-
.setFacets(attribute)
146-
.optimize()
147-
}
148-
val queries = listOf(queryForResults) + queriesForDisjunctiveFacets + queriesForHierarchicalFacets
149-
val response = EndpointMultipleIndexImpl(transport).multipleQueries(queries, requestOptions = requestOptions)
150-
151-
return response.aggregateResult(disjunctiveFacets.size)
152-
}
153-
154-
private fun List<ResponseSearch>.aggregateFacets(): Map<Attribute, List<Facet>> {
155-
return fold(mapOf()) { acc, result ->
156-
result.facetsOrNull?.let { acc + it } ?: acc
157-
}
158-
}
159-
160-
private fun List<ResponseSearch>.aggregateFacetStats(): Map<Attribute, FacetStats> {
161-
return fold(mapOf()) { acc, result ->
162-
result.facetStatsOrNull?.let { acc + it } ?: acc
163-
}
164-
}
165-
166-
private fun List<Filter>.combine(hierarchicalFilter: Filter.Facet?): List<Filter> {
167-
return hierarchicalFilter?.let { this + it } ?: this
168-
}
169-
170-
private fun ResponseSearches.aggregateResult(disjunctiveFacetCount: Int): ResponseSearch {
171-
val resultsDisjunctiveFacets = results.subList(1, 1 + disjunctiveFacetCount)
172-
val resultHierarchicalFacets = results.subList(1 + disjunctiveFacetCount, results.size)
173-
val facets = resultsDisjunctiveFacets.aggregateFacets()
174-
val facetStats = results.aggregateFacetStats()
175-
val hierarchicalFacets = resultHierarchicalFacets.aggregateFacets()
176-
177-
return results.first().copy(
178-
facetStatsOrNull = if (facetStats.isEmpty()) null else facetStats,
179-
disjunctiveFacetsOrNull = facets,
180-
hierarchicalFacetsOrNull = if (hierarchicalFacets.isEmpty()) null else hierarchicalFacets,
181-
exhaustiveFacetsCountOrNull = resultsDisjunctiveFacets.all { it.exhaustiveFacetsCountOrNull == true }
182-
)
183-
}
184-
185-
private fun IndexQuery.optimize(): IndexQuery {
186-
query.apply {
187-
attributesToRetrieve = listOf()
188-
attributesToHighlight = listOf()
189-
hitsPerPage = 0
190-
analytics = false
191-
}
192-
return this
193-
}
194-
195-
private fun Query.toIndexQuery(): IndexQuery {
196-
return IndexQuery(indexName, copy())
197-
}
198-
199-
private fun IndexQuery.filters(
200-
filtersAnd: List<Filter>,
201-
filtersOrFacet: List<Filter.Facet>,
202-
filtersOrTag: List<Filter.Tag>,
203-
filtersOrNumeric: List<Filter.Numeric>
204-
): IndexQuery {
205-
query.apply {
206-
filters {
207-
and { +filtersAnd }
208-
orFacet { +filtersOrFacet }
209-
orTag { +filtersOrTag }
210-
orNumeric { +filtersOrNumeric }
211-
}
212-
}
213-
return this
214-
}
215-
216-
private fun IndexQuery.setFacets(facet: Attribute?): IndexQuery {
217-
if (facet != null) query.facets = setOf(facet)
218-
return this
219-
}
220112
}

src/commonMain/kotlin/com/algolia/search/dsl/filtering/DSLFacetFilters.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import com.algolia.search.model.filter.FilterGroup
1111
*/
1212
@DSLParameters
1313
public class DSLFacetFilters(
14-
private val groups: MutableList<FilterGroup<Filter.Facet>> = mutableListOf()
14+
private val groups: MutableSet<FilterGroup<Filter.Facet>> = mutableSetOf()
1515
) {
1616

1717
/**
@@ -35,10 +35,10 @@ public class DSLFacetFilters(
3535
+FilterGroup.Or.Facet(DSLGroupFacet(block))
3636
}
3737

38-
public companion object : DSL<DSLFacetFilters, List<FilterGroup<Filter.Facet>>> {
38+
public companion object : DSL<DSLFacetFilters, Set<FilterGroup<Filter.Facet>>> {
3939

40-
override operator fun invoke(block: DSLFacetFilters.() -> Unit): List<FilterGroup<Filter.Facet>> {
41-
return DSLFacetFilters().apply(block).groups.toList()
40+
override operator fun invoke(block: DSLFacetFilters.() -> Unit): Set<FilterGroup<Filter.Facet>> {
41+
return DSLFacetFilters().apply(block).groups
4242
}
4343
}
4444
}

src/commonMain/kotlin/com/algolia/search/dsl/filtering/DSLFilters.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import com.algolia.search.model.filter.FilterGroup
1111
*/
1212
@DSLParameters
1313
public class DSLFilters(
14-
private val groups: MutableList<FilterGroup<*>> = mutableListOf()
14+
private val groups: MutableSet<FilterGroup<*>> = mutableSetOf()
1515
) {
1616

1717
/**
@@ -49,10 +49,10 @@ public class DSLFilters(
4949
+FilterGroup.Or.Tag(DSLGroupTag(block))
5050
}
5151

52-
public companion object : DSL<DSLFilters, List<FilterGroup<*>>> {
52+
public companion object : DSL<DSLFilters, Set<FilterGroup<*>>> {
5353

54-
override operator fun invoke(block: DSLFilters.() -> Unit): List<FilterGroup<*>> {
55-
return DSLFilters().apply(block).groups.toList()
54+
override operator fun invoke(block: DSLFilters.() -> Unit): Set<FilterGroup<*>> {
55+
return DSLFilters().apply(block).groups
5656
}
5757
}
5858
}

src/commonMain/kotlin/com/algolia/search/dsl/filtering/DSLNumericFilters.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import com.algolia.search.model.filter.FilterGroup
1111
*/
1212
@DSLParameters
1313
public class DSLNumericFilters(
14-
private val groups: MutableList<FilterGroup<Filter.Numeric>> = mutableListOf()
14+
private val groups: MutableSet<FilterGroup<Filter.Numeric>> = mutableSetOf()
1515
) {
1616

1717
/**
@@ -35,10 +35,10 @@ public class DSLNumericFilters(
3535
+FilterGroup.Or.Numeric(DSLGroupNumeric(block))
3636
}
3737

38-
public companion object : DSL<DSLNumericFilters, List<FilterGroup<Filter.Numeric>>> {
38+
public companion object : DSL<DSLNumericFilters, Set<FilterGroup<Filter.Numeric>>> {
3939

40-
override operator fun invoke(block: DSLNumericFilters.() -> Unit): List<FilterGroup<Filter.Numeric>> {
41-
return DSLNumericFilters().apply(block).groups.toList()
40+
override operator fun invoke(block: DSLNumericFilters.() -> Unit): Set<FilterGroup<Filter.Numeric>> {
41+
return DSLNumericFilters().apply(block).groups
4242
}
4343
}
4444
}

src/commonMain/kotlin/com/algolia/search/dsl/filtering/DSLTagFilters.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import com.algolia.search.model.filter.FilterGroup
1111
*/
1212
@DSLParameters
1313
public class DSLTagFilters(
14-
private val groups: MutableList<FilterGroup<Filter.Tag>> = mutableListOf()
14+
private val groups: MutableSet<FilterGroup<Filter.Tag>> = mutableSetOf()
1515
) {
1616

1717
/**
@@ -35,10 +35,10 @@ public class DSLTagFilters(
3535
+FilterGroup.Or.Tag(DSLGroupTag(block))
3636
}
3737

38-
public companion object : DSL<DSLTagFilters, List<FilterGroup<Filter.Tag>>> {
38+
public companion object : DSL<DSLTagFilters, Set<FilterGroup<Filter.Tag>>> {
3939

40-
override operator fun invoke(block: DSLTagFilters.() -> Unit): List<FilterGroup<Filter.Tag>> {
41-
return DSLTagFilters().apply(block).groups.toList()
40+
override operator fun invoke(block: DSLTagFilters.() -> Unit): Set<FilterGroup<Filter.Tag>> {
41+
return DSLTagFilters().apply(block).groups
4242
}
4343
}
4444
}

src/commonMain/kotlin/com/algolia/search/endpoint/EndpointSearch.kt

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package com.algolia.search.endpoint
22

33
import com.algolia.search.model.Attribute
44
import com.algolia.search.model.IndexName
5-
import com.algolia.search.model.filter.Filter
5+
import com.algolia.search.model.filter.FilterGroup
6+
import com.algolia.search.model.response.ResponseHitWithPosition
67
import com.algolia.search.model.response.ResponseSearch
78
import com.algolia.search.model.response.ResponseSearchForFacets
89
import com.algolia.search.model.search.Cursor
@@ -108,17 +109,38 @@ public interface EndpointSearch {
108109
/**
109110
* Perform a [EndpointMultipleIndex.multipleQueries] and aggregate the results into a single [ResponseSearch].
110111
* The aggregated result will have a correct [com.algolia.search.model.search.Facet.count] for [Attribute] that are
111-
* marked as disjunctive.
112+
* marked as disjunctive, and will populate [ResponseSearch.hierarchicalFacets] if a [FilterGroup.And.Hierarchical]
113+
* is present in [filterGroups]
112114
*
113115
* @param query The [Query] used to search.
114-
* @param disjunctiveFacets List of [Attribute] that are marked as disjunctive facets.
115-
* @param filters The [Filter] to be applied.
116+
* @param filterGroups List of [FilterGroup] to apply to the query.
116117
* @param requestOptions Configure request locally with [RequestOptions].
117118
*/
118-
suspend fun searchDisjunctiveFacets(
119-
query: Query,
120-
disjunctiveFacets: List<Attribute>,
121-
filters: Set<Filter>,
119+
suspend fun advancedSearch(
120+
query: Query = Query(),
121+
filterGroups: Set<FilterGroup<*>> = setOf(),
122122
requestOptions: RequestOptions? = null
123123
): ResponseSearch
124-
}
124+
125+
/**
126+
* Search iteratively through the search response [ResponseSearch.hits] field to find the first response hit that
127+
* would match against the given [match] function.
128+
* If no object has been found within the first result set, the function
129+
* will perform a new search operation on the next page of results, if any,
130+
* until a matching object is found or the end of results is reached, whichever
131+
* happens first.
132+
* [doNotPaginate] will stop the function at the end of the first page of search results even if no object does
133+
* match.
134+
*
135+
* @param match Predicate to match a given [ResponseSearch.Hit]
136+
* @param query The [Query] used to search.
137+
* @param doNotPaginate To prevent the iteration through pages of results.
138+
* @param requestOptions Configure request locally with [RequestOptions].
139+
*/
140+
tailrec suspend fun findFirstObject(
141+
match: (ResponseSearch.Hit) -> Boolean,
142+
query: Query = Query(),
143+
doNotPaginate: Boolean = false,
144+
requestOptions: RequestOptions? = null
145+
): ResponseHitWithPosition?
146+
}

0 commit comments

Comments
 (0)