diff --git a/.github/.cache_version b/.github/.cache_version index 166b94d637..b93692a052 100644 --- a/.github/.cache_version +++ b/.github/.cache_version @@ -1 +1 @@ -8.0.9.0.8 +8.0.10.0.8 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e12a123ce9..5d6fcb27a7 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -222,7 +222,7 @@ jobs: with: path: | ${{ format('{0}/algoliasearch-core/src/com/algolia/api/{1}.java', matrix.client.path, matrix.client.api) }} - ${{ format('{0}/{1}/**', matrix.client.path, matrix.client.capitalizedName) }} + ${{ format('{0}/algoliasearch-core/src/com/algolia/model/{1}/**', matrix.client.path, matrix.client.camelizedName) }} key: | ${{ env.CACHE_VERSION }}-${{ hashFiles( diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaJavaGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaJavaGenerator.java index 375645ec81..4831df2527 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaJavaGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaJavaGenerator.java @@ -152,7 +152,7 @@ public Map postProcessAllModels(Map objs) { .get(0) .get("model"); if (!model.oneOf.isEmpty()) { - List> listOneOf = new ArrayList(); + List> oneOfList = new ArrayList(); for (String iterateModel : model.oneOf) { HashMap oneOfModel = new HashMap(); @@ -163,11 +163,11 @@ public Map postProcessAllModels(Map objs) { iterateModel.replace("<", "").replace(">", "") ); - listOneOf.add(oneOfModel); + oneOfList.add(oneOfModel); } model.vendorExtensions.put("x-is-one-of-interface", true); - model.vendorExtensions.put("x-is-one-of-list", listOneOf); + model.vendorExtensions.put("x-is-one-of-list", oneOfList); } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/ParametersWithDataType.java b/generators/src/main/java/com/algolia/codegen/cts/ParametersWithDataType.java index bf93cade72..f20ef92492 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/ParametersWithDataType.java +++ b/generators/src/main/java/com/algolia/codegen/cts/ParametersWithDataType.java @@ -6,6 +6,7 @@ import io.swagger.util.Json; import java.util.*; import java.util.Map.Entry; +import org.openapitools.codegen.CodegenComposedSchemas; import org.openapitools.codegen.CodegenModel; import org.openapitools.codegen.CodegenOperation; import org.openapitools.codegen.CodegenParameter; @@ -205,7 +206,24 @@ private void handleModel( int suffix ) throws CTSException { if (!spec.getHasVars()) { - throw new CTSException("Spec has no vars."); + // In this case we might have a complex `allOf`, we will first check + // if it exists + CodegenComposedSchemas composedSchemas = spec.getComposedSchemas(); + + if (composedSchemas != null) { + List allOf = composedSchemas.getAllOf(); + + if (allOf != null && !allOf.isEmpty()) { + traverseParams(paramName, param, allOf.get(0), parent, suffix); + + return; + } + } + // We only throw if there is no `composedSchemas`, because `oneOf` can also + // be handled below + else { + throw new CTSException("Spec has no vars."); + } } if (spec.getItems() != null) { @@ -224,12 +242,21 @@ private void handleModel( ); HashMap oneOfModel = new HashMap<>(); + String typeName = getTypeName(match).replace("<", "").replace(">", ""); oneOfModel.put("classname", Utils.capitalize(baseType)); - oneOfModel.put( - "name", - getTypeName(match).replace("<", "").replace(">", "") - ); + + if (typeName.equals("List")) { + CodegenProperty items = match.getItems(); + + if (items == null) { + throw new CTSException("Unhandled case for empty oneOf List items."); + } + + typeName += getTypeName(items); + } + + oneOfModel.put("name", typeName); testOutput.put("oneOfModel", oneOfModel); return; @@ -466,7 +493,18 @@ private IJsonSchemaValidationProperties findMatchingOneOf( return bestOneOf; } if (param instanceof List) { - // no idea for list + // NICE ---> no idea for list <--- NICE + CodegenComposedSchemas composedSchemas = model.getComposedSchemas(); + + if (composedSchemas != null) { + List oneOf = composedSchemas.getOneOf(); + + // Somehow this is not yet enough + if (oneOf != null && !oneOf.isEmpty()) { + return oneOf.get(0); + } + } + return null; } diff --git a/scripts/ci/createMatrix.ts b/scripts/ci/createMatrix.ts index a5920bc8f0..74143bd598 100644 --- a/scripts/ci/createMatrix.ts +++ b/scripts/ci/createMatrix.ts @@ -1,5 +1,5 @@ import { CLIENTS, GENERATORS } from '../common'; -import { createClientName } from '../cts/utils'; +import { camelize, createClientName } from '../cts/utils'; import type { Language } from '../types'; import { getNbGitDiff } from './utils'; @@ -16,9 +16,10 @@ type BaseMatrix = { }; type ClientMatrix = BaseMatrix & { - config?: string; - api?: string; - capitalizedName?: string; + config: string; + api: string; + capitalizedName: string; + camelizedName: string; }; type SpecMatrix = BaseMatrix; @@ -56,18 +57,16 @@ async function getClientMatrix({ continue; } - const matchedGenerator: ClientMatrix = { - name: client, - path: output, - }; - const clientName = createClientName(client, language); - matchedGenerator.config = `${clientName}Config`; - matchedGenerator.api = `${clientName}Client`; - matchedGenerator.capitalizedName = clientName; - - matrix.client.push(matchedGenerator); + matrix.client.push({ + name: client, + path: output, + config: `${clientName}Config`, + api: `${clientName}Client`, + capitalizedName: clientName, + camelizedName: camelize(client), + }); } return matrix; diff --git a/scripts/cts/utils.ts b/scripts/cts/utils.ts index 18968d7b20..5871b9976d 100644 --- a/scripts/cts/utils.ts +++ b/scripts/cts/utils.ts @@ -14,23 +14,47 @@ export async function* walk( } } +/** + * Sets the first letter of the given string in capital. + * + * `searchClient` -> `SearchClient`. + */ export function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } -export function createClientName(client: string, language: string): string { - const clientName = client - .split('-') +/** + * Splits a string for a given `delimiter` (defaults to `-`) and capitalize each + * parts except the first letter. + * + * `search-client` -> `searchClient`. + */ +export function camelize(str: string, delimiter: string = '-'): string { + return str + .split(delimiter) .map((part, i) => { - if (language === 'javascript' && i === 0) { + if (i === 0) { return part; } return capitalize(part); }) .join(''); +} + +/** + * Returns the client name with the correct casing for its language. + * + * `search-client`, `java` -> `SearchClient`. + * + * `search-client`, `javascript` -> `searchClient`. + */ +export function createClientName(client: string, language: string): string { + if (language === 'javascript') { + return camelize(client); + } - return clientName; + return capitalize(camelize(client)); } export async function createOutputDir({ diff --git a/scripts/index.ts b/scripts/index.ts index caf2030dab..97dc0cab0a 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -194,12 +194,7 @@ buildCommand clientsTodo = CLIENTS; } // ignore cache when building from cli - await buildSpecs( - clientsTodo, - outputFormat!, - Boolean(verbose), - !skipCache - ); + await buildSpecs(clientsTodo, outputFormat, Boolean(verbose), !skipCache); } ); diff --git a/specs/common/schemas/SearchParams.yml b/specs/common/schemas/SearchParams.yml index 49d7ead6a1..d83e550b5c 100644 --- a/specs/common/schemas/SearchParams.yml +++ b/specs/common/schemas/SearchParams.yml @@ -30,31 +30,14 @@ baseSearchParams: type: string description: Filter the query with numeric, facet and/or tag filters. default: '' - # There could be a pattern for this one (complicated one) facetFilters: - type: array - items: - type: string - description: Filter hits by facet value. - default: [] + $ref: '#/facetFilters' optionalFilters: - type: array - items: - type: string - description: Create filters for ranking purposes, where records that match the filter are ranked higher, or lower in the case of a negative optional filter. - default: [] + $ref: '#/optionalFilters' numericFilters: - type: array - items: - type: string - description: Filter on numeric attributes. - default: [] + $ref: '#/numericFilters' tagFilters: - type: array - items: - type: string - description: Filter hits by tags. - default: [] + $ref: '#/tagFilters' sumOrFiltersScores: type: boolean description: Determines how to calculate the total score for filtering. @@ -164,6 +147,8 @@ baseSearchParams: type: boolean description: Whether this search should use AI Re-Ranking. default: true + reRankingApplyFilter: + $ref: '#/reRankingApplyFilter' searchParamsString: type: object @@ -202,3 +187,47 @@ aroundRadius: aroundRadiusAll: type: string enum: [all] + +# There is duplicated logic here because we want to keep a correct description +# and using `$ref` override everything. +searchFiltersArrayString: + type: array + items: + type: string + +searchFiltersNestedArrayString: + type: array + items: + type: array + items: + type: string + +facetFilters: + description: Filter hits by facet value. + oneOf: + - $ref: '#/searchFiltersArrayString' + - $ref: '#/searchFiltersNestedArrayString' + +reRankingApplyFilter: + description: When Dynamic Re-Ranking is enabled, only records that match these filters will be impacted by Dynamic Re-Ranking. + oneOf: + - $ref: '#/searchFiltersArrayString' + - $ref: '#/searchFiltersNestedArrayString' + +tagFilters: + description: Filter hits by tags. + oneOf: + - $ref: '#/searchFiltersArrayString' + - $ref: '#/searchFiltersNestedArrayString' + +numericFilters: + description: Filter on numeric attributes. + oneOf: + - $ref: '#/searchFiltersArrayString' + - $ref: '#/searchFiltersNestedArrayString' + +optionalFilters: + description: Create filters for ranking purposes, where records that match the filter are ranked higher, or lower in the case of a negative optional filter. + oneOf: + - $ref: '#/searchFiltersArrayString' + - $ref: '#/searchFiltersNestedArrayString' diff --git a/specs/search/common/schemas/Hit.yml b/specs/search/common/schemas/Hit.yml index 2fbf258e75..cbc0c5ab19 100644 --- a/specs/search/common/schemas/Hit.yml +++ b/specs/search/common/schemas/Hit.yml @@ -64,22 +64,9 @@ rankingInfo: type: integer description: Precision used when computing the geo distance, in meters. matchedGeoLocation: - type: object - additionalProperties: - type: object - additionalProperties: false - properties: - lat: - type: number - format: double - description: Latitude of the matched location. - lng: - type: number - format: double - description: Longitude of the matched location. - distance: - type: integer - description: Distance between the matched location and the search location (in meters). + $ref: '#/matchedGeoLocation' + personalization: + $ref: '#/personalization' nbExactWords: type: integer description: Number of exactly matched words. @@ -95,9 +82,21 @@ rankingInfo: userScore: type: integer description: Custom ranking for the object, expressed as a single integer value. - word: + words: type: integer description: Number of matched words, including prefixes and typos. + promotedByReRanking: + type: boolean + description: Wether the record are promoted by the re-ranking strategy. + required: + - promoted + - nbTypos + - firstMatchedWord + - geoDistance + - nbExactWords + - words + - filters + - userScore highlightedValue: type: string @@ -108,3 +107,31 @@ matchLevel: type: string description: Indicates how well the attribute matched the search query. enum: [none, partial, full] + +matchedGeoLocation: + type: object + properties: + lat: + type: number + format: double + description: Latitude of the matched location. + lng: + type: number + format: double + description: Longitude of the matched location. + distance: + type: integer + description: Distance between the matched location and the search location (in meters). + +personalization: + type: object + properties: + filtersScore: + type: integer + description: The score of the filters. + rankingScore: + type: integer + description: The score of the ranking. + score: + type: integer + description: The score of the event. diff --git a/tests/CTS/methods/requests/search/search.json b/tests/CTS/methods/requests/search/search.json index 59e762fc6e..4f7f0c1317 100644 --- a/tests/CTS/methods/requests/search/search.json +++ b/tests/CTS/methods/requests/search/search.json @@ -1,6 +1,7 @@ [ { "method": "search", + "testName": "search with minimal parameters", "parameters": { "indexName": "indexName", "searchParams": { @@ -14,5 +15,28 @@ "query": "myQuery" } } + }, + { + "method": "search", + "testName": "search with facetFilters", + "parameters": { + "indexName": "indexName", + "searchParams": { + "query": "myQuery", + "facetFilters": [ + "tags:algolia" + ] + } + }, + "request": { + "path": "/1/indexes/indexName/query", + "method": "POST", + "data": { + "query": "myQuery", + "facetFilters": [ + "tags:algolia" + ] + } + } } ] diff --git a/tests/output/java/src/test/java/com/algolia/methods/requests/recommend.test.java b/tests/output/java/src/test/java/com/algolia/methods/requests/recommend.test.java index 1ed96929b5..bbc86aeda7 100644 --- a/tests/output/java/src/test/java/com/algolia/methods/requests/recommend.test.java +++ b/tests/output/java/src/test/java/com/algolia/methods/requests/recommend.test.java @@ -234,7 +234,9 @@ void getRecommendationsTest1() { facetFilters4.add(facetFilters_05); } - queryParameters3.setFacetFilters(facetFilters4); + queryParameters3.setFacetFilters( + FacetFilters.ofListString(facetFilters4) + ); } requests_02.setQueryParameters(queryParameters3); @@ -250,7 +252,9 @@ void getRecommendationsTest1() { facetFilters4.add(facetFilters_05); } - fallbackParameters3.setFacetFilters(facetFilters4); + fallbackParameters3.setFacetFilters( + FacetFilters.ofListString(facetFilters4) + ); } requests_02.setFallbackParameters(fallbackParameters3); } @@ -364,7 +368,9 @@ void getRecommendationsTest3() { facetFilters4.add(facetFilters_05); } - queryParameters3.setFacetFilters(facetFilters4); + queryParameters3.setFacetFilters( + FacetFilters.ofListString(facetFilters4) + ); } requests_02.setQueryParameters(queryParameters3); @@ -380,7 +386,9 @@ void getRecommendationsTest3() { facetFilters4.add(facetFilters_05); } - fallbackParameters3.setFacetFilters(facetFilters4); + fallbackParameters3.setFacetFilters( + FacetFilters.ofListString(facetFilters4) + ); } requests_02.setFallbackParameters(fallbackParameters3); } @@ -521,7 +529,9 @@ void getRecommendationsTest5() { facetFilters4.add(facetFilters_05); } - queryParameters3.setFacetFilters(facetFilters4); + queryParameters3.setFacetFilters( + FacetFilters.ofListString(facetFilters4) + ); } requests_02.setQueryParameters(queryParameters3); @@ -537,7 +547,9 @@ void getRecommendationsTest5() { facetFilters4.add(facetFilters_05); } - fallbackParameters3.setFacetFilters(facetFilters4); + fallbackParameters3.setFacetFilters( + FacetFilters.ofListString(facetFilters4) + ); } requests_02.setFallbackParameters(fallbackParameters3); } @@ -580,7 +592,9 @@ void getRecommendationsTest5() { facetFilters4.add(facetFilters_05); } - queryParameters3.setFacetFilters(facetFilters4); + queryParameters3.setFacetFilters( + FacetFilters.ofListString(facetFilters4) + ); } requests_12.setQueryParameters(queryParameters3); @@ -596,7 +610,9 @@ void getRecommendationsTest5() { facetFilters4.add(facetFilters_05); } - fallbackParameters3.setFacetFilters(facetFilters4); + fallbackParameters3.setFacetFilters( + FacetFilters.ofListString(facetFilters4) + ); } requests_12.setFallbackParameters(fallbackParameters3); } diff --git a/tests/output/java/src/test/java/com/algolia/methods/requests/search.test.java b/tests/output/java/src/test/java/com/algolia/methods/requests/search.test.java index c3e9bcdc4a..dafbcceb5e 100644 --- a/tests/output/java/src/test/java/com/algolia/methods/requests/search.test.java +++ b/tests/output/java/src/test/java/com/algolia/methods/requests/search.test.java @@ -2098,7 +2098,7 @@ void saveSynonymsTest0() { } @Test - @DisplayName("search") + @DisplayName("search with minimal parameters") void searchTest0() { String indexName0 = "indexName"; @@ -2129,6 +2129,46 @@ void searchTest0() { }); } + @Test + @DisplayName("search with facetFilters") + void searchTest1() { + String indexName0 = "indexName"; + + SearchParamsObject searchParams0 = new SearchParamsObject(); + { + String query1 = "myQuery"; + + searchParams0.setQuery(query1); + + List facetFilters1 = new ArrayList<>(); + { + String facetFilters_02 = "tags:algolia"; + + facetFilters1.add(facetFilters_02); + } + searchParams0.setFacetFilters(FacetFilters.ofListString(facetFilters1)); + } + + EchoResponseInterface req = (EchoResponseInterface) assertDoesNotThrow(() -> { + return client.search( + indexName0, + SearchParams.ofSearchParamsObject(searchParams0) + ); + } + ); + + assertEquals(req.getPath(), "/1/indexes/indexName/query"); + assertEquals(req.getMethod(), "POST"); + + assertDoesNotThrow(() -> { + JSONAssert.assertEquals( + "{\"query\":\"myQuery\",\"facetFilters\":[\"tags:algolia\"]}", + req.getBody(), + JSONCompareMode.STRICT_ORDER + ); + }); + } + @Test @DisplayName("get searchDictionaryEntries results with minimal parameters") void searchDictionaryEntriesTest0() { diff --git a/tests/output/javascript/src/methods/requests/search.test.ts b/tests/output/javascript/src/methods/requests/search.test.ts index bf914390b5..b06e82288a 100644 --- a/tests/output/javascript/src/methods/requests/search.test.ts +++ b/tests/output/javascript/src/methods/requests/search.test.ts @@ -1011,7 +1011,7 @@ describe('saveSynonyms', () => { }); describe('search', () => { - test('search', async () => { + test('search with minimal parameters', async () => { const req = (await client.search({ indexName: 'indexName', searchParams: { query: 'myQuery' }, @@ -1022,6 +1022,21 @@ describe('search', () => { expect(req.data).toEqual({ query: 'myQuery' }); expect(req.searchParams).toEqual(undefined); }); + + test('search with facetFilters', async () => { + const req = (await client.search({ + indexName: 'indexName', + searchParams: { query: 'myQuery', facetFilters: ['tags:algolia'] }, + })) as unknown as EchoResponse; + + expect(req.path).toEqual('/1/indexes/indexName/query'); + expect(req.method).toEqual('POST'); + expect(req.data).toEqual({ + query: 'myQuery', + facetFilters: ['tags:algolia'], + }); + expect(req.searchParams).toEqual(undefined); + }); }); describe('searchDictionaryEntries', () => {