Skip to content

Commit

Permalink
fix(specs): search w/ hits & facets responses (#1774)
Browse files Browse the repository at this point in the history
  • Loading branch information
aallam committed Jul 28, 2023
1 parent 769ef54 commit 2ac508f
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ void main() async {
queries: [querySuggestions, queryHits],
);
// Decompose the search results into separate results and suggestions.
final [searchResult, suggestionsResult] = responseMulti.results;
final [searchResult, suggestionsResult] = responseMulti.toList();
// Print the search results and suggestions.
printQuerySuggestion(searchResult);
printHits(suggestionsResult);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ extension SearchClientExt on SearchClient {
searchMethodParams: SearchMethodParams(requests: [request]),
requestOptions: requestOptions,
);
return response.results.first;
return SearchResponse.fromJson(response.results.first);
}

/// Perform a search operation targeting one index.
Future<SearchResponses> searchMultiIndex({
Future<Iterable<SearchResponse>> searchMultiIndex({
required List<SearchForHits> queries,
SearchStrategy? strategy,
RequestOptions? requestOptions,
}) {
final request = SearchMethodParams(requests: queries, strategy: strategy);
return search(searchMethodParams: request, requestOptions: requestOptions);
return search(searchMethodParams: request, requestOptions: requestOptions)
.then((res) => res.results.map((e) => SearchResponse.fromJson(e)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ void main() async {
hitsPerPage: 5,
);
// Execute the search request.
final responseHits = await client.searchIndex(
final responseHits = await client.searchSingleIndex(
indexName: 'instant_search',
request: queryHits,
searchParams: queryHits,
);
// Print the search hits.
printHits(responseHits);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ import 'package:algolia_client_search/algolia_client_search.dart';
extension SearchClientExt on SearchClient {
/// Perform a search operation targeting one index.
Future<SearchResponse> searchIndex({
required String indexName,
required SearchParamsObject request,
required SearchForHits request,
RequestOptions? requestOptions,
}) async {
return searchSingleIndex(
searchParams: request,
final response = await search(
searchMethodParams: SearchMethodParams(requests: [request]),
requestOptions: requestOptions,
indexName: indexName,
);
return SearchResponse.fromJson(response.results.first);
}

/// Perform a search operation targeting one index.
Future<SearchResponses> searchMultiIndex({
Future<Iterable<SearchResponse>> searchMultiIndex({
required List<SearchForHits> queries,
SearchStrategy? strategy,
RequestOptions? requestOptions,
}) async {
}) {
final request = SearchMethodParams(requests: queries, strategy: strategy);
return search(searchMethodParams: request, requestOptions: requestOptions);
return search(searchMethodParams: request, requestOptions: requestOptions)
.then((res) => res.results.map((e) => SearchResponse.fromJson(e)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.servers.Server;
import java.util.*;
import java.util.stream.Collectors;
import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.JavaClientCodegen;
import org.openapitools.codegen.model.ModelMap;
Expand Down Expand Up @@ -89,14 +90,26 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
CodegenModel model = modelContainer.getModels().get(0).getModel();

if (!model.oneOf.isEmpty()) {
List<HashMap<String, String>> oneOfList = new ArrayList();
List<HashMap<String, Object>> oneOfList = new ArrayList();

for (String oneOf : model.oneOf) {
HashMap<String, String> oneOfModel = new HashMap();

HashMap<String, Object> oneOfModel = new HashMap();
oneOfModel.put("type", oneOf);
oneOfModel.put("name", oneOf.replace("<", "Of").replace(">", ""));

oneOfModel.put("isList", oneOf.contains("List"));
ModelsMap modelsMap = models.get(oneOf);
if (modelsMap != null) {
oneOfModel.put("isObject", true);
CodegenModel compoundModel = modelsMap.getModels().get(0).getModel();
List<String> values = (List<String>) compoundModel.vendorExtensions.get("x-discriminator-fields");
if (values != null) {
List<Map<String, String>> newValues = values
.stream()
.map(value -> Collections.singletonMap("field", value))
.collect(Collectors.toList());
oneOfModel.put("discriminators", newValues);
}
}
oneOfList.add(oneOfModel);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.KotlinClientCodegen;
Expand Down Expand Up @@ -218,6 +219,14 @@ private void modelsOneOf(Map<String, ModelsMap> models) {
HashMap<String, String> parentInfo = new HashMap<>();
parentInfo.put("parent_classname", model.classname);
compoundParent(compoundModel).add(parentInfo);
List<String> values = (List<String>) compoundModel.vendorExtensions.get("x-discriminator-fields");
if (values != null) {
List<Map<String, String>> newValues = values
.stream()
.map(value -> Collections.singletonMap("field", value))
.collect(Collectors.toList());
oneOfModel.put("discriminators", newValues);
}
}
oneOfList.add(oneOfModel);
}
Expand Down
28 changes: 28 additions & 0 deletions specs/search/common/schemas/SearchForFacetValuesResponse.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
searchForFacetValuesResponse:
type: object
additionalProperties: false
required:
- facetHits
x-discriminator-fields:
- facetHits
properties:
facetHits:
type: array
items:
type: object
title: facetHits
additionalProperties: false
required:
- value
- highlighted
- count
properties:
value:
description: Facet value.
example: 'Mobile phone'
type: string
highlighted:
$ref: '../../common/schemas/Hit.yml#/highlightedValue'
count:
description: Number of records containing this facet value. This takes into account the extra search parameters specified in the query. Like for a regular search query, the [counts may not be exhaustive](https://support.algolia.com/hc/en-us/articles/4406975248145-Why-are-my-facet-and-hit-counts-not-accurate-).
type: integer
4 changes: 4 additions & 0 deletions specs/search/common/schemas/SearchResult.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
searchResult:
oneOf:
- $ref: 'SearchResponse.yml#/searchResponse'
- $ref: 'SearchForFacetValuesResponse.yml#/searchForFacetValuesResponse'
2 changes: 1 addition & 1 deletion specs/search/paths/search/search.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ post:
results:
type: array
items:
$ref: '../../common/schemas/SearchResponse.yml#/searchResponse'
$ref: '../../common/schemas/SearchResult.yml#/searchResult'
required:
- results
'400':
Expand Down
27 changes: 1 addition & 26 deletions specs/search/paths/search/searchForFacetValues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,7 @@ post:
content:
application/json:
schema:
title: searchForFacetValuesResponse
type: object
additionalProperties: false
required:
- facetHits
properties:
facetHits:
type: array
items:
type: object
title: facetHits
additionalProperties: false
required:
- value
- highlighted
- count
properties:
value:
description: Facet value.
example: 'Mobile phone'
type: string
highlighted:
$ref: '../../common/schemas/Hit.yml#/highlightedValue'
count:
description: Number of records containing this facet value. This takes into account the extra search parameters specified in the query. Like for a regular search query, the [counts may not be exhaustive](https://support.algolia.com/hc/en-us/articles/4406975248145-Why-are-my-facet-and-hit-counts-not-accurate-).
type: integer
$ref: '../../common/schemas/SearchForFacetValuesResponse.yml#/searchForFacetValuesResponse'
'400':
$ref: '../../../common/responses/BadRequest.yml'
'402':
Expand Down
54 changes: 54 additions & 0 deletions templates/java/api_helpers.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,58 @@ public Iterable<Rule> browseRules(String indexName, SearchRulesParams params) {
public Iterable<Rule> browseRules(String indexName) {
return browseRules(indexName, null, null);
}
/**
* (asynchronously) Send multiple search queries to one or more indices.
*
* @param searchMethodParams Query requests and strategies. Results will be received in the same
* order as the queries. (required)
* @return CompletableFuture<SearchResponses<Object>> The awaitable future
* @throws AlgoliaRuntimeException If it fails to process the API call
*/
public CompletableFuture<SearchResponses<Object>> searchAsync(SearchMethodParams searchMethodParams, RequestOptions requestOptions)
throws AlgoliaRuntimeException {
return this.searchAsync(searchMethodParams, Object.class, requestOptions);
}
/**
* (asynchronously) Send multiple search queries to one or more indices.
*
* @param searchMethodParams Query requests and strategies. Results will be received in the same
* order as the queries. (required)
* @return CompletableFuture<SearchResponses<Object>> The awaitable future
* @throws AlgoliaRuntimeException If it fails to process the API call
*/
public CompletableFuture<SearchResponses<Object>> searchAsync(SearchMethodParams searchMethodParams)
throws AlgoliaRuntimeException {
return this.searchAsync(searchMethodParams, Object.class, null);
}
/**
* Send multiple search queries to one or more indices.
*
* @param searchMethodParams Query requests and strategies. Results will be received in the same
* order as the queries. (required)
* @param requestOptions The requestOptions to send along with the query, they will be merged with
* the transporter requestOptions.
* @return SearchResponses<Object>
* @throws AlgoliaRuntimeException If it fails to process the API call
*/
public SearchResponses<Object> search(SearchMethodParams searchMethodParams, RequestOptions requestOptions)
throws AlgoliaRuntimeException {
return this.search(searchMethodParams, Object.class, requestOptions);
}
/**
* Send multiple search queries to one or more indices.
*
* @param searchMethodParams Query requests and strategies. Results will be received in the same
* order as the queries. (required)
* @return SearchResponses<Object>
* @throws AlgoliaRuntimeException If it fails to process the API call
*/
public SearchResponses<Object> search(SearchMethodParams searchMethodParams)
throws AlgoliaRuntimeException {
return this.search(searchMethodParams, Object.class, null);
}
{{/isSearchClient}}
53 changes: 17 additions & 36 deletions templates/java/oneof_interface.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.algolia.exceptions.AlgoliaRuntimeException;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;

/**
* {{{description}}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}}
Expand All @@ -18,7 +20,10 @@ import java.util.List;
@JsonDeserialize(using = {{classname}}.{{classname}}Deserializer.class)
@JsonSerialize(using = {{classname}}.{{classname}}Serializer.class)
{{>additionalModelTypeAnnotations}}
public abstract class {{classname}} implements CompoundType {
public abstract class {{classname}}{{#vendorExtensions.x-propagated-generic}}<T>{{/vendorExtensions.x-propagated-generic}} implements CompoundType {
private static final Logger LOGGER = Logger.getLogger({{classname}}.class.getName());

{{#vendorExtensions.x-one-of-list}}
public static {{classname}} of{{#vendorExtensions.x-one-of-explicit-name}}{{{name}}}{{/vendorExtensions.x-one-of-explicit-name}}({{{type}}} inside) {
return new {{classname}}{{name}}(inside);
Expand Down Expand Up @@ -51,46 +56,22 @@ public abstract class {{classname}} implements CompoundType {
}

@Override
public {{classname}} deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
public {{classname}} deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode tree = jp.readValueAsTree();
{{{classname}}} deserialized = null;

int match = 0;
JsonToken token = tree.traverse(jp.getCodec()).nextToken();
String currentType = "";
{{#vendorExtensions.x-one-of-list}}

// deserialize {{{type}}}
try {
boolean attemptParsing = true;
currentType = "{{{type}}}";
if (((currentType.equals("Integer") || currentType.equals("Long")) && token == JsonToken.VALUE_NUMBER_INT) |
((currentType.equals("Float") || currentType.equals("Double")) && token == JsonToken.VALUE_NUMBER_FLOAT) |
(currentType.equals("Boolean") && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)) |
(currentType.equals("String") && token == JsonToken.VALUE_STRING) |
(currentType.startsWith("List<") && token == JsonToken.START_ARRAY)
{{#isNullable}}
| (token == JsonToken.VALUE_NULL)
{{/isNullable}}) {
deserialized = {{{classname}}}.of{{#vendorExtensions.x-one-of-explicit-name}}{{{name}}}{{/vendorExtensions.x-one-of-explicit-name}}(({{{type}}}) tree.traverse(jp.getCodec()).readValueAs(new TypeReference<{{{type}}}>() {}));
match++;
} else if (token == JsonToken.START_OBJECT) {
try {
deserialized = {{{classname}}}.of{{#vendorExtensions.x-one-of-explicit-name}}{{{name}}}{{/vendorExtensions.x-one-of-explicit-name}}(({{{type}}}) tree.traverse(jp.getCodec()).readValueAs(new TypeReference<{{{type}}}>() {}));
match++;
} catch(IOException e) {
// do nothing
}
}
} catch (Exception e) {
// deserialization failed, continue
System.err.println("Failed to deserialize oneOf {{{type}}} (error: " + e.getMessage() + ") (type: " + currentType + ")");
if (tree.{{#isObject}}isObject(){{/isObject}}{{#isList}}isArray(){{/isList}}{{^isObject}}{{^isList}}isValueNode(){{/isList}}{{/isObject}}{{#discriminators}} && tree.has("{{field}}"){{/discriminators}}) {
try(JsonParser parser = tree.traverse(jp.getCodec())) {
{{{type}}} value = parser.readValueAs(new TypeReference<{{{type}}}>() {});
return {{{classname}}}.of{{#vendorExtensions.x-one-of-explicit-name}}{{{name}}}{{/vendorExtensions.x-one-of-explicit-name}}(value);
} catch (Exception e) {
// deserialization failed, continue
LOGGER.finest("Failed to deserialize oneOf {{{type}}} (error: " + e.getMessage() + ") (type: {{{type}}})");
}
}

{{/vendorExtensions.x-one-of-list}}
if (match == 1) {
return deserialized;
}
throw new IOException(String.format("Failed deserialization for {{classname}}: %d classes match result, expected 1", match));
throw new AlgoliaRuntimeException(String.format("Failed to deserialize json element: %s", tree));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion templates/kotlin/data_class_field_type.mustache
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isFreeFormObject}}JsonObject{{/isFreeFormObject}}{{^isFreeFormObject}}{{#vendorExtensions}}{{#x-has-child-generic}}{{#isUniqueItems}}Set{{/isUniqueItems}}{{^isUniqueItems}}List{{/isUniqueItems}}<{{{complexType}}}>{{/x-has-child-generic}}{{^x-has-child-generic}}{{#x-propagated-generic}}{{#isUniqueItems}}Set{{/isUniqueItems}}{{^isUniqueItems}}List{{/isUniqueItems}}<JsonObject>{{/x-propagated-generic}}{{^x-propagated-generic}}{{{datatypeWithEnum}}}{{/x-propagated-generic}}{{/x-has-child-generic}}{{/vendorExtensions}}{{/isFreeFormObject}}
{{#isFreeFormObject}}JsonObject{{/isFreeFormObject}}{{^isFreeFormObject}}{{{datatypeWithEnum}}}{{/isFreeFormObject}}
2 changes: 1 addition & 1 deletion templates/kotlin/oneof_interface.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal class {{classname}}Serializer : KSerializer<{{classname}}> {
{{#vendorExtensions.x-one-of-list}}

// deserialize {{{type}}}
if (tree is {{#isObject}}JsonObject{{/isObject}}{{#isList}}JsonArray{{/isList}}{{^isObject}}{{^isList}}JsonPrimitive{{/isList}}{{/isObject}}) {
if (tree is {{#isObject}}JsonObject{{/isObject}}{{#isList}}JsonArray{{/isList}}{{^isObject}}{{^isList}}JsonPrimitive{{/isList}}{{/isObject}}{{#discriminators}} && tree.containsKey("{{field}}"){{/discriminators}}) {
try {
return codec.json.decodeFromJsonElement<{{>oneof_compound}}>(tree)
} catch (e: Exception) {
Expand Down

0 comments on commit 2ac508f

Please sign in to comment.